Repository: ant Updated Branches: refs/heads/master 3f36f0b82 -> c9ca84fd5
http://git-wip-us.apache.org/repos/asf/ant/blob/c9ca84fd/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/StandaloneLauncher.java ---------------------------------------------------------------------- diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/StandaloneLauncher.java b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/StandaloneLauncher.java new file mode 100644 index 0000000..7e342cc --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/StandaloneLauncher.java @@ -0,0 +1,259 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junitlauncher; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamReader; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Properties; + +import static javax.xml.stream.XMLStreamConstants.END_DOCUMENT; +import static javax.xml.stream.XMLStreamConstants.END_ELEMENT; +import static javax.xml.stream.XMLStreamConstants.START_DOCUMENT; +import static javax.xml.stream.XMLStreamConstants.START_ELEMENT; +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.Constants.LD_XML_ATTR_HALT_ON_FAILURE; +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.Constants.LD_XML_ATTR_PRINT_SUMMARY; +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.Constants.LD_XML_ELM_LAUNCH_DEF; +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.Constants.LD_XML_ELM_LISTENER; +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.Constants.LD_XML_ELM_TEST; +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.Constants.LD_XML_ELM_TEST_CLASSES; + +/** + * Used for launching forked tests from the {@link JUnitLauncherTask}. + * <p> + * Although this class is public, this isn't meant for external use. The contract of what + * program arguments {@link #main(String[]) the main method} accepts and how it interprets it, + * is also an internal detail and can change across Ant releases. + * + * @since Ant 1.10.6 + */ +public class StandaloneLauncher { + + /** + * Entry point to launching the forked test. + * + * @param args The arguments passed to this program for launching the tests + * @throws Exception + */ + public static void main(final String[] args) throws Exception { + // The main responsibility of this entry point is to create a LaunchDefinition, + // by parsing the passed arguments and then use the LauncherSupport to + // LauncherSupport#launch the tests + try { + ForkedLaunch launchDefinition = null; + final ForkedExecution forkedExecution = new ForkedExecution(); + for (int i = 0; i < args.length; ) { + final String arg = args[i]; + int numArgsConsumed = 1; + switch (arg) { + case Constants.ARG_PROPERTIES: { + final Path propsPath = Paths.get(args[i + 1]); + if (!Files.isRegularFile(propsPath)) { + throw new IllegalArgumentException(propsPath + " does not point to a properties file"); + } + final Properties properties = new Properties(); + try (final InputStream is = Files.newInputStream(propsPath)) { + properties.load(is); + } + forkedExecution.setProperties(properties); + numArgsConsumed = 2; + break; + } + case Constants.ARG_LAUNCH_DEFINITION: { + final Path launchDefXmlPath = Paths.get(args[i + 1]); + if (!Files.isRegularFile(launchDefXmlPath)) { + throw new IllegalArgumentException(launchDefXmlPath + " does not point to a launch definition file"); + } + launchDefinition = parseLaunchDefinition(launchDefXmlPath); + numArgsConsumed = 2; + break; + } + } + i = i + numArgsConsumed; + } + + + launchDefinition.setTestExecutionContext(forkedExecution); + final LauncherSupport launcherSupport = new LauncherSupport(launchDefinition); + try { + launcherSupport.launch(); + } catch (Throwable t) { + if (launcherSupport.hasTestFailures()) { + System.exit(Constants.FORK_EXIT_CODE_TESTS_FAILED); + throw t; + } + } + if (launcherSupport.hasTestFailures()) { + System.exit(Constants.FORK_EXIT_CODE_TESTS_FAILED); + return; + } + System.exit(Constants.FORK_EXIT_CODE_SUCCESS); + return; + } catch (Throwable t) { + t.printStackTrace(); + throw t; + } + } + + private static ForkedLaunch parseLaunchDefinition(final Path pathToLaunchDefXml) { + if (pathToLaunchDefXml == null || !Files.isRegularFile(pathToLaunchDefXml)) { + throw new IllegalArgumentException(pathToLaunchDefXml + " is not a file"); + } + final ForkedLaunch forkedLaunch = new ForkedLaunch(); + try (final InputStream is = Files.newInputStream(pathToLaunchDefXml)) { + final XMLStreamReader reader = XMLInputFactory.newFactory().createXMLStreamReader(is); + reader.require(START_DOCUMENT, null, null); + reader.nextTag(); + reader.require(START_ELEMENT, null, LD_XML_ELM_LAUNCH_DEF); + final String haltOnfFailure = reader.getAttributeValue(null, LD_XML_ATTR_HALT_ON_FAILURE); + if (haltOnfFailure != null) { + forkedLaunch.setHaltOnFailure(Boolean.parseBoolean(haltOnfFailure)); + } + final String printSummary = reader.getAttributeValue(null, LD_XML_ATTR_PRINT_SUMMARY); + if (printSummary != null) { + forkedLaunch.setPrintSummary(Boolean.parseBoolean(printSummary)); + } + if (haltOnfFailure != null) { + forkedLaunch.setHaltOnFailure(Boolean.parseBoolean(haltOnfFailure)); + } + reader.nextTag(); + reader.require(START_ELEMENT, null, null); + final String elementName = reader.getLocalName(); + switch (elementName) { + case LD_XML_ELM_TEST: { + forkedLaunch.addTests(Collections.singletonList(SingleTestClass.fromForkedRepresentation(reader))); + break; + } + case LD_XML_ELM_TEST_CLASSES: { + forkedLaunch.addTests(TestClasses.fromForkedRepresentation(reader)); + break; + } + case LD_XML_ELM_LISTENER: { + forkedLaunch.addListener(ListenerDefinition.fromForkedRepresentation(reader)); + break; + } + } + reader.nextTag(); + reader.require(END_ELEMENT, null, LD_XML_ELM_LAUNCH_DEF); + reader.next(); + reader.require(END_DOCUMENT, null, null); + return forkedLaunch; + } catch (Exception e) { + throw new BuildException("Failed to construct definition from forked representation", e); + } + } + + + private static final class ForkedExecution implements TestExecutionContext { + private Properties properties = new Properties(); + + private ForkedExecution() { + } + + private ForkedExecution setProperties(final Properties properties) { + this.properties = properties; + return this; + } + + @Override + public Properties getProperties() { + return this.properties; + } + + @Override + public Optional<Project> getProject() { + // forked execution won't have access to the Ant Project + return Optional.empty(); + } + } + + private static final class ForkedLaunch implements LaunchDefinition { + + private boolean printSummary; + private boolean haltOnFailure; + private TestExecutionContext testExecutionContext; + private List<TestDefinition> tests = new ArrayList<>(); + private List<ListenerDefinition> listeners = new ArrayList<>(); + + @Override + public List<TestDefinition> getTests() { + return this.tests; + } + + ForkedLaunch addTests(final List<TestDefinition> tests) { + this.tests.addAll(tests); + return this; + } + + @Override + public List<ListenerDefinition> getListeners() { + return this.listeners; + } + + ForkedLaunch addListener(final ListenerDefinition listener) { + this.listeners.add(listener); + return this; + } + + @Override + public boolean isPrintSummary() { + return this.printSummary; + } + + private ForkedLaunch setPrintSummary(final boolean printSummary) { + this.printSummary = printSummary; + return this; + } + + @Override + public boolean isHaltOnFailure() { + return this.haltOnFailure; + } + + public ForkedLaunch setHaltOnFailure(final boolean haltOnFailure) { + this.haltOnFailure = haltOnFailure; + return this; + } + + public ForkedLaunch setTestExecutionContext(final TestExecutionContext testExecutionContext) { + this.testExecutionContext = testExecutionContext; + return this; + } + + @Override + public ClassLoader getClassLoader() { + return this.getClass().getClassLoader(); + } + + @Override + public TestExecutionContext getTestExecutionContext() { + return this.testExecutionContext; + } + } +} http://git-wip-us.apache.org/repos/asf/ant/blob/c9ca84fd/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/TestClasses.java ---------------------------------------------------------------------- diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/TestClasses.java b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/TestClasses.java index 45d8bf0..cbba484 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/TestClasses.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/TestClasses.java @@ -21,11 +21,17 @@ import org.apache.tools.ant.types.Resource; import org.apache.tools.ant.types.ResourceCollection; import org.apache.tools.ant.types.resources.Resources; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.Constants.LD_XML_ELM_TEST_CLASSES; + /** * Represents a {@code testclasses} that's configured to be launched by the {@link JUnitLauncherTask} */ @@ -42,14 +48,14 @@ public class TestClasses extends TestDefinition { } @Override - List<TestRequest> createTestRequests(final JUnitLauncherTask launcherTask) { + List<TestRequest> createTestRequests() { final List<SingleTestClass> tests = this.getTests(); if (tests.isEmpty()) { return Collections.emptyList(); } final List<TestRequest> requests = new ArrayList<>(); for (final SingleTestClass test : tests) { - requests.addAll(test.createTestRequests(launcherTask)); + requests.addAll(test.createTestRequests()); } return requests; } @@ -126,4 +132,26 @@ public class TestClasses extends TestDefinition { return TestClasses.this.getExcludeEngines(); } } + + @Override + protected void toForkedRepresentation(final JUnitLauncherTask task, final XMLStreamWriter writer) throws XMLStreamException { + writer.writeStartElement(LD_XML_ELM_TEST_CLASSES); + // write out as multiple SingleTestClass representations + for (final SingleTestClass singleTestClass : getTests()) { + singleTestClass.toForkedRepresentation(task, writer); + } + writer.writeEndElement(); + } + + static List<TestDefinition> fromForkedRepresentation(final XMLStreamReader reader) throws XMLStreamException { + reader.require(XMLStreamConstants.START_ELEMENT, null, LD_XML_ELM_TEST_CLASSES); + final List<TestDefinition> testDefinitions = new ArrayList<>(); + // read out as multiple SingleTestClass representations + while (reader.nextTag() != XMLStreamConstants.END_ELEMENT) { + reader.require(XMLStreamConstants.START_ELEMENT, null, Constants.LD_XML_ELM_TEST); + testDefinitions.add(SingleTestClass.fromForkedRepresentation(reader)); + } + reader.require(XMLStreamConstants.END_ELEMENT, null, LD_XML_ELM_TEST_CLASSES); + return testDefinitions; + } } http://git-wip-us.apache.org/repos/asf/ant/blob/c9ca84fd/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/TestDefinition.java ---------------------------------------------------------------------- diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/TestDefinition.java b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/TestDefinition.java index 7ca4499..9fe452f 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/TestDefinition.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/TestDefinition.java @@ -17,9 +17,12 @@ */ package org.apache.tools.ant.taskdefs.optional.junitlauncher; +import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.PropertyHelper; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -28,6 +31,7 @@ import java.util.List; * Represents the configuration details of a test that needs to be launched by the {@link JUnitLauncherTask} */ abstract class TestDefinition { + protected String ifProperty; protected String unlessProperty; protected Boolean haltOnFailure; @@ -35,6 +39,7 @@ abstract class TestDefinition { protected String outputDir; protected String includeEngines; protected String excludeEngines; + protected ForkDefinition forkDefinition; protected List<ListenerDefinition> listeners = new ArrayList<>(); @@ -90,7 +95,19 @@ abstract class TestDefinition { return this.outputDir; } - abstract List<TestRequest> createTestRequests(final JUnitLauncherTask launcherTask); + public ForkDefinition createFork() { + if (this.forkDefinition != null) { + throw new BuildException("Test definition cannot have more than one fork elements"); + } + this.forkDefinition = new ForkDefinition(); + return this.forkDefinition; + } + + ForkDefinition getForkDefinition() { + return this.forkDefinition; + } + + abstract List<TestRequest> createTestRequests(); protected boolean shouldRun(final Project project) { final PropertyHelper propertyHelper = PropertyHelper.getPropertyHelper(project); @@ -127,4 +144,7 @@ abstract class TestDefinition { } return parts.toArray(new String[parts.size()]); } + + protected abstract void toForkedRepresentation(JUnitLauncherTask task, XMLStreamWriter writer) throws XMLStreamException; + } http://git-wip-us.apache.org/repos/asf/ant/blob/c9ca84fd/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junitlauncher/JUnitLauncherTaskTest.java ---------------------------------------------------------------------- diff --git a/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junitlauncher/JUnitLauncherTaskTest.java b/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junitlauncher/JUnitLauncherTaskTest.java index 90754b5..c17ca57 100644 --- a/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junitlauncher/JUnitLauncherTaskTest.java +++ b/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junitlauncher/JUnitLauncherTaskTest.java @@ -125,4 +125,13 @@ public class JUnitLauncherTaskTest { public void testTestClasses() { buildRule.executeTarget("test-batch"); } + + /** + * Tests the execution of a forked test + */ + @Test + public void testBasicFork() { + buildRule.executeTarget("test-basic-fork"); + + } }
