Skip to content

Commit

Permalink
JDK9 modules support for JUnit by Tomáš Zezula
Browse files Browse the repository at this point in the history
this is the combined patch or #18 which couldn't be applied via `git am`
  • Loading branch information
bodewig committed Sep 11, 2016
1 parent 1d7b624 commit 31cc48c
Show file tree
Hide file tree
Showing 3 changed files with 389 additions and 9 deletions.
59 changes: 57 additions & 2 deletions manual/Tasks/junit.html
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,21 @@ <h4>assertions</h4>

<p><em>since Ant 1.6.</em></p>

<h4>modulepath</h4>

<p>The location of modules can be specified using this <a href="../using.html#path">PATH like structure</a>.<br/>
The modulepath requires <i>fork</i> to be set to <code>true</code>.

<p><em>since Ant 1.10</em></p>

<h4>upgrademodulepath</h4>

<p>The location of modules that replace upgradeable modules in the runtime image
can be specified using this <a href="../using.html#path">PATH like structure</a>.<br/>
The upgrademodulepath requires <i>fork</i> to be set to <code>true</code>.

<p><em>since Ant 1.10</em></p>

<h4>formatter</h4>

<p>The results of the tests can be printed in different
Expand Down Expand Up @@ -796,7 +811,47 @@ <h3>Examples</h3>
The two nested formatters are for displaying (for the user) and for updating the collector
class.
</p>


<pre>
&lt;junit fork="true"
jvm="${platform.java}"&gt;
&lt;jvmarg value="-Xpatch:${module.name}=${build.test.classes}"/&gt;
&lt;jvmarg line="-addmods ${module.name}"/&gt;
&lt;jvmarg value="-XaddReads:${module.name}=ALL-UNNAMED"/&gt;
&lt;jvmarg value="-XaddExports:${module.name}/my.test=ALL-UNNAMED"/&gt;
&lt;classpath&gt;
&lt;pathelement path="${libs.junit}"/&gt;
&lt;/classpath&gt;
&lt;modulepath&gt;
&lt;pathelement path="${modules}:${build.classes}"/&gt;
&lt;/modulepath&gt;
&lt;formatter type="plain"/&gt;
&lt;test name="my.test.TestCase"/&gt;
&lt;/junit&gt;
</pre>
<p>Runs my.test.TestCase as a white-box test in the forked VM given by the <code>platform.java</code> property.
The junit library is a part of an unnamed module while the tested project and required modules are on the module path. The tests
do not have module-info file and are executed in the project module given by <code>module.name</code> property.<br/>
The <code>-Xpatch</code> java option executes the tests built into <code>${build.test.classes}</code> in a module given
by <code>module.name</code> property.<br/>
The <code>-addmods</code> java option enables the tested module.<br/>
The <code>-XaddReads</code> java option makes the unnamed module containing the junit readable by tested module.<br/>
The <code>-XaddExports</code> java option makes the non-exported test package <code>my.test</code> accessible from the unnamed module containing the junit.<br/>
<pre>
&lt;junit fork="true"
jvm="${platform.java}"&gt;
&lt;jvmarg line="-addmods ${test.module.name}"/&gt;
&lt;jvmarg value="-XaddExports:${test.module.name}/my.test=junit,ALL-UNNAMED"/&gt;
&lt;modulepath&gt;
&lt;pathelement path="${modules}:${build.classes}:${libs.junit}"/&gt;
&lt;/modulepath&gt;
&lt;formatter type="plain"/&gt;
&lt;test name="my.test.TestCase"/&gt;
&lt;/junit&gt;
</pre>
<p>Runs my.test.TestCase as a black-box test in the forked VM given by the <code>platform.java</code> property.
The junit library is used as an automatic module. The tests module-info requires the tested module and junit.<br/>
The <code>-addmods</code> java option enables the test module.<br/>
The <code>-XaddExports</code> java option makes the non-exported test package <code>my.test</code> accessible from the junit module and Ant's test runner.
Another possibility is to export the test package in the tests module-info by <code>exports my.test</code> directive.<br/>
</body>
</html>
135 changes: 128 additions & 7 deletions src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;
Expand Down Expand Up @@ -510,6 +511,26 @@ public Path createBootclasspath() {
return getCommandline().createBootclasspath(getProject()).createPath();
}

/**
* Add a path to the modulepath.
*
* @return created modulepath.
* @since 1.10
*/
public Path createModulepath() {
return getCommandline().createModulepath(getProject()).createPath();
}

/**
* Add a path to the upgrademodulepath.
*
* @return created upgrademodulepath.
* @since 1.10
*/
public Path createUpgrademodulepath() {
return getCommandline().createUpgrademodulepath(getProject()).createPath();
}

/**
* Adds an environment variable; used when forking.
*
Expand Down Expand Up @@ -749,7 +770,7 @@ private static JUnitTaskMirror createMirror(final JUnitTask task, final ClassLoa
loader.loadClass("junit.framework.Test"); // sanity check
} catch (final ClassNotFoundException e) {
throw new BuildException(
"The <classpath> for <junit> must include junit.jar "
"The <classpath> or <modulepath> for <junit> must include junit.jar "
+ "if not in Ant's own classpath",
e, task.getLocation());
}
Expand Down Expand Up @@ -777,10 +798,14 @@ protected void setupJUnitDelegate() {
if (splitJUnit) {
final Path path = new Path(getProject());
path.add(antRuntimeClasses);
final Path extra = getCommandline().getClasspath();
Path extra = getCommandline().getClasspath();
if (extra != null) {
path.add(extra);
}
extra = getCommandline().getModulepath();
if (extra != null && !hasJunit(path)) {
path.add(expandModulePath(extra));
}
mirrorLoader = (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new SplitClassLoader(myLoader, path, getProject(),
Expand Down Expand Up @@ -818,7 +843,7 @@ public Object run() {
@Override
public void execute() throws BuildException {
checkMethodLists();

checkModules();
setupJUnitDelegate();

final List<List> testLists = new ArrayList<List>();
Expand Down Expand Up @@ -1697,6 +1722,75 @@ private void checkMethodLists() throws BuildException {
}
}

/**
* Checks a validity of module specific options.
* @since 1.10
*/
private void checkModules() {
if (hasPath(getCommandline().getModulepath()) ||
hasPath(getCommandline().getUpgrademodulepath())) {
for (int i = 0, count = batchTests.size(); i < count; i++) {
if(!batchTests.elementAt(i).getFork()) {
throw new BuildException("The module path requires fork attribute to be set to true.");
}
}
for (int i = 0, count = tests.size(); i < count; i++) {
if (!tests.elementAt(i).getFork()) {
throw new BuildException("The module path requires fork attribute to be set to true.");
}
}
}
}

/**
* Checks is a junit is on given path.
* @param path the {@link Path} to check
* @return true when given {@link Path} contains junit
* @since 1.10
*/
private boolean hasJunit(final Path path) {
try (AntClassLoader loader = AntClassLoader.newAntClassLoader(
null,
getProject(),
path,
true)) {
try {
loader.loadClass("junit.framework.Test");
return true;
} catch (final Exception ex) {
return false;
}
}
}

/**
* Expands a module path to flat path of jars and root folders usable by classloader.
* @param modulePath to be expanded
* @return the expanded path
* @since 1.10
*/
private Path expandModulePath(Path modulePath) {
final Path expanded = new Path(getProject());
for (String path : modulePath.list()) {
final File modulePathEntry = getProject().resolveFile(path);
if (modulePathEntry.isDirectory() && !hasModuleInfo(modulePathEntry)) {
final File[] modules = modulePathEntry.listFiles((dir,name)->name.toLowerCase(Locale.ENGLISH).endsWith(".jar"));
if (modules != null) {
for (File module : modules) {
expanded.add(new Path(getProject(), String.format(
"%s%s%s", //NOI18N
path,
File.separator,
module.getName())));
}
}
} else {
expanded.add(new Path(getProject(), path));
}
}
return expanded;
}

/**
* return an enumeration listing each test, then each batchtest
* @return enumeration
Expand Down Expand Up @@ -1892,16 +1986,23 @@ private void logVmExit(final FormatterElement[] feArray, final JUnitTest test,
*/
private void createClassLoader() {
final Path userClasspath = getCommandline().getClasspath();
if (userClasspath != null) {
final Path userModulepath = getCommandline().getModulepath();
if (userClasspath != null || userModulepath != null) {
if (reloading || classLoader == null) {
deleteClassLoader();
final Path classpath = (Path) userClasspath.clone();
final Path path = new Path(getProject());
if (userClasspath != null) {
path.add((Path) userClasspath.clone());
}
if (userModulepath != null && !hasJunit(path)) {
path.add(expandModulePath(userModulepath));
}
if (includeAntRuntime) {
log("Implicitly adding " + antRuntimeClasses
+ " to CLASSPATH", Project.MSG_VERBOSE);
classpath.append(antRuntimeClasses);
path.append(antRuntimeClasses);
}
classLoader = getProject().createClassLoader(classpath);
classLoader = getProject().createClassLoader(path);
if (getClass().getClassLoader() != null
&& getClass().getClassLoader() != Project.class.getClassLoader()) {
classLoader.setParent(getClass().getClassLoader());
Expand Down Expand Up @@ -2280,4 +2381,24 @@ private static void printlnDual(final BufferedWriter w, final PrintStream s, fin
w.newLine();
s.println(text);
}

/**
* Checks if a path exists and is non empty.
* @param path to be checked
* @return true if the path is non <code>null</code> and non empty.
* @since 1.10
*/
private static boolean hasPath(final Path path) {
return path != null && path.size() > 0;
}

/**
* Checks if a given folder is an unpacked module.
* @param root the fodler to be checked
* @return true if the root is an unpacked module
* @since 1.10
*/
private static boolean hasModuleInfo(final File root) {
return new File(root, "module-info.class").exists(); //NOI18N
}
}

0 comments on commit 31cc48c

Please sign in to comment.