1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 package lib;
24 
25 import java.io.PrintWriter;
26 import java.lang.reflect.Field;
27 import java.lang.reflect.InvocationTargetException;
28 import java.lang.reflect.Method;
29 import java.util.Vector;
30 
31 import com.sun.star.uno.UnoRuntime;
32 import com.sun.star.uno.XInterface;
33 
34 import share.DescEntry;
35 import lib.TestParameters;
36 import stats.Summarizer;
37 
38 /**
39  * The class supports method based interface tests development.
40  *
41  * <p>There are some points that should be fulfilled in a subclass to work
42  * correctly in the multi-method framework:
43  *
44  *   1. each subclass should define a public field named oObj of type tested
45  *   by the subclass, e.g. 'public XText oObj;'. That field will be initialized
46  *   by the MultiMethodTest code with the instance of the interface to test.
47  *   In a case of service testing the field type should be XPropertySet.
48  *
49  *   2. for the test of each method of the tested interface(or a property in the
50  *   case of service testing) should be method with the following signature
51  *   provided: 'public void _<method name>()', e.g. 'public void _getText()'.
52  *   The methods will be called by MultiMethodText code using reflection API
53  *   for each method in the interface description.
54  *
55  *   3. to set status for a call 'tRes.tested(String method,
56  *   boolean result)' should be used. For example 'tRes.tested("getText()",
57  *   true)'. Also 'tRes.assert(String assertion, boolean result)' call can
58  *   be used. Note, that one can call the methods not necessarily from the
59  *   test for the tested method, but from other method tests too (in the
60  *   MultiMethodTest subclass). See also TestResult and MultiMethodTest.tRes
61  *   documentation.
62  *
63  *   4. the before() and after() methods can be overriden to perform some
64  *   actions, accordingly, before and after calling the test methods.
65  *
66  *   5. besides tRes, there are some fields initialized in the MultiMethodTest,
67  *   that can be used for implementing tests:
68  *
69  *     - tEnv contains the environment tested
70  *     - tParam contains parameters of the test
71  *     - log a writer to log information about the test
72  *
73  * @see TestResult
74  */
75 public class MultiMethodTest
76 {
77 
78     /**
79      * Contains the TestEnvironment being tested, to allow for tests to access
80      * it.
81      */
82     protected TestEnvironment tEnv;
83     /**
84      * Contains the TestParameters for the tests, to allow for tests to access
85      * it.
86      */
87     protected TestParameters tParam;
88     /**
89      * Contains the Description for the test
90      * it.
91      */
92     protected DescEntry entry;
93     /**
94      * Contains a writer to log an information about the interface testing, to
95      * allows for tests to access it.
96      */
97     protected PrintWriter log;
98     /**
99      * Contains the TestResult instance for the interface test to collect
100      * information about methods test.
101      */
102     protected TestResult tRes;
103     /**
104      * Contains names of the methods have been alreadycalled
105      */
106     private Vector methCalled = new Vector(10);
107 
108     /**
109      * Disposes the test environment, which was corrupted by the test.
110      *
111      * @param tEnv the environment to dispose
112      */
113     public void disposeEnvironment(TestEnvironment tEnv)
114     {
115         disposeEnvironment();
116     }
117 
118     /**
119      * Disposes the current test environment, which was corrupted by the test.
120      *
121      * @see #disposeEnvironment(TestEnvironment)
122      */
123     public void disposeEnvironment()
124     {
125         tEnv.dispose();
126         TestCase tCase = tEnv.getTestCase();
127         tCase.disposeTestEnvironment(tEnv, tParam);
128     }
129 
130     /**
131      * Runs the interface test: its method tests. First, it initializes some
132      * of MultiMethodTest fields, like tRes, log, tEnv, etc. Then, it queries
133      * the tested interface and initializes 'oObj' field (defined in a
134      * subclass). Before calling method tests, before() method calles to allow
135      * initialization of s stuff before testing. Then, the method tests are
136      * called. After them, after() method is called, to allow cleaning up the
137      * stuff initialized in before() and test methods.
138      *
139      * @param entry the interface test state
140      * @param tEnv the environment to test
141      * @param tParam the parameters of the test
142      *
143      * @see #before
144      * @see #after
145      */
146     public TestResult run(DescEntry entry, TestEnvironment tEnv, TestParameters tParam)
147     {
148 
149         log = (PrintWriter) entry.Logger;
150 
151         this.tEnv = tEnv;
152         this.tParam = tParam;
153         // this.log = log;
154         this.entry = entry;
155         this.tRes = new TestResult();
156         Class testedClass;
157 
158         // Some fake code for a self test.
159         // For normal test we must not be a "ifc.qadevooo._SelfTest"
160         if (! entry.entryName.equals("ifc.qadevooo._SelfTest"))
161         {
162             String ifcName = getInterfaceName();
163             // System.out.println("checking : " + ifcName);
164             System.out.print("checking: [" + entry.longName + "]");
165 
166             // defining a name of the class corresponding to the tested interface
167             // or service
168             String testedClassName;
169 
170             testedClassName = getTestedClassName();
171 
172             if (entry.EntryType.equals("service"))
173             {
174                 testedClassName = "com.sun.star.beans.XPropertySet";
175             }
176 
177             try
178             {
179                 testedClass = Class.forName(testedClassName);
180             }
181             catch (ClassNotFoundException cnfE)
182             {
183                 System.out.println();
184                 cnfE.printStackTrace(log);
185                 log.println("could not find a class : " + getTestedClassName());
186                 return null;
187             }
188             System.out.println(" is iface: [" + testedClassName + "] testcode: [" + entry.entryName + "]");
189 
190             // quering the tested interface from the tested object
191             XInterface tCase = tEnv.getTestObject();
192             Object oObj = UnoRuntime.queryInterface(testedClass, tEnv.getTestObject());
193 
194             if (oObj == null)
195             {
196                 if (entry.isOptional)
197                 {
198                     Summarizer.summarizeDown(entry, "Not supported but optional.OK");
199                 }
200                 else
201                 {
202                     Summarizer.summarizeDown(entry, "queryInterface returned null.FAILED");
203                     entry.ErrorMsg = "queryInterface returned null";
204                     entry.hasErrorMsg = true;
205                 }
206 
207                 return null;
208             }
209 
210             //setting the field oObj
211             setField("oObj", oObj);
212         }
213 
214         // to perform some stuff before all method tests
215         try
216         {
217             before();
218         }
219         catch (Exception e)
220         {
221             setSubStates(e.toString());
222             return tRes;
223         }
224 
225         // executing methods tests
226         for (int i = 0; i < entry.SubEntryCount; i++)
227         {
228             DescEntry aSubEntry = entry.SubEntries[i];
229             try
230             {
231                 final String sEntryName = aSubEntry.entryName;
232                 executeMethod(sEntryName);
233             }
234             catch (Exception e)
235             {
236                 log.println("Exception while checking: " + aSubEntry.entryName + " : " + e.getMessage());
237             }
238         }
239 
240         // to perform some stuff after all method tests
241         try
242         {
243             after();
244         }
245         catch (Exception e)
246         {
247         }
248 
249         return tRes;
250     }
251 
252     /**
253      * Is called before calling method tests, but after initialization.
254      * Subclasses may override to perform actions before method tests.
255      */
256     protected void before()
257     {
258     }
259 
260     /**
261      * Is called after calling method tests. Subclasses may override
262      * to perform actions after method tests.
263      */
264     protected void after()
265     {
266     }
267 
268     /**
269      * @return the name of the interface or the service tested.
270      */
271     protected String getTestedClassName()
272     {
273         String clsName = this.getClass().getName();
274 
275         int firstDot = clsName.indexOf(".");
276         int lastDot = clsName.lastIndexOf(".");
277 
278         String append = "com.sun.star.";
279 
280         if (entry.longName.indexOf("::drafts::com::") > -1)
281         {
282             append = "drafts.com.sun.star.";
283         }
284 
285         return append + clsName.substring(firstDot + 1, lastDot + 1) + clsName.substring(lastDot + 2);
286     }
287 
288     /**
289      * Sets a method status.
290      *
291      * @param methName the method name to set status
292      * @param methStatus the status to set to the method
293      */
294     protected void setStatus(String methName, Status methStatus)
295     {
296         tRes.tested(methName, methStatus);
297     }
298 
299     /**
300      * sets the substates
301      */
302     protected void setSubStates(String msg)
303     {
304         for (int k = 0; k < entry.SubEntryCount; k++)
305         {
306             entry.SubEntries[k].hasErrorMsg = true;
307             entry.SubEntries[k].ErrorMsg = msg;
308             if (entry.SubEntries[k].State.equals("UNKNOWN"))
309             {
310                 entry.SubEntries[k].State = msg;
311             }
312         }
313 
314     }
315 
316     /**
317      * Checks if the <code>method</code> is optional in the service.
318      */
319     protected boolean isOptional(String _method)
320     {
321         for (int k = 0; k < entry.SubEntryCount; k++)
322         {
323             final String sName = entry.SubEntries[k].entryName;
324             if (sName.equals(_method))
325             {
326                 final boolean bIsOptional = entry.SubEntries[k].isOptional;
327                 return bIsOptional;
328             }
329         }
330         return false;
331     }
332 
333     /**
334      * Checks if the <code>method</code> test has been already called.
335      */
336     protected boolean isCalled(String method)
337     {
338         return methCalled.contains(method);
339     }
340 
341     /**
342      * Calling of the method indicates that the <code>method</code> test should
343      * be called. The method checks this and if it is not called, calls it.
344      * If the method is failed or skipped, it throws StatusException.
345      */
346     protected void requiredMethod(String method)
347     {
348         log.println("starting required method: " + method);
349         executeMethod(method);
350         Status mtStatus = tRes.getStatusFor(method);
351 
352         if (mtStatus != null && (!mtStatus.isPassed() || mtStatus.isFailed()))
353         {
354             log.println("! Required method " + method + " failed");
355             throw new StatusException(mtStatus);
356         }
357     }
358 
359     /**
360      * Checks if the <code>method</code> was called, and if not, call it.
361      * On contrary to requiredMethod(), he method doesn't check its status.
362      */
363     protected void executeMethod(String method)
364     {
365         if (!isCalled(method))
366         {
367             log.println("Execute: " + method);
368             callMethod(method);
369             log.println(method + ": " + tRes.getStatusFor(method));
370             log.println();
371         }
372     }
373 
374     /**
375      * Just calls the <code>method</code> test.
376      */
377     protected void callMethod(String method)
378     {
379         methCalled.add(method);
380         invokeTestMethod(getMethodFor(method), method);
381     }
382 
383     /**
384      * Invokes a test method of the subclass using reflection API. Handles
385      * the method results and sets its status.
386      *
387      * @param meth the subclass' method to invoke
388      * @param methName the name of the method
389      */
390     protected void invokeTestMethod(Method meth, String methName)
391     {
392         if (meth == null)
393         {
394             setStatus(methName, Status.skipped(false));
395         }
396         else
397         {
398             Status stat;
399 
400             try
401             {
402                 meth.invoke(this, new Object[0]);
403                 return;
404             }
405             catch (InvocationTargetException itE)
406             {
407                 Throwable t = itE.getTargetException();
408 
409                 if (t instanceof StatusException)
410                 {
411                     stat = ((StatusException) t).getStatus();
412                 }
413                 else
414                 {
415                     t.printStackTrace(log);
416                     stat = Status.exception(t);
417                 }
418             }
419             catch (IllegalAccessException iaE)
420             {
421                 iaE.printStackTrace(log);
422                 stat = Status.exception(iaE);
423             }
424             catch (IllegalArgumentException iaE)
425             {
426                 iaE.printStackTrace(log);
427                 stat = Status.exception(iaE);
428             }
429             catch (ClassCastException ccE)
430             {
431                 ccE.printStackTrace(log);
432                 stat = Status.exception(ccE);
433             }
434 
435             setStatus(methName, stat);
436         }
437     }
438 
439     /**
440      * Finds a testing method for the <code>method</code> of the interface.
441      *
442      * @return the testing method, if found, <tt>null</tt> otherwise
443      */
444     protected Method getMethodFor(String method)
445     {
446         String mName = "_" + method;
447 
448         if (mName.endsWith("()"))
449         {
450             mName = mName.substring(0, mName.length() - 2);
451         }
452 
453         final Class[] paramTypes = new Class[0];
454 
455         try
456         {
457             return this.getClass().getDeclaredMethod(mName, paramTypes);
458         }
459         catch (NoSuchMethodException nsmE)
460         {
461             return null;
462         }
463     }
464 
465     /**
466      * @return the name of the interface tested
467      */
468     public String getInterfaceName()
469     {
470         String clName = this.getClass().getName();
471         return clName.substring(clName.lastIndexOf('.') + 1);
472     }
473 
474     /**
475      * Initializes <code>fieldName</code> of the subclass with
476      * <code>value</code>.
477      *
478      * @return Status describing the result of the operation.
479      */
480     protected Status setField(String fieldName, Object value)
481     {
482         Field objField;
483 
484         try
485         {
486             objField = this.getClass().getField(fieldName);
487         }
488         catch (NoSuchFieldException nsfE)
489         {
490             return Status.exception(nsfE);
491         }
492 
493         try
494         {
495             objField.set(this, value);
496             return Status.passed(true);
497         }
498         catch (IllegalArgumentException iaE)
499         {
500             return Status.exception(iaE);
501         }
502         catch (IllegalAccessException iaE)
503         {
504             return Status.exception(iaE);
505         }
506     }
507 }
508