blob: 3f9d99fa5f91e83b449fd823434faf80334fc388 [file] [log] [blame]
/*
* 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;
}
}
}