Skip to content

Commit

Permalink
junitlauncher - Added fork mode support
Browse files Browse the repository at this point in the history
  • Loading branch information
azotcsit committed Dec 23, 2021
1 parent 002596a commit df9d846
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 3 deletions.
14 changes: 12 additions & 2 deletions manual/Tasks/junitlauncher.html
Original file line number Diff line number Diff line change
Expand Up @@ -582,8 +582,7 @@ <h4>testclasses</h4>

<p>
The <a href="#fork">fork</a> nested element can be used to run the tests in a newly forked
JVM. All tests that are part of this <code>testclasses</code> element will run in one single
instance of the newly forked JVM.
JVM.
</p>

<h4 id="fork">fork</h4>
Expand Down Expand Up @@ -613,6 +612,17 @@ <h4 id="fork">fork</h4>
than this configured value, then the JVM is killed</td>
<td>No</td>
</tr>
<tr>
<td>mode</td>
<td>Controls how many JVMs get created if you want to fork some tests. Possible values
are <q>once</q> (the default) and <q>perTest</q>. <q>once</q> creates only a single
JVM for all tests while <q>perTest</q> creates a new JVM for each test suite class.
Note that only tests within the same <q>fork</q> element can share a JVM, so even
if you set <var>mode</var> to <q>once</q> Ant may have to create more than one JVM.
<p><em>Since Ant 1.10.13</em></p>
</td>
<td>No; defaults to <q>once</q></td>
</tr>
<tr>
<td>includeJUnitPlatformLibraries</td>
<td>If set to <code>true</code>, then the jar files that make up the
Expand Down
23 changes: 23 additions & 0 deletions src/etc/testcases/taskdefs/optional/junitlauncher.xml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,29 @@
</junitlauncher>
</target>

<target name="test-per-test-fork-mode" depends="init">
<junitlauncher>
<classpath refid="test.classpath"/>
<testclasses outputdir="${output.dir}">
<!-- the same test is duplicated intentionally; if both are run in the same JVM the second one will
fail, it is a nice way to ensure "perTest" mode runs every test suite class in a separate JVM -->
<fileset dir="${build.classes.dir}" includes="org/example/**/junitlauncher/**/ForkedTest.class"/>
<fileset dir="${build.classes.dir}" includes="org/example/**/junitlauncher/**/ForkedTest.class"/>

<listener classname="org.example.junitlauncher.Tracker"
outputDir="${output.dir}"
resultFile="${test-per-test-fork-mode.tracker}"
if="test-per-test-fork-mode.tracker"/>

<fork dir="${basedir}" mode="perTest">
<sysproperty key="junitlauncher.test.sysprop.one" value="forked"/>
</fork>

<listener type="legacy-xml" sendSysErr="true" sendSysOut="true" useLegacyReportingName="false"/>
</testclasses>
</junitlauncher>
</target>

<target name="test-junit-platform-lib-excluded" depends="init">
<junitlauncher>
<classpath refid="junit.engine.jupiter.classpath"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.Commandline;
import org.apache.tools.ant.types.CommandlineJava;
import org.apache.tools.ant.types.EnumeratedAttribute;
import org.apache.tools.ant.types.Environment;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.PropertySet;
Expand All @@ -42,6 +43,7 @@ public class ForkDefinition {

private String dir;
private long timeout = -1;
private Mode mode = new Mode("once");

ForkDefinition() {
this.commandLineJava = new CommandlineJava();
Expand All @@ -63,14 +65,37 @@ long getTimeout() {
return this.timeout;
}

/**
* Possible values are "once" and "perTest". If set to "once" (the default),
* only a single Java VM will be forked for all tests, with "perTest" each test
* will be run in a fresh Java VM.
* @param mode the mode to use.
* @since Ant 1.10.13
*/
public void setMode(final Mode mode) {
this.mode = mode;
}

public Mode getMode() {
return mode;
}

public void setIncludeJUnitPlatformLibraries(final boolean include) {
this.includeJUnitPlatformLibraries = include;
}

public boolean isIncludeJUnitPlatformLibraries() {
return includeJUnitPlatformLibraries;
}

public void setIncludeAntRuntimeLibraries(final boolean include) {
this.includeAntRuntimeLibraries = include;
}

public boolean isIncludeAntRuntimeLibraries() {
return includeAntRuntimeLibraries;
}

public Commandline.Argument createJvmArg() {
return this.commandLineJava.createVmArgument();
}
Expand Down Expand Up @@ -140,4 +165,39 @@ CommandlineJava generateCommandLine(final JUnitLauncherTask task) {
return cmdLine;
}

/**
* Forking option. There are two available: "once" and "perTest".
* @since Ant 1.10.13
*/
public static final class Mode extends EnumeratedAttribute {

/**
* fork once only
*/
public static final String ONCE = "once";
/**
* fork once per test class
*/
public static final String PER_TEST = "perTest";

/** No arg constructor. */
public Mode() {
super();
}

/**
* Constructor using a value.
* @param value the value to use - once or perTest.
*/
public Mode(final String value) {
super();
setValue(value);
}

/** {@inheritDoc}. */
@Override
public String[] getValues() {
return new String[] {ONCE, PER_TEST};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,41 @@ public void execute() throws BuildException {
continue;
}
if (test.getForkDefinition() != null) {
forkTest(test);
if (test instanceof TestClasses
&& test.getForkDefinition().getMode().getValue().equals(ForkDefinition.Mode.PER_TEST)) {
TestClasses testClasses = (TestClasses) test;
for (String testClassName : testClasses.getTestClassNames()) {
forkTest(convertToSingleTestClass(testClasses, testClassName));
}
} else {
forkTest(test);
}
} else {
launchViaReflection(new InVMLaunch(Collections.singletonList(test)));
}
}
}

private SingleTestClass convertToSingleTestClass(TestClasses testClasses, String testClassName) {
SingleTestClass singleTestClass = new SingleTestClass();
singleTestClass.setName(testClassName);
singleTestClass.setIf(testClasses.ifProperty);
singleTestClass.setUnless(testClasses.unlessProperty);
singleTestClass.setHaltOnFailure(testClasses.haltOnFailure);
singleTestClass.setFailureProperty(testClasses.failureProperty);
singleTestClass.setOutputDir(testClasses.outputDir);
singleTestClass.setIncludeEngines(testClasses.includeEngines);
singleTestClass.setExcludeEngines(testClasses.excludeEngines);

for (ListenerDefinition listener : testClasses.getListeners()) {
singleTestClass.addConfiguredListener(listener);
}

singleTestClass.setForkDefinition(testClasses.getForkDefinition());

return singleTestClass;
}

/**
* Adds the {@link Path} to the classpath which will be used for execution of the tests
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ ForkDefinition getForkDefinition() {
return this.forkDefinition;
}

public void setForkDefinition(ForkDefinition forkDefinition) {
this.forkDefinition = forkDefinition;
}

protected boolean shouldRun(final Project project) {
final PropertyHelper propertyHelper = PropertyHelper.getPropertyHelper(project);
return propertyHelper.testIfCondition(this.ifProperty) && propertyHelper.testUnlessCondition(this.unlessProperty);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,26 @@ public void testBasicFork() throws Exception {
ForkedTest.class.getName(), "testSysProp"));
}

/**
* Tests the execution of a forked test with "perTest" mode
*/
@Test
public void testPerTestForkMode() throws Exception {
final String targetName = "test-per-test-fork-mode";
final Path trackerFile = setupTrackerProperty(targetName);
// setup a dummy and incorrect value of a sysproperty that's used in the test being forked
System.setProperty(ForkedTest.SYS_PROP_ONE, "dummy");
buildRule.executeTarget(targetName);
// verify that our JVM's sysprop value didn't get changed
Assert.assertEquals("System property " + ForkedTest.SYS_PROP_ONE + " was unexpected updated",
"dummy", System.getProperty(ForkedTest.SYS_PROP_ONE));

Assert.assertTrue("At least one run of ForkedTest#testSysProp was expected to succeed",
verifySuccess(trackerFile, ForkedTest.class.getName(), "testSysProp"));
Assert.assertFalse("No runs of ForkedTest#testSysProp was expected to fail",
verifyFailed(trackerFile, ForkedTest.class.getName(), "testSysProp"));
}

/**
* Tests that in a forked mode execution of tests, when the {@code includeJUnitPlatformLibraries} attribute
* is set to false, then the execution of such tests fails with a classloading error for the JUnit platform
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ public class ForkedTest {
public void testSysProp() {
Assert.assertEquals("Unexpected value for system property",
"forked", System.getProperty(SYS_PROP_ONE));
System.setProperty(SYS_PROP_ONE, "changed_inside_fork");
}
}

0 comments on commit df9d846

Please sign in to comment.