/* ******************************************************************* * 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 Common Public License v1.0 * which accompanies this distribution and is available at * http://www.eclipse.org/legal/cpl-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.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; /** * Test harness for running AjcTest.Suite test suites. * This can be easily extended by subclassing. * * 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 = LangUtil.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 && (null != result)) { 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); } } } /** * @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: *