• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the  "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 /*
19  * $Id$
20  */
21 
22 /*
23  *
24  * ThreadedTestletDriver.java
25  *
26  */
27 package org.apache.qetest.xsl;
28 
29 import java.util.Properties;
30 import java.util.Vector;
31 
32 import org.apache.qetest.Logger;
33 import org.apache.qetest.QetestUtils;
34 import org.apache.qetest.Reporter;
35 import org.apache.qetest.xslwrapper.TransformWrapper;
36 import org.apache.qetest.xslwrapper.TransformWrapperFactory;
37 
38 //-------------------------------------------------------------------------
39 
40 /**
41  * Test driver for XSLT stylesheet Testlets.
42  *
43  * This is a specific driver for XSLT-oriented Testlets, testing
44  * them in an explicitly threaded model.  Currently, this class is
45  * tightly bound to ThreadedStylesheetTestlet/Datalet.
46  *
47  * @author shane_curcuru@lotus.com
48  * @version $Id$
49  */
50 public class ThreadedTestletDriver extends StylesheetTestletDriver
51 {
52 
53 
54     /** Just initialize test name, comment; numTestCases is not used. */
ThreadedTestletDriver()55     public ThreadedTestletDriver()
56     {
57         testName = "ThreadedTestletDriver";
58         testComment = "Threaded test driver for XSLT stylesheet Testlets";
59     }
60 
61 
62     /**
63      * Do the default: test all stylesheets found in subdirs
64      * of our inputDir, using FilenameFilters for dirs and files.
65      * This only goes down one level in the tree, eg:
66      * <ul>inputDir = tests/conf
67      * <li>tests/conf - not tested</li>
68      * <li>tests/conf/boolean - test all boolean*.xsl files</li>
69      * <li>tests/conf/copy - test all copy*.xsl files</li>
70      * <li>tests/conf/copy/foo - not tested</li>
71      * <li>tests/conf/xmanual - not tested, since default
72      * ConformanceDirRules excludes dirs starting with 'x|X'</li>
73      * <li>tests/whitespace - test all whitespace*.xsl files</li>
74      * <li>etc.</li>
75      * </ul>
76      * Parameters: none, uses our internal members inputDir,
77      * outputDir, testlet, etc.
78      */
processInputDir()79     public void processInputDir()
80     {
81         // Implement this later!
82         reporter.checkErr("processInputDir not yet implemented use -fileList list.txt instead!");
83     }
84 
85 
86     /**
87      * Run a list of stylesheet tests through a Testlet.
88      * The file names are assumed to be fully specified, and we assume
89      * the corresponding directories exist.
90      * Each fileList is turned into a testcase.
91      * The first file in the list is used as a common Template that
92      * is passed to each ThreadedStylesheetTestlet that is created
93      * from every other item in the file list.
94      * i.e. the first file is used commonly throughout every
95      * testlet.  For every other file in the list, a new Thread is
96      * created that will perform processes on both the shared
97      * Templates from the first file and from this listed file.
98      *
99      * @param vector of Datalet objects to pass in
100      * @param desc String to use as testCase description
101      */
processFileList(Vector datalets, String desc)102     public void processFileList(Vector datalets, String desc)
103     {
104         // Validate arguments - must have at least two files to test
105         if ((null == datalets) || (datalets.size() < 2))
106         {
107             // Bad arguments, report it as an error
108             // Note: normally, this should never happen, since
109             //  this class normally validates these arguments
110             //  before calling us
111             reporter.checkErr("Testlet or datalets are null/less than 2, nothing to test!");
112             return;
113         }
114 
115         // Put everything else into a testCase
116         //  This is not necessary, but feels a lot nicer to
117         //  break up large test sets
118         reporter.testCaseInit(desc);
119 
120         // Now just go through the list and process each set
121         int numDatalets = datalets.size();
122         reporter.logInfoMsg("processFileList() with " + numDatalets
123                             + " potential tests");
124 
125         // Take the first Datalet and create a Templates from it;
126         //  this is going to be shared by all other threads/testlets
127         StylesheetDatalet firstDatalet = (StylesheetDatalet)datalets.elementAt(0);
128         TransformWrapper transformWrapper = null;
129         // Create a TransformWrapper of appropriate flavor
130         try
131         {
132             transformWrapper = TransformWrapperFactory.newWrapper(firstDatalet.flavor);
133             transformWrapper.newProcessor(null);
134             reporter.logMsg(Logger.INFOMSG, "Created transformWrapper, about to process shared: " + firstDatalet.inputName);
135             transformWrapper.buildStylesheet(firstDatalet.inputName);
136         }
137         catch (Throwable t)
138         {
139             reporter.logThrowable(Logger.ERRORMSG, t, "Creating transformWrapper: newWrapper/newProcessor threw");
140             reporter.checkErr("Creating transformWrapper: newWrapper/newProcessor threw: " + t.toString());
141             return;
142         }
143 
144         // Create a ThreadedStylesheetDatalet for shared use
145         ThreadedStylesheetDatalet sharedDatalet = new ThreadedStylesheetDatalet();
146         // Copy all other info over
147         sharedDatalet.inputName = firstDatalet.inputName;
148         sharedDatalet.outputName = firstDatalet.outputName;
149         sharedDatalet.goldName = firstDatalet.goldName;
150         sharedDatalet.transformWrapper = transformWrapper;
151         sharedDatalet.setDescription(firstDatalet.getDescription());
152 
153 
154         // Prepare array to store all datalets for later joining
155         //  Note: store all but first datalet, which is used as
156         //  the common shared stylesheet/Templates
157         ThreadedTestletInfo[] testletThreads = new ThreadedTestletInfo[numDatalets - 1];
158 
159         // Iterate over every OTHER datalet and test it
160         for (int ctr = 1; ctr < numDatalets; ctr++)
161         {
162             try
163             {
164                 // Create ThreadedStylesheetDatalets from the common
165                 //  one we already have and each of the normal
166                 //  StylesheetDatalets that we were handed
167                 ThreadedStylesheetTestlet testlet = getTestlet(ctr);
168                 testlet.sharedDatalet = sharedDatalet;
169                 testlet.setDefaultDatalet((StylesheetDatalet)datalets.elementAt(ctr));
170                 testlet.threadIdentifier = ctr;
171                 // Save a copy of each datalet for later joining
172                 //  Note off-by-one necessary for arrays
173                 testletThreads[ctr - 1] = new ThreadedTestletInfo(testlet, new Thread(testlet));
174                 //@todo (optional) start this testlet - should allow
175                 //  user to start sequentially or all at once later
176                 ((testletThreads[ctr - 1]).thread).start();
177                 reporter.logMsg(Logger.INFOMSG, "Started testlet(" + ctr + ")");
178                 // Continue looping and creating each Testlet
179             }
180             catch (Throwable t)
181             {
182                 // Log any exceptions as fails and keep going
183                 //@todo improve the below to output more useful info
184                 reporter.checkFail("Datalet num " + ctr + " threw: " + t.toString());
185                 reporter.logThrowable(Logger.ERRORMSG, t, "Datalet threw");
186             }
187         }  // of while...
188 
189         // We now wait for every thread to finish, and only then
190         //  will we write a final report and finish the test
191         //@todo probably an easier way; for now, just join the last one
192         reporter.logMsg(Logger.STATUSMSG, "Driver Attempting-to-Join last thread");
193         long maxWaitMillis = 100000; // Wait at most xxxx milliseconds
194         // Try waiting for the last thread several times
195         testletThreads[testletThreads.length - 1].waitForComplete
196                 (reporter, maxWaitMillis, 10);
197         reporter.logMsg(Logger.TRACEMSG, "Driver Apparently-Joined last thread");
198 
199         // Also join all other threads
200         for (int i = 0; i < (testletThreads.length - 1); i++)
201         {
202             // Only wait a little while for these
203             testletThreads[i].waitForComplete(reporter, maxWaitMillis, 2);
204         }
205         reporter.logMsg(Logger.INFOMSG, "Driver Apparently-joined all threads");
206 
207         // Log results from all threads
208         for (int i = 0; i < testletThreads.length; i++)
209         {
210             switch (testletThreads[i].result)
211             {
212                 case Logger.PASS_RESULT:
213                     if (testletThreads[i].complete)
214                     {
215                         reporter.checkPass("Thread(" + i + ") " + testletThreads[i].lastStatus);
216                     }
217                     else
218                     {
219                         reporter.checkPass("Thread(" + i + ") " + testletThreads[i].lastStatus);
220                         //@todo What kind of status do we do here?
221                         // In theory the testlet successfully ran
222                         //  transforms and checked results, but
223                         //  for some reason we don't think the
224                         //  thread completed properly/in time
225                         reporter.checkErr("Thread(" + i + ") NOT COMPLETE! " + testletThreads[i].lastStatus);
226                     }
227                     break;
228                 case Logger.FAIL_RESULT:
229                     reporter.checkFail("Thread(" + i + ") " + testletThreads[i].lastStatus);
230                     break;
231                 case Logger.AMBG_RESULT:
232                     reporter.checkAmbiguous("Thread(" + i + ") " + testletThreads[i].lastStatus);
233                     break;
234                 case Logger.ERRR_RESULT:
235                     reporter.checkErr("Thread(" + i + ") " + testletThreads[i].lastStatus);
236                     break;
237                 case Logger.INCP_RESULT:
238                     reporter.checkErr("Thread(" + i + ") INCP! " + testletThreads[i].lastStatus);
239                     break;
240                 default:
241                     reporter.checkErr("Thread(" + i + ") BAD RESULT! " + testletThreads[i].lastStatus);
242                     break;
243             }
244             //@todo optimizaion: null out vars for gc?
245             //   or should we do this earlier?
246             testletThreads[i].thread = null;
247             testletThreads[i].testlet = null;
248         }
249         reporter.testCaseClose();
250     }
251 
252 
253     /**
254      * Convenience method to get a Testlet to use.
255      * Attempts to return one as specified by our testlet parameter,
256      * otherwise returns a default ThreadedStylesheetTestlet.
257      * Overrides StylesheetTestletDriver to use a separate logger
258      * for every testlet, since currently loggers may not be threadsafe.
259      * Note: We actually cheat and pass a Reporter to each Testlet,
260      * since they need to keep their own pass/fail state.
261      *
262      * @return Testlet for use in this test; null if error
263      */
getTestlet(int ctr)264     public ThreadedStylesheetTestlet getTestlet(int ctr)
265     {
266         // Find a Testlet class to use if we haven't already
267         if (null == cachedTestletClazz)
268         {
269             cachedTestletClazz = QetestUtils.testClassForName(testlet,
270                                                               QetestUtils.defaultPackages,
271                                                               defaultTestlet);
272         }
273         try
274         {
275             // Create it and set a new logger into it
276             ThreadedStylesheetTestlet t = (ThreadedStylesheetTestlet)cachedTestletClazz.newInstance();
277             //@todo Note assumption we have a file name to log to!
278             //  This is too tightly bound to the file-based Loggers
279             //  this design should be cleaned up so we don't know
280             //  what/where/how the loggers are outputing stuff, and
281             //  so each testlet can automatically get a new logger
282             //  based off of our logger
283             String testletLogFile = testProps.getProperty(Logger.OPT_LOGFILE, "threadedTestlet");
284             int idx = testletLogFile.lastIndexOf("."); // Assumption: there'll be a .extension
285             testletLogFile = testletLogFile.substring(0, idx) + ctr + testletLogFile.substring(idx);
286             Properties testletLoggerProperties = new Properties(testProps);
287             testletLoggerProperties.put(Logger.OPT_LOGFILE, testletLogFile);
288             t.setLogger(new Reporter(testletLoggerProperties));
289             return t;
290         }
291         catch (Exception e)
292         {
293             // Ooops, none found! This should be very rare, since
294             //  we know the defaultTestlet should be found
295             return null;
296         }
297     }
298 
299 
300     /**
301      * Local class to store info about threads we spawn.
302      * A simple little data holding class that stores info about
303      * various Threads we spawn (presumably each
304      * ThreadedStylesheetTestlets or the like).
305      * <ul>
306      * <li>ThreadedStylesheetTestlet</li>
307      * <li>Thread - the actual thread started</li>
308      * <li>lastStatus - copy of testlet.getDescription,
309      * which the testlet changes to reflect it's current state</li>
310      * <li>result - copy of testlet.getResult</li>
311      * <li>complete - convenience variable, if we think
312      * the testlet is done (or if we think it's ready to be
313      * reported on; may force this to true if we timeout or think
314      * the testlet/thread has hung, etc.)</li>
315      * </ul>
316      */
317     class ThreadedTestletInfo
318     {
319         ThreadedStylesheetTestlet testlet = null;
320         Thread thread = null;
321         String lastStatus = Logger.INCP;
322         int result = Logger.INCP_RESULT;
323         boolean complete = false;
ThreadedTestletInfo(ThreadedStylesheetTestlet tst, Thread t)324         ThreadedTestletInfo(ThreadedStylesheetTestlet tst,
325                             Thread t)
326         {
327             testlet = tst;
328             thread = t;
329             // Should help ease debugging
330             thread.setName(((StylesheetDatalet)testlet.getDefaultDatalet()).inputName);
331         }
332 
333         /**
334          * Attempt to wait for this thread to complete.
335          * Essentially does a .join on our thread, waiting
336          * millisWait.  If this doesn't work, it logs a message
337          * and then tries again waitNumTimes.  If the thread is
338          * still not done, then log a message and return anyway.
339          * //@todo should we kill the thread at this point?
340          */
waitForComplete(Logger l, long millisWait, int waitNumTimes)341         void waitForComplete(Logger l, long millisWait, int waitNumTimes)
342         {
343             // If we think we're already done, just return
344             //@todo ensure coordination between this and actual
345             //  Thread state; or remove this
346             if (complete)
347                 return;
348 
349             try
350             {
351                 thread.join(millisWait);
352             }
353             catch (InterruptedException ie)
354             {
355                 l.logMsg(Logger.WARNINGMSG, "waitForComplete threw: " + ie.toString());
356             }
357             if (!thread.isAlive())
358             {
359                 // If the Thread is already done, then copy over
360                 //  each value and return
361                 complete = true;
362                 lastStatus = testlet.getDescription();
363                 result = testlet.getResult();
364                 return;
365             }
366             // Otherwise, keep waiting for the thread
367             for (int i = 0; i < waitNumTimes; i++)
368             {
369                 l.logMsg(Logger.TRACEMSG, "waitForComplete(" + i + ") of "
370                          + thread.getName());
371                 try
372                 {
373                     thread.join(millisWait);
374                 }
375                 catch (InterruptedException ie)
376                 {
377                     l.logMsg(Logger.WARNINGMSG, "waitForComplete(" + i + ") threw: " + ie.toString());
378                 }
379             }
380             if (!thread.isAlive())
381             {
382                 // If the Thread is already done, then set complete
383                 //  (which means the thread did finish normally)
384                 complete = true;
385             }
386             // Copy over the rest of the testlet/thread's status
387             lastStatus = testlet.getDescription();
388             result = testlet.getResult();
389             return;
390         }
391     } // end of inner class ThreadedTestletInfo
392 
393 
394     /**
395      * Convenience method to print out usage information - update if needed.
396      * //@todo update this for iteration, etc.
397       * @return String denoting usage of this test class
398      */
usage()399     public String usage()
400     {
401         return ("Common [optional] options supported by ThreadedTestletDriver:\n"
402                 + "    -" + OPT_FILELIST
403                 + "  <name of listfile of tests to run>\n"
404                 + "    -" + OPT_DIRFILTER
405                 + "  <classname of FilenameFilter for dirs>\n"
406                 + "    -" + OPT_FILEFILTER
407                 + "  <classname of FilenameFilter for files>\n"
408                 + "    -" + OPT_TESTLET
409                 + "  <classname of Testlet to execute tests with>\n"
410                 + super.usage());   // Grab our parent classes usage as well
411     }
412 
413 
414     /**
415      * Main method to run test from the command line - can be left alone.
416      * @param args command line argument array
417      */
main(String[] args)418     public static void main(String[] args)
419     {
420         ThreadedTestletDriver app = new ThreadedTestletDriver();
421         app.doMain(args);
422     }
423 }
424