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