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