| /* |
| * 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 |
| * |
| * https://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 org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants; |
| import org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.JUnitLauncherTask; |
| import org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.LaunchDefinition; |
| import org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.ListenerDefinition; |
| import org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.SingleTestClass; |
| import org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.TestClasses; |
| import org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.TestDefinition; |
| |
| 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 java.util.stream.Stream; |
| |
| 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.confined.Constants.LD_XML_ATTR_EXCLUDE_TAGS; |
| import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_HALT_ON_FAILURE; |
| import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_INCLUDE_TAGS; |
| import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_PRINT_SUMMARY; |
| import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ELM_LAUNCH_DEF; |
| import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ELM_LISTENER; |
| import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ELM_TEST; |
| import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.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 If any exception occurs during the execution |
| */ |
| 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; |
| } |
| |
| final LauncherSupport launcherSupport = new LauncherSupport(launchDefinition, forkedExecution); |
| 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 haltOnFailure = reader.getAttributeValue(null, LD_XML_ATTR_HALT_ON_FAILURE); |
| if (haltOnFailure != null) { |
| forkedLaunch.setHaltOnFailure(Boolean.parseBoolean(haltOnFailure)); |
| } |
| final String includeTags = reader.getAttributeValue(null, LD_XML_ATTR_INCLUDE_TAGS); |
| if (includeTags != null) { |
| Stream.of(includeTags.split(",")).forEach(i -> forkedLaunch.addIncludeTag(i)); |
| } |
| final String excludeTags = reader.getAttributeValue(null, LD_XML_ATTR_EXCLUDE_TAGS); |
| if (excludeTags != null) { |
| Stream.of(excludeTags.split(",")).forEach(e -> forkedLaunch.addExcludeTag(e)); |
| } |
| final String printSummary = reader.getAttributeValue(null, LD_XML_ATTR_PRINT_SUMMARY); |
| if (printSummary != null) { |
| forkedLaunch.setPrintSummary(Boolean.parseBoolean(printSummary)); |
| } |
| 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 List<TestDefinition> tests = new ArrayList<>(); |
| private List<ListenerDefinition> listeners = new ArrayList<>(); |
| private List<String> includeTags = new ArrayList<>(); |
| private List<String> excludeTags = 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; |
| } |
| |
| @Override |
| public ClassLoader getClassLoader() { |
| return this.getClass().getClassLoader(); |
| } |
| |
| void addIncludeTag(final String filter) { |
| this.includeTags.add(filter); |
| } |
| |
| @Override |
| public List<String> getIncludeTags() { |
| return includeTags; |
| } |
| |
| void addExcludeTag(final String filter) { |
| this.excludeTags.add(filter); |
| } |
| |
| @Override |
| public List<String> getExcludeTags() { |
| return excludeTags; |
| } |
| } |
| } |