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 * BugzillaTestletDriver.java 25 * 26 */ 27 package org.apache.qetest.xsl; 28 29 import java.io.File; 30 import java.io.FilenameFilter; 31 import java.io.IOException; 32 import java.lang.reflect.Constructor; 33 import java.util.Enumeration; 34 import java.util.Properties; 35 import java.util.Vector; 36 37 import org.apache.qetest.Datalet; 38 import org.apache.qetest.Logger; 39 import org.apache.qetest.QetestUtils; 40 import org.apache.qetest.Testlet; 41 42 //------------------------------------------------------------------------- 43 44 /** 45 * Test driver for Bugzilla tests with .java/.xsl files.. 46 * 47 * This driver does not iterate over a directory tree; only 48 * over a single directory. It supports either 'classic' tests 49 * with matching .xsl/.xml/.out files like the conformance test, 50 * or tests that also include a .java file that is the specific 51 * testlet to execute for that test. 52 * 53 * 54 * 55 * @author shane_curcuru@lotus.com 56 * @version $Id$ 57 */ 58 public class BugzillaTestletDriver extends StylesheetTestletDriver 59 { 60 61 /** Convenience constant: .java extension for Java Testlet source. */ 62 public static final String JAVA_EXTENSION = ".java"; 63 64 /** Convenience constant: Property key for java filenames. */ 65 public static final String JAVA_SOURCE_NAME = "java.source.name"; 66 67 /** Convenience constant: Default .xml file to use. */ 68 public static final String DEFAULT_XML_FILE = "identity.xml"; 69 70 /** 71 * Default FilenameFilter FQCN for files - overridden. 72 * By default, use a custom FilenameFilter that picks up 73 * both .java and .xsl files, with slightly different 74 * naming conventions than normal. 75 */ 76 protected String defaultFileFilter = "org.apache.qetest.xsl.BugzillaFileRules"; 77 78 79 /** Just initialize test name, comment; numTestCases is not used. */ BugzillaTestletDriver()80 public BugzillaTestletDriver() 81 { 82 testName = "BugzillaTestletDriver"; 83 testComment = "Test driver for Bugzilla tests with .java/.xsl files."; 84 } 85 86 87 /** 88 * Special: test all Bugzilla* files in just the bugzilla directory. 89 * This does not iterate down directories. 90 * This is a specific test driver for testlets that may have 91 * matching foo*.java and foo*.xml/xsl/out 92 * Parameters: none, uses our internal members inputDir, 93 * outputDir, testlet, etc. 94 */ processInputDir()95 public void processInputDir() 96 { 97 // Ensure the inputDir is there - we must have a valid location for input files 98 File testDirectory = new File(inputDir); 99 100 if (!testDirectory.exists()) 101 { 102 // Try a default inputDir 103 String oldInputDir = inputDir; // cache for potential error message 104 testDirectory = new File((inputDir = getDefaultInputDir())); 105 if (!testDirectory.exists()) 106 { 107 // No inputDir, can't do any tests! 108 // @todo check if this is the best way to express this 109 reporter.checkErr("inputDir(" + oldInputDir 110 + ", or " + inputDir + ") does not exist, aborting!"); 111 return; 112 } 113 } 114 115 // Validate that each of the specified dirs exists 116 // Returns directory references like so: 117 // testDirectory = 0, outDirectory = 1, goldDirectory = 2 118 File[] dirs = validateDirs(new File[] { testDirectory }, 119 new File[] { new File(outputDir), new File(goldDir) }); 120 121 if (null == dirs) // this should never happen... 122 { 123 // No inputDir, can't do any tests! 124 // @todo check if this is the best way to express this 125 reporter.checkErr("inputDir(" + dirs[0] + ") does not exist, aborting!"); 126 return; 127 } 128 129 // Call worker method to process the individual directory 130 // and get a list of .java or .xsl files to test 131 Vector files = getFilesFromDir(dirs[0], getFileFilter(), embedded); 132 133 // 'Transform' the list of individual test files into a 134 // list of Datalets with all fields filled in 135 //@todo should getFilesFromDir and buildDatalets be combined? 136 Vector datalets = buildDatalets(files, dirs[0], dirs[1], dirs[2]); 137 138 if ((null == datalets) || (0 == datalets.size())) 139 { 140 // No tests, log error and return 141 // other directories to test 142 reporter.checkErr("inputDir(" + dirs[0] + ") did not contain any tests, aborting!"); 143 return; 144 } 145 146 // Now process the list of files found in this dir 147 processFileList(datalets, "Bugzilla tests of: " + dirs[0]); 148 } 149 150 151 /** 152 * Run a list of bugzilla-specific tests. 153 * Bugzilla tests may either be encoded as a .java file that 154 * defines a Testlet, or as a normal .xsl/.xml file pair that 155 * should simply be transformed simply, by a StylesheetTestlet. 156 * 157 * @param vector of Datalet objects to pass in 158 * @param desc String to use as testCase description 159 */ processFileList(Vector datalets, String desc)160 public void processFileList(Vector datalets, String desc) 161 { 162 // Validate arguments 163 if ((null == datalets) || (0 == datalets.size())) 164 { 165 // Bad arguments, report it as an error 166 // Note: normally, this should never happen, since 167 // this class normally validates these arguments 168 // before calling us 169 reporter.checkErr("Testlet or datalets are null/blank, nothing to test!"); 170 return; 171 } 172 173 // Now just go through the list and process each set 174 int numDatalets = datalets.size(); 175 reporter.logInfoMsg("processFileList() with " + numDatalets 176 + " potential Bugzillas"); 177 // Iterate over every datalet and test it 178 for (int ctr = 0; ctr < numDatalets; ctr++) 179 { 180 try 181 { 182 // Depending on the Datalet class, run a different algorithim 183 Datalet d = (Datalet)datalets.elementAt(ctr); 184 if (d instanceof TraxDatalet) 185 { 186 // Assume we the datalet holds the name of a 187 // .java file that's a testlet, and just 188 // execute that itself 189 // Note: Since they're packageless and have 190 // hardcoded paths to the current dir, must 191 // change user.dir each time in worker method 192 Testlet t = getTestlet((TraxDatalet)d); 193 // Each Bugzilla is it's own testcase 194 reporter.testCaseInit(t.getDescription()); 195 executeTestletInDir(t, d, inputDir); 196 } 197 else if (d instanceof StylesheetDatalet) 198 { 199 // Create plain Testlet to execute a test with this 200 // next datalet - the Testlet will log all info 201 // about the test, including calling check*() 202 // Each Bugzilla is it's own testcase 203 reporter.testCaseInit(d.getDescription()); 204 getTestlet().execute(d); 205 } 206 else 207 { 208 reporter.checkErr("Unknown Datalet type: " + d); 209 } 210 } 211 catch (Throwable t) 212 { 213 // Log any exceptions as fails and keep going 214 //@todo improve the below to output more useful info 215 reporter.checkFail("Datalet num " + ctr + " threw: " + t.toString()); 216 reporter.logThrowable(Logger.ERRORMSG, t, "Datalet threw"); 217 } 218 reporter.testCaseClose(); 219 } // of while... 220 } 221 222 223 /** 224 * Transform a vector of individual test names into a Vector 225 * of filled-in datalets to be tested - Bugzilla-specific. 226 * 227 * This does special processing since we may either have .java 228 * files that should be compiled, or we may have plain .xsl/.xml 229 * file pairs that we should simpy execute through a default 230 * StylesheetTestlet as-is. 231 * This basically just calculates local path\filenames across 232 * the three presumably-parallel directory trees of testLocation 233 * (inputDir), outLocation (outputDir) and goldLocation 234 * (forced to be same as inputDir). It then stuffs each of 235 * these values plus some generic info like our testProps 236 * into each datalet it creates. 237 * 238 * @param files Vector of local path\filenames to be tested 239 * @param testLocation File denoting directory where all 240 * .xml/.xsl tests are found 241 * @param outLocation File denoting directory where all 242 * output files should be put 243 * @param goldLocation File denoting directory where all 244 * gold files are found - IGNORED; forces testLocation instead 245 * @return Vector of StylesheetDatalets that are fully filled in, 246 * i.e. outputName, goldName, etc are filled in respectively 247 * to inputName 248 */ buildDatalets(Vector files, File testLocation, File outLocation, File goldLocation)249 public Vector buildDatalets(Vector files, File testLocation, 250 File outLocation, File goldLocation) 251 { 252 // Validate arguments 253 if ((null == files) || (files.size() < 1)) 254 { 255 // Bad arguments, report it as an error 256 // Note: normally, this should never happen, since 257 // this class normally validates these arguments 258 // before calling us 259 reporter.logWarningMsg("buildDatalets null or empty file vector"); 260 return null; 261 } 262 Vector v = new Vector(files.size()); 263 int xslCtr = 0; 264 int javaCtr = 0; 265 266 // For every file in the vector, construct the matching 267 // out, gold, and xml/xsl files; plus see if we have 268 // a .java file as well 269 for (Enumeration elements = files.elements(); 270 elements.hasMoreElements(); /* no increment portion */ ) 271 { 272 String file = null; 273 try 274 { 275 file = (String)elements.nextElement(); 276 } 277 catch (ClassCastException cce) 278 { 279 // Just skip this entry 280 reporter.logWarningMsg("Bad file element found, skipping: " + cce.toString()); 281 continue; 282 } 283 284 Datalet d = null; 285 // If it's a .java file: just set java.source.name/java.class.name 286 if (file.endsWith(JAVA_EXTENSION)) 287 { 288 // Use TraxDatalets if we have .java 289 d = new TraxDatalet(); 290 ((TraxDatalet)d).options = new Properties(testProps); 291 ((TraxDatalet)d).options.put("java.source.dir", testLocation); 292 ((TraxDatalet)d).options.put(JAVA_SOURCE_NAME, file); 293 ((TraxDatalet)d).options.put("fileCheckerImpl", fileChecker); 294 // That's it - when we execute tests later on, if 295 // there's a JAVA_SOURCE_NAME we simply use that to 296 // find the testlet to execute 297 javaCtr++; 298 } 299 // If it's a .xsl file, just set the filenames as usual 300 else if (file.endsWith(XSL_EXTENSION)) 301 { 302 // Use plain StylesheetDatalets if we just have .xsl 303 d = new StylesheetDatalet(); 304 ((StylesheetDatalet)d).inputName = testLocation.getPath() + File.separator + file; 305 306 String fileNameRoot = file.substring(0, file.indexOf(XSL_EXTENSION)); 307 // Check for existence of xml - if not there, then set to some default 308 //@todo this would be a perfect use of TraxDatalet.setXMLString() 309 String xmlFileName = testLocation.getPath() + File.separator + fileNameRoot + XML_EXTENSION; 310 if ((new File(xmlFileName)).exists()) 311 { 312 ((StylesheetDatalet)d).xmlName = xmlFileName; 313 } 314 else 315 { 316 ((StylesheetDatalet)d).xmlName = testLocation.getPath() + File.separator + DEFAULT_XML_FILE; 317 } 318 ((StylesheetDatalet)d).outputName = outLocation.getPath() + File.separator + fileNameRoot + OUT_EXTENSION; 319 ((StylesheetDatalet)d).goldName = testLocation.getPath() + File.separator + fileNameRoot + OUT_EXTENSION; 320 ((StylesheetDatalet)d).flavor = flavor; 321 ((StylesheetDatalet)d).options = new Properties(testProps); 322 ((StylesheetDatalet)d).options.put("fileCheckerImpl", fileChecker); 323 // These tests will be run by a plain StylesheetTestlet 324 xslCtr++; 325 } 326 else 327 { 328 // Hmmm - I'm not sure what we should do here 329 reporter.logWarningMsg("Unexpected test file found, skipping: " + file); 330 continue; 331 } 332 d.setDescription(file); 333 v.addElement(d); 334 } 335 reporter.logTraceMsg("Bugzilla buildDatalets with " + javaCtr 336 + " .java Testlets, and " + xslCtr + " .xsl files to test"); 337 return v; 338 } 339 340 341 /** 342 * Execute a Testlet with a specific user.dir. 343 * Bugzilla testlets hardcode their input file names, assuming 344 * they're in the current directory. But this automation is 345 * frequently run in another directory, and uses the inputDir 346 * setting to point where the files are. Hence this worker 347 * method to change user.dir, execute the Testlet, and then 348 * switch back. 349 * Note: will not work in Applet context, obviously. 350 * 351 * @param t Testlet to execute 352 * @param dir to change user.dir to first 353 * @throws propagates any non-user.dir exceptions 354 */ executeTestletInDir(Testlet t, Datalet d, String dir)355 public void executeTestletInDir(Testlet t, Datalet d, String dir) 356 throws Exception 357 { 358 final String USER_DIR = "user.dir"; 359 try 360 { 361 // Note: we must actually keep a cloned copy of the 362 // whole system properties block to replace later 363 // in case a Bugzilla testlet changes any other 364 // properties during it's execution 365 Properties p = System.getProperties(); 366 Properties cacheProps = (Properties)p.clone(); 367 // This should, I hope, properly get the correct path 368 // for what the inputDir would be, whether it's a 369 // relative or absolute path from where we are now 370 File f = new File(inputDir); 371 try 372 { 373 // Note the canonical form seems to be the most reliable for our purpose 374 p.put(USER_DIR, f.getCanonicalPath()); 375 } 376 catch (IOException ioe) 377 { 378 p.put(USER_DIR, f.getAbsolutePath()); 379 } 380 System.setProperties(p); 381 382 // Now just execute the Testlet from here 383 t.execute(d); 384 385 // Replace the system properties to be polite! 386 System.setProperties(cacheProps); 387 } 388 catch (SecurityException se) 389 { 390 reporter.logThrowable(Logger.ERRORMSG, se, "executeTestletInDir threw"); 391 reporter.checkErr("executeTestletInDir threw :" + se 392 + " cannot execute Testlet in correct dir " + dir); 393 } 394 } 395 396 397 /** 398 * Convenience method to get a Bugzilla Testlet to use. 399 * Take the TraxDatalet given and find the java classname 400 * from it. Then just load an instance of that Testlet class. 401 * 402 * @return Testlet for use in this test; null if error 403 */ getTestlet(TraxDatalet d)404 public Testlet getTestlet(TraxDatalet d) 405 { 406 try 407 { 408 // Calculate the java classname 409 String testletSourceName = (String)d.options.get(JAVA_SOURCE_NAME); 410 // Potential problem: what if the SourceName doesn't have .java at end? 411 String testletClassName = testletSourceName.substring(0, testletSourceName.indexOf(JAVA_EXTENSION)); 412 //@todo should we attempt to compile to a .class file 413 // if we can't find the class here? This adds a bunch 414 // of complexity here; so I'm thinking it's better to 415 // simply require the user to 'build all' first 416 Class testletClazz = Class.forName(testletClassName); 417 // Create it and set our reporter into it 418 Testlet t = (Testlet)testletClazz.newInstance(); 419 t.setLogger((Logger)reporter); 420 return (Testlet)t; 421 } 422 catch (Exception e) 423 { 424 // Ooops, none found, log an error 425 reporter.logThrowable(Logger.ERRORMSG, e, "getTestlet(d) threw"); 426 reporter.checkErr("getTestlet(d) threw: " + e.toString()); 427 return null; 428 } 429 } 430 431 432 /** 433 * Convenience method to get a default filter for files. 434 * Returns special file filter for our use. 435 * 436 * @return FilenameFilter using BugzillaFileRules(excludes). 437 */ getFileFilter()438 public FilenameFilter getFileFilter() 439 { 440 // Find a Testlet class to use 441 Class clazz = QetestUtils.testClassForName("org.apache.qetest.xsl.BugzillaFileRules", 442 QetestUtils.defaultPackages, 443 defaultFileFilter); 444 try 445 { 446 // Create it, optionally with a category 447 String excludes = testProps.getProperty(OPT_EXCLUDES); 448 if ((null != excludes) && (excludes.length() > 1)) // Arbitrary check for non-null, non-blank string 449 { 450 Class[] parameterTypes = { java.lang.String.class }; 451 Constructor ctor = clazz.getConstructor(parameterTypes); 452 453 Object[] ctorArgs = { excludes }; 454 return (FilenameFilter) ctor.newInstance(ctorArgs); 455 } 456 else 457 { 458 return (FilenameFilter)clazz.newInstance(); 459 } 460 } 461 catch (Exception e) 462 { 463 // Ooops, none found! 464 return null; 465 } 466 } 467 468 469 /** 470 * Convenience method to get a default inputDir when none or 471 * a bad one was given. 472 * @return String pathname of default inputDir "tests\bugzilla". 473 */ getDefaultInputDir()474 public String getDefaultInputDir() 475 { 476 return "tests" + File.separator + "bugzilla"; 477 } 478 479 480 /** 481 * Convenience method to print out usage information - update if needed. 482 * @return String denoting usage of this test class 483 */ usage()484 public String usage() 485 { 486 return ("Additional options supported by BugzillaTestletDriver:\n" 487 + " (Note: assumes inputDir=test/tests/bugzilla)" 488 + " (Note: we do *not* support -embedded)" 489 + super.usage()); // Grab our parent classes usage as well 490 } 491 492 493 /** 494 * Main method to run test from the command line - can be left alone. 495 * @param args command line argument array 496 */ main(String[] args)497 public static void main(String[] args) 498 { 499 BugzillaTestletDriver app = new BugzillaTestletDriver(); 500 app.doMain(args); 501 } 502 } 503