http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/ForkDefinition.java ---------------------------------------------------------------------- diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/ForkDefinition.java b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/ForkDefinition.java new file mode 100644 index 0000000..28f7225 --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/ForkDefinition.java @@ -0,0 +1,142 @@ +/* + * 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.confined; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.optional.junitlauncher.StandaloneLauncher; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Environment; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.PropertySet; + +/** + * Represents the {@code fork} element within test definitions of the + * {@code junitlauncher} task + */ +public class ForkDefinition { + + private boolean includeAntRuntimeLibraries = true; + private boolean includeJUnitPlatformLibraries = true; + + private final CommandlineJava commandLineJava; + private final Environment env = new Environment(); + + private String dir; + private long timeout = -1; + + ForkDefinition() { + this.commandLineJava = new CommandlineJava(); + } + + public void setDir(final String dir) { + this.dir = dir; + } + + String getDir() { + return this.dir; + } + + public void setTimeout(final long timeout) { + this.timeout = timeout; + } + + long getTimeout() { + return this.timeout; + } + + public void setIncludeJUnitPlatformLibraries(final boolean include) { + this.includeJUnitPlatformLibraries = include; + } + + public void setIncludeAntRuntimeLibraries(final boolean include) { + this.includeAntRuntimeLibraries = include; + } + + public Commandline.Argument createJvmArg() { + return this.commandLineJava.createVmArgument(); + } + + public void addConfiguredSysProperty(final Environment.Variable sysProp) { + // validate that key/value are present + sysProp.validate(); + this.commandLineJava.addSysproperty(sysProp); + } + + public void addConfiguredSysPropertySet(final PropertySet propertySet) { + this.commandLineJava.addSyspropertyset(propertySet); + } + + public void addConfiguredEnv(final Environment.Variable var) { + this.env.addVariable(var); + } + + public void addConfiguredModulePath(final Path modulePath) { + this.commandLineJava.createModulepath(modulePath.getProject()).add(modulePath); + } + + public void addConfiguredUpgradeModulePath(final Path upgradeModulePath) { + this.commandLineJava.createUpgrademodulepath(upgradeModulePath.getProject()).add(upgradeModulePath); + } + + Environment getEnv() { + return this.env; + } + + /** + * Generates a new {@link CommandlineJava} constructed out of the configurations set on this + * {@link ForkDefinition} + * + * @param task The junitlaunchertask for which this is a fork definition + * @return + */ + CommandlineJava generateCommandLine(final JUnitLauncherTask task) { + final CommandlineJava cmdLine; + try { + cmdLine = (CommandlineJava) this.commandLineJava.clone(); + } catch (CloneNotSupportedException e) { + throw new BuildException(e); + } + cmdLine.setClassname(StandaloneLauncher.class.getName()); + // VM arguments + final Project project = task.getProject(); + final ClassLoader taskClassLoader = task.getClass().getClassLoader(); + // Ant runtime classes + if (this.includeAntRuntimeLibraries) { + final Path antRuntimeResources = new Path(project); + JUnitLauncherClassPathUtil.addAntRuntimeResourceLocations(antRuntimeResources, taskClassLoader); + final Path classPath = cmdLine.createClasspath(project); + classPath.createPath().append(antRuntimeResources); + } else { + task.log("Excluding Ant runtime libraries from forked JVM classpath", Project.MSG_DEBUG); + } + // JUnit platform classes + if (this.includeJUnitPlatformLibraries) { + final Path junitPlatformResources = new Path(project); + JUnitLauncherClassPathUtil.addJUnitPlatformResourceLocations(junitPlatformResources, taskClassLoader); + final Path classPath = cmdLine.createClasspath(project); + classPath.createPath().append(junitPlatformResources); + } else { + task.log("Excluding JUnit platform libraries from forked JVM classpath", Project.MSG_DEBUG); + } + return cmdLine; + } + +}
http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/JUnitLauncherClassPathUtil.java ---------------------------------------------------------------------- diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/JUnitLauncherClassPathUtil.java b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/JUnitLauncherClassPathUtil.java new file mode 100644 index 0000000..dcd4145 --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/JUnitLauncherClassPathUtil.java @@ -0,0 +1,76 @@ +/* + * 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.confined; + +import org.apache.tools.ant.Task; +import org.apache.tools.ant.launch.AntMain; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.util.LoaderUtils; + +import java.io.File; + +/** + * + */ +final class JUnitLauncherClassPathUtil { + + private static final String RESOURCE_IN_PLATFORM_ENGINE = "org/junit/platform/engine/TestEngine.class"; + private static final String RESOURCE_IN_PLATFORM_LAUNCHER = "org/junit/platform/launcher/core/LauncherFactory.class"; + private static final String RESOURCE_IN_PLATFORM_COMMON = "org/junit/platform/commons/annotation/Testable.class"; + private static final String RESOURCE_NAME_LAUNCHER_SUPPORT = "org/apache/tools/ant/taskdefs/optional/junitlauncher/LauncherSupport.class"; + + + static void addAntRuntimeResourceLocations(final Path path, final ClassLoader classLoader) { + addResourceLocationToPath(path, classLoader, toResourceName(AntMain.class)); + addResourceLocationToPath(path, classLoader, toResourceName(Task.class)); + addResourceLocationToPath(path, classLoader, RESOURCE_NAME_LAUNCHER_SUPPORT); + } + + static void addLauncherSupportResourceLocation(final Path path, final ClassLoader classLoader) { + addResourceLocationToPath(path, classLoader, RESOURCE_NAME_LAUNCHER_SUPPORT); + } + + static void addJUnitPlatformResourceLocations(final Path path, final ClassLoader classLoader) { + // platform-engine + addResourceLocationToPath(path, classLoader, RESOURCE_IN_PLATFORM_ENGINE); + // platform-launcher + addResourceLocationToPath(path, classLoader, RESOURCE_IN_PLATFORM_LAUNCHER); + // platform-commons + addResourceLocationToPath(path, classLoader, RESOURCE_IN_PLATFORM_COMMON); + } + + static boolean addResourceLocationToPath(final Path path, final ClassLoader classLoader, final String resource) { + final File f = LoaderUtils.getResourceSource(classLoader, resource); + if (f == null) { + return false; + } + path.createPath().setLocation(f); + return true; + } + + static boolean hasJUnitPlatformResources(final ClassLoader cl) { + final File f = LoaderUtils.getResourceSource(cl, RESOURCE_IN_PLATFORM_ENGINE); + return f != null; + } + + private static String toResourceName(final Class klass) { + final String name = klass.getName(); + return name.replaceAll("\\.", "/") + ".class"; + } +} http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/JUnitLauncherTask.java ---------------------------------------------------------------------- diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/JUnitLauncherTask.java b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/JUnitLauncherTask.java new file mode 100644 index 0000000..654211e --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/JUnitLauncherTask.java @@ -0,0 +1,436 @@ +/* + * 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.confined; + +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteWatchdog; +import org.apache.tools.ant.taskdefs.LogOutputStream; +import org.apache.tools.ant.taskdefs.PumpStreamHandler; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Environment; +import org.apache.tools.ant.types.Path; + +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Hashtable; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.TimeoutException; + +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_PRINT_SUMMARY; +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ELM_LAUNCH_DEF; + +/** + * An Ant {@link Task} responsible for launching the JUnit platform for running tests. + * This requires a minimum of JUnit 5, since that's the version in which the JUnit platform launcher + * APIs were introduced. + * <p> + * This task in itself doesn't run the JUnit tests, instead the sole responsibility of + * this task is to setup the JUnit platform launcher, build requests, launch those requests and then parse the + * result of the execution to present in a way that's been configured on this Ant task. + * </p> + * <p> + * Furthermore, this task allows users control over which classes to select for passing on to the JUnit 5 + * platform for test execution. It however, is solely the JUnit 5 platform, backed by test engines that + * decide and execute the tests. + * + * @see <a href="https://junit.org/junit5/">JUnit 5 documentation</a> + */ +public class JUnitLauncherTask extends Task { + + private static final String LAUNCHER_SUPPORT_CLASS_NAME = "org.apache.tools.ant.taskdefs.optional.junitlauncher.LauncherSupport"; + private static final String IN_VM_TEST_EXECUTION_CONTEXT_CLASS_NAME = "org.apache.tools.ant.taskdefs.optional.junitlauncher.InVMExecution"; + private static final String TEST_EXECUTION_CONTEXT_CLASS_NAME = "org.apache.tools.ant.taskdefs.optional.junitlauncher.TestExecutionContext"; + + private Path classPath; + private boolean haltOnFailure; + private String failureProperty; + private boolean printSummary; + private final List<TestDefinition> tests = new ArrayList<>(); + private final List<ListenerDefinition> listeners = new ArrayList<>(); + + public JUnitLauncherTask() { + } + + @Override + public void execute() throws BuildException { + if (this.tests.isEmpty()) { + return; + } + final Project project = getProject(); + for (final TestDefinition test : this.tests) { + if (!test.shouldRun(project)) { + log("Excluding test " + test + " since it's considered not to run " + + "in context of project " + project, Project.MSG_DEBUG); + continue; + } + if (test.getForkDefinition() != null) { + forkTest(test); + } else { + launchViaReflection(new InVMLaunch(Collections.singletonList(test))); + } + } + } + + /** + * Adds the {@link Path} to the classpath which will be used for execution of the tests + * + * @param path The classpath + */ + public void addConfiguredClassPath(final Path path) { + if (this.classPath == null) { + // create a "wrapper" path which can hold on to multiple + // paths that get passed to this method (if at all the task in the build is + // configured with multiple classpaht elements) + this.classPath = new Path(getProject()); + } + this.classPath.add(path); + } + + /** + * Adds a {@link SingleTestClass} that will be passed on to the underlying JUnit platform + * for possible execution of the test + * + * @param test The test + */ + public void addConfiguredTest(final SingleTestClass test) { + this.preConfigure(test); + this.tests.add(test); + } + + /** + * Adds {@link TestClasses} that will be passed on to the underlying JUnit platform for + * possible execution of the tests + * + * @param testClasses The test classes + */ + public void addConfiguredTestClasses(final TestClasses testClasses) { + this.preConfigure(testClasses); + this.tests.add(testClasses); + } + + /** + * Adds a {@link ListenerDefinition listener} which will be enrolled for listening to test + * execution events + * + * @param listener The listener + */ + public void addConfiguredListener(final ListenerDefinition listener) { + this.listeners.add(listener); + } + + public void setHaltonfailure(final boolean haltonfailure) { + this.haltOnFailure = haltonfailure; + } + + public void setFailureProperty(final String failureProperty) { + this.failureProperty = failureProperty; + } + + public void setPrintSummary(final boolean printSummary) { + this.printSummary = printSummary; + } + + private void preConfigure(final TestDefinition test) { + if (test.getHaltOnFailure() == null) { + test.setHaltOnFailure(this.haltOnFailure); + } + if (test.getFailureProperty() == null) { + test.setFailureProperty(this.failureProperty); + } + } + + private void launchViaReflection(final InVMLaunch launchDefinition) { + final ClassLoader cl = launchDefinition.getClassLoader(); + // instantiate a new TestExecutionContext instance using the launch definition's classloader + final Class<?> testExecutionCtxClass; + final Object testExecutionCtx; + try { + testExecutionCtxClass = Class.forName(TEST_EXECUTION_CONTEXT_CLASS_NAME, false, cl); + final Class<?> klass = Class.forName(IN_VM_TEST_EXECUTION_CONTEXT_CLASS_NAME, false, cl); + testExecutionCtx = klass.getConstructor(JUnitLauncherTask.class).newInstance(this); + } catch (Exception e) { + throw new BuildException("Failed to create a test execution context for in-vm tests", e); + } + // instantiate a new LauncherSupport instance using the launch definition's ClassLoader + try { + final Class<?> klass = Class.forName(LAUNCHER_SUPPORT_CLASS_NAME, false, cl); + final Object launcherSupport = klass.getConstructor(LaunchDefinition.class, testExecutionCtxClass) + .newInstance(launchDefinition, testExecutionCtx); + klass.getMethod("launch").invoke(launcherSupport); + } catch (Exception e) { + throw new BuildException("Failed to launch in-vm tests", e); + } + } + + private java.nio.file.Path dumpProjectProperties() throws IOException { + final java.nio.file.Path propsPath = Files.createTempFile(null, "properties"); + propsPath.toFile().deleteOnExit(); + final Hashtable<String, Object> props = this.getProject().getProperties(); + final Properties projProperties = new Properties(); + projProperties.putAll(props); + try (final OutputStream os = Files.newOutputStream(propsPath)) { + // TODO: Is it always UTF-8? + projProperties.store(os, StandardCharsets.UTF_8.name()); + } + return propsPath; + } + + private void forkTest(final TestDefinition test) { + // create launch command + final ForkDefinition forkDefinition = test.getForkDefinition(); + final CommandlineJava commandlineJava = forkDefinition.generateCommandLine(this); + if (this.classPath != null) { + commandlineJava.createClasspath(getProject()).createPath().append(this.classPath); + } + final java.nio.file.Path projectPropsPath; + try { + projectPropsPath = dumpProjectProperties(); + } catch (IOException e) { + throw new BuildException("Could not create the necessary properties file while forking a process" + + " for a test", e); + } + // --properties <path-to-properties-file> + commandlineJava.createArgument().setValue(Constants.ARG_PROPERTIES); + commandlineJava.createArgument().setValue(projectPropsPath.toAbsolutePath().toString()); + + final java.nio.file.Path launchDefXmlPath = newLaunchDefinitionXml(); + try (final OutputStream os = Files.newOutputStream(launchDefXmlPath)) { + final XMLStreamWriter writer = XMLOutputFactory.newFactory().createXMLStreamWriter(os, "UTF-8"); + try { + writer.writeStartDocument(); + writer.writeStartElement(LD_XML_ELM_LAUNCH_DEF); + if (this.printSummary) { + writer.writeAttribute(LD_XML_ATTR_PRINT_SUMMARY, "true"); + } + if (this.haltOnFailure) { + writer.writeAttribute(LD_XML_ATTR_HALT_ON_FAILURE, "true"); + } + // task level listeners + for (final ListenerDefinition listenerDef : this.listeners) { + if (!listenerDef.shouldUse(getProject())) { + continue; + } + // construct the listener definition argument + listenerDef.toForkedRepresentation(writer); + } + // test definition as XML + test.toForkedRepresentation(this, writer); + writer.writeEndElement(); + writer.writeEndDocument(); + } finally { + writer.close(); + } + } catch (Exception e) { + throw new BuildException("Failed to construct command line for test", e); + } + // --launch-definition <xml-file-path> + commandlineJava.createArgument().setValue(Constants.ARG_LAUNCH_DEFINITION); + commandlineJava.createArgument().setValue(launchDefXmlPath.toAbsolutePath().toString()); + + // launch the process and wait for process to complete + final int exitCode = executeForkedTest(forkDefinition, commandlineJava); + switch (exitCode) { + case Constants.FORK_EXIT_CODE_SUCCESS: { + // success + break; + } + case Constants.FORK_EXIT_CODE_EXCEPTION: { + // process failed with some exception + throw new BuildException("Forked test(s) failed with an exception"); + } + case Constants.FORK_EXIT_CODE_TESTS_FAILED: { + // test has failure(s) + try { + if (test.getFailureProperty() != null) { + // if there are test failures and the test is configured to set a property in case + // of failure, then set the property to true + this.getProject().setNewProperty(test.getFailureProperty(), "true"); + } + } finally { + if (test.isHaltOnFailure()) { + // if the test is configured to halt on test failures, throw a build error + final String errorMessage; + if (test instanceof NamedTest) { + errorMessage = "Test " + ((NamedTest) test).getName() + " has failure(s)"; + } else { + errorMessage = "Some test(s) have failure(s)"; + } + throw new BuildException(errorMessage); + } + } + break; + } + case Constants.FORK_EXIT_CODE_TIMED_OUT: { + throw new BuildException(new TimeoutException("Forked test(s) timed out")); + } + } + } + + private int executeForkedTest(final ForkDefinition forkDefinition, final CommandlineJava commandlineJava) { + final LogOutputStream outStream = new LogOutputStream(this, Project.MSG_INFO); + final LogOutputStream errStream = new LogOutputStream(this, Project.MSG_WARN); + final ExecuteWatchdog watchdog = forkDefinition.getTimeout() > 0 ? new ExecuteWatchdog(forkDefinition.getTimeout()) : null; + final Execute execute = new Execute(new PumpStreamHandler(outStream, errStream), watchdog); + execute.setCommandline(commandlineJava.getCommandline()); + execute.setAntRun(getProject()); + if (forkDefinition.getDir() != null) { + execute.setWorkingDirectory(Paths.get(forkDefinition.getDir()).toFile()); + } + final Environment env = forkDefinition.getEnv(); + if (env != null && env.getVariables() != null) { + execute.setEnvironment(env.getVariables()); + } + log(commandlineJava.describeCommand(), Project.MSG_VERBOSE); + int exitCode; + try { + exitCode = execute.execute(); + } catch (IOException e) { + throw new BuildException("Process fork failed", e, getLocation()); + } + return (watchdog != null && watchdog.killedProcess()) ? Constants.FORK_EXIT_CODE_TIMED_OUT : exitCode; + } + + private java.nio.file.Path newLaunchDefinitionXml() { + final java.nio.file.Path xmlFilePath; + try { + xmlFilePath = Files.createTempFile(null, ".xml"); + } catch (IOException e) { + throw new BuildException("Failed to construct command line for test", e); + } + xmlFilePath.toFile().deleteOnExit(); + return xmlFilePath; + } + + private final class InVMLaunch implements LaunchDefinition { + + private final List<TestDefinition> inVMTests; + private final ClassLoader executionCL; + + private InVMLaunch(final List<TestDefinition> inVMTests) { + this.inVMTests = inVMTests; + this.executionCL = createInVMExecutionClassLoader(); + } + + @Override + public List<TestDefinition> getTests() { + return this.inVMTests; + } + + @Override + public List<ListenerDefinition> getListeners() { + return listeners; + } + + @Override + public boolean isPrintSummary() { + return printSummary; + } + + @Override + public boolean isHaltOnFailure() { + return haltOnFailure; + } + + @Override + public ClassLoader getClassLoader() { + return this.executionCL; + } + + private ClassLoader createInVMExecutionClassLoader() { + final Path taskConfiguredClassPath = JUnitLauncherTask.this.classPath; + if (taskConfiguredClassPath == null) { + // no specific classpath configured for the task, so use the classloader + // of this task + return JUnitLauncherTask.class.getClassLoader(); + } + // there's a classpath configured for the task. + // we first check if the Ant runtime classpath has JUnit platform classes. + // - if it does, then we use the Ant runtime classpath plus the task's configured classpath + // with the traditional parent first loading. + // - else (i.e. Ant runtime classpath doesn't have JUnit platform classes), then we + // expect/assume the task's configured classpath to have the JUnit platform classes and we + // then create a "overriding" classloader which prefers certain resources (specifically the classes + // from org.apache.tools.ant.taskdefs.optional.junitlauncher package), from the task's + // classpath, even if the Ant's runtime classpath has those resources. + if (JUnitLauncherClassPathUtil.hasJUnitPlatformResources(JUnitLauncherTask.class.getClassLoader())) { + return new AntClassLoader(JUnitLauncherTask.class.getClassLoader(), getProject(), taskConfiguredClassPath, true); + } + final Path cp = new Path(getProject()); + cp.add(taskConfiguredClassPath); + // add the Ant runtime resources to this path + JUnitLauncherClassPathUtil.addLauncherSupportResourceLocation(cp, JUnitLauncherTask.class.getClassLoader()); + return new TaskConfiguredPathClassLoader(JUnitLauncherTask.class.getClassLoader(), cp, getProject()); + } + } + + /** + * A {@link ClassLoader}, very similar to the {@link org.apache.tools.ant.util.SplitClassLoader}, + * which uses the {@link #TaskConfiguredPathClassLoader(ClassLoader, Path, Project) configured Path} + * to load a class, if the class belongs to the {@code org.apache.tools.ant.taskdefs.optional.junitlauncher} + * package. + * <p> + * While looking for classes belonging to the {@code org.apache.tools.ant.taskdefs.optional.junitlauncher} + * package, this classloader completely ignores Ant runtime classpath, even if that classpath has + * those classes. This allows the users of this classloader to use a custom location and thus more control over + * where these classes reside, when running the {@code junitlauncher} task + */ + private final class TaskConfiguredPathClassLoader extends AntClassLoader { + + /** + * @param parent ClassLoader + * @param path Path + * @param project Project + */ + private TaskConfiguredPathClassLoader(ClassLoader parent, Path path, Project project) { + super(parent, project, path, true); + } + + // forceLoadClass is not convenient here since it would not + // properly deal with inner classes of these classes. + @Override + protected synchronized Class<?> loadClass(String classname, boolean resolve) + throws ClassNotFoundException { + Class<?> theClass = findLoadedClass(classname); + if (theClass != null) { + return theClass; + } + final String packageName = classname.substring(0, classname.lastIndexOf('.')); + if (packageName.equals("org.apache.tools.ant.taskdefs.optional.junitlauncher")) { + theClass = findClass(classname); + if (resolve) { + resolveClass(theClass); + } + return theClass; + } + return super.loadClass(classname, resolve); + } + } +} http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/LaunchDefinition.java ---------------------------------------------------------------------- diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/LaunchDefinition.java b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/LaunchDefinition.java new file mode 100644 index 0000000..10e7f4d --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/LaunchDefinition.java @@ -0,0 +1,58 @@ +/* + * 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.confined; + +import java.util.List; + +/** + * Defines the necessary context for launching the JUnit platform for running + * tests. + */ +public interface LaunchDefinition { + + /** + * @return Returns the {@link TestDefinition tests} that have to be launched + */ + List<TestDefinition> getTests(); + + /** + * @return Returns the default {@link ListenerDefinition listeners} that will be used + * for the tests, if the {@link #getTests() tests} themselves don't specify any + */ + List<ListenerDefinition> getListeners(); + + /** + * @return Returns true if a summary needs to be printed out after the execution of the + * tests. False otherwise. + */ + boolean isPrintSummary(); + + /** + * @return Returns true if any remaining tests launch need to be stopped if any test execution + * failed. False otherwise. + */ + boolean isHaltOnFailure(); + + /** + * @return Returns the {@link ClassLoader} that has to be used for launching and execution of the + * tests + */ + ClassLoader getClassLoader(); + +} http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/ListenerDefinition.java ---------------------------------------------------------------------- diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/ListenerDefinition.java b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/ListenerDefinition.java new file mode 100644 index 0000000..9eaf147 --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/ListenerDefinition.java @@ -0,0 +1,208 @@ +/* + * 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.confined; + +import org.apache.tools.ant.Project; +import org.apache.tools.ant.PropertyHelper; +import org.apache.tools.ant.types.EnumeratedAttribute; + +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; + +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_CLASS_NAME; +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_LISTENER_RESULT_FILE; +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_SEND_SYS_ERR; +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_SEND_SYS_OUT; +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ELM_LISTENER; + +/** + * Represents the {@code <listener>} element within the {@code <junitlauncher>} + * task + */ +public class ListenerDefinition { + + + private static final String LEGACY_PLAIN = "legacy-plain"; + private static final String LEGACY_BRIEF = "legacy-brief"; + private static final String LEGACY_XML = "legacy-xml"; + + private String ifProperty; + private String unlessProperty; + private String className; + private String resultFile; + private boolean sendSysOut; + private boolean sendSysErr; + private String outputDir; + + public ListenerDefinition() { + + } + + public void setClassName(final String className) { + this.className = className; + } + + public String getClassName() { + return this.className; + } + + String getIfProperty() { + return ifProperty; + } + + public void setIf(final String ifProperty) { + this.ifProperty = ifProperty; + } + + String getUnlessProperty() { + return unlessProperty; + } + + public void setUnless(final String unlessProperty) { + this.unlessProperty = unlessProperty; + } + + public void setType(final ListenerType type) { + switch (type.getValue()) { + case LEGACY_PLAIN: { + this.setClassName("org.apache.tools.ant.taskdefs.optional.junitlauncher.LegacyPlainResultFormatter"); + break; + } + case LEGACY_BRIEF: { + this.setClassName("org.apache.tools.ant.taskdefs.optional.junitlauncher.LegacyBriefResultFormatter"); + break; + } + case LEGACY_XML: { + this.setClassName("org.apache.tools.ant.taskdefs.optional.junitlauncher.LegacyXmlResultFormatter"); + break; + } + } + } + + public void setResultFile(final String filename) { + this.resultFile = filename; + } + + public String requireResultFile(final TestDefinition test) { + if (this.resultFile != null) { + return this.resultFile; + } + final StringBuilder sb = new StringBuilder("TEST-"); + if (test instanceof NamedTest) { + sb.append(((NamedTest) test).getName()); + } else { + sb.append("unknown"); + } + sb.append("."); + final String suffix; + if ("org.apache.tools.ant.taskdefs.optional.junitlauncher.LegacyXmlResultFormatter".equals(this.className)) { + suffix = "xml"; + } else { + suffix = "txt"; + } + sb.append(suffix); + return sb.toString(); + } + + public void setSendSysOut(final boolean sendSysOut) { + this.sendSysOut = sendSysOut; + } + + public boolean shouldSendSysOut() { + return this.sendSysOut; + } + + public void setSendSysErr(final boolean sendSysErr) { + this.sendSysErr = sendSysErr; + } + + public boolean shouldSendSysErr() { + return this.sendSysErr; + } + + /** + * Sets the output directory for this listener + * + * @param dir Path to the output directory + * @since Ant 1.10.6 + */ + public void setOutputDir(final String dir) { + this.outputDir = dir; + } + + public String getOutputDir() { + return this.outputDir; + } + + public boolean shouldUse(final Project project) { + final PropertyHelper propertyHelper = PropertyHelper.getPropertyHelper(project); + return propertyHelper.testIfCondition(this.ifProperty) && propertyHelper.testUnlessCondition(this.unlessProperty); + } + + public static class ListenerType extends EnumeratedAttribute { + + @Override + public String[] getValues() { + return new String[]{LEGACY_PLAIN, LEGACY_BRIEF, LEGACY_XML}; + } + } + + void toForkedRepresentation(final XMLStreamWriter writer) throws XMLStreamException { + writer.writeStartElement(LD_XML_ELM_LISTENER); + writer.writeAttribute(LD_XML_ATTR_CLASS_NAME, this.className); + writer.writeAttribute(LD_XML_ATTR_SEND_SYS_ERR, Boolean.toString(this.sendSysErr)); + writer.writeAttribute(LD_XML_ATTR_SEND_SYS_OUT, Boolean.toString(this.sendSysOut)); + if (this.resultFile != null) { + writer.writeAttribute(LD_XML_ATTR_LISTENER_RESULT_FILE, this.resultFile); + } + writer.writeEndElement(); + } + + public static ListenerDefinition fromForkedRepresentation(final XMLStreamReader reader) throws XMLStreamException { + reader.require(XMLStreamConstants.START_ELEMENT, null, LD_XML_ELM_LISTENER); + final ListenerDefinition listenerDef = new ListenerDefinition(); + final String className = requireAttributeValue(reader, LD_XML_ATTR_CLASS_NAME); + listenerDef.setClassName(className); + final String sendSysErr = reader.getAttributeValue(null, LD_XML_ATTR_SEND_SYS_ERR); + if (sendSysErr != null) { + listenerDef.setSendSysErr(Boolean.parseBoolean(sendSysErr)); + } + final String sendSysOut = reader.getAttributeValue(null, LD_XML_ATTR_SEND_SYS_OUT); + if (sendSysOut != null) { + listenerDef.setSendSysOut(Boolean.parseBoolean(sendSysOut)); + } + final String resultFile = reader.getAttributeValue(null, LD_XML_ATTR_LISTENER_RESULT_FILE); + if (resultFile != null) { + listenerDef.setResultFile(resultFile); + } + reader.nextTag(); + reader.require(XMLStreamConstants.END_ELEMENT, null, LD_XML_ELM_LISTENER); + return listenerDef; + } + + private static String requireAttributeValue(final XMLStreamReader reader, final String attrName) throws XMLStreamException { + final String val = reader.getAttributeValue(null, attrName); + if (val != null) { + return val; + } + throw new XMLStreamException("Attribute " + attrName + " is missing at " + reader.getLocation()); + } + +} http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/NamedTest.java ---------------------------------------------------------------------- diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/NamedTest.java b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/NamedTest.java new file mode 100644 index 0000000..295acd9 --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/NamedTest.java @@ -0,0 +1,29 @@ +/* + * 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.confined; + +/** + * A test that has a name associated with it + */ +public interface NamedTest { + + /** + * @return Returns the name of the test + */ + String getName(); +} http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/SingleTestClass.java ---------------------------------------------------------------------- diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/SingleTestClass.java b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/SingleTestClass.java new file mode 100644 index 0000000..7c0186f --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/SingleTestClass.java @@ -0,0 +1,171 @@ +/* + * 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.confined; + +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.StringTokenizer; + +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_CLASS_NAME; +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_EXCLUDE_ENGINES; +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_ENGINES; +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_METHODS; +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_OUTPUT_DIRECTORY; +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ELM_TEST; + +/** + * Represents the single {@code test} (class) that's configured to be launched by the {@link JUnitLauncherTask} + */ +public class SingleTestClass extends TestDefinition implements NamedTest { + + private String testClass; + private Set<String> testMethods; + + public SingleTestClass() { + + } + + public void setName(final String test) { + if (test == null || test.trim().isEmpty()) { + throw new IllegalArgumentException("Test name cannot be null or empty string"); + } + this.testClass = test; + } + + public String getName() { + return this.testClass; + } + + public void setMethods(final String methods) { + // parse the comma separated set of methods + if (methods == null || methods.trim().isEmpty()) { + this.testMethods = Collections.emptySet(); + return; + } + final StringTokenizer tokenizer = new StringTokenizer(methods, ","); + if (!tokenizer.hasMoreTokens()) { + this.testMethods = Collections.emptySet(); + return; + } + // maintain specified ordering + this.testMethods = new LinkedHashSet<>(); + while (tokenizer.hasMoreTokens()) { + final String method = tokenizer.nextToken().trim(); + if (method.isEmpty()) { + continue; + } + this.testMethods.add(method); + } + } + + boolean hasMethodsSpecified() { + return this.testMethods != null && !this.testMethods.isEmpty(); + } + + public String[] getMethods() { + if (!hasMethodsSpecified()) { + return null; + } + return this.testMethods.toArray(new String[this.testMethods.size()]); + } + + @Override + protected void toForkedRepresentation(final JUnitLauncherTask task, final XMLStreamWriter writer) throws XMLStreamException { + writer.writeStartElement(LD_XML_ELM_TEST); + writer.writeAttribute(LD_XML_ATTR_CLASS_NAME, testClass); + if (testMethods != null) { + final StringBuilder sb = new StringBuilder(); + for (final String method : testMethods) { + if (sb.length() != 0) { + sb.append(","); + } + sb.append(method); + } + writer.writeAttribute(LD_XML_ATTR_METHODS, sb.toString()); + } + if (haltOnFailure != null) { + writer.writeAttribute(LD_XML_ATTR_HALT_ON_FAILURE, haltOnFailure.toString()); + } + if (outputDir != null) { + writer.writeAttribute(LD_XML_ATTR_OUTPUT_DIRECTORY, outputDir); + } + if (includeEngines != null) { + writer.writeAttribute(LD_XML_ATTR_INCLUDE_ENGINES, includeEngines); + } + if (excludeEngines != null) { + writer.writeAttribute(LD_XML_ATTR_EXCLUDE_ENGINES, excludeEngines); + } + // listeners for this test + if (listeners != null) { + for (final ListenerDefinition listenerDef : getListeners()) { + if (!listenerDef.shouldUse(task.getProject())) { + // not applicable + continue; + } + listenerDef.toForkedRepresentation(writer); + } + } + writer.writeEndElement(); + } + + public static TestDefinition fromForkedRepresentation(final XMLStreamReader reader) throws XMLStreamException { + reader.require(XMLStreamConstants.START_ELEMENT, null, LD_XML_ELM_TEST); + final SingleTestClass testDefinition = new SingleTestClass(); + final String testClassName = requireAttributeValue(reader, LD_XML_ATTR_CLASS_NAME); + testDefinition.setName(testClassName); + final String methodNames = reader.getAttributeValue(null, LD_XML_ATTR_METHODS); + if (methodNames != null) { + testDefinition.setMethods(methodNames); + } + final String halt = reader.getAttributeValue(null, LD_XML_ATTR_HALT_ON_FAILURE); + if (halt != null) { + testDefinition.setHaltOnFailure(Boolean.parseBoolean(halt)); + } + final String outDir = reader.getAttributeValue(null, LD_XML_ATTR_OUTPUT_DIRECTORY); + if (outDir != null) { + testDefinition.setOutputDir(outDir); + } + final String includeEngs = reader.getAttributeValue(null, LD_XML_ATTR_INCLUDE_ENGINES); + if (includeEngs != null) { + testDefinition.setIncludeEngines(includeEngs); + } + final String excludeEngs = reader.getAttributeValue(null, LD_XML_ATTR_EXCLUDE_ENGINES); + if (excludeEngs != null) { + testDefinition.setExcludeEngines(excludeEngs); + } + while (reader.nextTag() != XMLStreamConstants.END_ELEMENT) { + reader.require(XMLStreamConstants.START_ELEMENT, null, Constants.LD_XML_ELM_LISTENER); + testDefinition.addConfiguredListener(ListenerDefinition.fromForkedRepresentation(reader)); + } + return testDefinition; + } + + private static String requireAttributeValue(final XMLStreamReader reader, final String attrName) throws XMLStreamException { + final String val = reader.getAttributeValue(null, attrName); + if (val != null) { + return val; + } + throw new XMLStreamException("Attribute " + attrName + " is missing at " + reader.getLocation()); + } +} http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/TestClasses.java ---------------------------------------------------------------------- diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/TestClasses.java b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/TestClasses.java new file mode 100644 index 0000000..f119a34 --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/TestClasses.java @@ -0,0 +1,152 @@ +/* + * 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.confined; + +import org.apache.tools.ant.types.Resource; +import org.apache.tools.ant.types.ResourceCollection; +import org.apache.tools.ant.types.resources.Resources; +import org.apache.tools.ant.types.resources.StringResource; + +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.confined.Constants.LD_XML_ATTR_CLASS_NAME; +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_EXCLUDE_ENGINES; +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_ENGINES; +import static org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_OUTPUT_DIRECTORY; +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; + +/** + * Represents a {@code testclasses} that's configured to be launched by the {@link JUnitLauncherTask} + */ +public class TestClasses extends TestDefinition { + + private final Resources resources = new Resources(); + + public TestClasses() { + + } + + public void add(final ResourceCollection resourceCollection) { + this.resources.add(resourceCollection); + } + + public List<String> getTestClassNames() { + if (this.resources.isEmpty()) { + return Collections.emptyList(); + } + final List<String> tests = new ArrayList<>(); + for (final Resource resource : resources) { + if (!resource.isExists()) { + continue; + } + final String name = resource.getName(); + // we only consider .class files + if (!name.endsWith(".class")) { + continue; + } + final String className = name.substring(0, name.lastIndexOf('.')); + tests.add(className.replace(File.separatorChar, '.').replace('/', '.').replace('\\', '.')); + } + return tests; + } + + @Override + protected void toForkedRepresentation(final JUnitLauncherTask task, final XMLStreamWriter writer) throws XMLStreamException { + writer.writeStartElement(LD_XML_ELM_TEST_CLASSES); + // write out each test class + for (final String test : getTestClassNames()) { + writer.writeStartElement(LD_XML_ELM_TEST); + writer.writeAttribute(LD_XML_ATTR_CLASS_NAME, test); + if (haltOnFailure != null) { + writer.writeAttribute(LD_XML_ATTR_HALT_ON_FAILURE, haltOnFailure.toString()); + } + if (outputDir != null) { + writer.writeAttribute(LD_XML_ATTR_OUTPUT_DIRECTORY, outputDir); + } + if (includeEngines != null) { + writer.writeAttribute(LD_XML_ATTR_INCLUDE_ENGINES, includeEngines); + } + if (excludeEngines != null) { + writer.writeAttribute(LD_XML_ATTR_EXCLUDE_ENGINES, excludeEngines); + } + // listeners for this test + if (listeners != null) { + for (final ListenerDefinition listenerDef : getListeners()) { + if (!listenerDef.shouldUse(task.getProject())) { + // not applicable + continue; + } + listenerDef.toForkedRepresentation(writer); + } + } + writer.writeEndElement(); + } + writer.writeEndElement(); + } + + public static List<TestDefinition> fromForkedRepresentation(final XMLStreamReader reader) throws XMLStreamException { + reader.require(XMLStreamConstants.START_ELEMENT, null, LD_XML_ELM_TEST_CLASSES); + final TestClasses testDefinition = new TestClasses(); + // read out as multiple SingleTestClass representations + while (reader.nextTag() != XMLStreamConstants.END_ELEMENT) { + reader.require(XMLStreamConstants.START_ELEMENT, null, Constants.LD_XML_ELM_TEST); + final String testClassName = requireAttributeValue(reader, LD_XML_ATTR_CLASS_NAME); + testDefinition.add(new StringResource(testClassName + ".class")); + final String halt = reader.getAttributeValue(null, LD_XML_ATTR_HALT_ON_FAILURE); + if (halt != null) { + testDefinition.setHaltOnFailure(Boolean.parseBoolean(halt)); + } + final String outDir = reader.getAttributeValue(null, LD_XML_ATTR_OUTPUT_DIRECTORY); + if (outDir != null) { + testDefinition.setOutputDir(outDir); + } + final String includeEngs = reader.getAttributeValue(null, LD_XML_ATTR_INCLUDE_ENGINES); + if (includeEngs != null) { + testDefinition.setIncludeEngines(includeEngs); + } + final String excludeEngs = reader.getAttributeValue(null, LD_XML_ATTR_EXCLUDE_ENGINES); + if (excludeEngs != null) { + testDefinition.setExcludeEngines(excludeEngs); + } + while (reader.nextTag() != XMLStreamConstants.END_ELEMENT) { + reader.require(XMLStreamConstants.START_ELEMENT, null, Constants.LD_XML_ELM_LISTENER); + testDefinition.addConfiguredListener(ListenerDefinition.fromForkedRepresentation(reader)); + } + reader.require(XMLStreamConstants.END_ELEMENT, null, Constants.LD_XML_ELM_TEST); + } + reader.require(XMLStreamConstants.END_ELEMENT, null, LD_XML_ELM_TEST_CLASSES); + return Collections.singletonList(testDefinition); + } + + private static String requireAttributeValue(final XMLStreamReader reader, final String attrName) throws XMLStreamException { + final String val = reader.getAttributeValue(null, attrName); + if (val != null) { + return val; + } + throw new XMLStreamException("Attribute " + attrName + " is missing at " + reader.getLocation()); + } +} http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/TestDefinition.java ---------------------------------------------------------------------- diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/TestDefinition.java b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/TestDefinition.java new file mode 100644 index 0000000..e2403e5 --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/TestDefinition.java @@ -0,0 +1,148 @@ +/* + * 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.confined; + +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; + +/** + * Represents the configuration details of a test that needs to be launched by the {@link JUnitLauncherTask} + */ +public abstract class TestDefinition { + + protected String ifProperty; + protected String unlessProperty; + protected Boolean haltOnFailure; + protected String failureProperty; + protected String outputDir; + protected String includeEngines; + protected String excludeEngines; + protected ForkDefinition forkDefinition; + + protected List<ListenerDefinition> listeners = new ArrayList<>(); + + String getIfProperty() { + return ifProperty; + } + + public void setIf(final String ifProperty) { + this.ifProperty = ifProperty; + } + + String getUnlessProperty() { + return unlessProperty; + } + + public void setUnless(final String unlessProperty) { + this.unlessProperty = unlessProperty; + } + + public boolean isHaltOnFailure() { + return this.haltOnFailure != null && this.haltOnFailure; + } + + Boolean getHaltOnFailure() { + return this.haltOnFailure; + } + + public void setHaltOnFailure(final boolean haltonfailure) { + this.haltOnFailure = haltonfailure; + } + + public String getFailureProperty() { + return failureProperty; + } + + public void setFailureProperty(final String failureProperty) { + this.failureProperty = failureProperty; + } + + public void addConfiguredListener(final ListenerDefinition listener) { + this.listeners.add(listener); + } + + public List<ListenerDefinition> getListeners() { + return Collections.unmodifiableList(this.listeners); + } + + public void setOutputDir(final String dir) { + this.outputDir = dir; + } + + public String getOutputDir() { + return this.outputDir; + } + + 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; + } + + protected boolean shouldRun(final Project project) { + final PropertyHelper propertyHelper = PropertyHelper.getPropertyHelper(project); + return propertyHelper.testIfCondition(this.ifProperty) && propertyHelper.testUnlessCondition(this.unlessProperty); + } + + public String[] getIncludeEngines() { + return includeEngines == null ? new String[0] : split(this.includeEngines, ","); + } + + public void setIncludeEngines(final String includeEngines) { + this.includeEngines = includeEngines; + } + + public String[] getExcludeEngines() { + return excludeEngines == null ? new String[0] : split(this.excludeEngines, ","); + } + + public void setExcludeEngines(final String excludeEngines) { + this.excludeEngines = excludeEngines; + } + + private static String[] split(final String value, final String delimiter) { + if (value == null) { + return new String[0]; + } + final List<String> parts = new ArrayList<>(); + for (final String part : value.split(delimiter)) { + if (part.trim().isEmpty()) { + // skip it + continue; + } + parts.add(part); + } + 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/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/package-info.java ---------------------------------------------------------------------- diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/package-info.java b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/package-info.java new file mode 100644 index 0000000..773c7b1 --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/package-info.java @@ -0,0 +1,27 @@ +/* + * 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. + * + */ +/** + * The classes/interfaces in this package <em>must not</em> + * have any compile time dependency on any of JUnit platform or + * engine classes/interfaces. They <em>must not</em> even have any + * compile time dependency on any classes/interfaces that belong to the + * {@link org.apache.tools.ant.taskdefs.optional.junitlauncher} package. + * + * @since Ant 1.10.6 + */ +package org.apache.tools.ant.taskdefs.optional.junitlauncher.confined; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/package-info.java ---------------------------------------------------------------------- diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/package-info.java b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/package-info.java new file mode 100644 index 0000000..df75b9b --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/package-info.java @@ -0,0 +1,28 @@ +/* + * 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. + * + */ +/** + * The classes/interfaces in this package are allowed + * to have compile time dependency on JUnit platform classes/interfaces. + * Furthermore classes/interfaces in this package are also + * allowed to have compile time dependency on the + * {@link org.apache.tools.ant.taskdefs.optional.junitlauncher.confined} + * package + * + * @since Ant 1.10.6 + */ +package org.apache.tools.ant.taskdefs.optional.junitlauncher; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/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 97a40c1..024a87b 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 @@ -20,6 +20,7 @@ package org.apache.tools.ant.taskdefs.optional.junitlauncher; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.BuildFileRule; import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.JUnitLauncherTask; import org.apache.tools.ant.util.LoaderUtils; import org.example.junitlauncher.jupiter.JupiterSampleTest; import org.example.junitlauncher.vintage.AlwaysFailingJUnit4Test;
