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 * ThreadedStylesheetTestlet.java 25 * 26 */ 27 package org.apache.qetest.xsl; 28 29 import java.io.File; 30 31 import org.apache.qetest.CheckService; 32 import org.apache.qetest.Datalet; 33 import org.apache.qetest.Logger; 34 import org.apache.qetest.TestletImpl; 35 import org.apache.qetest.xslwrapper.TransformWrapper; 36 import org.apache.qetest.xslwrapper.TransformWrapperFactory; 37 38 /** 39 * Testlet for basic thread testing of xsl stylesheet files. 40 * 41 * This class provides a simple testing algorithim for verifying 42 * that Xalan functions properly when run on multiple threads 43 * simultaneously. Currently it simply does most of what the 44 * normal StylesheetTestlet does, except it does it in the run() 45 * method on a thread - thus you'd commonly use this with 46 * ThreadedTestletDriver to start multiple of these testlets 47 * up at the same time. 48 * We implement Runnable, so classically a test driver would 49 * create us then pass us to new Thread(us).start(), instead 50 * of calling execute(). @todo find a better way to integrate! 51 * 52 * @author Shane_Curcuru@lotus.com 53 * @version $Id$ 54 */ 55 public class ThreadedStylesheetTestlet 56 extends TestletImpl 57 implements Runnable 58 { 59 // Initialize our classname for TestletImpl's main() method 60 static { thisClassName = "org.apache.qetest.xsl.ThreadedStylesheetTestlet"; } 61 62 // Initialize our defaultDatalet 63 { defaultDatalet = (Datalet)new StylesheetDatalet(); } 64 65 /** Accessor for our Datalet instead of calling execute(). */ setDefaultDatalet(StylesheetDatalet d)66 public void setDefaultDatalet(StylesheetDatalet d) 67 { 68 defaultDatalet = d; 69 } 70 71 /* Special ThreadedStylesheetDatalet with a Templates. */ 72 public ThreadedStylesheetDatalet sharedDatalet = new ThreadedStylesheetDatalet(); 73 74 /* Description of our current state; changes during our lifecycle. */ 75 protected String description = "ThreadedStylesheetTestlet - before execute()"; 76 77 /** 78 * Accesor method for a brief description of this test. 79 * When created, this returns a description of this testlet. 80 * After you've called execute(), this returns a brief 81 * description of our current result or status. 82 * 83 * @return String describing what this ThreadedStylesheetTestlet does. 84 * @see #getResult() 85 */ getDescription()86 public String getDescription() 87 { 88 return description; 89 } 90 91 /** 92 * Accesor method for a brief description of this test. 93 * Automatically adds our identifier at the start. 94 * 95 * @param d String to set as our current description. 96 */ setDescription(String d)97 protected void setDescription(String d) 98 { 99 description = "[" + threadIdentifier + "]" + d; 100 } 101 102 /* Our 'final' test result; actually changes during our lifecycle. */ 103 protected int result = Logger.DEFAULT_RESULT; 104 105 /** 106 * Accesor method for the final result of this test. 107 * Note: this starts as INCP_RESULT, and given that we're 108 * threaded, may end up as INCP_RESULT and you may not know 109 * the difference. Could use more thought. 110 * 111 * @return int one of of Logger.*_RESULT. 112 */ getResult()113 public int getResult() 114 { 115 return result; 116 } 117 118 /* Cheap-o counter: so driver can differentiate each thread. */ 119 public int threadIdentifier = 0; 120 121 /** 122 * Run this ThreadedStylesheetTestlet: start this test as 123 * a thread and return immediately. 124 * Note that you must join() this thread later if you want 125 * to wait until we're done. 126 * //@todo improve docs on how to communicate between threads 127 * 128 * @param Datalet to use as data point for the test. 129 */ execute(Datalet d)130 public void execute(Datalet d) 131 { 132 StylesheetDatalet datalet = null; 133 try 134 { 135 datalet = (StylesheetDatalet)d; 136 } 137 catch (ClassCastException e) 138 { 139 logger.checkErr("Datalet provided is not a StylesheetDatalet; cannot continue with " + d); 140 return; 141 } 142 143 logger.logMsg(Logger.STATUSMSG, "About to test: " 144 + (null == datalet.inputName 145 ? datalet.xmlName 146 : datalet.inputName) 147 + " plus " + sharedDatalet.xmlName); 148 149 // All the rest of the test is executed in our thread. 150 //if (true) //@todo check defaultDatalet.options... 151 // this.start(); 152 //@todo Um, how do we do this? Or do we just ask caller to do it? 153 logger.logMsg(Logger.CRITICALMSG, "//@todo execute() is not yet implemented - you must start our thread yourself"); 154 155 // Return to caller; they must join() us later if they 156 // want to know when we're actually complete 157 return; 158 } 159 160 /** 161 * Called by execute() to perform the looping and actual test. 162 * Note: You must have set our defaultDatalet first! 163 */ run()164 public void run() 165 { 166 // Relies on defaultDatalet being set! 167 logger.logMsg(Logger.STATUSMSG, "Beginning thread shared output into: " 168 + ((StylesheetDatalet)defaultDatalet).outputName); 169 // Also set our description so outside users know what 170 // point in our Thread lifetime we're at 171 setDescription("ThreadedStylesheetTestlet.run() just started..."); 172 173 StylesheetDatalet datalet = null; 174 try 175 { 176 datalet = (StylesheetDatalet)defaultDatalet; 177 } 178 catch (ClassCastException e) 179 { 180 setDescription("Datalet provided is not a StylesheetDatalet; cannot continue with " + datalet); 181 logger.checkErr(description); 182 return; 183 } 184 //@todo validate our Datalet - ensure it has valid 185 // and/or existing files available. 186 187 // Cleanup outName(s) only if asked to - delete the file on disk 188 // Optimization: this takes extra time and often is not 189 // needed, so only do this if the option is set 190 if ("true".equalsIgnoreCase(datalet.options.getProperty("deleteOutFile"))) 191 { 192 try 193 { 194 boolean btmp = (new File(datalet.outputName)).delete(); 195 logger.logMsg(Logger.TRACEMSG, "Deleting OutFile of::" + datalet.outputName 196 + " status: " + btmp); 197 } 198 catch (SecurityException se) 199 { 200 logger.logMsg(Logger.WARNINGMSG, "Deleting OutFile of::" + datalet.outputName 201 + " threw: " + se.toString()); 202 // But continue anyways... 203 } 204 //@todo make sure all sharedDatalets use different 205 // output files! No sense in having them use 206 // the same file all the time in all threads! 207 } 208 209 // Ask our independent datalet how many iterations to use 210 int iterations = sharedDatalet.iterations; // default value 211 try 212 { 213 iterations = Integer.parseInt(datalet.options.getProperty("iterations")); 214 } 215 catch (NumberFormatException numEx) 216 { 217 // no-op; leave as default 218 } 219 220 // Now loop a number of times as specified, and for each 221 // loop, use the presupplied Templates object, then run 222 // one independent process, plus validate on first & last 223 setDescription("...about to iterate... " + datalet.outputName); 224 for (int ctr = 1; (ctr <= iterations); ctr++) 225 { 226 // Only validate on first and last iteration 227 boolean doValidation = ((1 == ctr) || (iterations == ctr)); 228 logger.logMsg(Logger.TRACEMSG, "About to do iteration " + ctr); 229 // Note: logic moved to worker methods for clarity; 230 // these methods just use our local vars and datalet 231 //@todo Note: while I've tried to mirror as much 232 // structure from StylesheetTestlet as possible, 233 // this has made this class very inefficent (note 234 // we iterate, and within each method do the same 235 // name munging and some basic setup in each worker 236 // method every time!) 237 // Long-term, we should just redesign this to be 238 // a custom class with it's own algorithim 239 processExistingTemplates(sharedDatalet, doValidation); 240 processNewStylesheet(datalet, doValidation); 241 setDescription("...done iteration # " + ctr); 242 } 243 // That's it! We're done! 244 logger.logMsg(Logger.STATUSMSG, "Completed thread with: " + datalet.getDescription()); 245 setDescription("All iterations complete! " + datalet.outputName); 246 // Also set our result, now that we think we're done 247 try 248 { 249 result = ((org.apache.qetest.Reporter)logger).getCurrentCaseResult(); 250 } 251 catch (ClassCastException cce) 252 { 253 // Basically a no-op; just log out for info 254 logger.logMsg(Logger.WARNINGMSG, "logger is not a Reporter; overall result may be incorrect!"); 255 } 256 } 257 258 259 /** 260 * Worker method to process premade Templates object. 261 * Uses various local variables. 262 * //@todo Should we simply propagate exceptions instead of just logging them? 263 */ processExistingTemplates(ThreadedStylesheetDatalet datalet, boolean doValidation)264 private void processExistingTemplates(ThreadedStylesheetDatalet datalet, boolean doValidation) 265 { 266 // First: use our (presumably) shared Templates object to 267 // perform a Transformation - using the existing 268 // TransformWrapper object that already has built a stylesheet 269 if (!datalet.transformWrapper.isStylesheetReady()) 270 { 271 // Can't continue if the Templates is not ready 272 logger.logMsg(Logger.WARNINGMSG, "datalet shared Templates isStylesheetReady false!"); 273 // Anything else we should log out here? In case someone 274 // care about this, don't have it fail 275 return; 276 } 277 278 // Since the wrapper's ready (and flavor, etc. setup) then 279 // just go ahead and ask it to transform 280 try 281 { 282 String outputName = datalet.outputName + threadIdentifier; 283 284 //@todo Should we log a custom logElement here instead? 285 logger.logMsg(Logger.TRACEMSG, "About to test shared Templates: " 286 + " xmlName=" + datalet.xmlName 287 + " outputName=" + outputName 288 + " goldName=" + datalet.goldName); 289 290 // Simply have the wrapper do all the transforming 291 // or processing for us - we handle either normal .xsl 292 // stylesheet tests or just .xml embedded tests 293 long retVal = 0L; 294 // Here, we only use the existing Templates to do the transform 295 long[] times = datalet.transformWrapper.transformWithStylesheet(datalet.xmlName, outputName); 296 retVal = times[TransformWrapper.IDX_OVERALL]; 297 298 if (!doValidation) 299 { 300 logger.logMsg(Logger.TRACEMSG, "Skipping validation of outputName=" + outputName); 301 // Only bother to validate the output if asked 302 return; 303 } 304 // If we get here, attempt to validate the contents of 305 // the last outputFile created - only first and last time through loop! 306 CheckService fileChecker = (CheckService)datalet.options.get("fileCheckerImpl"); 307 // Supply default value 308 if (null == fileChecker) 309 fileChecker = new XHTFileCheckService(); 310 fileChecker.check(logger, 311 new File(outputName), 312 new File(datalet.goldName), 313 "Shared Templates of: " + datalet.getDescription()); 314 } 315 // Note that this class can only validate positive test 316 // cases - we don't handle ExpectedExceptions 317 catch (Throwable t) 318 { 319 // Put the logThrowable first, so it appears before 320 // the Fail record, and gets color-coded 321 logger.logThrowable(Logger.ERRORMSG, t, "Shared Templates of: " + datalet.getDescription()); 322 logger.checkFail("Shared Templates of: " + datalet.getDescription() 323 + " threw: " + t.toString()); 324 } 325 } 326 /** 327 * Worker method to process a new stylesheet/xml document. 328 * Uses various local variables. 329 * Note this is essentially a copy of StylesheetTestlet.execute(). 330 * //@todo Should we simply propagate exceptions instead of just logging them? 331 */ processNewStylesheet(StylesheetDatalet datalet, boolean doValidation)332 private void processNewStylesheet(StylesheetDatalet datalet, boolean doValidation) 333 { 334 // Test our supplied input file, and compare with gold 335 try 336 { 337 String outputName = datalet.outputName + threadIdentifier; 338 339 //@todo Should we log a custom logElement here instead? 340 logger.logMsg(Logger.TRACEMSG, "About to test: inputName=" + datalet.inputName 341 + " xmlName=" + datalet.xmlName + " outputName=" + outputName 342 + " goldName=" + datalet.goldName + " flavor=" + datalet.flavor); 343 344 // Create a new TransformWrapper of appropriate flavor 345 // null arg is unused liaison for TransformWrapper 346 TransformWrapper transformWrapper = null; 347 try 348 { 349 transformWrapper = TransformWrapperFactory.newWrapper(datalet.flavor); 350 transformWrapper.newProcessor(null); 351 } 352 catch (Throwable t) 353 { 354 logger.logThrowable(Logger.ERRORMSG, t, getDescription() + " newWrapper/newProcessor threw"); 355 logger.checkErr(getDescription() + " newWrapper/newProcessor threw: " + t.toString()); 356 return; 357 } 358 359 // Simply have the wrapper do all the transforming 360 // or processing for us - we handle either normal .xsl 361 // stylesheet tests or just .xml embedded tests 362 long retVal = 0L; 363 if (null == datalet.inputName) 364 { 365 // presume it's an embedded test 366 long [] times = transformWrapper.transformEmbedded(datalet.xmlName, outputName); 367 retVal = times[TransformWrapper.IDX_OVERALL]; 368 } 369 else 370 { 371 // presume it's a normal stylesheet test 372 long[] times = transformWrapper.transform(datalet.xmlName, datalet.inputName, outputName); 373 retVal = times[TransformWrapper.IDX_OVERALL]; 374 } 375 376 if (!doValidation) 377 { 378 logger.logMsg(Logger.TRACEMSG, "Skipping validation of outputName=" + outputName); 379 // Only bother to validate the output if asked 380 return; 381 } 382 // If we get here, attempt to validate the contents of 383 // the last outputFile created 384 CheckService fileChecker = (CheckService)datalet.options.get("fileCheckerImpl"); 385 // Supply default value 386 if (null == fileChecker) 387 fileChecker = new XHTFileCheckService(); 388 fileChecker.check(logger, 389 new File(outputName), 390 new File(datalet.goldName), 391 getDescription() + " " + datalet.getDescription()); 392 } 393 // Note that this class can only validate positive test 394 // cases - we don't handle ExpectedExceptions 395 catch (Throwable t) 396 { 397 // Put the logThrowable first, so it appears before 398 // the Fail record, and gets color-coded 399 logger.logThrowable(Logger.ERRORMSG, t, "New stylesheet of: " + datalet.getDescription()); 400 logger.checkFail("New stylesheet of: " + datalet.getDescription() 401 + " threw: " + t.toString()); 402 } 403 } 404 405 } // end of class ThreadedStylesheetTestlet 406 407