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