java/src/org/apache/qetest/xsl/BugzillaTestletDriver.java (247 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * $Id$ */ /* * * BugzillaTestletDriver.java * */ package org.apache.qetest.xsl; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.lang.reflect.Constructor; import java.util.Enumeration; import java.util.Properties; import java.util.Vector; import org.apache.qetest.Datalet; import org.apache.qetest.Logger; import org.apache.qetest.QetestUtils; import org.apache.qetest.Testlet; //------------------------------------------------------------------------- /** * Test driver for Bugzilla tests with .java/.xsl files.. * * This driver does not iterate over a directory tree; only * over a single directory. It supports either 'classic' tests * with matching .xsl/.xml/.out files like the conformance test, * or tests that also include a .java file that is the specific * testlet to execute for that test. * * * * @author shane_curcuru@lotus.com * @version $Id$ */ public class BugzillaTestletDriver extends StylesheetTestletDriver { /** Convenience constant: .java extension for Java Testlet source. */ public static final String JAVA_EXTENSION = ".java"; /** Convenience constant: Property key for java filenames. */ public static final String JAVA_SOURCE_NAME = "java.source.name"; /** Convenience constant: Default .xml file to use. */ public static final String DEFAULT_XML_FILE = "identity.xml"; /** * Default FilenameFilter FQCN for files - overridden. * By default, use a custom FilenameFilter that picks up * both .java and .xsl files, with slightly different * naming conventions than normal. */ protected String defaultFileFilter = "org.apache.qetest.xsl.BugzillaFileRules"; /** Just initialize test name, comment; numTestCases is not used. */ public BugzillaTestletDriver() { testName = "BugzillaTestletDriver"; testComment = "Test driver for Bugzilla tests with .java/.xsl files."; } /** * Special: test all Bugzilla* files in just the bugzilla directory. * This does not iterate down directories. * This is a specific test driver for testlets that may have * matching foo*.java and foo*.xml/xsl/out * Parameters: none, uses our internal members inputDir, * outputDir, testlet, etc. */ public void processInputDir() { // Ensure the inputDir is there - we must have a valid location for input files File testDirectory = new File(inputDir); if (!testDirectory.exists()) { // Try a default inputDir String oldInputDir = inputDir; // cache for potential error message testDirectory = new File((inputDir = getDefaultInputDir())); if (!testDirectory.exists()) { // No inputDir, can't do any tests! // @todo check if this is the best way to express this reporter.checkErr("inputDir(" + oldInputDir + ", or " + inputDir + ") does not exist, aborting!"); return; } } // Validate that each of the specified dirs exists // Returns directory references like so: // testDirectory = 0, outDirectory = 1, goldDirectory = 2 File[] dirs = validateDirs(new File[] { testDirectory }, new File[] { new File(outputDir), new File(goldDir) }); if (null == dirs) // this should never happen... { // No inputDir, can't do any tests! // @todo check if this is the best way to express this reporter.checkErr("inputDir(" + dirs[0] + ") does not exist, aborting!"); return; } // Call worker method to process the individual directory // and get a list of .java or .xsl files to test Vector files = getFilesFromDir(dirs[0], getFileFilter(), embedded); // 'Transform' the list of individual test files into a // list of Datalets with all fields filled in //@todo should getFilesFromDir and buildDatalets be combined? Vector datalets = buildDatalets(files, dirs[0], dirs[1], dirs[2]); if ((null == datalets) || (0 == datalets.size())) { // No tests, log error and return // other directories to test reporter.checkErr("inputDir(" + dirs[0] + ") did not contain any tests, aborting!"); return; } // Now process the list of files found in this dir processFileList(datalets, "Bugzilla tests of: " + dirs[0]); } /** * Run a list of bugzilla-specific tests. * Bugzilla tests may either be encoded as a .java file that * defines a Testlet, or as a normal .xsl/.xml file pair that * should simply be transformed simply, by a StylesheetTestlet. * * @param vector of Datalet objects to pass in * @param desc String to use as testCase description */ public void processFileList(Vector datalets, String desc) { // Validate arguments if ((null == datalets) || (0 == datalets.size())) { // Bad arguments, report it as an error // Note: normally, this should never happen, since // this class normally validates these arguments // before calling us reporter.checkErr("Testlet or datalets are null/blank, nothing to test!"); return; } // Now just go through the list and process each set int numDatalets = datalets.size(); reporter.logInfoMsg("processFileList() with " + numDatalets + " potential Bugzillas"); // Iterate over every datalet and test it for (int ctr = 0; ctr < numDatalets; ctr++) { // Factor out of try so it's available in catch Datalet d = (Datalet)datalets.elementAt(ctr); try { // Depending on the Datalet class, run a different algorithim if (d instanceof TraxDatalet) { // Assume we the datalet holds the name of a // .java file that's a testlet, and just // execute that itself // Note: Since they're packageless and have // hardcoded paths to the current dir, must // change user.dir each time in worker method Testlet t = getTestlet((TraxDatalet)d); // Each Bugzilla is it's own testcase reporter.testCaseInit(t.getDescription()); executeTestletInDir(t, d, inputDir); } else if (d instanceof StylesheetDatalet) { // Create plain Testlet to execute a test with this // next datalet - the Testlet will log all info // about the test, including calling check*() // Each Bugzilla is it's own testcase reporter.testCaseInit(d.getDescription()); getTestlet().execute(d); } else { reporter.checkErr("Unknown Datalet type: " + d); } } catch (Throwable t) { // Log any exceptions as fails and keep going //@todo further improve the below to output more useful info reporter.checkFail("Datalet num " + ctr + " (" + d.getDescription() + ") threw: " + t.toString()); reporter.logThrowable(Logger.ERRORMSG, t, "Datalet "+d.getDescription()+" threw"); } reporter.testCaseClose(); } // of while... } /** * Transform a vector of individual test names into a Vector * of filled-in datalets to be tested - Bugzilla-specific. * * This does special processing since we may either have .java * files that should be compiled, or we may have plain .xsl/.xml * file pairs that we should simpy execute through a default * StylesheetTestlet as-is. * This basically just calculates local path\filenames across * the three presumably-parallel directory trees of testLocation * (inputDir), outLocation (outputDir) and goldLocation * (forced to be same as inputDir). It then stuffs each of * these values plus some generic info like our testProps * into each datalet it creates. * * @param files Vector of local path\filenames to be tested * @param testLocation File denoting directory where all * .xml/.xsl tests are found * @param outLocation File denoting directory where all * output files should be put * @param goldLocation File denoting directory where all * gold files are found - IGNORED; forces testLocation instead * @return Vector of StylesheetDatalets that are fully filled in, * i.e. outputName, goldName, etc are filled in respectively * to inputName */ public Vector buildDatalets(Vector files, File testLocation, File outLocation, File goldLocation) { // Validate arguments if ((null == files) || (files.size() < 1)) { // Bad arguments, report it as an error // Note: normally, this should never happen, since // this class normally validates these arguments // before calling us reporter.logWarningMsg("buildDatalets null or empty file vector"); return null; } Vector v = new Vector(files.size()); int xslCtr = 0; int javaCtr = 0; // For every file in the vector, construct the matching // out, gold, and xml/xsl files; plus see if we have // a .java file as well for (Enumeration elements = files.elements(); elements.hasMoreElements(); /* no increment portion */ ) { String file = null; try { file = (String)elements.nextElement(); } catch (ClassCastException cce) { // Just skip this entry reporter.logWarningMsg("Bad file element found, skipping: " + cce.toString()); continue; } Datalet d = null; // If it's a .java file: just set java.source.name/java.class.name if (file.endsWith(JAVA_EXTENSION)) { // Use TraxDatalets if we have .java d = new TraxDatalet(); ((TraxDatalet)d).options = new Properties(testProps); ((TraxDatalet)d).options.put("java.source.dir", testLocation); ((TraxDatalet)d).options.put(JAVA_SOURCE_NAME, file); ((TraxDatalet)d).options.put("fileCheckerImpl", fileChecker); // That's it - when we execute tests later on, if // there's a JAVA_SOURCE_NAME we simply use that to // find the testlet to execute javaCtr++; } // If it's a .xsl file, just set the filenames as usual else if (file.endsWith(XSL_EXTENSION)) { // Use plain StylesheetDatalets if we just have .xsl d = new StylesheetDatalet(); ((StylesheetDatalet)d).inputName = testLocation.getPath() + File.separator + file; String fileNameRoot = file.substring(0, file.indexOf(XSL_EXTENSION)); // Check for existence of xml - if not there, then set to some default //@todo this would be a perfect use of TraxDatalet.setXMLString() String xmlFileName = testLocation.getPath() + File.separator + fileNameRoot + XML_EXTENSION; if ((new File(xmlFileName)).exists()) { ((StylesheetDatalet)d).xmlName = xmlFileName; } else { ((StylesheetDatalet)d).xmlName = testLocation.getPath() + File.separator + DEFAULT_XML_FILE; } ((StylesheetDatalet)d).outputName = outLocation.getPath() + File.separator + fileNameRoot + OUT_EXTENSION; ((StylesheetDatalet)d).goldName = testLocation.getPath() + File.separator + fileNameRoot + OUT_EXTENSION; ((StylesheetDatalet)d).flavor = flavor; ((StylesheetDatalet)d).options = new Properties(testProps); ((StylesheetDatalet)d).options.put("fileCheckerImpl", fileChecker); // These tests will be run by a plain StylesheetTestlet xslCtr++; } else { // Hmmm - I'm not sure what we should do here reporter.logWarningMsg("Unexpected test file found, skipping: " + file); continue; } d.setDescription(file); v.addElement(d); } reporter.logTraceMsg("Bugzilla buildDatalets with " + javaCtr + " .java Testlets, and " + xslCtr + " .xsl files to test"); return v; } /** * Execute a Testlet with a specific user.dir. * Bugzilla testlets hardcode their input file names, assuming * they're in the current directory. But this automation is * frequently run in another directory, and uses the inputDir * setting to point where the files are. Hence this worker * method to change user.dir, execute the Testlet, and then * switch back. * Note: will not work in Applet context, obviously. * * @param t Testlet to execute * @param dir to change user.dir to first * @throws propagates any non-user.dir exceptions */ public void executeTestletInDir(Testlet t, Datalet d, String dir) throws Exception { final String USER_DIR = "user.dir"; try { // Note: we must actually keep a cloned copy of the // whole system properties block to replace later // in case a Bugzilla testlet changes any other // properties during it's execution Properties p = System.getProperties(); Properties cacheProps = (Properties)p.clone(); // This should, I hope, properly get the correct path // for what the inputDir would be, whether it's a // relative or absolute path from where we are now File f = new File(inputDir); try { // Note the canonical form seems to be the most reliable for our purpose p.put(USER_DIR, f.getCanonicalPath()); } catch (IOException ioe) { p.put(USER_DIR, f.getAbsolutePath()); } System.setProperties(p); // Now just execute the Testlet from here t.execute(d); // Replace the system properties to be polite! System.setProperties(cacheProps); } catch (SecurityException se) { reporter.logThrowable(Logger.ERRORMSG, se, "executeTestletInDir threw"); reporter.checkErr("executeTestletInDir threw :" + se + " cannot execute Testlet in correct dir " + dir); } } /** * Convenience method to get a Bugzilla Testlet to use. * Take the TraxDatalet given and find the java classname * from it. Then just load an instance of that Testlet class. * * @return Testlet for use in this test; null if error */ public Testlet getTestlet(TraxDatalet d) { try { // Calculate the java classname String testletSourceName = (String)d.options.get(JAVA_SOURCE_NAME); // Potential problem: what if the SourceName doesn't have .java at end? String testletClassName = testletSourceName.substring(0, testletSourceName.indexOf(JAVA_EXTENSION)); //@todo should we attempt to compile to a .class file // if we can't find the class here? This adds a bunch // of complexity here; so I'm thinking it's better to // simply require the user to 'build all' first Class testletClazz = Class.forName(testletClassName); // Create it and set our reporter into it Testlet t = (Testlet)testletClazz.newInstance(); t.setLogger((Logger)reporter); return (Testlet)t; } catch (Exception e) { // Ooops, none found, log an error reporter.logThrowable(Logger.ERRORMSG, e, "getTestlet(d) threw"); reporter.checkErr("getTestlet(d) threw: " + e.toString()); return null; } } /** * Convenience method to get a default filter for files. * Returns special file filter for our use. * * @return FilenameFilter using BugzillaFileRules(excludes). */ public FilenameFilter getFileFilter() { // Find a Testlet class to use Class clazz = QetestUtils.testClassForName("org.apache.qetest.xsl.BugzillaFileRules", QetestUtils.defaultPackages, defaultFileFilter); try { // Create it, optionally with a category String excludes = testProps.getProperty(OPT_EXCLUDES); if ((null != excludes) && (excludes.length() > 1)) // Arbitrary check for non-null, non-blank string { Class[] parameterTypes = { java.lang.String.class }; Constructor ctor = clazz.getConstructor(parameterTypes); Object[] ctorArgs = { excludes }; return (FilenameFilter) ctor.newInstance(ctorArgs); } else { return (FilenameFilter)clazz.newInstance(); } } catch (Exception e) { // Ooops, none found! return null; } } /** * Convenience method to get a default inputDir when none or * a bad one was given. * @return String pathname of default inputDir "tests\bugzilla". */ public String getDefaultInputDir() { return "tests" + File.separator + "bugzilla"; } /** * Convenience method to print out usage information - update if needed. * @return String denoting usage of this test class */ public String usage() { return ("Additional options supported by BugzillaTestletDriver:\n" + " (Note: assumes inputDir=test/tests/bugzilla)" + " (Note: we do *not* support -embedded)" + super.usage()); // Grab our parent classes usage as well } /** * Main method to run test from the command line - can be left alone. * @param args command line argument array */ public static void main(String[] args) { BugzillaTestletDriver app = new BugzillaTestletDriver(); app.doMain(args); } }