]> source.dussan.org Git - aspectj.git/commitdiff
mavenizing testing-drivers - done'
authorAndy Clement <aclement@pivotal.io>
Thu, 31 Jan 2019 00:50:34 +0000 (16:50 -0800)
committerAndy Clement <aclement@pivotal.io>
Thu, 31 Jan 2019 00:50:34 +0000 (16:50 -0800)
testing-drivers/pom.xml
testing-drivers/src/main/java/org/aspectj/testing/drivers/Harness.java [deleted file]
testing-drivers/src/main/java/org/aspectj/testing/drivers/package.html [deleted file]
testing-drivers/src/test/java/org/aspectj/testing/drivers/Harness.java [new file with mode: 0644]
testing-drivers/src/test/java/org/aspectj/testing/drivers/package.html [new file with mode: 0644]

index a4c399ae3202ffe7a42bd9ffcc9531f08fe53465..13e83f066fcf593b2cbfe884e75ab51bccc1823e 100644 (file)
       <scope>system</scope>
       <systemPath>${project.basedir}/../lib/commons/commons.jar</systemPath>
     </dependency>
+    <dependency>
+      <groupId>org.aspectj</groupId>
+      <artifactId>testing</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+    </dependency>
   </dependencies>
 </project>
diff --git a/testing-drivers/src/main/java/org/aspectj/testing/drivers/Harness.java b/testing-drivers/src/main/java/org/aspectj/testing/drivers/Harness.java
deleted file mode 100644 (file)
index 09b8a71..0000000
+++ /dev/null
@@ -1,1485 +0,0 @@
-/* *******************************************************************
- * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC),
- *               2003 Contributors.
- * All rights reserved. 
- * This program and the accompanying materials are made available 
- * under the terms of the Eclipse Public License v1.0 
- * which accompanies this distribution and is available at 
- * http://www.eclipse.org/legal/epl-v10.html 
- *  
- * Contributors: 
- *     Xerox/PARC     initial implementation 
- *     Wes Isberg     2003 changes.
- * ******************************************************************/
-
-package org.aspectj.testing.drivers;
-
-import org.aspectj.bridge.IMessage;
-import org.aspectj.bridge.IMessageHolder;
-import org.aspectj.bridge.MessageHandler;
-import org.aspectj.bridge.MessageUtil;
-import org.aspectj.testing.harness.bridge.AbstractRunSpec;
-import org.aspectj.testing.harness.bridge.AjcTest;
-import org.aspectj.testing.harness.bridge.CompilerRun;
-import org.aspectj.testing.harness.bridge.FlatSuiteReader;
-import org.aspectj.testing.harness.bridge.IncCompilerRun;
-import org.aspectj.testing.harness.bridge.JavaRun;
-import org.aspectj.testing.harness.bridge.RunSpecIterator;
-import org.aspectj.testing.harness.bridge.Sandbox;
-import org.aspectj.testing.harness.bridge.Validator;
-import org.aspectj.testing.run.IRun;
-import org.aspectj.testing.run.IRunIterator;
-import org.aspectj.testing.run.IRunListener;
-import org.aspectj.testing.run.IRunStatus;
-import org.aspectj.testing.run.IRunValidator;
-import org.aspectj.testing.run.RunListener;
-import org.aspectj.testing.run.RunStatus;
-import org.aspectj.testing.run.RunValidator;
-import org.aspectj.testing.run.Runner;
-import org.aspectj.testing.util.BridgeUtil;
-import org.aspectj.testing.util.RunUtils;
-import org.aspectj.testing.util.StreamsHandler;
-import org.aspectj.testing.util.StreamsHandler.Result;
-import org.aspectj.testing.xml.AjcSpecXmlReader;
-import org.aspectj.testing.xml.XMLWriter;
-import org.aspectj.util.FileUtil;
-import org.aspectj.util.LangUtil;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.io.PrintWriter;
-import java.text.DecimalFormat;
-import java.text.SimpleDateFormat;
-import java.util.*;
-
-/**
- * Test harness for running AjcTest.Suite test suites.
- * This can be easily extended by subclassing.
- * <ul>
- * <li>template algorithms for reading arguments, printing syntax,
- *     reading suites, and reporting results all
- *     delegate to methods that subclasses can override to support
- *     additional arguments or different reporting.</li>
- * <li>implement arbitrary features as IRunListeners</li>
- * <li>support single-option aliases to any number of single-options </li>
- * </ul>
- * See {@link report(IRunStatus, int, int)} for an explanation of test result
- * categories.
- */
-public class Harness {
-    /** 
-     * Spaces up to the width that an option should take in the syntax,
-     * including the two-space leader
-     */
-    protected static final String SYNTAX_PAD = "                    "; 
-    protected static final String OPTION_DELIM = ";";
-    private static final String JAVA_VERSION;
-    private static final String ASPECTJ_VERSION;
-    static {
-        String version = "UNKNOWN";
-        try {  version = System.getProperty("java.version", "UNKNOWN"); }
-        catch (Throwable t) {}
-        JAVA_VERSION = version;
-        
-        version = "UNKNOWN";
-        try {
-            Class c = Class.forName("org.aspectj.bridge.Version");
-            version = (String) c.getField("text").get(null);            
-        } catch (Throwable t) {
-            // ignore
-        }
-        ASPECTJ_VERSION = version;
-    }
-    
-    /** factory for the subclass currently anointed as default */
-    public static Harness makeHarness() {
-        return new FeatureHarness();
-    }
-    
-    /** @param args String[] like runMain(String[]) args */
-    public static void main(String[] args) throws Exception {
-        if (LangUtil.isEmpty(args)) {
-            File argFile = new File("HarnessArgs.txt");
-            if (argFile.canRead()) {
-                args = readArgs(argFile);
-            } else {
-                args = new String[] { "-help" };
-            }
-        }
-        makeHarness().runMain(args, null);
-    }
-
-    /**
-     * Get known option aliases.
-     * Subclasses may add new aliases, where the key is the alias option,
-     * and the value is a comma-delimited String of target options.
-     * @return Properties with feature aliases or null
-     */
-    protected static Properties getOptionAliases() {
-        if (null == optionAliases) {
-            optionAliases = new Properties();
-            // XXX load from **OptionAliases.properties
-        }
-        return optionAliases;
-    }
-
-    /**
-     * Read argFile contents into String[],
-     * delimiting at any whitespace
-     */ 
-    private static String[] readArgs(File argFile) {
-        ArrayList args = new ArrayList();
-//        int lineNum = 0;
-
-        try {
-            BufferedReader stream =
-                new BufferedReader(new FileReader(argFile));
-            String line;
-            while (null != (line = stream.readLine())) {
-                StringTokenizer st = new StringTokenizer(line);
-                while (st.hasMoreTokens()) {
-                    args.add(st.nextToken());
-                }
-            }
-        } catch (IOException e) {
-            e.printStackTrace(System.err);
-        }    
-        return (String[]) args.toArray(new String[0]);
-    }
-    
-    /** aliases key="option" value="option{,option}" */
-    private static Properties optionAliases;
-    
-    /** be extra noisy if true */
-    private boolean verboseHarness;
-    
-    /** be extra quiet if true */
-    private boolean quietHarness;
-    
-    /** just don't say anything! */
-    protected boolean silentHarness;
-
-    /** map of feature names to features */
-    private HashMap features;
-
-       /** if true, do not delete temporary files. */
-       private boolean keepTemp;
-
-    /** if true, delete temporary files as each test completes. */
-    private boolean killTemp;
-
-       /** if true, then log results in report(..) when done */
-       private boolean logResults;
-    
-    /** if true and there were failures, do System.exit({numFailures})*/
-    private boolean exitOnFailure;
-    
-    protected Harness() {
-        features = new HashMap();
-    }
-
-    
-    /** 
-     * Entry point for a test.
-     * This reads in the arguments, 
-     * creates the test suite(s) from the input file(s),
-     * and for each suite does setup, run, report, and cleanup. 
-     * When arguments are read, any option ending with "-" causes
-     * option variants, a set of args with and another without the
-     * option. See {@link LangUtil.optionVariants(String[])} for 
-     * more details.
-     * @param args the String[] for the test suite - use -help to get options,
-     *         and use "-" suffixes for variants.
-     * @param resultList List for IRunStatus results - ignored if null 
-     */
-    public void runMain(String[] args, List resultList) {
-        LangUtil.throwIaxIfFalse(!LangUtil.isEmpty(args), "empty args");
-        // read arguments
-        final ArrayList globals = new ArrayList();
-        final ArrayList files = new ArrayList();
-        final LinkedList argList = new LinkedList();
-        argList.addAll(Arrays.asList(args));
-        for (int i = 0; i < argList.size(); i++) {
-            String arg = (String) argList.get(i);
-            List aliases = aliasOptions(arg);
-            if (!LangUtil.isEmpty(aliases)) {
-                argList.remove(i);
-                argList.addAll(i, aliases);
-                i--;
-                continue;
-            }
-            if ("-help".equals(arg)) {
-                logln("java " + Harness.class.getName() + " {option|suiteFile}..");
-                printSyntax(getLogStream());
-                return;
-            } else if (isSuiteFile(arg)) {
-                files.add(arg); 
-            } else if (!acceptOption(arg)) {
-                globals.add(arg); 
-            } // else our options absorbed
-        }
-        if (0 == files.size()) {
-            logln("## Error reading arguments: at least 1 suite file required");
-            logln("java " + Harness.class.getName() + " {option|suiteFile}..");
-            printSyntax(getLogStream());
-            return;
-        }
-        String[] globalOptions = (String[]) globals.toArray(new String[0]);
-        String[][] globalOptionVariants = optionVariants(globalOptions);
-        AbstractRunSpec.RT runtime = new AbstractRunSpec.RT();
-        if (verboseHarness) {
-            runtime.setVerbose(true);
-        }
-        
-        // run suites read from each file
-        AjcTest.Suite.Spec spec;
-        for (Iterator iter = files.iterator(); iter.hasNext();) {
-            File suiteFile = new File((String) iter.next());
-            if (!suiteFile.canRead()) {
-                logln("runMain(..) cannot read file: " + suiteFile);
-                continue;
-            }
-            if (null == (spec = readSuite(suiteFile))) {
-                logln("runMain(..) cannot read suite from file: " + suiteFile);
-                continue;
-            }
-
-            MessageHandler holder = new MessageHandler();
-            for (int i = 0; i < globalOptionVariants.length; i++) {
-                runtime.setOptions(globalOptionVariants[i]);
-                holder.init();
-                boolean skip = !spec.adoptParentValues(runtime, holder);                
-                // awful/brittle assumption about number of skips == number of skip messages
-                final List skipList = MessageUtil.getMessages(holder, IMessage.INFO, false, "skip");
-                if ((verboseHarness || skip || (0 < skipList.size()))) {
-                    final List curArgs = Arrays.asList(globalOptionVariants[i]);
-                                       logln("runMain(" + suiteFile + ", " + curArgs + ")");
-                    if (verboseHarness) {
-                        String format = "yyyy.MM.dd G 'at' hh:mm:ss a zzz";
-                        SimpleDateFormat formatter = new SimpleDateFormat (format);
-                        String date = formatter.format(new Date());
-                        logln("test date: " + date);
-                        logln("harness features: " + listFeatureNames());
-                        logln("Java version: " + JAVA_VERSION);
-                        logln("AspectJ version: " + ASPECTJ_VERSION);
-                    }
-                    if (!(quietHarness || silentHarness) && holder.hasAnyMessage(null, true)) {
-                        MessageUtil.print(getLogStream(), holder, "skip - ");
-                        MessageUtil.printMessageCounts(getLogStream(), holder, "skip - ");
-                    }
-                }
-                if (!skip) {
-                    doStartSuite(suiteFile);            
-                    long elapsed = 0;
-                    RunResult result = null;
-                    try {                    
-                        final long startTime = System.currentTimeMillis();
-                        result = run(spec);
-                        if (null != resultList) {
-                            resultList.add(result);
-                        }
-                        elapsed = System.currentTimeMillis() - startTime; 
-                        report(result.status, skipList.size(), result.numIncomplete, elapsed);                   
-                    } finally {
-                        doEndSuite(suiteFile,elapsed);
-                    }
-                    if (exitOnFailure) {
-                        int numFailures = RunUtils.numFailures(result.status, true);
-                        if (0 < numFailures) {
-                            System.exit(numFailures);
-                        }
-                        Object value = result.status.getResult();
-                        if ((value instanceof Boolean) 
-                                && !((Boolean) value).booleanValue()) {
-                            System.exit(-1);
-                        }
-                    }
-                }
-            }
-               }
-    }   
-    
-
-       /**
-        * Tell all IRunListeners that we are about to start a test suite
-        * @param suiteFile
-        * @param elapsed
-        */
-       private void doEndSuite(File suiteFile, long elapsed) {
-               Collection c = features.values();
-               for (Iterator iter = c.iterator(); iter.hasNext();) {
-                       Feature element = (Feature) iter.next();
-                       if (element.listener instanceof TestCompleteListener) {
-                               ((TestCompleteListener)element.listener).doEndSuite(suiteFile,elapsed);
-                       }
-               }                                       
-       }
-    /**
-     * Generate variants of String[] options by creating an extra set for
-     * each option that ends with "-".  If none end with "-", then an
-     * array equal to <code>new String[][] { options }</code> is returned;
-     * if one ends with "-", then two sets are returned,
-     * three causes eight sets, etc.
-     * @return String[][] with each option set.
-     * @throws IllegalArgumentException if any option is null or empty.
-     */
-    public static String[][] optionVariants(String[] options) {
-        if ((null == options) || (0 == options.length)) {
-            return new String[][] { new String[0]};            
-        }
-        // be nice, don't stomp input
-        String[] temp = new String[options.length];
-        System.arraycopy(options, 0, temp, 0, temp.length);
-        options = temp;
-        boolean[] dup = new boolean[options.length];
-        int numDups = 0;
-        
-        for (int i = 0; i < options.length; i++) {
-            String option = options[i];
-            if (LangUtil.isEmpty(option)) {
-                throw new IllegalArgumentException("empty option at " + i);
-            }
-            if (option.endsWith("-")) {
-                options[i] = option.substring(0, option.length()-1);
-                dup[i] = true;
-                numDups++;
-            }
-        }
-        final String[] NONE = new String[0];
-        final int variants = exp(2, numDups);
-        final String[][] result = new String[variants][];
-        // variant is a bitmap wrt doing extra value when dup[k]=true
-        for (int variant = 0; variant < variants; variant++) { 
-            ArrayList next = new ArrayList();
-            int nextOption = 0;
-            for (int k = 0; k < options.length; k++) {
-                if (!dup[k] || (0 != (variant & (1 << (nextOption++))))) {
-                    next.add(options[k]);
-                }                   
-            }
-            result[variant] = (String[]) next.toArray(NONE);
-        }
-        return result;
-    }
-    
-    private static int exp(int base, int power) { // not in Math?
-        if (0 > power) {
-            throw new IllegalArgumentException("negative power: " + power);
-        } 
-        int result = 1;
-        while (0 < power--) {
-            result *= base;
-        }
-        return result;
-    }
-
-       /**
-        * @param suiteFile
-        */
-       private void doStartSuite(File suiteFile) {
-               Collection c = features.values();
-               for (Iterator iter = c.iterator(); iter.hasNext();) {
-                       Feature element = (Feature) iter.next();
-                       if (element.listener instanceof TestCompleteListener) {
-                               ((TestCompleteListener)element.listener).doStartSuite(suiteFile);
-                       }
-               }               
-       }
-
-       /** Run the test suite specified by the spec */
-    protected RunResult run(AjcTest.Suite.Spec spec) {
-        LangUtil.throwIaxIfNull(spec, "spec");
-        /*
-         * For each run, initialize the runner and validator,
-         * create a new set of IRun{Iterator} tests,
-         * and run them. 
-         * Delete all temp files when done.
-         */
-        Runner runner = new Runner();
-        if (0 != features.size()) {
-            for (Iterator iter = features.entrySet().iterator(); iter.hasNext();) {
-                Feature feature = (Feature) ((Map.Entry) iter.next()).getValue();
-                runner.registerListener(feature.clazz, feature.listener);
-            }
-        }
-        IMessageHolder holder = new MessageHandler();
-        int numIncomplete = 0;
-        RunStatus status = new RunStatus(holder, runner);
-        status.setIdentifier(spec);
-        // validator is used for all setup in entire tree...
-        Validator validator = new Validator(status);
-        if (!killTemp) {
-            validator.lock(this);
-        }
-        Sandbox sandbox = null;
-        try {
-            sandbox = new Sandbox(spec.getSuiteDirFile(), validator);
-            IRunIterator tests = spec.makeRunIterator(sandbox, validator);
-            runner.runIterator(tests, status, null);
-            if (tests instanceof RunSpecIterator) {
-                numIncomplete = ((RunSpecIterator) tests).getNumIncomplete();
-            }
-        } finally {
-               if (!keepTemp) {
-                if (!killTemp) {
-                    validator.unlock(this);
-                }
-                   validator.deleteTempFiles(verboseHarness);
-               }
-        }
-        return new RunResult(status, numIncomplete);
-    }
-    
-    /** 
-     * Report the results of a test run after it is completed.
-     * Clients should be able to identify the number of:
-     * <ul>
-     * <li>tests run and passed</li>
-     * <li>tests failed, i.e., run and not passed (fail, error, etc.)</li>
-     * <li>tests incomplete, i.e., test definition read but test run setup failed</li>
-     * <li>tests skipped, i.e., test definition read and found incompatible with
-     *     the current configuration.</li>
-     * <ul>
-     * 
-     * @param status returned from the run
-     * @param numSkipped int tests that were skipped because of 
-     *         configuration incompatibilities
-     * @param numIncomplete int tests that failed during setup,
-     *         usually indicating a test definition or configuration error.
-     * @param msElapsed elapsed time in milliseconds
-     * */
-    protected void report(IRunStatus status, int numSkipped, int numIncomplete, 
-        long msElapsed ) {
-       if (logResults) {
-            RunUtils.AJCSUITE_PRINTER.printRunStatus(getLogStream(), status);
-        } else if (!(quietHarness || silentHarness) && (0 < status.numMessages(null, true))) {
-               if (!silentHarness) {
-               MessageUtil.print(getLogStream(), status, "");    
-               }
-        }
-        
-               logln(BridgeUtil.childString(status, numSkipped, numIncomplete) 
-            + " " + (msElapsed/1000) + " seconds");
-        
-    }
-
-    // --------------- delegate methods
-    protected void logln(String s) {
-       if (!silentHarness) {
-               getLogStream().println(s);
-       }
-    }
-    
-    protected PrintStream getLogStream() {
-        return System.out;
-    }
-    
-    protected boolean isSuiteFile(String arg) {
-        return ((null != arg) 
-                && (arg.endsWith(".txt") || arg.endsWith(".xml"))
-                && new File(arg).canRead());
-    }
-    
-    /** 
-     * Get the options that the input option is an alias for.
-     * Subclasses may add options directly to the getFeatureAliases result
-     * or override this.
-     * @return null if the input is not an alias for other options,
-     * or a non-empty List (String) of options that this option is an alias for 
-     */
-    protected List aliasOptions(String option) {
-        Properties aliases = Harness.getOptionAliases();
-        if (null != aliases) {
-            String args = aliases.getProperty(option);
-            if (!LangUtil.isEmpty(args)) {
-                return LangUtil.anySplit(args, OPTION_DELIM);
-            }
-        }
-        return null;
-    }
-
-    /** 
-     * Read and implement any of our options.
-     * Options other than this and suite files will be
-     * passed down as parent options through the test spec hierarchy.
-     * Subclasses override this to implement new options.
-     */
-    protected boolean acceptOption(String option) {
-//        boolean result = false;
-        if (LangUtil.isEmpty(option)) {
-            return true; // skip bad input
-        } else if ("-verboseHarness".equals(option)) {
-            verboseHarness = true;
-               } else if ("-quietHarness".equals(option)) {
-                       quietHarness = true;
-               } else if ("-silentHarness".equals(option)) {
-                       silentHarness = true;
-        } else if ("-keepTemp".equals(option)) {
-            keepTemp = true; 
-        } else if ("-killTemp".equals(option)) {
-            killTemp = true; 
-        } else if ("-logResults".equals(option)) {
-            logResults = true; 
-        } else if ("-exitOnFailure".equals(option)) {
-            exitOnFailure = true; 
-        } else {
-               return false;
-        }
-        return true;    
-    }    
-    
-    /** 
-     * Read a test suite file.
-     * This implementation knows how to read .txt and .xml files
-     * and logs any errors.
-     * Subclasses override this to read new kinds of suites.
-     * @return null if unable to read (logging errors) or AjcTest.Suite.Spec otherwise
-     */
-    protected AjcTest.Suite.Spec readSuite(File suiteFile) {
-        if (null != suiteFile) {
-            String path = suiteFile.getPath();
-            try {
-                if (path.endsWith(".xml")) {
-                    return AjcSpecXmlReader.getReader().readAjcSuite(suiteFile);
-                } else if (path.endsWith(".txt")) {
-                    return FlatSuiteReader.ME.readSuite(suiteFile);
-                } else {
-                    logln("unrecognized extension? " + path);
-                }
-            } catch (IOException e) {
-                e.printStackTrace(getLogStream());
-            }
-        }
-        return null;
-    }
-    
-    /** Add feature to take effect during the next runMain(..) invocation.
-     * @param feature the Feature to add, using feature.name as key.
-     */
-    protected void addFeature(Feature feature) {
-        if (null != feature) {
-            features.put(feature.name, feature);
-        }
-    }
-    
-    /** remove feature by name (same as feature.name) */
-    protected void removeFeature(String name) {
-        if (!LangUtil.isEmpty(name)) {
-            features.remove(name);
-        }
-    }
-    
-    /** @return unmodifiable Set of feature names */
-    protected Set listFeatureNames() {
-        return Collections.unmodifiableSet(features.keySet());
-    }
-
-    /** print detail message for syntax of main(String[]) command-line */
-    protected void printSyntax(PrintStream out) {
-        out.println("  {??}              unrecognized options are used as test spec globals");
-        out.println("  -help             print this help message");
-        out.println("  -verboseHarness   harness components log verbosely");
-        out.println("  -quietHarness     harness components suppress logging");
-        out.println("  -keepTemp         do not delete temporary files");
-        out.println("  -logResults       log results at end, verbosely if fail");
-        out.println("  -exitOnFailure    do System.exit({num-failures}) if suite fails");
-        out.println("  {suiteFile}.xml.. specify test suite XML file");
-        out.println("  {suiteFile}.txt.. specify test suite .txt file (deprecated)");
-    }
-
-    /** print known aliases at the end of the syntax message */
-    protected void printAliases(PrintStream out) {
-        LangUtil.throwIaxIfNull(out, "out");
-        Properties props = getOptionAliases();
-        if (null == props) {
-            return;
-        }
-        int pdLength = SYNTAX_PAD.length();
-        Set entries = props.entrySet();
-        for (Iterator iter = entries.iterator(); iter.hasNext();) {
-                       Map.Entry entry = (Map.Entry) iter.next();
-                       String alias = "  " + (String) entry.getKey();
-            int buf = pdLength - alias.length();
-            if (0 < buf) {
-                alias += SYNTAX_PAD.substring(0, buf);
-            } else {
-                alias += " ";
-            }
-            out.println(alias + entry.getValue());
-        }
-    }
-    
-    /** result struct for run(AjcTest.Spec) */
-    public static class RunResult {
-        public final IRunStatus status;
-        public final int numIncomplete;
-        public RunResult(IRunStatus status, int numIncomplete) {
-            this.status = status;
-            this.numIncomplete = numIncomplete;
-        }
-    }
-    /** feature implemented as named IRunIterator/IRun association */
-    public static class Feature {
-        /** never null, always assignable to IRun */
-        public final Class clazz;
-        
-        /** never null */
-        public final IRunListener listener;
-        
-        /** never null or empty */
-        public final String name;
-        
-        /** @throws IllegalArgumentException if any is null/empty or clazz is
-         *           not assignable to IRun
-         */
-        public Feature(String name, Class clazz, IRunListener listener) {
-            LangUtil.throwIaxIfNull(clazz, "class");
-            if (!IRun.class.isAssignableFrom(clazz)
-                && !IRunIterator.class.isAssignableFrom(clazz)) {
-                String s = clazz.getName() + "is not assignable to IRun or IRunIterator";
-                LangUtil.throwIaxIfFalse(false, s);
-            }
-            LangUtil.throwIaxIfNull(listener, "listener");
-            LangUtil.throwIaxIfNull(name, "name");
-            LangUtil.throwIaxIfFalse(0 < name.length(), "empty name");        
-            this.clazz = clazz;
-            this.listener = listener;
-            this.name = name;
-        }
-        
-        /** @return feature name */
-        public String toString() {
-            return name;
-        }
-    }
-}
-
-
-/** 
- * Harness with features for controlling output
- * (logging results and hiding streams).
- * Use -help to get a list of feature options.
- */
-class FeatureHarness extends Harness {
-
-    private static final String[] ALIASES = new String[] 
-        { "-hideStreams", 
-            "-hideCompilerStreams" 
-            + OPTION_DELIM + "-hideRunStreams",
-          "-jim", 
-            "-logMinFail"   
-            + OPTION_DELIM + "-hideStreams",
-          "-loud", 
-            "-verboseHarness",
-          "-baseline", 
-            "-verboseHarness" 
-            + OPTION_DELIM + "-traceTestsMin" 
-            + OPTION_DELIM + "-hideStreams",
-          "-release", 
-              "-baseline" 
-              + OPTION_DELIM + "-ajctestSkipKeywords=knownLimitation,purejava",
-          "-junit",
-                 "-silentHarness" + OPTION_DELIM + "-logJUnit" + OPTION_DELIM +
-                 "-hideStreams", 
-          "-cruisecontrol",
-                 "-junit" + OPTION_DELIM + "-ajctestSkipKeywords=knownLimitation,purejava"
-        };
-    static {
-        Properties optionAliases = Harness.getOptionAliases();
-        if (null != optionAliases) {
-            for (int i = 1; i < ALIASES.length; i += 2) {
-                optionAliases.put(ALIASES[i-1], ALIASES[i]);
-                       }
-        }
-    }
-
-    /** controller for suppressing and sniffing error and output streams. */
-    StreamsHandler streamsHandler; 
-
-    /** facility of hiding-streams may be applied in many features */
-    IRunListener streamHider;
-
-    /** facility of capture/log may be applied in many features */
-    IRunListener captureLogger;
-    
-    /** when making tests, do not run them */
-    TestMaker testMaker;
-    
-    public FeatureHarness() {
-        super();
-        streamsHandler = new StreamsHandler(false, true);
-    }
-    /** override to make tests or run as usual */
-    protected RunResult run(AjcTest.Suite.Spec spec) {
-        if (null != testMaker) {
-            System.out.println("generating rather than running tests...");
-            return testMaker.run(spec);
-        } else {
-            return super.run(spec);
-        }
-    }
-
-    /**
-     * Log via StreamsHandler-designated log stream.
-     * @see org.aspectj.testing.drivers.Harness#log(String)
-        */
-       protected void logln(String s) {
-               if (!silentHarness)
-               streamsHandler.lnlog(s);
-       }
-
-    /**
-        * @see org.aspectj.testing.drivers.Harness#getLogStream()
-     * @return StreamsHandler-designated log stream.
-        */
-       protected PrintStream getLogStream() {
-               return streamsHandler.out;
-       }
-
-
-    /** print detail message for syntax of main(String[]) command-line */
-    protected void printSyntax(PrintStream out) {
-        super.printSyntax(out);
-        out.println("  -progressDots     log . or ! for each AjcTest pass or fail");
-        out.println("  -logFail          log each failed AjcTest");
-        out.println("  -logPass          log each passed AjcTest");
-        out.println("  -logAll           log each AjcTest");
-        out.println("  -logMinFail       log each AjcTest failure with minimal excess data");
-        out.println("  -logMinPass       log each AjcTest success with minimal excess data");
-        out.println("  -logMinAll        log all AjcTest with minimal excess data");
-        out.println("  -logXMLFail       log XML definition for each failed AjcTest");
-        out.println("  -logXMLPass       log XML definition for each passed AjcTest");
-        out.println("  -logXMLAll        log XML definition for each AjcTest");
-        out.println("  -logJUnit         log all tests in JUnit XML report style");
-        out.println("  -hideRunStreams   hide err/out streams during java runs");
-        out.println("  -hideCompilerStreams   hide err/out streams during compiler runs");
-        out.println("  -traceTests       log pass|fail, /time/memory taken after each test");
-        out.println("  -traceTestsMin    log pass|fail after each test");
-        out.println("  -XmakeTests       create source files/dirs for initial compile run of each test");
-        out.println("  -XlogPublicType   log test XML if \"public type\" in an error message");
-        out.println("  -XlogSourceIn=Y,Z log test XML if Y or Z is in path of any sources");
-        super.printAliases(out);
-    }
-
-    /** Accept a number of logging and output options */
-       protected boolean acceptOption(String option) {
-        if (null == option) {
-            return false;
-        }
-        
-        final StreamsHandler streams = streamsHandler;
-        final IRunValidator validator = RunValidator.NORMAL;
-        final RunUtils.IRunStatusPrinter verbose 
-            = RunUtils.VERBOSE_PRINTER;
-        final RunUtils.IRunStatusPrinter terse 
-            = RunUtils.TERSE_PRINTER;
-//        final boolean LOGPASS = true;
-//        final boolean LOGFAIL = true;
-//        final boolean SKIPPASS = false;
-//        final boolean SKIPFAIL = false;
-//        final boolean LOGSTREAMS = true;
-        final boolean SKIPSTREAMS = false;
-        
-               Feature feature = null;
-        if (super.acceptOption(option)) {
-            // ok, result returned below
-            
-        } else if ("-XmakeTests".equals(option)) {
-            testMaker = TestMaker.ME;
-        } else if (option.startsWith("-traceTestsMin")) {
-            feature = new Feature(option, AjcTest.class,new TestTraceLogger(streams, false));
-        } else if (option.startsWith("-traceTests")) {
-            feature = new Feature(option, AjcTest.class,new TestTraceLogger(streams, true));
-        } else if (option.startsWith("-logMin")) {
-            feature = new Feature(option, AjcTest.class, 
-                new RunLogger(option, SKIPSTREAMS, streams, validator, terse));
-        } else if (option.startsWith("-logXML")) {
-            feature = new Feature(option, AjcTest.class, 
-                new XmlLogger(option, streams, validator));
-        } else if (option.startsWith("-logJUnit")) {
-               feature = new Feature(option, AjcTest.class,
-                       new JUnitXMLLogger(option,streams,validator));
-        } else if (option.startsWith("-log")) {
-            feature = new Feature(option, AjcTest.class, 
-                new RunLogger(option, SKIPSTREAMS, streams, validator, verbose));
-        } else if ("-hideRunStreams".equals(option)) {
-            feature = new Feature(option, JavaRun.class, getStreamHider());       
-        } else if ("-hideCompilerStreams".equals(option)) {
-            addFeature(new Feature(option, IncCompilerRun.class, getStreamHider()));   // hmmm    
-            feature = new Feature(option, CompilerRun.class, getStreamHider());       
-        } else if ("-progressDots".equals(option)) {
-            IRunListener listener = new RunListener() {
-                               public void runCompleted(IRunStatus run) {
-                    streamsHandler.log((validator.runPassed(run) ? "." : "!"));
-                               }
-            };
-            feature = new Feature(option, AjcTest.class, listener);       
-        } else if (option.startsWith("-XlogPublicType")) {
-            String label = option + TestCompleteListener.PASS; // print when validator true
-            feature = new Feature(option, AjcTest.class, 
-                new XmlLogger(label, streams, MessageRunValidator.PUBLIC_TYPE_ERROR));
-        } else if (option.startsWith("-XlogSourceIn")) {
-            String input = option.substring("-XlogSourceIn=".length());
-            LangUtil.throwIaxIfFalse(0 < input.length(), option);
-            String label = "-XlogSourceIn="  + TestCompleteListener.PASS; // print when validator true
-            StringRunner sr = new SubstringRunner(input, false);
-            feature = new Feature(option, AjcTest.class, 
-                new XmlLogger(label, streams, new SourcePathValidator(sr)));
-        } else {
-            return false;
-        }       
-        addFeature(feature);
-        return true;
-       }
-
-    /** lazy construction for shared hider */
-    protected IRunListener getStreamHider() {
-        if (null == streamHider) {
-            streamHider =  new RunListener() {
-                public void runStarting(IRunStatus run) {
-                    streamsHandler.hide();
-                }
-                public void runCompleted(IRunStatus run) {
-                    streamsHandler.show();
-                }
-                public String toString() { return "Harness StreamHider"; }
-            };
-        }
-        return streamHider;
-    }
-}
-
-/** Generate any needed test case files for any test. */
-class TestMaker  {
-    
-    static TestMaker ME = new TestMaker();
-    
-    /** @throws Error if unable to make dir */
-    static void mkdirs(File dir) {
-        if (null != dir && !dir.exists()) {
-            if (!dir.mkdirs()) {
-                throw new Error("unable to make dir: " + dir);
-            }
-        }
-    }
-    static String getFileContents(File baseDir, File file, String label) {
-        String fileName = file.getName();
-        if (fileName.endsWith(".java")) {
-            fileName = fileName.substring(0, fileName.length() - 5);
-        }
-        StringBuffer sb = new StringBuffer();
-        String filePath = file.getParentFile().getAbsolutePath();
-        String dirPath = baseDir.getAbsolutePath();
-        String pack = null;
-        if (filePath.startsWith(dirPath)) {
-            pack = filePath.substring(dirPath.length()).replace('/', '.');
-        }
-        if (!LangUtil.isEmpty(pack)) {
-           sb.append("package " + pack + ";");
-        }
-        final String EOL = "\n"; // XXX find discovered EOL
-        sb.append( EOL 
-            + EOL + "import org.aspectj.testing.Tester;"
-            + EOL + ""
-            + EOL + "/** @testcase " + label + " */"
-            + EOL + "public class " + fileName + " {"
-            + EOL + "\tpublic static void main(String[] args) { "
-            + EOL + "\t\tTester.check(null != args, \"null args\"); "
-            + EOL + "\t}"
-            + EOL + "}"
-            + EOL 
-            );
-            
-        return sb.toString();
-    }
-    
-    /** create a minimal source file for a test */
-    static void createSrcFile(File baseDir, File file, String testName) {
-        if (file.exists()) {
-            return;
-        }
-        String contents = getFileContents(baseDir, file, testName);
-        String error = FileUtil.writeAsString(file, contents);
-        if (null != error) {
-            throw new Error(error);
-        }
-    }
-    
-    /** create an empty arg file for a test */
-    static void createArgFile(File baseDir, File file, String testName) {
-        if (file.exists()) {
-            return;
-        }
-        String contents = "// argfile " + file;
-        String error = FileUtil.writeAsString(file, contents);
-        if (null != error) {
-            throw new Error(error);
-        }
-    }
-       
-    public Harness.RunResult run(AjcTest.Suite.Spec spec) {
-        ArrayList kids = spec.getChildren();
-        for (Iterator iter = kids.iterator(); iter.hasNext();) {
-                       makeTest( (AjcTest.Spec) iter.next());                  
-               }
-        IRunStatus status = new RunStatus(new MessageHandler(), new Runner());
-        status.start();
-        status.finish(IRunStatus.PASS);
-        return new Harness.RunResult(status, 0);
-    }
-    
-    private void makeTest(AjcTest.Spec spec) {
-        CompilerRun.Spec compileSpec = AjcTest.unwrapCompilerRunSpec(spec);
-        if (null == spec) {
-            throw new Error("null spec");
-        }
-        System.out.println("  generating test files for test: " + spec.getDescription());
-        File dir = spec.getSuiteDir();
-        if (null != dir) {
-            TestMaker.mkdirs(dir);
-        }
-        String offset = spec.getTestDirOffset();
-        if (!LangUtil.isEmpty(offset)) {
-            if (null == dir) {
-                dir = new File(offset);
-            } else {
-                dir = new File(dir.getAbsolutePath() + "/" + offset);
-            }
-        } else if (null == dir) {
-            dir = new File(".");
-        }
-        StringBuffer testName = new StringBuffer();
-        int pr = spec.getBugId();
-        if (0 < pr) {
-            testName.append("PR#" + pr + " ");
-        }
-        
-        testName.append(spec.getDescription());
-        final String label = testName.toString();
-        final File[] srcFiles = FileUtil.getBaseDirFiles(dir, compileSpec.getPathsArray());
-        if (!LangUtil.isEmpty(srcFiles)) {
-            for (int i = 0; i < srcFiles.length; i++) {
-                TestMaker.createSrcFile(dir, srcFiles[i], label);            
-            }
-        }
-        final File[] argFiles = FileUtil.getBaseDirFiles(dir, compileSpec.getArgfilesArray());
-        if (!LangUtil.isEmpty(argFiles)) {
-            for (int i = 0; i < argFiles.length; i++) {
-                TestMaker.createArgFile(dir, argFiles[i], label);            
-            }
-        }
-        
-       }
-    
-    /** @return "Testmaker()" */    
-    public String toString() {
-        return "TestMaker()";
-    }
-}
-
-interface StringRunner {
-    boolean accept(String s);
-}
-
-/**
- * StringRunner than accepts input matching 0+ substrings,
- * optionally case-insensitive.
- */
-class SubstringRunner implements StringRunner {
-    private static String[] extractSubstrings(
-            String substrings, 
-            boolean caseSensitive) {
-        if (null == substrings) {
-            return null;
-        }
-        StringTokenizer st = new StringTokenizer(substrings, ",");
-        String[] result = new String[st.countTokens()];
-        for (int i = 0; i < result.length; i++) {
-                       result[i] = st.nextToken().trim();
-                   LangUtil.throwIaxIfFalse(0 < result[i].length(), "empty entry");
-            if (!caseSensitive) {
-                result[i] = result[i].toLowerCase();
-            }
-        }
-        return result;
-    }
-    
-    private final String[] substrings;
-    private final boolean caseSensitive;
-    
-    /**
-     * @param substrings the String containing comma-separated substrings
-     *                    to find in input - if null, any input accepted
-     * @param caseSensitive if true, do case-sensitive comparison
-     * @throws IllegalArgumentException if any substrings contains empty entry ", ,"
-     */
-    SubstringRunner(String substrings, boolean caseSensitive) {
-        this.caseSensitive = caseSensitive;
-        this.substrings = extractSubstrings(substrings, caseSensitive); 
-    }
-    
-    public boolean accept(String input) {
-        if (null == substrings) {
-            return true;
-        }
-        if (null == input) {
-            return false;
-        }
-        
-        if (!caseSensitive) {
-            input = input.toLowerCase();
-        }
-        for (int i = 0; i < substrings.length; i++) {
-                       if (-1 != input.indexOf(substrings[i])) {
-                return true;
-            }
-               }
-        return false;
-    }
-}
-
-/** 
- * Signal whether run "passed" based on validating absolute source paths.
- * (Static evaluation - no run necessary)
- */
-class SourcePathValidator implements IRunValidator { // static - no run needed
-    private final StringRunner validator;
-    // XXX hoist common
-    SourcePathValidator(StringRunner validator) {
-        LangUtil.throwIaxIfNull(validator, "validator");
-        this.validator = validator;        
-    }
-    /**
-     * @return true if any source files in compile spec are
-     *          accepted by the validator.
-        * @see org.aspectj.testing.run.IRunValidator#runPassed(IRunStatus)
-        */
-       public boolean runPassed(IRunStatus run) {
-        AjcTest.Spec testSpec = AjcTest.unwrapSpec(run);
-        if (null != testSpec) {
-            CompilerRun.Spec compileSpec = AjcTest.unwrapCompilerRunSpec(testSpec);
-            File basedir = new File(testSpec.getSuiteDir(), testSpec.getTestDirOffset());
-            String[] paths = compileSpec.getPathsArray();
-            File[] files = FileUtil.getBaseDirFiles(basedir, paths);
-            for (int i = 0; i < files.length; i++) {
-                               if (validator.accept(files[i].getAbsolutePath())) {
-                    return true;
-                }
-                       }
-        }
-               return false;
-       }
-    
-}
-
-/** Signal whether run "passed" based on message kind and content */
-class MessageRunValidator implements IRunValidator {
-    
-    /** signals "passed" if any error contains "public type" */
-    static final IRunValidator PUBLIC_TYPE_ERROR 
-        = new MessageRunValidator("public type", IMessage.ERROR, false);
-    
-    private final IMessage.Kind kind;
-    private final String sought;
-    private final boolean orGreater;
-    
-    /**
-     * @param sought the String to seek anywhere in any message of the right kind
-     *         if null, accept any message of the right kind.
-     * @param kind the IMessage.Kind of messages to search - all if null
-     */
-    MessageRunValidator(String sought, IMessage.Kind kind, boolean orGreater) {
-        this.sought = sought;
-        this.kind = kind;
-        this.orGreater = orGreater;
-    }
-    
-    /** @return true if this run has messages of the right kind and text */
-       public boolean runPassed(IRunStatus run) {
-        return gotMessage(new IRunStatus[] {run});
-    }
-    
-    /**
-     * Search these children and their children recursively
-     * for messages of the right kind and content.
-     * @return true at first match of message of the right kind and content
-     */
-    private boolean gotMessage(IRunStatus[] children) {
-        if (LangUtil.isEmpty(children)) {
-            return false;
-        }
-        for (int i = 0; i < children.length; i++) {
-                       IRunStatus run = children[i];
-            if (null == run) {
-                continue; // hmm
-            }
-            IMessage[] messages = run.getMessages(kind, orGreater);
-            if (!LangUtil.isEmpty(messages)) {
-                if (LangUtil.isEmpty(sought)) {
-                    return true;
-                } else {
-                    for (int j = 0; j < messages.length; j++) {
-                        if (null == messages[j]) {
-                            continue; // hmm
-                        }
-                               String text = messages[j].getMessage();
-                        if ((null != text) && (-1 != text.indexOf(sought))) {
-                            return true;
-                        }
-                    }
-                }
-               }
-            if (gotMessage(run.getChildren())) {
-                return true;
-            }
-        }
-       return false;
-       }
-}
-
-/** 
- * Base class for listeners that run depending on pass/fail status of input.
- * Template method runCompleted handled whether to run.
- * Subclasses implement doRunCompleted(..).
- */
-abstract class TestCompleteListener extends RunListener {
-    /** label suffix indicating both pass and fail */
-    public static final String ALL = "All";
-
-    /** label suffix indicating fail */
-    public static final String FAIL = "Fail";
-
-    /** label suffix indicating pass */
-    public static final String PASS = "Pass";
-    
-
-    /** runValidator determines if a given run passed */
-    protected final IRunValidator runValidator;
-
-    /** label for this listener */
-    final String label;
-    
-    /** if trun and run passed, then run doRunCompleted(..) */
-    final boolean logOnPass;
-
-    /** if true and run did not pass, then run doRunCompleted(..) */
-    final boolean logOnNotPass;
-
-    /** may be null */
-    protected final StreamsHandler streamsHandler;
-    
-    /** true if the last run evaluation was ok */
-    boolean lastRunOk;
-    
-    /** last run evaluated */
-    IRunStatus lastRun; // XXX small memory leak - cache hashcode instead?
-    
-    /** @param label endsWith PASS || FAIL || ALL */
-    protected TestCompleteListener(
-        String label, 
-        IRunValidator runValidator,
-        StreamsHandler streamsHandler) {
-        if (null == runValidator) {
-            runValidator = RunValidator.NORMAL;
-        }
-        this.label = (null == label? "" : label);
-        this.logOnPass = label.endsWith(PASS) || label.endsWith(ALL);
-        this.logOnNotPass = label.endsWith(FAIL) || label.endsWith(ALL);
-        this.runValidator = runValidator;
-        this.streamsHandler = streamsHandler;
-    }
-    
-    public void runStarted(IRunStatus run) {
-        if (null != streamsHandler) {
-            streamsHandler.startListening();
-        }
-    }
-    
-    /** subclasses implement this to do some per-test initialization */
-    protected void doRunStarted(IRunStatus run) {
-    }
-    
-    
-    /** subclasses implement this to do some per-suite initialization */
-    protected void doStartSuite(File suite) {
-    }
-    
-    /** subclasses implement this to do end-of-suite processing */
-    protected void doEndSuite(File suite, long duration) {
-    }
-
-    public final void runCompleted(IRunStatus run) {
-        boolean doit = lastRunOk(run);
-        StreamsHandler.Result result = null;
-        if (null != streamsHandler) {
-            streamsHandler.endListening(doit);
-        }
-        if (doit) {
-            doRunCompleted(run, result);
-        }
-    }
-
-    /**
-     * @return true if run is ok per constructor specifications
-     */
-    protected boolean lastRunOk(IRunStatus run) {
-        if (lastRun != run) {
-            boolean passed = runValidator.runPassed(run);
-            lastRunOk = (passed ? logOnPass : logOnNotPass);
-        }
-        return lastRunOk;
-    }
-
-    /** @return "{classname}({pass}{,fail})" indicating when this runs */    
-    public String toString() { // XXX add label?
-        return LangUtil.unqualifiedClassName(this)
-            + "(" + (logOnPass ? (logOnNotPass ? "pass, fail)" : "pass)") 
-                                       : (logOnNotPass ? "fail)" : ")"));
-    }
-    /** 
-     * Subclasses implement this to do some completion action 
-     * @param run the IRunStatus for this completed run
-     * @param result the StreamsHandler.Result (if any - may be null)
-     */
-    public abstract void doRunCompleted(IRunStatus run, StreamsHandler.Result result);
-}
-
-/** 
- * Write XML for any test passed and/or failed.
- * Must register with Runner for RunSpecIterator.class,
- * most sensibly AjcTest.class.
- */
-class XmlLogger extends TestCompleteListener {
-    /** 
-     * @param printer the component that prints any status - not null
-     * @param runValidator if null, use RunValidator.NORMAL
-     */
-    public XmlLogger(
-        String label,
-        StreamsHandler streamsHandler, 
-        IRunValidator runValidator) {
-        super(label, runValidator, streamsHandler);
-    }
-    
-    public void doRunCompleted(IRunStatus run, StreamsHandler.Result result) {
-        PrintStream out = streamsHandler.getLogStream();
-        out.println("");
-        XMLWriter writer = new XMLWriter(new PrintWriter(out, true));
-        Object id = run.getIdentifier();
-        if (!(id instanceof Runner.IteratorWrapper)) {                
-            out.println(this + " not IteratorWrapper: " 
-                + id.getClass().getName() + ": " + id);
-            return;
-        }
-        IRunIterator iter = ((Runner.IteratorWrapper) id).iterator;
-        if (!(iter instanceof RunSpecIterator)) {                
-            out.println(this + " not RunSpecIterator: " + iter.getClass().getName()
-                + ": " + iter);
-            return;
-        }
-        ((RunSpecIterator) iter).spec.writeXml(writer);
-        out.flush();
-    }
-    
-}
-
-/**
- * Write junit style XML output (for incorporation into html test results and
- * cruise control reports
- * format is...
- * <?xml version="1.0" encoding="UTF-8" ?>
- * <testsuite errors="x" failures="x" name="suite-name" tests="xx" time="ss.ssss">
- * <properties/>
- * <testcase name="passingTest" time="s.hh"></testcase>
- * <testcase name="failingTest" time="s.hh">
- *   <failure message="failureMessage" type="ExceptionType">free text</failure>
- * </testcase>
- * </testsuite>
- */
-class JUnitXMLLogger extends TestCompleteListener {
-
-//  private File suite;
-  private StringBuffer junitOutput;
-  private long startTimeMillis;
-  private int numTests = 0;
-  private int numFails = 0;
-  private DecimalFormat timeFormatter = new DecimalFormat("#.##");
-
-  public JUnitXMLLogger(        
-       String label,
-       StreamsHandler streamsHandler, 
-       IRunValidator runValidator) {
-               super(label + ALL, runValidator, streamsHandler);
-               junitOutput = new StringBuffer();       
-  }
-  
-       /* (non-Javadoc)
-        * @see org.aspectj.testing.drivers.TestCompleteListener#doRunCompleted(org.aspectj.testing.run.IRunStatus, org.aspectj.testing.util.StreamsHandler.Result)
-        */
-       public void doRunCompleted(IRunStatus run, Result result) {             
-               long duration = System.currentTimeMillis()  - startTimeMillis;
-               numTests++;
-               junitOutput.append("<testcase name=\"" + run.getIdentifier() + "\" ");
-               junitOutput.append("time=\"" + timeFormatter.format((duration)/1000.0f) + "\"");
-               junitOutput.append(">");
-               if (!run.runResult())  {
-                       numFails++;
-                       junitOutput.append("\n");
-                       junitOutput.append("<failure message=\"test failed\" type=\"unknown\">\n");
-//                     junitOutput.println(result.junitOutput);
-//                     junitOutput.println(result.err);
-                       junitOutput.append("</failure>\n");
-               }
-               junitOutput.append("</testcase>\n");
-       }
-
-       /* (non-Javadoc)
-        * @see org.aspectj.testing.drivers.TestCompleteListener#runStarted(org.aspectj.testing.run.IRunStatus)
-        */
-       public void runStarting(IRunStatus run) {
-                super.runStarting(run);                
-                startTimeMillis = System.currentTimeMillis();
-       }
-
-       /* (non-Javadoc)
-        * @see org.aspectj.testing.drivers.TestCompleteListener#doEndSuite(java.io.File, long)
-        */
-       protected void doEndSuite(File suite, long duration) {
-               super.doEndSuite(suite, duration);
-               String suiteName = suite.getName();
-               // junit reporter doesn't like ".xml" on the end
-               suiteName = suiteName.substring(0,suiteName.indexOf('.'));
-               PrintStream out = streamsHandler.getLogStream();
-               out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
-               String timeStr = new DecimalFormat("#.##").format(duration/1000.0);                                     
-               out.print("<testsuite errors=\"" + numFails + "\" failures=\"0\" ");
-               out.print("name=\"" + suite.getName() + "\" " );
-               out.println("tests=\"" + numTests + "\" time=\"" + timeStr + "\">");
-               out.print(junitOutput.toString());
-               out.println("</testsuite>");    
-       }
-
-       /* (non-Javadoc)
-        * @see org.aspectj.testing.drivers.TestCompleteListener#doStartSuite(java.io.File)
-        */
-       protected void doStartSuite(File suite) {
-               super.doStartSuite(suite);
-//             this.suite = suite;
-               numTests = 0;
-               numFails = 0;
-               junitOutput = new StringBuffer();
-       }
-
-}
-
-/** log pass and/or failed runs */
-class RunLogger extends TestCompleteListener {
-    final boolean logStreams;
-    final RunUtils.IRunStatusPrinter printer;
-
-    /** 
-     * @param printer the component that prints any status - not null
-     * @param runValidator if null, use RunValidator.NORMAL
-     */
-    public RunLogger(
-        String label,
-        boolean logStreams,
-        StreamsHandler streamsHandler,
-        IRunValidator runValidator,
-        RunUtils.IRunStatusPrinter printer) {
-        super(label, runValidator, streamsHandler);
-        LangUtil.throwIaxIfNull(streamsHandler, "streamsHandler");
-        LangUtil.throwIaxIfNull(printer, "printer");
-        this.logStreams = logStreams;
-        this.printer = printer;
-    }
-
-    public void doRunCompleted(IRunStatus run, StreamsHandler.Result result) {
-        PrintStream out = streamsHandler.getLogStream();
-        printer.printRunStatus(out, run);
-        if (logStreams) {
-            if (!LangUtil.isEmpty(result.err)) {
-                out.println("--- error");
-                out.println(result.err); 
-            }
-            if (!LangUtil.isEmpty(result.out)) {
-                out.println("--- ouput");
-                out.println(result.out); 
-            }
-        }
-        out.println("");
-    }
-}
-
-/** trace time and memory between runStaring and runCompleted */
-class TestTraceLogger extends TestCompleteListener {
-    private static final Runtime runtime = Runtime.getRuntime();
-    private long startTime;
-    private long startMemoryFree;
-    private final boolean verbose;
-    
-    public TestTraceLogger(StreamsHandler handler) {
-        this(handler, true);
-    }
-    public TestTraceLogger(StreamsHandler handler, boolean verbose) {
-        super("-traceTestsAll", null, handler);
-        this.verbose = verbose;
-    }
-    public void runStarting(IRunStatus run) {
-        super.runStarting(run);
-        startTime = System.currentTimeMillis();
-        startMemoryFree = runtime.freeMemory();    
-    }
-    
-    public void doRunCompleted(IRunStatus run, StreamsHandler.Result result) {
-        long elapsed = System.currentTimeMillis() - startTime;
-        long free = runtime.freeMemory();
-        long used = startMemoryFree - free;
-        String label = run.runResult() ? "PASS " : "FAIL ";
-        PrintStream out = streamsHandler.getLogStream();
-        if (verbose) {
-            label = label
-                    + "elapsed: " + LangUtil.toSizedString(elapsed, 7)
-                    + " free: " + LangUtil.toSizedString(free, 10)
-                    + " used: " + LangUtil.toSizedString(used, 10)
-                    + " id: ";
-        }
-        out.println(label + renderId(run));
-    }
-    
-    /** @return true - always trace tests */
-    protected boolean isFailLabel(String label) {
-        return true;
-    }
-    
-    /** @return true - always trace tests */
-    protected boolean isPassLabel(String label) {
-        return true;
-    }
-
-    /**
-     * This implementation returns run identifier toString(). 
-     * Subclasses override this to render id as message suffix.
-     */
-    protected String renderId(IRunStatus run) {
-        return "" + run.getIdentifier();
-    }
-}
-        // printing files
-//        AjcTest.Spec testSpec = AjcTest.unwrapSpec(run);
-//        if (null != testSpec) {
-//            CompilerRun.Spec compileSpec = AjcTest.unwrapCompilerRunSpec(testSpec);
-//            File dir = new File(testSpec.getSuiteDir(), testSpec.getTestDirOffset());
-//            List files = compileSpec.getPathsAsFile(dir);
-//            StringBuffer sb = new StringBuffer();
-//            for (Iterator iter = files.iterator(); iter.hasNext();) {
-//                File file = (File) iter.next();
-//                sb.append(" " + file.getPath().replace('\\','/').substring(2));         
-//            }
-//            out.println("files: " + sb);
-//        }
-
-
diff --git a/testing-drivers/src/main/java/org/aspectj/testing/drivers/package.html b/testing-drivers/src/main/java/org/aspectj/testing/drivers/package.html
deleted file mode 100644 (file)
index a244665..0000000
+++ /dev/null
@@ -1,428 +0,0 @@
-<html>
-       <head><title>Harness Package Documentation</title></head>
-<body>
-<p>
-The AspectJ compiler test harness can compile and run AspectJ programs
-as specified by the test definitions.
-This document tells you how to run the harness.
-It describes the options you can specify on the command-line to
-control various components that form the harness, either to 
-specify options that augment the test definitions or to
-change how the harness works, e.g., selecting particular tests
-or logging more information.
-For information on how to write a test definition, see
-<code>readme-writing-compiler-tests.html</code> in the testing module.
-</p>
-<p>
-The harness drives compiler tests, using  
-a chain of responsibility to map elements
-in the schema of a test definition to implementing classes.
-
-</p>
-<table border="1" cellpadding="1">
-<tr><th align="left">Test feature</th>
-    <th align="left">Description</th>
-    <th align="left">Implementing class</th>
-</tr>
-<tr><td>(loading suites...)</td>
-    <td>general harness</td>
-    <td>Harness</td>
-</tr>
-<tr><td>(logging...)</td>
-    <td>subclass feature harness</td>
-    <td>FeatureHarness</td>
-</tr>
-<tr><td><code>&lt;suite&gt;</code></td>
-    <td>Test suite</td>
-    <td>AjcTest.Suite</td>
-</tr>
-<tr><td>&nbsp;&nbsp;<code>&lt;ajc-test&gt;</code></td>
-    <td>Test case</td>
-    <td>AjcTest</td>
-</tr>
-<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;<code>&lt;compile&gt;</code></td>
-    <td>Initial (batch) compile run</td>
-    <td>CompilerRun</td>
-</tr>
-<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;<code>&lt;inc-compile&gt;</code></td>
-    <td>Incremental re-compile</td>
-    <td>IncCompilerRun</td>
-</tr>
-<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;<code>&lt;run&gt;</code></td>
-    <td>Run class</td>
-    <td>JavaRun</td>
-</tr>
-</table>
-<!--
-   general harness                                 (Harness)
-      subclass feature harness                     (FeatureHarness)
-        &lt;ajc-test&gt; run component             (AjcTest)
-          &lt;compile&gt; {sub-} run component     (CompilerRun)
-          &lt;inc-compile&gt; {sub-} run component (IncCompilerRun)
-          &lt;run&gt; {sub-} run component         (JavaRun)
-          ...
--->
-<p/>
-The compiler used is the AspectJ compiler <code>ajc</code>
-(optionally as wrapped by the Ant task or AJDE API's), but
-in principle any compiler accepting similar options can be
-used.
-<p/>
-To run from the command-line, use 
-<code>Harness.main(String[])</code>.
-To run programmatically, use <code>Harness.getHarness()</code>.
-<code>Harness.runMain(String[])</code> takes arguments that
-each component in the chain may accept and interpret, so
-you can modify how the tests run by specifying the following 
-arguments on the harness command line:
-<p/>
-<table cellpadding="1" border="1">
-<tr><th>Component</th><th>Options</th></tr>
-
-<tr><td rowspan="6" valign="top">Harness 
-    <p>suite files, harness verbosity, temp files, option variants
-    </p></td></tr>
-  <tr><td><u>suite files</u>: ajcTest-compliant .txt or .xml files are accepted.
-     <!-- XXX link to ajcTestSuite.dtd and .txt definitions -->
-     </td></tr>
-  <tr><td><u><code>-verboseHarness</code></u>, 
-         <u><code>-quietHarness</code></u>: 
-      Log accepted options and skipped tests,
-      or do not print even info messages.
-     </td></tr>
-  <tr><td><u><code>-keepTemp</code></u>: Normally the harness saves temp files until
-     the end of the run, and deletes them.  If you abort the run or specify
-     <code>-keepTemp</code>, then temporary (sandbox) directories will remain for analysis.
-     In either case, the file system accumulates all temporary directories
-     and files used for a give harness run; for the <code>ajcTests.xml</code>
-     suite, this runs into thousands of files.
-     </td></tr>
-  <tr><td><u><code>-killTemp</code></u>: The opposite of <code>-keepTemp</code>,
-     this causes the harness to delete temporary (sandbox) directories at 
-     the end of each test run. 
-     In this case, the file system only accumulates files for 
-     the current test.
-     </td></tr>
-  <tr><td><u>*- variants</u>: Options with a trailing "-" cause two sets of
-      option lists to be produced, one with and one without the corresponding
-      option.  E.g., "-emacssym-" will run the suite twice, once with and
-      once without the "-emacssym" flag.
-      That means if you use this on each of three options, you will
-      get 8 variant sets (1 with no options, 1 with all 3 options, 
-      3 with 2 options, and 3 with 1 option). 
-     </td></tr>
-    
-<tr><td rowspan="5" valign="top">FeatureHarness 
-    <p>output and logging options
-    </p></td></tr>
-  <tr><td><u>tracing</u>: 
-     <code>-progressDots</code> will print "." for every passed
-     test completed and "!" for every test completed but not passed.
-     <code>-traceTests</code> will print a one-line summary for each test
-     of the time and space taken and whether the test passed.
-     <code>-traceTestsMin</code> will print only the test and whether it passed.
-     <code>-baseline</code> is an alias for
-     <code>-traceTestsMin</code> 
-     <code>-hideStreams</code> and 
-     <code>!eclipse</code>, used to emit tests results in a form
-     comparable by <code>org.aspectj.testing.util.TestDiffs</code>. 
-     or usable to select tests by title for options like 
-     <code>-ajctestTitleList</code>.
-     </td></tr>
-     
-  <tr><td><u>output</u>: <code>-hide{Compiler|Run}Streams</code> will prevent output and
-     error streams from being printed to System.err and System.out, 
-     optionally only for run or compile steps.
-     </td></tr>
-  <tr><td><u>logging</u>: 
-     Log variants take the form <code>-log{Min|Xml}[Fail|Pass|All]</code>.
-     The suffix {All|Pass|Fail} selects all tests or only passing or failing tests.
-     The infix {Min} means to log with minimal information, typically only any
-     fail messages.
-     The infix {Xml} means to log the XML form of the test definition, so that
-     you can inspect the input or re-run arbitrary tests.  
-     (You can also re-run a set of tests using keywords
-     (e.g., "<code>-ajctestsRequireKeywords=...</code>" or using titles
-     (e.g., "<code>-ajctestsTitleFailList=ajcTestResults.txt</code>".)
-     Finally, the experimental option <code>-XlogPublicType</code> will 
-      log the XML test definition for 
-         any test run that emits any ERROR messages containing the text "public type".
-     </td></tr>
-  <tr><td><u>interaction of output streams and logging</u>: 
-     Streams will be emitted in real-time,
-     <em>before</em> the test is logged, unless streams are hidden.
-     When logging in normal (non-Min or -XML) form, the log will emit the streams
-     with the test report, so e.g., you can use -hideStreams -logFail to 
-     hide streams for passing tests but emit them for failing tests
-     in the context of the log.
-     </td></tr>
-    
-<tr><td rowspan="5" valign="top">AjcTest
-    <p>selection options for keywords, bugID (PR), or title (description)
-    </p></td></tr>
-  <tr><td><u>keywords</u>: <code>-ajctest[Require|Skip]Keywords=one{,two}</code>
-     will either require or skip a test that has one of the 
-     specified keywords.
-     </td></tr>
-  <tr><td><u>Bugs</u>: <code>-ajctestPR=101{,102}</code>
-     will require that a test have one of the listed bug id's.
-     </td></tr>
-  <tr><td><u>title</u>: 
-     <code>"-ajctestTitleContains=one,two"</code>
-     will require that the title (description) of a test contain 
-     one of the specified substrings, here either "one" or "two".
-     Use this to select a few tests you know generally.
-     <br/>
-     <code>"-ajctestTitleList=first title\, in theory, second title"</code>
-     will require that the title (description) of a test be
-     exactly "first title, in theory" or "second title".  
-     The entire option must be one argument on the command line.
-     Use this when working with just a few specific tests.
-     <br/>
-     <code>"-ajctestTitleList=../tests/ajcTestResults.txt"</code>
-     will require that the title (description) of a test be
-     equal to one listed in <code>../tests/ajcTestResults.txt</code>
-     as a line of the form "[PASS|FAIL] {title}(.."
-     (This form is emitted by the <code>-traceTestsMin</code> option).
-     This option only differs from the prior in that the parameter
-     is a valid file to read.
-     Use this to re-run a large set of tests.
-     <br/>
-     <code>"-ajctestTitleFailList=../tests/ajcTestResults.txt"</code>
-     is the same as the <code>-ajctestTitleList={file}</code> variant,
-     except that only results prefixed "FAIL" are included.
-     Use this to re-run only the tests that failed from a large set.
-     </td></tr>
-     
-  <tr><td><u>Combinations</u>: all selectors are applied to each test,
-     so all tests selected will comply with all constraints.
-     Specifying lists within a particular constraints will match
-     a union of tests for that constraint 
-     (e.g., all tests with bug id's 101 or 102),     
-     but there is no way to get a union of constraints 
-     (e.g., all tests with bug id's 101 or 102 <em>or</em> 
-     with keywords pure-java or knownLimitation).
-     However, <code>-ajctestSkipKeywords=...</code> can return all
-     tests without the specified keywords, so it can form unions like
-     "all tests without the knownLimitation keyword, but with 
-     bug id's 101 or 102".
-     Title lists can work similarly.  E.g., to run the failed
-     incremental tests from ajcTestResults.txt, specify
-     <code>-ajctestTitleFailList=../tests/ajcTestResults.txt</code>
-     <code>-ajctestRequireKeywords=incremental-test</code>.
-     </td></tr>
-    
-<tr><td rowspan="6" valign="top">CompilerRun
-<p>compiler options and side-effects
-    </p></td></tr>
-  <tr><td><u>supported options</u>: 
-     The command line passed to the compiler by <code>CompilerRun</code>
-     is composed of entries derived from the <code>&lt;compile&gt;</code>
-     attributes and recognized from the harness command line.
-     <code>&lt;compile&gt;</code> has specific attributes like
-     <code>files</code>, 
-     <code>argfiles</code>, 
-     <code>classpath</code> and       
-     <code>sourceroot</code> 
-     which translate directly to their counterparts.
-     The output option <code>-d</code> is defined by <code>CompilerRun</code> and
-     may not be specified (and <code>-outjar</code> is not supported).
-     Most other compiler options are defined in 
-     <code>CompilerRun.Spec.CRSOptions</code> and may be specified
-     on the harness command-line
-     or in the <code>options</code> attribute of 
-     <code>&lt;compile&gt;</code>.  
-     In the <code>options</code> attribute, each argument is comma-delimited,
-     so an option with an argument would look like 
-     <code>&lt;compile options="-source,1.4" ...&gt;</code>.  
-     If options collide, duplicates
-     can be resolved using option dominance (below).
-     </td></tr>
-  <tr><td><u>compiler selectors</u>: 
-        Use <code>-ajc</code> or <code>-eclipse</code> to select the old
-     (ajc 1.0) or new (eajc 1.1) compilers.  
-      Note that the old compiler is not
-      available in the CVS source tree at eclipse.org.
-      Use <code>-ajdeCompiler</code> to run a wrapper around the
-      AJDE interface
-      and <code>-ajctaskCompiler</code> to run a wrapper around the
-      AjcTask (Ant task) interface.
-     </td></tr>
-  <tr><td><u>option dominance <code>[-|!|^]</code></u>: 
-     Some tests require or prohibit certain options; 
-     likewise, sometime you want to force all tests
-     run with or without an option specified on the command-line,
-     regardless of its setting in the <code>&lt;compile options=".." ...&gt;</code> 
-     attribute. 
-     For this reason an option may be specified in the options attribute
-     or on the harness command-line as
-      <code>-option</code>,
-      <code>!option</code>, or 
-      <code>^option</code>.
-     <ul>
-     <li><u>- set</u>: If the leading character of an option is "-", then it is set unless forced-off.</li>
-     <li><u>^ force-off</u>: If the leading character of an option is "^", then it is forced off.
-            Any other matching option will be removed.</li>
-     <li><u>! force-on</u>: If the leading character of an option is "!", then it is forced on.
-         Any other non-force-on matching option will be removed.</li>
-     <li><u>force conflict</u>: If there are two matching force-on options, the test is skipped.</li>
-     <li><u>related options</u>: Two options match if they are the same or
-        if they are in the same family.  For example, <code>-ajc</code> and
-        <code>eclipse</code> are both compiler, and <code>-source 1.4</code>
-        and <code>-source 1.3</code> are both source.
-               <br/>
-        </li>
-     </ul>
-     </td></tr>
-  <tr><td><u>auto-skip</u>: After combining global and local options, there may be
-     conflicting or impossible options, which cause the test to be skipped:
-     <ul>
-     <li><u>semantic conflicts</u>: two options may conflict in meaning 
-           - e.g., <code>-lenient</code> and <code>-strict</code></li>
-     <li><u>impossible option</u>: It may not be possible in the current configuration to 
-         implement an option - e.g., <code>-usejavac</code> or <code>-eclipse</code> 
-         when javac or the eclipse implementation is not on the classpath              
-               <br/></li>
-     </ul>
-     </td></tr>
-     
-  <tr><td><u>source searching</u>: Given <code>-seek:{literal}</code>,  
-         as a side-effect, 
-         <code>CompilerRun</code> will search source files for {literal},
-      emitting for each instance an INFO message of the form: 
-     <tt>found: {file}:{line}:{column}</tt>
-      (Note that the harness does not display INFO messages unless <tt>-verboseHarness</tt>
-      or <tt>-loud</tt> is used.) 
-     </td></tr>
-    
-     
-  <tr><td rowspan="2" valign="top">JavaRun
-         <p>Options and forking</p></td>
-     <td><u>options</u>: options specified in the test are passed
-      to the main method as they would be on the command-line.
-      No options passed to the harness are passed through to
-      the main class.
-     </td></tr>
-  <tr><td><u>forking</u>: 
-        Forking is useful to run in a different version of Java
-        than can be supported by the harness (i.e., some 1.1 flavor);
-        it's very time-consuming otherwise.
-               Currently forking is only controllable through system properties
-               of the invoking vm (defined in JavaRun.java):
-               <ul>
-                  <li><u>javarun.fork</u>: anything to run in a new vm.
-                     </li>
-                  <li><u>javarun.java</u>: path to the java executable to run
-                     (suffix included).  If not supplied, the harness tries to
-                     find the java that invoked the harness.
-                     </li>
-                  <li><u>javarun.java.home</u>: the value of the JAVA_HOME 
-                     environment variable, if it needs to be set.
-                     </li>
-                  <li><u>javarun.bootclasspath</u>: this is prepended to the
-                     run classpath.  Multiple entries must be separated by
-                     the system-dependent path separator.
-                     </li>
-                  <li><u>javarun.vmargs</u>: this is added to the fork command-line
-                       right after java.  Multiple entries must be separated by a comma
-                       (and the whole thing should be one parameter), e.g.,
-                       <code>-Djavarun.vmargs=-Dname=value,-Dname2="value 2"</code>
-                     </li>
-               </ul>
-     </td></tr>
-</table>
-<br/>
-Following are some sample configurations:
-<ul>
-<li><code>java {harness} -hideStreams {suiteFile}</code>
- <p>Use this to output only a 1-line summary of the test results
-    (tests skipped, incomplete, failed, passed).<br/></p>
-  </li>
-  
-<li><code>java {harness} -hideStreams -traceTestsMin {suiteFile} > results.txt</code>
- <p>This writes to result.txt one line [PASS|FAIL] per test, plus a
-    1-line summary of the test results.<br/></p>
-  </li>
-  
-<li><code>java {harness} -logFail {suiteFile} -ajctestTitleFailList=results.txt</code>
- <p>This re-runs any test that failed from the "results.txt" run,
-    verbosely logging any fails.<br/></p>
-  </li>
-  
-<li><code>java {harness} -hideStreams -logMinFail {suiteFile}</code>
- <p>Use this when running tests mainly to see if they pass or
-    if the failure messages are typically enough information
-    to indicate why the test is failing.  It produces only minimal
-    output for failed tests.<br/></p>
-  </li>
-  
-<li><code>java {harness} -hideStreams -verboseHarness -logFail {suiteFile}</code>
- <p>When it's not clear at first glance why a test is failing, before
-    looking at the test code you can run it and print any harness or test
-    setup failures and all the associated messages from the test components.<br/></p>
-  </li>
-
-<li><code>java {harness} -hideStreams -usejavac- -ajc -Xlint- {suiteFile}</code>
- <p>Because of the trailing '-' on two of the options,
-    this would do four complete runs with the old (Ajc 1.0) compiler: one with
-    no options, one with -lenient, one with -Xlint, and one with both.<br/></p>
-  </li>
-  
-
-<li><code>java {harness} --ajctestPR=101,102 -Xlint- ^usejavac !eclipse {suiteFile}</code>
- <p>Run any tests associated with bugs 101 and 102, with and without -Xlint,
-    forcing off -usejavac and forcing the use of the new eclipse-based compiler.<br/></p>
-  </li>
-  
-</ul>
-
-If you have a set of options you use often, you can define a single-word
-option alias for it; see <code>Harness.optionAliases</code>.
-
-<br/><u>Configuration</u>: Most tests use the library jars in 
-<code>modules/lib/test</code>, defined in 
-<code>org.aspectj.testing.harness.bridge.Globals</code>.  
-Normally the harness finds these by relative path 
-<code>../lib/tests/*.jar</code>, which works whenever the tests are
-run from a peer module directory.  When running tests elsewhere,
-define the environment variable <code>harness.libdir</code> - e.g., 
-<pre>
-    $ cd aspectj/tests
-    $ java -Dharness.libdir=../modules/lib/test ...
-</pre>
-
-<br/><u>Forking:</u>:
-The harness must be run in a compiler-compatible VM, and the
-compiler steps run in-process.
-However, the java steps can be run in forked mode, which is useful
-when compiling for a VM which can't run the compiler.
-To compile for a different target VM could require
-setting the options for bootclasspath, target, and source.
-To run the harness so that any &lt;run.. tasks run in a
-separate vm, do something like this:
-<pre>
-   java -Djavarun.java=d:\jdk1.1.8\bin\java.exe \
-        -Djavarun.bootclasspath=d:\jdk1.1.8\lib\classes.zip \
-        -Djavarun.java.home=d:\jdk1.1.8 \
-        -Djavarun.fork=true \
-        -jar ../aj-build/jars/testing-drivers-all.jar \
-        ajcTests.xml -logFail
-</pre>
-
-Here <code>CompilerRun</code> would add the bootclasspath as such when compiling.
-JavaRun would fork using the 1.1 vm and prepend the bootclasspath
-to the classpath, with an effect like these commands
-(ignoring the line splitting in the classpath):
-<pre>
-   set JAVA_HOME=d:\jdk1.1.8
-   d:\jdk1.1.8\bin\java.exe \
-     -classpath "d:\jdk1.1.8\lib\classes.zip;
-                 d:\aspectj-src\lib\test\testing-client.jar;
-                 d:\aspectj-src\lib\test\aspectjrt.jar;
-                 c:\TEMP\sandbox7wers\classes"
-     {mainClass} {option..}
-</pre>
-
-
-</body>
-</html>
diff --git a/testing-drivers/src/test/java/org/aspectj/testing/drivers/Harness.java b/testing-drivers/src/test/java/org/aspectj/testing/drivers/Harness.java
new file mode 100644 (file)
index 0000000..09b8a71
--- /dev/null
@@ -0,0 +1,1485 @@
+/* *******************************************************************
+ * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC),
+ *               2003 Contributors.
+ * All rights reserved. 
+ * This program and the accompanying materials are made available 
+ * under the terms of the Eclipse Public License v1.0 
+ * which accompanies this distribution and is available at 
+ * http://www.eclipse.org/legal/epl-v10.html 
+ *  
+ * Contributors: 
+ *     Xerox/PARC     initial implementation 
+ *     Wes Isberg     2003 changes.
+ * ******************************************************************/
+
+package org.aspectj.testing.drivers;
+
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHolder;
+import org.aspectj.bridge.MessageHandler;
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.testing.harness.bridge.AbstractRunSpec;
+import org.aspectj.testing.harness.bridge.AjcTest;
+import org.aspectj.testing.harness.bridge.CompilerRun;
+import org.aspectj.testing.harness.bridge.FlatSuiteReader;
+import org.aspectj.testing.harness.bridge.IncCompilerRun;
+import org.aspectj.testing.harness.bridge.JavaRun;
+import org.aspectj.testing.harness.bridge.RunSpecIterator;
+import org.aspectj.testing.harness.bridge.Sandbox;
+import org.aspectj.testing.harness.bridge.Validator;
+import org.aspectj.testing.run.IRun;
+import org.aspectj.testing.run.IRunIterator;
+import org.aspectj.testing.run.IRunListener;
+import org.aspectj.testing.run.IRunStatus;
+import org.aspectj.testing.run.IRunValidator;
+import org.aspectj.testing.run.RunListener;
+import org.aspectj.testing.run.RunStatus;
+import org.aspectj.testing.run.RunValidator;
+import org.aspectj.testing.run.Runner;
+import org.aspectj.testing.util.BridgeUtil;
+import org.aspectj.testing.util.RunUtils;
+import org.aspectj.testing.util.StreamsHandler;
+import org.aspectj.testing.util.StreamsHandler.Result;
+import org.aspectj.testing.xml.AjcSpecXmlReader;
+import org.aspectj.testing.xml.XMLWriter;
+import org.aspectj.util.FileUtil;
+import org.aspectj.util.LangUtil;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * Test harness for running AjcTest.Suite test suites.
+ * This can be easily extended by subclassing.
+ * <ul>
+ * <li>template algorithms for reading arguments, printing syntax,
+ *     reading suites, and reporting results all
+ *     delegate to methods that subclasses can override to support
+ *     additional arguments or different reporting.</li>
+ * <li>implement arbitrary features as IRunListeners</li>
+ * <li>support single-option aliases to any number of single-options </li>
+ * </ul>
+ * See {@link report(IRunStatus, int, int)} for an explanation of test result
+ * categories.
+ */
+public class Harness {
+    /** 
+     * Spaces up to the width that an option should take in the syntax,
+     * including the two-space leader
+     */
+    protected static final String SYNTAX_PAD = "                    "; 
+    protected static final String OPTION_DELIM = ";";
+    private static final String JAVA_VERSION;
+    private static final String ASPECTJ_VERSION;
+    static {
+        String version = "UNKNOWN";
+        try {  version = System.getProperty("java.version", "UNKNOWN"); }
+        catch (Throwable t) {}
+        JAVA_VERSION = version;
+        
+        version = "UNKNOWN";
+        try {
+            Class c = Class.forName("org.aspectj.bridge.Version");
+            version = (String) c.getField("text").get(null);            
+        } catch (Throwable t) {
+            // ignore
+        }
+        ASPECTJ_VERSION = version;
+    }
+    
+    /** factory for the subclass currently anointed as default */
+    public static Harness makeHarness() {
+        return new FeatureHarness();
+    }
+    
+    /** @param args String[] like runMain(String[]) args */
+    public static void main(String[] args) throws Exception {
+        if (LangUtil.isEmpty(args)) {
+            File argFile = new File("HarnessArgs.txt");
+            if (argFile.canRead()) {
+                args = readArgs(argFile);
+            } else {
+                args = new String[] { "-help" };
+            }
+        }
+        makeHarness().runMain(args, null);
+    }
+
+    /**
+     * Get known option aliases.
+     * Subclasses may add new aliases, where the key is the alias option,
+     * and the value is a comma-delimited String of target options.
+     * @return Properties with feature aliases or null
+     */
+    protected static Properties getOptionAliases() {
+        if (null == optionAliases) {
+            optionAliases = new Properties();
+            // XXX load from **OptionAliases.properties
+        }
+        return optionAliases;
+    }
+
+    /**
+     * Read argFile contents into String[],
+     * delimiting at any whitespace
+     */ 
+    private static String[] readArgs(File argFile) {
+        ArrayList args = new ArrayList();
+//        int lineNum = 0;
+
+        try {
+            BufferedReader stream =
+                new BufferedReader(new FileReader(argFile));
+            String line;
+            while (null != (line = stream.readLine())) {
+                StringTokenizer st = new StringTokenizer(line);
+                while (st.hasMoreTokens()) {
+                    args.add(st.nextToken());
+                }
+            }
+        } catch (IOException e) {
+            e.printStackTrace(System.err);
+        }    
+        return (String[]) args.toArray(new String[0]);
+    }
+    
+    /** aliases key="option" value="option{,option}" */
+    private static Properties optionAliases;
+    
+    /** be extra noisy if true */
+    private boolean verboseHarness;
+    
+    /** be extra quiet if true */
+    private boolean quietHarness;
+    
+    /** just don't say anything! */
+    protected boolean silentHarness;
+
+    /** map of feature names to features */
+    private HashMap features;
+
+       /** if true, do not delete temporary files. */
+       private boolean keepTemp;
+
+    /** if true, delete temporary files as each test completes. */
+    private boolean killTemp;
+
+       /** if true, then log results in report(..) when done */
+       private boolean logResults;
+    
+    /** if true and there were failures, do System.exit({numFailures})*/
+    private boolean exitOnFailure;
+    
+    protected Harness() {
+        features = new HashMap();
+    }
+
+    
+    /** 
+     * Entry point for a test.
+     * This reads in the arguments, 
+     * creates the test suite(s) from the input file(s),
+     * and for each suite does setup, run, report, and cleanup. 
+     * When arguments are read, any option ending with "-" causes
+     * option variants, a set of args with and another without the
+     * option. See {@link LangUtil.optionVariants(String[])} for 
+     * more details.
+     * @param args the String[] for the test suite - use -help to get options,
+     *         and use "-" suffixes for variants.
+     * @param resultList List for IRunStatus results - ignored if null 
+     */
+    public void runMain(String[] args, List resultList) {
+        LangUtil.throwIaxIfFalse(!LangUtil.isEmpty(args), "empty args");
+        // read arguments
+        final ArrayList globals = new ArrayList();
+        final ArrayList files = new ArrayList();
+        final LinkedList argList = new LinkedList();
+        argList.addAll(Arrays.asList(args));
+        for (int i = 0; i < argList.size(); i++) {
+            String arg = (String) argList.get(i);
+            List aliases = aliasOptions(arg);
+            if (!LangUtil.isEmpty(aliases)) {
+                argList.remove(i);
+                argList.addAll(i, aliases);
+                i--;
+                continue;
+            }
+            if ("-help".equals(arg)) {
+                logln("java " + Harness.class.getName() + " {option|suiteFile}..");
+                printSyntax(getLogStream());
+                return;
+            } else if (isSuiteFile(arg)) {
+                files.add(arg); 
+            } else if (!acceptOption(arg)) {
+                globals.add(arg); 
+            } // else our options absorbed
+        }
+        if (0 == files.size()) {
+            logln("## Error reading arguments: at least 1 suite file required");
+            logln("java " + Harness.class.getName() + " {option|suiteFile}..");
+            printSyntax(getLogStream());
+            return;
+        }
+        String[] globalOptions = (String[]) globals.toArray(new String[0]);
+        String[][] globalOptionVariants = optionVariants(globalOptions);
+        AbstractRunSpec.RT runtime = new AbstractRunSpec.RT();
+        if (verboseHarness) {
+            runtime.setVerbose(true);
+        }
+        
+        // run suites read from each file
+        AjcTest.Suite.Spec spec;
+        for (Iterator iter = files.iterator(); iter.hasNext();) {
+            File suiteFile = new File((String) iter.next());
+            if (!suiteFile.canRead()) {
+                logln("runMain(..) cannot read file: " + suiteFile);
+                continue;
+            }
+            if (null == (spec = readSuite(suiteFile))) {
+                logln("runMain(..) cannot read suite from file: " + suiteFile);
+                continue;
+            }
+
+            MessageHandler holder = new MessageHandler();
+            for (int i = 0; i < globalOptionVariants.length; i++) {
+                runtime.setOptions(globalOptionVariants[i]);
+                holder.init();
+                boolean skip = !spec.adoptParentValues(runtime, holder);                
+                // awful/brittle assumption about number of skips == number of skip messages
+                final List skipList = MessageUtil.getMessages(holder, IMessage.INFO, false, "skip");
+                if ((verboseHarness || skip || (0 < skipList.size()))) {
+                    final List curArgs = Arrays.asList(globalOptionVariants[i]);
+                                       logln("runMain(" + suiteFile + ", " + curArgs + ")");
+                    if (verboseHarness) {
+                        String format = "yyyy.MM.dd G 'at' hh:mm:ss a zzz";
+                        SimpleDateFormat formatter = new SimpleDateFormat (format);
+                        String date = formatter.format(new Date());
+                        logln("test date: " + date);
+                        logln("harness features: " + listFeatureNames());
+                        logln("Java version: " + JAVA_VERSION);
+                        logln("AspectJ version: " + ASPECTJ_VERSION);
+                    }
+                    if (!(quietHarness || silentHarness) && holder.hasAnyMessage(null, true)) {
+                        MessageUtil.print(getLogStream(), holder, "skip - ");
+                        MessageUtil.printMessageCounts(getLogStream(), holder, "skip - ");
+                    }
+                }
+                if (!skip) {
+                    doStartSuite(suiteFile);            
+                    long elapsed = 0;
+                    RunResult result = null;
+                    try {                    
+                        final long startTime = System.currentTimeMillis();
+                        result = run(spec);
+                        if (null != resultList) {
+                            resultList.add(result);
+                        }
+                        elapsed = System.currentTimeMillis() - startTime; 
+                        report(result.status, skipList.size(), result.numIncomplete, elapsed);                   
+                    } finally {
+                        doEndSuite(suiteFile,elapsed);
+                    }
+                    if (exitOnFailure) {
+                        int numFailures = RunUtils.numFailures(result.status, true);
+                        if (0 < numFailures) {
+                            System.exit(numFailures);
+                        }
+                        Object value = result.status.getResult();
+                        if ((value instanceof Boolean) 
+                                && !((Boolean) value).booleanValue()) {
+                            System.exit(-1);
+                        }
+                    }
+                }
+            }
+               }
+    }   
+    
+
+       /**
+        * Tell all IRunListeners that we are about to start a test suite
+        * @param suiteFile
+        * @param elapsed
+        */
+       private void doEndSuite(File suiteFile, long elapsed) {
+               Collection c = features.values();
+               for (Iterator iter = c.iterator(); iter.hasNext();) {
+                       Feature element = (Feature) iter.next();
+                       if (element.listener instanceof TestCompleteListener) {
+                               ((TestCompleteListener)element.listener).doEndSuite(suiteFile,elapsed);
+                       }
+               }                                       
+       }
+    /**
+     * Generate variants of String[] options by creating an extra set for
+     * each option that ends with "-".  If none end with "-", then an
+     * array equal to <code>new String[][] { options }</code> is returned;
+     * if one ends with "-", then two sets are returned,
+     * three causes eight sets, etc.
+     * @return String[][] with each option set.
+     * @throws IllegalArgumentException if any option is null or empty.
+     */
+    public static String[][] optionVariants(String[] options) {
+        if ((null == options) || (0 == options.length)) {
+            return new String[][] { new String[0]};            
+        }
+        // be nice, don't stomp input
+        String[] temp = new String[options.length];
+        System.arraycopy(options, 0, temp, 0, temp.length);
+        options = temp;
+        boolean[] dup = new boolean[options.length];
+        int numDups = 0;
+        
+        for (int i = 0; i < options.length; i++) {
+            String option = options[i];
+            if (LangUtil.isEmpty(option)) {
+                throw new IllegalArgumentException("empty option at " + i);
+            }
+            if (option.endsWith("-")) {
+                options[i] = option.substring(0, option.length()-1);
+                dup[i] = true;
+                numDups++;
+            }
+        }
+        final String[] NONE = new String[0];
+        final int variants = exp(2, numDups);
+        final String[][] result = new String[variants][];
+        // variant is a bitmap wrt doing extra value when dup[k]=true
+        for (int variant = 0; variant < variants; variant++) { 
+            ArrayList next = new ArrayList();
+            int nextOption = 0;
+            for (int k = 0; k < options.length; k++) {
+                if (!dup[k] || (0 != (variant & (1 << (nextOption++))))) {
+                    next.add(options[k]);
+                }                   
+            }
+            result[variant] = (String[]) next.toArray(NONE);
+        }
+        return result;
+    }
+    
+    private static int exp(int base, int power) { // not in Math?
+        if (0 > power) {
+            throw new IllegalArgumentException("negative power: " + power);
+        } 
+        int result = 1;
+        while (0 < power--) {
+            result *= base;
+        }
+        return result;
+    }
+
+       /**
+        * @param suiteFile
+        */
+       private void doStartSuite(File suiteFile) {
+               Collection c = features.values();
+               for (Iterator iter = c.iterator(); iter.hasNext();) {
+                       Feature element = (Feature) iter.next();
+                       if (element.listener instanceof TestCompleteListener) {
+                               ((TestCompleteListener)element.listener).doStartSuite(suiteFile);
+                       }
+               }               
+       }
+
+       /** Run the test suite specified by the spec */
+    protected RunResult run(AjcTest.Suite.Spec spec) {
+        LangUtil.throwIaxIfNull(spec, "spec");
+        /*
+         * For each run, initialize the runner and validator,
+         * create a new set of IRun{Iterator} tests,
+         * and run them. 
+         * Delete all temp files when done.
+         */
+        Runner runner = new Runner();
+        if (0 != features.size()) {
+            for (Iterator iter = features.entrySet().iterator(); iter.hasNext();) {
+                Feature feature = (Feature) ((Map.Entry) iter.next()).getValue();
+                runner.registerListener(feature.clazz, feature.listener);
+            }
+        }
+        IMessageHolder holder = new MessageHandler();
+        int numIncomplete = 0;
+        RunStatus status = new RunStatus(holder, runner);
+        status.setIdentifier(spec);
+        // validator is used for all setup in entire tree...
+        Validator validator = new Validator(status);
+        if (!killTemp) {
+            validator.lock(this);
+        }
+        Sandbox sandbox = null;
+        try {
+            sandbox = new Sandbox(spec.getSuiteDirFile(), validator);
+            IRunIterator tests = spec.makeRunIterator(sandbox, validator);
+            runner.runIterator(tests, status, null);
+            if (tests instanceof RunSpecIterator) {
+                numIncomplete = ((RunSpecIterator) tests).getNumIncomplete();
+            }
+        } finally {
+               if (!keepTemp) {
+                if (!killTemp) {
+                    validator.unlock(this);
+                }
+                   validator.deleteTempFiles(verboseHarness);
+               }
+        }
+        return new RunResult(status, numIncomplete);
+    }
+    
+    /** 
+     * Report the results of a test run after it is completed.
+     * Clients should be able to identify the number of:
+     * <ul>
+     * <li>tests run and passed</li>
+     * <li>tests failed, i.e., run and not passed (fail, error, etc.)</li>
+     * <li>tests incomplete, i.e., test definition read but test run setup failed</li>
+     * <li>tests skipped, i.e., test definition read and found incompatible with
+     *     the current configuration.</li>
+     * <ul>
+     * 
+     * @param status returned from the run
+     * @param numSkipped int tests that were skipped because of 
+     *         configuration incompatibilities
+     * @param numIncomplete int tests that failed during setup,
+     *         usually indicating a test definition or configuration error.
+     * @param msElapsed elapsed time in milliseconds
+     * */
+    protected void report(IRunStatus status, int numSkipped, int numIncomplete, 
+        long msElapsed ) {
+       if (logResults) {
+            RunUtils.AJCSUITE_PRINTER.printRunStatus(getLogStream(), status);
+        } else if (!(quietHarness || silentHarness) && (0 < status.numMessages(null, true))) {
+               if (!silentHarness) {
+               MessageUtil.print(getLogStream(), status, "");    
+               }
+        }
+        
+               logln(BridgeUtil.childString(status, numSkipped, numIncomplete) 
+            + " " + (msElapsed/1000) + " seconds");
+        
+    }
+
+    // --------------- delegate methods
+    protected void logln(String s) {
+       if (!silentHarness) {
+               getLogStream().println(s);
+       }
+    }
+    
+    protected PrintStream getLogStream() {
+        return System.out;
+    }
+    
+    protected boolean isSuiteFile(String arg) {
+        return ((null != arg) 
+                && (arg.endsWith(".txt") || arg.endsWith(".xml"))
+                && new File(arg).canRead());
+    }
+    
+    /** 
+     * Get the options that the input option is an alias for.
+     * Subclasses may add options directly to the getFeatureAliases result
+     * or override this.
+     * @return null if the input is not an alias for other options,
+     * or a non-empty List (String) of options that this option is an alias for 
+     */
+    protected List aliasOptions(String option) {
+        Properties aliases = Harness.getOptionAliases();
+        if (null != aliases) {
+            String args = aliases.getProperty(option);
+            if (!LangUtil.isEmpty(args)) {
+                return LangUtil.anySplit(args, OPTION_DELIM);
+            }
+        }
+        return null;
+    }
+
+    /** 
+     * Read and implement any of our options.
+     * Options other than this and suite files will be
+     * passed down as parent options through the test spec hierarchy.
+     * Subclasses override this to implement new options.
+     */
+    protected boolean acceptOption(String option) {
+//        boolean result = false;
+        if (LangUtil.isEmpty(option)) {
+            return true; // skip bad input
+        } else if ("-verboseHarness".equals(option)) {
+            verboseHarness = true;
+               } else if ("-quietHarness".equals(option)) {
+                       quietHarness = true;
+               } else if ("-silentHarness".equals(option)) {
+                       silentHarness = true;
+        } else if ("-keepTemp".equals(option)) {
+            keepTemp = true; 
+        } else if ("-killTemp".equals(option)) {
+            killTemp = true; 
+        } else if ("-logResults".equals(option)) {
+            logResults = true; 
+        } else if ("-exitOnFailure".equals(option)) {
+            exitOnFailure = true; 
+        } else {
+               return false;
+        }
+        return true;    
+    }    
+    
+    /** 
+     * Read a test suite file.
+     * This implementation knows how to read .txt and .xml files
+     * and logs any errors.
+     * Subclasses override this to read new kinds of suites.
+     * @return null if unable to read (logging errors) or AjcTest.Suite.Spec otherwise
+     */
+    protected AjcTest.Suite.Spec readSuite(File suiteFile) {
+        if (null != suiteFile) {
+            String path = suiteFile.getPath();
+            try {
+                if (path.endsWith(".xml")) {
+                    return AjcSpecXmlReader.getReader().readAjcSuite(suiteFile);
+                } else if (path.endsWith(".txt")) {
+                    return FlatSuiteReader.ME.readSuite(suiteFile);
+                } else {
+                    logln("unrecognized extension? " + path);
+                }
+            } catch (IOException e) {
+                e.printStackTrace(getLogStream());
+            }
+        }
+        return null;
+    }
+    
+    /** Add feature to take effect during the next runMain(..) invocation.
+     * @param feature the Feature to add, using feature.name as key.
+     */
+    protected void addFeature(Feature feature) {
+        if (null != feature) {
+            features.put(feature.name, feature);
+        }
+    }
+    
+    /** remove feature by name (same as feature.name) */
+    protected void removeFeature(String name) {
+        if (!LangUtil.isEmpty(name)) {
+            features.remove(name);
+        }
+    }
+    
+    /** @return unmodifiable Set of feature names */
+    protected Set listFeatureNames() {
+        return Collections.unmodifiableSet(features.keySet());
+    }
+
+    /** print detail message for syntax of main(String[]) command-line */
+    protected void printSyntax(PrintStream out) {
+        out.println("  {??}              unrecognized options are used as test spec globals");
+        out.println("  -help             print this help message");
+        out.println("  -verboseHarness   harness components log verbosely");
+        out.println("  -quietHarness     harness components suppress logging");
+        out.println("  -keepTemp         do not delete temporary files");
+        out.println("  -logResults       log results at end, verbosely if fail");
+        out.println("  -exitOnFailure    do System.exit({num-failures}) if suite fails");
+        out.println("  {suiteFile}.xml.. specify test suite XML file");
+        out.println("  {suiteFile}.txt.. specify test suite .txt file (deprecated)");
+    }
+
+    /** print known aliases at the end of the syntax message */
+    protected void printAliases(PrintStream out) {
+        LangUtil.throwIaxIfNull(out, "out");
+        Properties props = getOptionAliases();
+        if (null == props) {
+            return;
+        }
+        int pdLength = SYNTAX_PAD.length();
+        Set entries = props.entrySet();
+        for (Iterator iter = entries.iterator(); iter.hasNext();) {
+                       Map.Entry entry = (Map.Entry) iter.next();
+                       String alias = "  " + (String) entry.getKey();
+            int buf = pdLength - alias.length();
+            if (0 < buf) {
+                alias += SYNTAX_PAD.substring(0, buf);
+            } else {
+                alias += " ";
+            }
+            out.println(alias + entry.getValue());
+        }
+    }
+    
+    /** result struct for run(AjcTest.Spec) */
+    public static class RunResult {
+        public final IRunStatus status;
+        public final int numIncomplete;
+        public RunResult(IRunStatus status, int numIncomplete) {
+            this.status = status;
+            this.numIncomplete = numIncomplete;
+        }
+    }
+    /** feature implemented as named IRunIterator/IRun association */
+    public static class Feature {
+        /** never null, always assignable to IRun */
+        public final Class clazz;
+        
+        /** never null */
+        public final IRunListener listener;
+        
+        /** never null or empty */
+        public final String name;
+        
+        /** @throws IllegalArgumentException if any is null/empty or clazz is
+         *           not assignable to IRun
+         */
+        public Feature(String name, Class clazz, IRunListener listener) {
+            LangUtil.throwIaxIfNull(clazz, "class");
+            if (!IRun.class.isAssignableFrom(clazz)
+                && !IRunIterator.class.isAssignableFrom(clazz)) {
+                String s = clazz.getName() + "is not assignable to IRun or IRunIterator";
+                LangUtil.throwIaxIfFalse(false, s);
+            }
+            LangUtil.throwIaxIfNull(listener, "listener");
+            LangUtil.throwIaxIfNull(name, "name");
+            LangUtil.throwIaxIfFalse(0 < name.length(), "empty name");        
+            this.clazz = clazz;
+            this.listener = listener;
+            this.name = name;
+        }
+        
+        /** @return feature name */
+        public String toString() {
+            return name;
+        }
+    }
+}
+
+
+/** 
+ * Harness with features for controlling output
+ * (logging results and hiding streams).
+ * Use -help to get a list of feature options.
+ */
+class FeatureHarness extends Harness {
+
+    private static final String[] ALIASES = new String[] 
+        { "-hideStreams", 
+            "-hideCompilerStreams" 
+            + OPTION_DELIM + "-hideRunStreams",
+          "-jim", 
+            "-logMinFail"   
+            + OPTION_DELIM + "-hideStreams",
+          "-loud", 
+            "-verboseHarness",
+          "-baseline", 
+            "-verboseHarness" 
+            + OPTION_DELIM + "-traceTestsMin" 
+            + OPTION_DELIM + "-hideStreams",
+          "-release", 
+              "-baseline" 
+              + OPTION_DELIM + "-ajctestSkipKeywords=knownLimitation,purejava",
+          "-junit",
+                 "-silentHarness" + OPTION_DELIM + "-logJUnit" + OPTION_DELIM +
+                 "-hideStreams", 
+          "-cruisecontrol",
+                 "-junit" + OPTION_DELIM + "-ajctestSkipKeywords=knownLimitation,purejava"
+        };
+    static {
+        Properties optionAliases = Harness.getOptionAliases();
+        if (null != optionAliases) {
+            for (int i = 1; i < ALIASES.length; i += 2) {
+                optionAliases.put(ALIASES[i-1], ALIASES[i]);
+                       }
+        }
+    }
+
+    /** controller for suppressing and sniffing error and output streams. */
+    StreamsHandler streamsHandler; 
+
+    /** facility of hiding-streams may be applied in many features */
+    IRunListener streamHider;
+
+    /** facility of capture/log may be applied in many features */
+    IRunListener captureLogger;
+    
+    /** when making tests, do not run them */
+    TestMaker testMaker;
+    
+    public FeatureHarness() {
+        super();
+        streamsHandler = new StreamsHandler(false, true);
+    }
+    /** override to make tests or run as usual */
+    protected RunResult run(AjcTest.Suite.Spec spec) {
+        if (null != testMaker) {
+            System.out.println("generating rather than running tests...");
+            return testMaker.run(spec);
+        } else {
+            return super.run(spec);
+        }
+    }
+
+    /**
+     * Log via StreamsHandler-designated log stream.
+     * @see org.aspectj.testing.drivers.Harness#log(String)
+        */
+       protected void logln(String s) {
+               if (!silentHarness)
+               streamsHandler.lnlog(s);
+       }
+
+    /**
+        * @see org.aspectj.testing.drivers.Harness#getLogStream()
+     * @return StreamsHandler-designated log stream.
+        */
+       protected PrintStream getLogStream() {
+               return streamsHandler.out;
+       }
+
+
+    /** print detail message for syntax of main(String[]) command-line */
+    protected void printSyntax(PrintStream out) {
+        super.printSyntax(out);
+        out.println("  -progressDots     log . or ! for each AjcTest pass or fail");
+        out.println("  -logFail          log each failed AjcTest");
+        out.println("  -logPass          log each passed AjcTest");
+        out.println("  -logAll           log each AjcTest");
+        out.println("  -logMinFail       log each AjcTest failure with minimal excess data");
+        out.println("  -logMinPass       log each AjcTest success with minimal excess data");
+        out.println("  -logMinAll        log all AjcTest with minimal excess data");
+        out.println("  -logXMLFail       log XML definition for each failed AjcTest");
+        out.println("  -logXMLPass       log XML definition for each passed AjcTest");
+        out.println("  -logXMLAll        log XML definition for each AjcTest");
+        out.println("  -logJUnit         log all tests in JUnit XML report style");
+        out.println("  -hideRunStreams   hide err/out streams during java runs");
+        out.println("  -hideCompilerStreams   hide err/out streams during compiler runs");
+        out.println("  -traceTests       log pass|fail, /time/memory taken after each test");
+        out.println("  -traceTestsMin    log pass|fail after each test");
+        out.println("  -XmakeTests       create source files/dirs for initial compile run of each test");
+        out.println("  -XlogPublicType   log test XML if \"public type\" in an error message");
+        out.println("  -XlogSourceIn=Y,Z log test XML if Y or Z is in path of any sources");
+        super.printAliases(out);
+    }
+
+    /** Accept a number of logging and output options */
+       protected boolean acceptOption(String option) {
+        if (null == option) {
+            return false;
+        }
+        
+        final StreamsHandler streams = streamsHandler;
+        final IRunValidator validator = RunValidator.NORMAL;
+        final RunUtils.IRunStatusPrinter verbose 
+            = RunUtils.VERBOSE_PRINTER;
+        final RunUtils.IRunStatusPrinter terse 
+            = RunUtils.TERSE_PRINTER;
+//        final boolean LOGPASS = true;
+//        final boolean LOGFAIL = true;
+//        final boolean SKIPPASS = false;
+//        final boolean SKIPFAIL = false;
+//        final boolean LOGSTREAMS = true;
+        final boolean SKIPSTREAMS = false;
+        
+               Feature feature = null;
+        if (super.acceptOption(option)) {
+            // ok, result returned below
+            
+        } else if ("-XmakeTests".equals(option)) {
+            testMaker = TestMaker.ME;
+        } else if (option.startsWith("-traceTestsMin")) {
+            feature = new Feature(option, AjcTest.class,new TestTraceLogger(streams, false));
+        } else if (option.startsWith("-traceTests")) {
+            feature = new Feature(option, AjcTest.class,new TestTraceLogger(streams, true));
+        } else if (option.startsWith("-logMin")) {
+            feature = new Feature(option, AjcTest.class, 
+                new RunLogger(option, SKIPSTREAMS, streams, validator, terse));
+        } else if (option.startsWith("-logXML")) {
+            feature = new Feature(option, AjcTest.class, 
+                new XmlLogger(option, streams, validator));
+        } else if (option.startsWith("-logJUnit")) {
+               feature = new Feature(option, AjcTest.class,
+                       new JUnitXMLLogger(option,streams,validator));
+        } else if (option.startsWith("-log")) {
+            feature = new Feature(option, AjcTest.class, 
+                new RunLogger(option, SKIPSTREAMS, streams, validator, verbose));
+        } else if ("-hideRunStreams".equals(option)) {
+            feature = new Feature(option, JavaRun.class, getStreamHider());       
+        } else if ("-hideCompilerStreams".equals(option)) {
+            addFeature(new Feature(option, IncCompilerRun.class, getStreamHider()));   // hmmm    
+            feature = new Feature(option, CompilerRun.class, getStreamHider());       
+        } else if ("-progressDots".equals(option)) {
+            IRunListener listener = new RunListener() {
+                               public void runCompleted(IRunStatus run) {
+                    streamsHandler.log((validator.runPassed(run) ? "." : "!"));
+                               }
+            };
+            feature = new Feature(option, AjcTest.class, listener);       
+        } else if (option.startsWith("-XlogPublicType")) {
+            String label = option + TestCompleteListener.PASS; // print when validator true
+            feature = new Feature(option, AjcTest.class, 
+                new XmlLogger(label, streams, MessageRunValidator.PUBLIC_TYPE_ERROR));
+        } else if (option.startsWith("-XlogSourceIn")) {
+            String input = option.substring("-XlogSourceIn=".length());
+            LangUtil.throwIaxIfFalse(0 < input.length(), option);
+            String label = "-XlogSourceIn="  + TestCompleteListener.PASS; // print when validator true
+            StringRunner sr = new SubstringRunner(input, false);
+            feature = new Feature(option, AjcTest.class, 
+                new XmlLogger(label, streams, new SourcePathValidator(sr)));
+        } else {
+            return false;
+        }       
+        addFeature(feature);
+        return true;
+       }
+
+    /** lazy construction for shared hider */
+    protected IRunListener getStreamHider() {
+        if (null == streamHider) {
+            streamHider =  new RunListener() {
+                public void runStarting(IRunStatus run) {
+                    streamsHandler.hide();
+                }
+                public void runCompleted(IRunStatus run) {
+                    streamsHandler.show();
+                }
+                public String toString() { return "Harness StreamHider"; }
+            };
+        }
+        return streamHider;
+    }
+}
+
+/** Generate any needed test case files for any test. */
+class TestMaker  {
+    
+    static TestMaker ME = new TestMaker();
+    
+    /** @throws Error if unable to make dir */
+    static void mkdirs(File dir) {
+        if (null != dir && !dir.exists()) {
+            if (!dir.mkdirs()) {
+                throw new Error("unable to make dir: " + dir);
+            }
+        }
+    }
+    static String getFileContents(File baseDir, File file, String label) {
+        String fileName = file.getName();
+        if (fileName.endsWith(".java")) {
+            fileName = fileName.substring(0, fileName.length() - 5);
+        }
+        StringBuffer sb = new StringBuffer();
+        String filePath = file.getParentFile().getAbsolutePath();
+        String dirPath = baseDir.getAbsolutePath();
+        String pack = null;
+        if (filePath.startsWith(dirPath)) {
+            pack = filePath.substring(dirPath.length()).replace('/', '.');
+        }
+        if (!LangUtil.isEmpty(pack)) {
+           sb.append("package " + pack + ";");
+        }
+        final String EOL = "\n"; // XXX find discovered EOL
+        sb.append( EOL 
+            + EOL + "import org.aspectj.testing.Tester;"
+            + EOL + ""
+            + EOL + "/** @testcase " + label + " */"
+            + EOL + "public class " + fileName + " {"
+            + EOL + "\tpublic static void main(String[] args) { "
+            + EOL + "\t\tTester.check(null != args, \"null args\"); "
+            + EOL + "\t}"
+            + EOL + "}"
+            + EOL 
+            );
+            
+        return sb.toString();
+    }
+    
+    /** create a minimal source file for a test */
+    static void createSrcFile(File baseDir, File file, String testName) {
+        if (file.exists()) {
+            return;
+        }
+        String contents = getFileContents(baseDir, file, testName);
+        String error = FileUtil.writeAsString(file, contents);
+        if (null != error) {
+            throw new Error(error);
+        }
+    }
+    
+    /** create an empty arg file for a test */
+    static void createArgFile(File baseDir, File file, String testName) {
+        if (file.exists()) {
+            return;
+        }
+        String contents = "// argfile " + file;
+        String error = FileUtil.writeAsString(file, contents);
+        if (null != error) {
+            throw new Error(error);
+        }
+    }
+       
+    public Harness.RunResult run(AjcTest.Suite.Spec spec) {
+        ArrayList kids = spec.getChildren();
+        for (Iterator iter = kids.iterator(); iter.hasNext();) {
+                       makeTest( (AjcTest.Spec) iter.next());                  
+               }
+        IRunStatus status = new RunStatus(new MessageHandler(), new Runner());
+        status.start();
+        status.finish(IRunStatus.PASS);
+        return new Harness.RunResult(status, 0);
+    }
+    
+    private void makeTest(AjcTest.Spec spec) {
+        CompilerRun.Spec compileSpec = AjcTest.unwrapCompilerRunSpec(spec);
+        if (null == spec) {
+            throw new Error("null spec");
+        }
+        System.out.println("  generating test files for test: " + spec.getDescription());
+        File dir = spec.getSuiteDir();
+        if (null != dir) {
+            TestMaker.mkdirs(dir);
+        }
+        String offset = spec.getTestDirOffset();
+        if (!LangUtil.isEmpty(offset)) {
+            if (null == dir) {
+                dir = new File(offset);
+            } else {
+                dir = new File(dir.getAbsolutePath() + "/" + offset);
+            }
+        } else if (null == dir) {
+            dir = new File(".");
+        }
+        StringBuffer testName = new StringBuffer();
+        int pr = spec.getBugId();
+        if (0 < pr) {
+            testName.append("PR#" + pr + " ");
+        }
+        
+        testName.append(spec.getDescription());
+        final String label = testName.toString();
+        final File[] srcFiles = FileUtil.getBaseDirFiles(dir, compileSpec.getPathsArray());
+        if (!LangUtil.isEmpty(srcFiles)) {
+            for (int i = 0; i < srcFiles.length; i++) {
+                TestMaker.createSrcFile(dir, srcFiles[i], label);            
+            }
+        }
+        final File[] argFiles = FileUtil.getBaseDirFiles(dir, compileSpec.getArgfilesArray());
+        if (!LangUtil.isEmpty(argFiles)) {
+            for (int i = 0; i < argFiles.length; i++) {
+                TestMaker.createArgFile(dir, argFiles[i], label);            
+            }
+        }
+        
+       }
+    
+    /** @return "Testmaker()" */    
+    public String toString() {
+        return "TestMaker()";
+    }
+}
+
+interface StringRunner {
+    boolean accept(String s);
+}
+
+/**
+ * StringRunner than accepts input matching 0+ substrings,
+ * optionally case-insensitive.
+ */
+class SubstringRunner implements StringRunner {
+    private static String[] extractSubstrings(
+            String substrings, 
+            boolean caseSensitive) {
+        if (null == substrings) {
+            return null;
+        }
+        StringTokenizer st = new StringTokenizer(substrings, ",");
+        String[] result = new String[st.countTokens()];
+        for (int i = 0; i < result.length; i++) {
+                       result[i] = st.nextToken().trim();
+                   LangUtil.throwIaxIfFalse(0 < result[i].length(), "empty entry");
+            if (!caseSensitive) {
+                result[i] = result[i].toLowerCase();
+            }
+        }
+        return result;
+    }
+    
+    private final String[] substrings;
+    private final boolean caseSensitive;
+    
+    /**
+     * @param substrings the String containing comma-separated substrings
+     *                    to find in input - if null, any input accepted
+     * @param caseSensitive if true, do case-sensitive comparison
+     * @throws IllegalArgumentException if any substrings contains empty entry ", ,"
+     */
+    SubstringRunner(String substrings, boolean caseSensitive) {
+        this.caseSensitive = caseSensitive;
+        this.substrings = extractSubstrings(substrings, caseSensitive); 
+    }
+    
+    public boolean accept(String input) {
+        if (null == substrings) {
+            return true;
+        }
+        if (null == input) {
+            return false;
+        }
+        
+        if (!caseSensitive) {
+            input = input.toLowerCase();
+        }
+        for (int i = 0; i < substrings.length; i++) {
+                       if (-1 != input.indexOf(substrings[i])) {
+                return true;
+            }
+               }
+        return false;
+    }
+}
+
+/** 
+ * Signal whether run "passed" based on validating absolute source paths.
+ * (Static evaluation - no run necessary)
+ */
+class SourcePathValidator implements IRunValidator { // static - no run needed
+    private final StringRunner validator;
+    // XXX hoist common
+    SourcePathValidator(StringRunner validator) {
+        LangUtil.throwIaxIfNull(validator, "validator");
+        this.validator = validator;        
+    }
+    /**
+     * @return true if any source files in compile spec are
+     *          accepted by the validator.
+        * @see org.aspectj.testing.run.IRunValidator#runPassed(IRunStatus)
+        */
+       public boolean runPassed(IRunStatus run) {
+        AjcTest.Spec testSpec = AjcTest.unwrapSpec(run);
+        if (null != testSpec) {
+            CompilerRun.Spec compileSpec = AjcTest.unwrapCompilerRunSpec(testSpec);
+            File basedir = new File(testSpec.getSuiteDir(), testSpec.getTestDirOffset());
+            String[] paths = compileSpec.getPathsArray();
+            File[] files = FileUtil.getBaseDirFiles(basedir, paths);
+            for (int i = 0; i < files.length; i++) {
+                               if (validator.accept(files[i].getAbsolutePath())) {
+                    return true;
+                }
+                       }
+        }
+               return false;
+       }
+    
+}
+
+/** Signal whether run "passed" based on message kind and content */
+class MessageRunValidator implements IRunValidator {
+    
+    /** signals "passed" if any error contains "public type" */
+    static final IRunValidator PUBLIC_TYPE_ERROR 
+        = new MessageRunValidator("public type", IMessage.ERROR, false);
+    
+    private final IMessage.Kind kind;
+    private final String sought;
+    private final boolean orGreater;
+    
+    /**
+     * @param sought the String to seek anywhere in any message of the right kind
+     *         if null, accept any message of the right kind.
+     * @param kind the IMessage.Kind of messages to search - all if null
+     */
+    MessageRunValidator(String sought, IMessage.Kind kind, boolean orGreater) {
+        this.sought = sought;
+        this.kind = kind;
+        this.orGreater = orGreater;
+    }
+    
+    /** @return true if this run has messages of the right kind and text */
+       public boolean runPassed(IRunStatus run) {
+        return gotMessage(new IRunStatus[] {run});
+    }
+    
+    /**
+     * Search these children and their children recursively
+     * for messages of the right kind and content.
+     * @return true at first match of message of the right kind and content
+     */
+    private boolean gotMessage(IRunStatus[] children) {
+        if (LangUtil.isEmpty(children)) {
+            return false;
+        }
+        for (int i = 0; i < children.length; i++) {
+                       IRunStatus run = children[i];
+            if (null == run) {
+                continue; // hmm
+            }
+            IMessage[] messages = run.getMessages(kind, orGreater);
+            if (!LangUtil.isEmpty(messages)) {
+                if (LangUtil.isEmpty(sought)) {
+                    return true;
+                } else {
+                    for (int j = 0; j < messages.length; j++) {
+                        if (null == messages[j]) {
+                            continue; // hmm
+                        }
+                               String text = messages[j].getMessage();
+                        if ((null != text) && (-1 != text.indexOf(sought))) {
+                            return true;
+                        }
+                    }
+                }
+               }
+            if (gotMessage(run.getChildren())) {
+                return true;
+            }
+        }
+       return false;
+       }
+}
+
+/** 
+ * Base class for listeners that run depending on pass/fail status of input.
+ * Template method runCompleted handled whether to run.
+ * Subclasses implement doRunCompleted(..).
+ */
+abstract class TestCompleteListener extends RunListener {
+    /** label suffix indicating both pass and fail */
+    public static final String ALL = "All";
+
+    /** label suffix indicating fail */
+    public static final String FAIL = "Fail";
+
+    /** label suffix indicating pass */
+    public static final String PASS = "Pass";
+    
+
+    /** runValidator determines if a given run passed */
+    protected final IRunValidator runValidator;
+
+    /** label for this listener */
+    final String label;
+    
+    /** if trun and run passed, then run doRunCompleted(..) */
+    final boolean logOnPass;
+
+    /** if true and run did not pass, then run doRunCompleted(..) */
+    final boolean logOnNotPass;
+
+    /** may be null */
+    protected final StreamsHandler streamsHandler;
+    
+    /** true if the last run evaluation was ok */
+    boolean lastRunOk;
+    
+    /** last run evaluated */
+    IRunStatus lastRun; // XXX small memory leak - cache hashcode instead?
+    
+    /** @param label endsWith PASS || FAIL || ALL */
+    protected TestCompleteListener(
+        String label, 
+        IRunValidator runValidator,
+        StreamsHandler streamsHandler) {
+        if (null == runValidator) {
+            runValidator = RunValidator.NORMAL;
+        }
+        this.label = (null == label? "" : label);
+        this.logOnPass = label.endsWith(PASS) || label.endsWith(ALL);
+        this.logOnNotPass = label.endsWith(FAIL) || label.endsWith(ALL);
+        this.runValidator = runValidator;
+        this.streamsHandler = streamsHandler;
+    }
+    
+    public void runStarted(IRunStatus run) {
+        if (null != streamsHandler) {
+            streamsHandler.startListening();
+        }
+    }
+    
+    /** subclasses implement this to do some per-test initialization */
+    protected void doRunStarted(IRunStatus run) {
+    }
+    
+    
+    /** subclasses implement this to do some per-suite initialization */
+    protected void doStartSuite(File suite) {
+    }
+    
+    /** subclasses implement this to do end-of-suite processing */
+    protected void doEndSuite(File suite, long duration) {
+    }
+
+    public final void runCompleted(IRunStatus run) {
+        boolean doit = lastRunOk(run);
+        StreamsHandler.Result result = null;
+        if (null != streamsHandler) {
+            streamsHandler.endListening(doit);
+        }
+        if (doit) {
+            doRunCompleted(run, result);
+        }
+    }
+
+    /**
+     * @return true if run is ok per constructor specifications
+     */
+    protected boolean lastRunOk(IRunStatus run) {
+        if (lastRun != run) {
+            boolean passed = runValidator.runPassed(run);
+            lastRunOk = (passed ? logOnPass : logOnNotPass);
+        }
+        return lastRunOk;
+    }
+
+    /** @return "{classname}({pass}{,fail})" indicating when this runs */    
+    public String toString() { // XXX add label?
+        return LangUtil.unqualifiedClassName(this)
+            + "(" + (logOnPass ? (logOnNotPass ? "pass, fail)" : "pass)") 
+                                       : (logOnNotPass ? "fail)" : ")"));
+    }
+    /** 
+     * Subclasses implement this to do some completion action 
+     * @param run the IRunStatus for this completed run
+     * @param result the StreamsHandler.Result (if any - may be null)
+     */
+    public abstract void doRunCompleted(IRunStatus run, StreamsHandler.Result result);
+}
+
+/** 
+ * Write XML for any test passed and/or failed.
+ * Must register with Runner for RunSpecIterator.class,
+ * most sensibly AjcTest.class.
+ */
+class XmlLogger extends TestCompleteListener {
+    /** 
+     * @param printer the component that prints any status - not null
+     * @param runValidator if null, use RunValidator.NORMAL
+     */
+    public XmlLogger(
+        String label,
+        StreamsHandler streamsHandler, 
+        IRunValidator runValidator) {
+        super(label, runValidator, streamsHandler);
+    }
+    
+    public void doRunCompleted(IRunStatus run, StreamsHandler.Result result) {
+        PrintStream out = streamsHandler.getLogStream();
+        out.println("");
+        XMLWriter writer = new XMLWriter(new PrintWriter(out, true));
+        Object id = run.getIdentifier();
+        if (!(id instanceof Runner.IteratorWrapper)) {                
+            out.println(this + " not IteratorWrapper: " 
+                + id.getClass().getName() + ": " + id);
+            return;
+        }
+        IRunIterator iter = ((Runner.IteratorWrapper) id).iterator;
+        if (!(iter instanceof RunSpecIterator)) {                
+            out.println(this + " not RunSpecIterator: " + iter.getClass().getName()
+                + ": " + iter);
+            return;
+        }
+        ((RunSpecIterator) iter).spec.writeXml(writer);
+        out.flush();
+    }
+    
+}
+
+/**
+ * Write junit style XML output (for incorporation into html test results and
+ * cruise control reports
+ * format is...
+ * <?xml version="1.0" encoding="UTF-8" ?>
+ * <testsuite errors="x" failures="x" name="suite-name" tests="xx" time="ss.ssss">
+ * <properties/>
+ * <testcase name="passingTest" time="s.hh"></testcase>
+ * <testcase name="failingTest" time="s.hh">
+ *   <failure message="failureMessage" type="ExceptionType">free text</failure>
+ * </testcase>
+ * </testsuite>
+ */
+class JUnitXMLLogger extends TestCompleteListener {
+
+//  private File suite;
+  private StringBuffer junitOutput;
+  private long startTimeMillis;
+  private int numTests = 0;
+  private int numFails = 0;
+  private DecimalFormat timeFormatter = new DecimalFormat("#.##");
+
+  public JUnitXMLLogger(        
+       String label,
+       StreamsHandler streamsHandler, 
+       IRunValidator runValidator) {
+               super(label + ALL, runValidator, streamsHandler);
+               junitOutput = new StringBuffer();       
+  }
+  
+       /* (non-Javadoc)
+        * @see org.aspectj.testing.drivers.TestCompleteListener#doRunCompleted(org.aspectj.testing.run.IRunStatus, org.aspectj.testing.util.StreamsHandler.Result)
+        */
+       public void doRunCompleted(IRunStatus run, Result result) {             
+               long duration = System.currentTimeMillis()  - startTimeMillis;
+               numTests++;
+               junitOutput.append("<testcase name=\"" + run.getIdentifier() + "\" ");
+               junitOutput.append("time=\"" + timeFormatter.format((duration)/1000.0f) + "\"");
+               junitOutput.append(">");
+               if (!run.runResult())  {
+                       numFails++;
+                       junitOutput.append("\n");
+                       junitOutput.append("<failure message=\"test failed\" type=\"unknown\">\n");
+//                     junitOutput.println(result.junitOutput);
+//                     junitOutput.println(result.err);
+                       junitOutput.append("</failure>\n");
+               }
+               junitOutput.append("</testcase>\n");
+       }
+
+       /* (non-Javadoc)
+        * @see org.aspectj.testing.drivers.TestCompleteListener#runStarted(org.aspectj.testing.run.IRunStatus)
+        */
+       public void runStarting(IRunStatus run) {
+                super.runStarting(run);                
+                startTimeMillis = System.currentTimeMillis();
+       }
+
+       /* (non-Javadoc)
+        * @see org.aspectj.testing.drivers.TestCompleteListener#doEndSuite(java.io.File, long)
+        */
+       protected void doEndSuite(File suite, long duration) {
+               super.doEndSuite(suite, duration);
+               String suiteName = suite.getName();
+               // junit reporter doesn't like ".xml" on the end
+               suiteName = suiteName.substring(0,suiteName.indexOf('.'));
+               PrintStream out = streamsHandler.getLogStream();
+               out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+               String timeStr = new DecimalFormat("#.##").format(duration/1000.0);                                     
+               out.print("<testsuite errors=\"" + numFails + "\" failures=\"0\" ");
+               out.print("name=\"" + suite.getName() + "\" " );
+               out.println("tests=\"" + numTests + "\" time=\"" + timeStr + "\">");
+               out.print(junitOutput.toString());
+               out.println("</testsuite>");    
+       }
+
+       /* (non-Javadoc)
+        * @see org.aspectj.testing.drivers.TestCompleteListener#doStartSuite(java.io.File)
+        */
+       protected void doStartSuite(File suite) {
+               super.doStartSuite(suite);
+//             this.suite = suite;
+               numTests = 0;
+               numFails = 0;
+               junitOutput = new StringBuffer();
+       }
+
+}
+
+/** log pass and/or failed runs */
+class RunLogger extends TestCompleteListener {
+    final boolean logStreams;
+    final RunUtils.IRunStatusPrinter printer;
+
+    /** 
+     * @param printer the component that prints any status - not null
+     * @param runValidator if null, use RunValidator.NORMAL
+     */
+    public RunLogger(
+        String label,
+        boolean logStreams,
+        StreamsHandler streamsHandler,
+        IRunValidator runValidator,
+        RunUtils.IRunStatusPrinter printer) {
+        super(label, runValidator, streamsHandler);
+        LangUtil.throwIaxIfNull(streamsHandler, "streamsHandler");
+        LangUtil.throwIaxIfNull(printer, "printer");
+        this.logStreams = logStreams;
+        this.printer = printer;
+    }
+
+    public void doRunCompleted(IRunStatus run, StreamsHandler.Result result) {
+        PrintStream out = streamsHandler.getLogStream();
+        printer.printRunStatus(out, run);
+        if (logStreams) {
+            if (!LangUtil.isEmpty(result.err)) {
+                out.println("--- error");
+                out.println(result.err); 
+            }
+            if (!LangUtil.isEmpty(result.out)) {
+                out.println("--- ouput");
+                out.println(result.out); 
+            }
+        }
+        out.println("");
+    }
+}
+
+/** trace time and memory between runStaring and runCompleted */
+class TestTraceLogger extends TestCompleteListener {
+    private static final Runtime runtime = Runtime.getRuntime();
+    private long startTime;
+    private long startMemoryFree;
+    private final boolean verbose;
+    
+    public TestTraceLogger(StreamsHandler handler) {
+        this(handler, true);
+    }
+    public TestTraceLogger(StreamsHandler handler, boolean verbose) {
+        super("-traceTestsAll", null, handler);
+        this.verbose = verbose;
+    }
+    public void runStarting(IRunStatus run) {
+        super.runStarting(run);
+        startTime = System.currentTimeMillis();
+        startMemoryFree = runtime.freeMemory();    
+    }
+    
+    public void doRunCompleted(IRunStatus run, StreamsHandler.Result result) {
+        long elapsed = System.currentTimeMillis() - startTime;
+        long free = runtime.freeMemory();
+        long used = startMemoryFree - free;
+        String label = run.runResult() ? "PASS " : "FAIL ";
+        PrintStream out = streamsHandler.getLogStream();
+        if (verbose) {
+            label = label
+                    + "elapsed: " + LangUtil.toSizedString(elapsed, 7)
+                    + " free: " + LangUtil.toSizedString(free, 10)
+                    + " used: " + LangUtil.toSizedString(used, 10)
+                    + " id: ";
+        }
+        out.println(label + renderId(run));
+    }
+    
+    /** @return true - always trace tests */
+    protected boolean isFailLabel(String label) {
+        return true;
+    }
+    
+    /** @return true - always trace tests */
+    protected boolean isPassLabel(String label) {
+        return true;
+    }
+
+    /**
+     * This implementation returns run identifier toString(). 
+     * Subclasses override this to render id as message suffix.
+     */
+    protected String renderId(IRunStatus run) {
+        return "" + run.getIdentifier();
+    }
+}
+        // printing files
+//        AjcTest.Spec testSpec = AjcTest.unwrapSpec(run);
+//        if (null != testSpec) {
+//            CompilerRun.Spec compileSpec = AjcTest.unwrapCompilerRunSpec(testSpec);
+//            File dir = new File(testSpec.getSuiteDir(), testSpec.getTestDirOffset());
+//            List files = compileSpec.getPathsAsFile(dir);
+//            StringBuffer sb = new StringBuffer();
+//            for (Iterator iter = files.iterator(); iter.hasNext();) {
+//                File file = (File) iter.next();
+//                sb.append(" " + file.getPath().replace('\\','/').substring(2));         
+//            }
+//            out.println("files: " + sb);
+//        }
+
+
diff --git a/testing-drivers/src/test/java/org/aspectj/testing/drivers/package.html b/testing-drivers/src/test/java/org/aspectj/testing/drivers/package.html
new file mode 100644 (file)
index 0000000..a244665
--- /dev/null
@@ -0,0 +1,428 @@
+<html>
+       <head><title>Harness Package Documentation</title></head>
+<body>
+<p>
+The AspectJ compiler test harness can compile and run AspectJ programs
+as specified by the test definitions.
+This document tells you how to run the harness.
+It describes the options you can specify on the command-line to
+control various components that form the harness, either to 
+specify options that augment the test definitions or to
+change how the harness works, e.g., selecting particular tests
+or logging more information.
+For information on how to write a test definition, see
+<code>readme-writing-compiler-tests.html</code> in the testing module.
+</p>
+<p>
+The harness drives compiler tests, using  
+a chain of responsibility to map elements
+in the schema of a test definition to implementing classes.
+
+</p>
+<table border="1" cellpadding="1">
+<tr><th align="left">Test feature</th>
+    <th align="left">Description</th>
+    <th align="left">Implementing class</th>
+</tr>
+<tr><td>(loading suites...)</td>
+    <td>general harness</td>
+    <td>Harness</td>
+</tr>
+<tr><td>(logging...)</td>
+    <td>subclass feature harness</td>
+    <td>FeatureHarness</td>
+</tr>
+<tr><td><code>&lt;suite&gt;</code></td>
+    <td>Test suite</td>
+    <td>AjcTest.Suite</td>
+</tr>
+<tr><td>&nbsp;&nbsp;<code>&lt;ajc-test&gt;</code></td>
+    <td>Test case</td>
+    <td>AjcTest</td>
+</tr>
+<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;<code>&lt;compile&gt;</code></td>
+    <td>Initial (batch) compile run</td>
+    <td>CompilerRun</td>
+</tr>
+<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;<code>&lt;inc-compile&gt;</code></td>
+    <td>Incremental re-compile</td>
+    <td>IncCompilerRun</td>
+</tr>
+<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;<code>&lt;run&gt;</code></td>
+    <td>Run class</td>
+    <td>JavaRun</td>
+</tr>
+</table>
+<!--
+   general harness                                 (Harness)
+      subclass feature harness                     (FeatureHarness)
+        &lt;ajc-test&gt; run component             (AjcTest)
+          &lt;compile&gt; {sub-} run component     (CompilerRun)
+          &lt;inc-compile&gt; {sub-} run component (IncCompilerRun)
+          &lt;run&gt; {sub-} run component         (JavaRun)
+          ...
+-->
+<p/>
+The compiler used is the AspectJ compiler <code>ajc</code>
+(optionally as wrapped by the Ant task or AJDE API's), but
+in principle any compiler accepting similar options can be
+used.
+<p/>
+To run from the command-line, use 
+<code>Harness.main(String[])</code>.
+To run programmatically, use <code>Harness.getHarness()</code>.
+<code>Harness.runMain(String[])</code> takes arguments that
+each component in the chain may accept and interpret, so
+you can modify how the tests run by specifying the following 
+arguments on the harness command line:
+<p/>
+<table cellpadding="1" border="1">
+<tr><th>Component</th><th>Options</th></tr>
+
+<tr><td rowspan="6" valign="top">Harness 
+    <p>suite files, harness verbosity, temp files, option variants
+    </p></td></tr>
+  <tr><td><u>suite files</u>: ajcTest-compliant .txt or .xml files are accepted.
+     <!-- XXX link to ajcTestSuite.dtd and .txt definitions -->
+     </td></tr>
+  <tr><td><u><code>-verboseHarness</code></u>, 
+         <u><code>-quietHarness</code></u>: 
+      Log accepted options and skipped tests,
+      or do not print even info messages.
+     </td></tr>
+  <tr><td><u><code>-keepTemp</code></u>: Normally the harness saves temp files until
+     the end of the run, and deletes them.  If you abort the run or specify
+     <code>-keepTemp</code>, then temporary (sandbox) directories will remain for analysis.
+     In either case, the file system accumulates all temporary directories
+     and files used for a give harness run; for the <code>ajcTests.xml</code>
+     suite, this runs into thousands of files.
+     </td></tr>
+  <tr><td><u><code>-killTemp</code></u>: The opposite of <code>-keepTemp</code>,
+     this causes the harness to delete temporary (sandbox) directories at 
+     the end of each test run. 
+     In this case, the file system only accumulates files for 
+     the current test.
+     </td></tr>
+  <tr><td><u>*- variants</u>: Options with a trailing "-" cause two sets of
+      option lists to be produced, one with and one without the corresponding
+      option.  E.g., "-emacssym-" will run the suite twice, once with and
+      once without the "-emacssym" flag.
+      That means if you use this on each of three options, you will
+      get 8 variant sets (1 with no options, 1 with all 3 options, 
+      3 with 2 options, and 3 with 1 option). 
+     </td></tr>
+    
+<tr><td rowspan="5" valign="top">FeatureHarness 
+    <p>output and logging options
+    </p></td></tr>
+  <tr><td><u>tracing</u>: 
+     <code>-progressDots</code> will print "." for every passed
+     test completed and "!" for every test completed but not passed.
+     <code>-traceTests</code> will print a one-line summary for each test
+     of the time and space taken and whether the test passed.
+     <code>-traceTestsMin</code> will print only the test and whether it passed.
+     <code>-baseline</code> is an alias for
+     <code>-traceTestsMin</code> 
+     <code>-hideStreams</code> and 
+     <code>!eclipse</code>, used to emit tests results in a form
+     comparable by <code>org.aspectj.testing.util.TestDiffs</code>. 
+     or usable to select tests by title for options like 
+     <code>-ajctestTitleList</code>.
+     </td></tr>
+     
+  <tr><td><u>output</u>: <code>-hide{Compiler|Run}Streams</code> will prevent output and
+     error streams from being printed to System.err and System.out, 
+     optionally only for run or compile steps.
+     </td></tr>
+  <tr><td><u>logging</u>: 
+     Log variants take the form <code>-log{Min|Xml}[Fail|Pass|All]</code>.
+     The suffix {All|Pass|Fail} selects all tests or only passing or failing tests.
+     The infix {Min} means to log with minimal information, typically only any
+     fail messages.
+     The infix {Xml} means to log the XML form of the test definition, so that
+     you can inspect the input or re-run arbitrary tests.  
+     (You can also re-run a set of tests using keywords
+     (e.g., "<code>-ajctestsRequireKeywords=...</code>" or using titles
+     (e.g., "<code>-ajctestsTitleFailList=ajcTestResults.txt</code>".)
+     Finally, the experimental option <code>-XlogPublicType</code> will 
+      log the XML test definition for 
+         any test run that emits any ERROR messages containing the text "public type".
+     </td></tr>
+  <tr><td><u>interaction of output streams and logging</u>: 
+     Streams will be emitted in real-time,
+     <em>before</em> the test is logged, unless streams are hidden.
+     When logging in normal (non-Min or -XML) form, the log will emit the streams
+     with the test report, so e.g., you can use -hideStreams -logFail to 
+     hide streams for passing tests but emit them for failing tests
+     in the context of the log.
+     </td></tr>
+    
+<tr><td rowspan="5" valign="top">AjcTest
+    <p>selection options for keywords, bugID (PR), or title (description)
+    </p></td></tr>
+  <tr><td><u>keywords</u>: <code>-ajctest[Require|Skip]Keywords=one{,two}</code>
+     will either require or skip a test that has one of the 
+     specified keywords.
+     </td></tr>
+  <tr><td><u>Bugs</u>: <code>-ajctestPR=101{,102}</code>
+     will require that a test have one of the listed bug id's.
+     </td></tr>
+  <tr><td><u>title</u>: 
+     <code>"-ajctestTitleContains=one,two"</code>
+     will require that the title (description) of a test contain 
+     one of the specified substrings, here either "one" or "two".
+     Use this to select a few tests you know generally.
+     <br/>
+     <code>"-ajctestTitleList=first title\, in theory, second title"</code>
+     will require that the title (description) of a test be
+     exactly "first title, in theory" or "second title".  
+     The entire option must be one argument on the command line.
+     Use this when working with just a few specific tests.
+     <br/>
+     <code>"-ajctestTitleList=../tests/ajcTestResults.txt"</code>
+     will require that the title (description) of a test be
+     equal to one listed in <code>../tests/ajcTestResults.txt</code>
+     as a line of the form "[PASS|FAIL] {title}(.."
+     (This form is emitted by the <code>-traceTestsMin</code> option).
+     This option only differs from the prior in that the parameter
+     is a valid file to read.
+     Use this to re-run a large set of tests.
+     <br/>
+     <code>"-ajctestTitleFailList=../tests/ajcTestResults.txt"</code>
+     is the same as the <code>-ajctestTitleList={file}</code> variant,
+     except that only results prefixed "FAIL" are included.
+     Use this to re-run only the tests that failed from a large set.
+     </td></tr>
+     
+  <tr><td><u>Combinations</u>: all selectors are applied to each test,
+     so all tests selected will comply with all constraints.
+     Specifying lists within a particular constraints will match
+     a union of tests for that constraint 
+     (e.g., all tests with bug id's 101 or 102),     
+     but there is no way to get a union of constraints 
+     (e.g., all tests with bug id's 101 or 102 <em>or</em> 
+     with keywords pure-java or knownLimitation).
+     However, <code>-ajctestSkipKeywords=...</code> can return all
+     tests without the specified keywords, so it can form unions like
+     "all tests without the knownLimitation keyword, but with 
+     bug id's 101 or 102".
+     Title lists can work similarly.  E.g., to run the failed
+     incremental tests from ajcTestResults.txt, specify
+     <code>-ajctestTitleFailList=../tests/ajcTestResults.txt</code>
+     <code>-ajctestRequireKeywords=incremental-test</code>.
+     </td></tr>
+    
+<tr><td rowspan="6" valign="top">CompilerRun
+<p>compiler options and side-effects
+    </p></td></tr>
+  <tr><td><u>supported options</u>: 
+     The command line passed to the compiler by <code>CompilerRun</code>
+     is composed of entries derived from the <code>&lt;compile&gt;</code>
+     attributes and recognized from the harness command line.
+     <code>&lt;compile&gt;</code> has specific attributes like
+     <code>files</code>, 
+     <code>argfiles</code>, 
+     <code>classpath</code> and       
+     <code>sourceroot</code> 
+     which translate directly to their counterparts.
+     The output option <code>-d</code> is defined by <code>CompilerRun</code> and
+     may not be specified (and <code>-outjar</code> is not supported).
+     Most other compiler options are defined in 
+     <code>CompilerRun.Spec.CRSOptions</code> and may be specified
+     on the harness command-line
+     or in the <code>options</code> attribute of 
+     <code>&lt;compile&gt;</code>.  
+     In the <code>options</code> attribute, each argument is comma-delimited,
+     so an option with an argument would look like 
+     <code>&lt;compile options="-source,1.4" ...&gt;</code>.  
+     If options collide, duplicates
+     can be resolved using option dominance (below).
+     </td></tr>
+  <tr><td><u>compiler selectors</u>: 
+        Use <code>-ajc</code> or <code>-eclipse</code> to select the old
+     (ajc 1.0) or new (eajc 1.1) compilers.  
+      Note that the old compiler is not
+      available in the CVS source tree at eclipse.org.
+      Use <code>-ajdeCompiler</code> to run a wrapper around the
+      AJDE interface
+      and <code>-ajctaskCompiler</code> to run a wrapper around the
+      AjcTask (Ant task) interface.
+     </td></tr>
+  <tr><td><u>option dominance <code>[-|!|^]</code></u>: 
+     Some tests require or prohibit certain options; 
+     likewise, sometime you want to force all tests
+     run with or without an option specified on the command-line,
+     regardless of its setting in the <code>&lt;compile options=".." ...&gt;</code> 
+     attribute. 
+     For this reason an option may be specified in the options attribute
+     or on the harness command-line as
+      <code>-option</code>,
+      <code>!option</code>, or 
+      <code>^option</code>.
+     <ul>
+     <li><u>- set</u>: If the leading character of an option is "-", then it is set unless forced-off.</li>
+     <li><u>^ force-off</u>: If the leading character of an option is "^", then it is forced off.
+            Any other matching option will be removed.</li>
+     <li><u>! force-on</u>: If the leading character of an option is "!", then it is forced on.
+         Any other non-force-on matching option will be removed.</li>
+     <li><u>force conflict</u>: If there are two matching force-on options, the test is skipped.</li>
+     <li><u>related options</u>: Two options match if they are the same or
+        if they are in the same family.  For example, <code>-ajc</code> and
+        <code>eclipse</code> are both compiler, and <code>-source 1.4</code>
+        and <code>-source 1.3</code> are both source.
+               <br/>
+        </li>
+     </ul>
+     </td></tr>
+  <tr><td><u>auto-skip</u>: After combining global and local options, there may be
+     conflicting or impossible options, which cause the test to be skipped:
+     <ul>
+     <li><u>semantic conflicts</u>: two options may conflict in meaning 
+           - e.g., <code>-lenient</code> and <code>-strict</code></li>
+     <li><u>impossible option</u>: It may not be possible in the current configuration to 
+         implement an option - e.g., <code>-usejavac</code> or <code>-eclipse</code> 
+         when javac or the eclipse implementation is not on the classpath              
+               <br/></li>
+     </ul>
+     </td></tr>
+     
+  <tr><td><u>source searching</u>: Given <code>-seek:{literal}</code>,  
+         as a side-effect, 
+         <code>CompilerRun</code> will search source files for {literal},
+      emitting for each instance an INFO message of the form: 
+     <tt>found: {file}:{line}:{column}</tt>
+      (Note that the harness does not display INFO messages unless <tt>-verboseHarness</tt>
+      or <tt>-loud</tt> is used.) 
+     </td></tr>
+    
+     
+  <tr><td rowspan="2" valign="top">JavaRun
+         <p>Options and forking</p></td>
+     <td><u>options</u>: options specified in the test are passed
+      to the main method as they would be on the command-line.
+      No options passed to the harness are passed through to
+      the main class.
+     </td></tr>
+  <tr><td><u>forking</u>: 
+        Forking is useful to run in a different version of Java
+        than can be supported by the harness (i.e., some 1.1 flavor);
+        it's very time-consuming otherwise.
+               Currently forking is only controllable through system properties
+               of the invoking vm (defined in JavaRun.java):
+               <ul>
+                  <li><u>javarun.fork</u>: anything to run in a new vm.
+                     </li>
+                  <li><u>javarun.java</u>: path to the java executable to run
+                     (suffix included).  If not supplied, the harness tries to
+                     find the java that invoked the harness.
+                     </li>
+                  <li><u>javarun.java.home</u>: the value of the JAVA_HOME 
+                     environment variable, if it needs to be set.
+                     </li>
+                  <li><u>javarun.bootclasspath</u>: this is prepended to the
+                     run classpath.  Multiple entries must be separated by
+                     the system-dependent path separator.
+                     </li>
+                  <li><u>javarun.vmargs</u>: this is added to the fork command-line
+                       right after java.  Multiple entries must be separated by a comma
+                       (and the whole thing should be one parameter), e.g.,
+                       <code>-Djavarun.vmargs=-Dname=value,-Dname2="value 2"</code>
+                     </li>
+               </ul>
+     </td></tr>
+</table>
+<br/>
+Following are some sample configurations:
+<ul>
+<li><code>java {harness} -hideStreams {suiteFile}</code>
+ <p>Use this to output only a 1-line summary of the test results
+    (tests skipped, incomplete, failed, passed).<br/></p>
+  </li>
+  
+<li><code>java {harness} -hideStreams -traceTestsMin {suiteFile} > results.txt</code>
+ <p>This writes to result.txt one line [PASS|FAIL] per test, plus a
+    1-line summary of the test results.<br/></p>
+  </li>
+  
+<li><code>java {harness} -logFail {suiteFile} -ajctestTitleFailList=results.txt</code>
+ <p>This re-runs any test that failed from the "results.txt" run,
+    verbosely logging any fails.<br/></p>
+  </li>
+  
+<li><code>java {harness} -hideStreams -logMinFail {suiteFile}</code>
+ <p>Use this when running tests mainly to see if they pass or
+    if the failure messages are typically enough information
+    to indicate why the test is failing.  It produces only minimal
+    output for failed tests.<br/></p>
+  </li>
+  
+<li><code>java {harness} -hideStreams -verboseHarness -logFail {suiteFile}</code>
+ <p>When it's not clear at first glance why a test is failing, before
+    looking at the test code you can run it and print any harness or test
+    setup failures and all the associated messages from the test components.<br/></p>
+  </li>
+
+<li><code>java {harness} -hideStreams -usejavac- -ajc -Xlint- {suiteFile}</code>
+ <p>Because of the trailing '-' on two of the options,
+    this would do four complete runs with the old (Ajc 1.0) compiler: one with
+    no options, one with -lenient, one with -Xlint, and one with both.<br/></p>
+  </li>
+  
+
+<li><code>java {harness} --ajctestPR=101,102 -Xlint- ^usejavac !eclipse {suiteFile}</code>
+ <p>Run any tests associated with bugs 101 and 102, with and without -Xlint,
+    forcing off -usejavac and forcing the use of the new eclipse-based compiler.<br/></p>
+  </li>
+  
+</ul>
+
+If you have a set of options you use often, you can define a single-word
+option alias for it; see <code>Harness.optionAliases</code>.
+
+<br/><u>Configuration</u>: Most tests use the library jars in 
+<code>modules/lib/test</code>, defined in 
+<code>org.aspectj.testing.harness.bridge.Globals</code>.  
+Normally the harness finds these by relative path 
+<code>../lib/tests/*.jar</code>, which works whenever the tests are
+run from a peer module directory.  When running tests elsewhere,
+define the environment variable <code>harness.libdir</code> - e.g., 
+<pre>
+    $ cd aspectj/tests
+    $ java -Dharness.libdir=../modules/lib/test ...
+</pre>
+
+<br/><u>Forking:</u>:
+The harness must be run in a compiler-compatible VM, and the
+compiler steps run in-process.
+However, the java steps can be run in forked mode, which is useful
+when compiling for a VM which can't run the compiler.
+To compile for a different target VM could require
+setting the options for bootclasspath, target, and source.
+To run the harness so that any &lt;run.. tasks run in a
+separate vm, do something like this:
+<pre>
+   java -Djavarun.java=d:\jdk1.1.8\bin\java.exe \
+        -Djavarun.bootclasspath=d:\jdk1.1.8\lib\classes.zip \
+        -Djavarun.java.home=d:\jdk1.1.8 \
+        -Djavarun.fork=true \
+        -jar ../aj-build/jars/testing-drivers-all.jar \
+        ajcTests.xml -logFail
+</pre>
+
+Here <code>CompilerRun</code> would add the bootclasspath as such when compiling.
+JavaRun would fork using the 1.1 vm and prepend the bootclasspath
+to the classpath, with an effect like these commands
+(ignoring the line splitting in the classpath):
+<pre>
+   set JAVA_HOME=d:\jdk1.1.8
+   d:\jdk1.1.8\bin\java.exe \
+     -classpath "d:\jdk1.1.8\lib\classes.zip;
+                 d:\aspectj-src\lib\test\testing-client.jar;
+                 d:\aspectj-src\lib\test\aspectjrt.jar;
+                 c:\TEMP\sandbox7wers\classes"
+     {mainClass} {option..}
+</pre>
+
+
+</body>
+</html>