Github user bodewig commented on a diff in the pull request: https://github.com/apache/ant/pull/60#discussion_r169245116 --- Diff: src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/JUnitLauncherTask.java --- @@ -0,0 +1,508 @@ +package org.apache.tools.ant.taskdefs.optional.junitlauncher; + +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.types.Path; +import org.apache.tools.ant.util.KeepAliveOutputStream; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestPlan; +import org.junit.platform.launcher.core.LauncherFactory; +import org.junit.platform.launcher.listeners.SummaryGeneratingListener; +import org.junit.platform.launcher.listeners.TestExecutionSummary; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * 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> for more details + * on how JUnit manages the platform and the test engines. + */ +public class JUnitLauncherTask extends Task { + + private Path classPath; + private boolean haltOnFailure; + private String failureProperty; + private final List<TestDefinition> tests = new ArrayList<>(); + private final List<ListenerDefinition> listeners = new ArrayList<>(); + + public JUnitLauncherTask() { + } + + @Override + public void execute() throws BuildException { + final ClassLoader previousClassLoader = Thread.currentThread().getContextClassLoader(); + try { + final ClassLoader executionCL = createClassLoaderForTestExecution(); + Thread.currentThread().setContextClassLoader(executionCL); + final Launcher launcher = LauncherFactory.create(); + final List<TestRequest> requests = buildTestRequests(); + for (final TestRequest testRequest : requests) { + try { + final TestDefinition test = testRequest.getOwner(); + final LauncherDiscoveryRequest request = testRequest.getDiscoveryRequest().build(); + final List<TestExecutionListener> testExecutionListeners = new ArrayList<>(); + // a listener that we always put at the front of list of listeners + // for this request. + final Listener firstListener = new Listener(); + // we always enroll the summary generating listener, to the request, so that we + // get to use some of the details of the summary for our further decision making + testExecutionListeners.add(firstListener); + testExecutionListeners.addAll(getListeners(testRequest, executionCL)); + final PrintStream originalSysOut = System.out; + final PrintStream originalSysErr = System.err; + try { + firstListener.switchedSysOutHandle = trySwitchSysOut(testRequest); + firstListener.switchedSysErrHandle = trySwitchSysErr(testRequest); + launcher.execute(request, testExecutionListeners.toArray(new TestExecutionListener[testExecutionListeners.size()])); + } finally { + // switch back sysout/syserr to the original + try { + System.setOut(originalSysOut); + } catch (Exception e) { + // ignore + } + try { + System.setErr(originalSysErr); + } catch (Exception e) { + // ignore + } + } + handleTestExecutionCompletion(test, firstListener.getSummary()); + } finally { + try { + testRequest.close(); + } catch (Exception e) { + // log and move on + log("Failed to cleanly close test request", e, Project.MSG_DEBUG); + } + } + } + } finally { + Thread.currentThread().setContextClassLoader(previousClassLoader); + } + } + + /** + * @return Creates and returns the a {@link Path} which will be used as the classpath of this + * task. This classpath will then be used for execution of the tests + */ + public Path createClassPath() { + this.classPath = new Path(getProject()); + return this.classPath; + } + + /** + * @return Creates and returns a {@link SingleTestClass}. This test will be considered part of the + * tests that will be passed on to the underlying JUnit platform for possible execution of the test + */ + public SingleTestClass createTest() { + final SingleTestClass test = new SingleTestClass(); + this.preConfigure(test); --- End diff -- `create*` is called before the attribute setters, If you call `preconfigure` here the test won't see the actual values set at the task level. `addConfigured` would get called after the attribute setters, alternatively you need to defer pre-configuration until `execute` is called. With both approaches you need to ensure you don't overwrite the test's explicit configuration with values from the task, of course. Same for `createTestClasses`.
--- --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@ant.apache.org For additional commands, e-mail: dev-h...@ant.apache.org