/* ******************************************************************* * Copyright (c) 2003 Contributors * All rights reserved. * This program and the accompanying materials are made available * under the terms of the Eclipse Public License v 2.0 * which accompanies this distribution and is available at * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt * * Contributors: * Wes Isberg initial implementation * ******************************************************************/ package org.aspectj.testing.drivers; import java.io.File; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.aspectj.ajdt.internal.core.builder.AjState; 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.AjcTest; import org.aspectj.testing.harness.bridge.AjcTest.Spec; 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.IRunIterator; import org.aspectj.testing.run.RunStatus; import org.aspectj.testing.run.Runner; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestResult; import junit.framework.TestSuite; /* * Adapt Harness tests to JUnit driver. This renders suite files as TestSuite * and AjcTest as TestCase. When run, aborts are reported as error and fails as * failures, with all messages stuffed into the one JUnit exception message. * Test options are supported, but no harness options. The TestSuite * implementation prevents us from re-running tests. In the Eclipse JUnit test * runner, the tests display hierarchically and with annotations and with * messages. You can stop the tests, but not traverse to the source or re-run * the test. */ public class AjctestsAdapter extends TestSuite { public static final String VERBOSE_NAME = AjctestsAdapter.class.getName() + ".VERBOSE"; private static final boolean VERBOSE = HarnessJUnitUtil .readBooleanSystemProperty(VERBOSE_NAME); /** * Factory to make and populate suite without options. * * @param suitePath * the String path to a harness suite file * @return AjctestJUnitSuite populated with tests */ public static AjctestsAdapter make(String suitePath) { return make(suitePath, null); } /** * Factory to make and populate suite * * @param suitePath * the String path to a harness suite file * @param options * the String[] options to use when creating tests * @return AjctestJUnitSuite populated with tests */ public static AjctestsAdapter make(String suitePath, String[] options) { AjctestsAdapter result = new AjctestsAdapter(suitePath, options); AjcTest.Spec[] tests = AjcTest.Suite.getTests(result.getSpec()); if (VERBOSE) { log("loading " + tests.length + " tests in " + suitePath); } for (Spec ajcTest : tests) { result.addTest(new AjcTestSpecAsTest(ajcTest, result)); } return result; } private static void log(String message) { System.err.println(message); System.err.flush(); } private final String suitePath; private final String[] options; private AjcTest.Suite.Spec spec; private Runner runner; private Validator validator; private IMessageHolder holder; private Sandbox sandbox; private File suiteDir; private String name; private AjctestsAdapter(String suitePath, String[] options) { this.suitePath = suitePath; String[] opts = new String[0]; if (!HarnessJUnitUtil.isEmpty(options)) { opts = new String[options.length]; System.arraycopy(options, 0, opts, 0, opts.length); } this.options = opts; } @Override public void addTest(Test test) { if (!(test instanceof AjcTestSpecAsTest)) { String m = "expecting AjcTestSpecAsTest, got " + (null == test ? "null test" : test.getClass().getName() + ": " + test); throw new IllegalArgumentException(m); } super.addTest(test); } @SuppressWarnings("rawtypes") @Override public void addTestSuite(Class testClass) { throw new Error("unimplemented"); } @Override public String getName() { if (null == name) { name = HarnessJUnitUtil.cleanTestName(suitePath + Arrays.asList(options)); } return name; } /** * Callback from test to run it using suite-wide holder, etc. The caller is * responsible for calling result.startTest(test) and result.endTest(test); * * @param test * the AjcTestSpecAsTest to run * @param result * the TestResult for any result messages (may be null) */ protected void runTest(AjcTestSpecAsTest test, TestResult result) { final Runner runner = getRunner(); final Sandbox sandbox = getSandbox(); final Validator validator = getValidator(); int numIncomplete = 0; final RunStatus status = new RunStatus(new MessageHandler(), runner); status.setIdentifier(test.toString()); try { IMessageHolder holder = getHolder(); holder.clearMessages(); IRunIterator steps = test.spec.makeRunIterator(sandbox, validator); if (0 < holder.numMessages(IMessage.ERROR, true)) { MessageUtil.handleAll(status, holder, IMessage.INFO, true, false); } else { runner.runIterator(steps, status, null); } if (steps instanceof RunSpecIterator) { numIncomplete = ((RunSpecIterator) steps).getNumIncomplete(); } } finally { try { // reportResult handles null TestResult HarnessJUnitUtil .reportResult(null, status, test, numIncomplete); } finally { validator.deleteTempFiles(true); } } } private File getSuiteDir() { if (null == suiteDir) { File file = new File(suitePath); file = file.getParentFile(); if (null == file) { file = new File("."); } suiteDir = file; } return suiteDir; } private Validator getValidator() { if (null == validator) { validator = new Validator(getHolder()); // XXX lock if keepTemp? } return validator; } private Runner getRunner() { if (null == runner) { runner = new Runner(); } return runner; } private IMessageHolder getHolder() { if (null == holder) { holder = new MessageHandler(); } return holder; } private AjcTest.Suite.Spec getSpec() { if (null == spec) { IMessageHolder holder = getHolder(); spec = HarnessJUnitUtil.getSuiteSpec(suitePath, options, getHolder()); if (VERBOSE && holder.hasAnyMessage(null, true)) { MessageUtil.print(System.err, holder, "skip ", MessageUtil.MESSAGE_MOST); } holder.clearMessages(); } return spec; } private Sandbox getSandbox() { if (null == sandbox) { sandbox = new Sandbox(spec.getSuiteDirFile(), getValidator()); } return sandbox; } /** * Wrap AjcTest.Spec for lookup by description * * @author wes */ public static class SpecTests { private static final HashMap TESTS = new HashMap<>(); // private static void putSpecTestsFor(String id, SpecTests tests) { // TESTS.put(id, tests); // } private static SpecTests getSpecTestsFor(String id) { SpecTests result = TESTS.get(id); if (null == result) { throw new Error("no tests found for " + id); } return result; } // ------------------------------------ final AjctestsAdapter mAjctestsAdapter; private final Map mDescriptionToAjcTestSpec; // ------------------------------------ private SpecTests(AjctestsAdapter ajctestsAdapter, AjcTest.Spec[] tests) { mAjctestsAdapter = ajctestsAdapter; Map map = new HashMap<>(); for (Spec test : tests) { map.put(test.getDescription(), test); } mDescriptionToAjcTestSpec = Collections.unmodifiableMap(map); } /** * @param description * the String description of the test * @throws IllegalArgumentException * if testName is not found */ protected void runTest(String description) { AjcTest.Spec spec = getSpec(description); AjctestsAdapter.AjcTestSpecAsTest ajcTestAsSpec = new AjctestsAdapter.AjcTestSpecAsTest( spec, mAjctestsAdapter); // runTest handles null TestResult mAjctestsAdapter.runTest(ajcTestAsSpec, null); } /** * @param description * the String description of the test * @throws IllegalArgumentException * if testName is not found */ private AjcTest.Spec getSpec(String description) { AjcTest.Spec spec = mDescriptionToAjcTestSpec .get(description); if (null == spec) { throw new IllegalArgumentException("no test for " + description); } return spec; } /** * makeUsingTestClass(..) extends this to create TestCase with * test_{name} for each test case. */ public static class TestClass extends TestCase { public TestClass() { } private SpecTests mTests; /** * Called by code generated in makeUsingTestClass(..) * * @param description * the String identifier of the test stored in SpecTests * mTests. * @throws Error * on first and later uses if getTestsFor() returns * null. */ public final void runTest(String description) { if (null == mTests) { String classname = getClass().getName(); mTests = getSpecTestsFor(classname); } mTests.runTest(description); } } } /** Wrap AjcTest.Spec as a TestCase. Run by delegation to suite */ private static class AjcTestSpecAsTest extends TestCase implements HarnessJUnitUtil.IHasAjcSpec { // this could implement Test, but Ant batchtest fails to pull name final String name; final AjcTest.Spec spec; AjctestsAdapter suite; AjcTestSpecAsTest(AjcTest.Spec spec, AjctestsAdapter suite) { super(HarnessJUnitUtil.cleanTestName(spec.getDescription())); this.name = HarnessJUnitUtil.cleanTestName(spec.getDescription()); this.suite = suite; this.spec = spec; spec.setSuiteDir(suite.getSuiteDir()); } @Override public int countTestCases() { return 1; } @Override public AjcTest.Spec getAjcTestSpec() { return spec; } @Override public void run(TestResult result) { if (null == suite) { throw new Error("need to re-init"); } try { AjState.FORCE_INCREMENTAL_DURING_TESTING = true; result.startTest(this); suite.runTest(this, result); } finally { result.endTest(this); suite = null; AjState.FORCE_INCREMENTAL_DURING_TESTING = false; } } @Override public String getName() { return name; } @Override public String toString() { return name; } } }