Browse Source

JDK9 modules support for JUnit by Tomáš Zezula

this is the combined patch or #18 which couldn't be applied via `git am`
master
Stefan Bodewig 9 years ago
parent
commit
31cc48c6f7
3 changed files with 389 additions and 9 deletions
  1. +57
    -2
      manual/Tasks/junit.html
  2. +128
    -7
      src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java
  3. +204
    -0
      src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskTest.java

+ 57
- 2
manual/Tasks/junit.html View File

@@ -371,6 +371,21 @@ subelement.</p>

<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
@@ -796,7 +811,47 @@ the single <code>&lt;test/&gt;</code> will run. So only the failing test cases a
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>

+ 128
- 7
src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java View File

@@ -38,6 +38,7 @@ import java.util.HashMap;
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;
@@ -510,6 +511,26 @@ public class JUnitTask extends Task {
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.
*
@@ -749,7 +770,7 @@ public class JUnitTask extends Task {
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());
}
@@ -777,10 +798,14 @@ public class JUnitTask extends Task {
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(),
@@ -818,7 +843,7 @@ public class JUnitTask extends Task {
@Override
public void execute() throws BuildException {
checkMethodLists();
checkModules();
setupJUnitDelegate();

final List<List> testLists = new ArrayList<List>();
@@ -1697,6 +1722,75 @@ public class JUnitTask extends Task {
}
}

/**
* 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
@@ -1892,16 +1986,23 @@ public class JUnitTask extends Task {
*/
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());
@@ -2280,4 +2381,24 @@ public class JUnitTask extends Task {
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
}
}

+ 204
- 0
src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskTest.java View File

@@ -27,18 +27,33 @@ import static org.apache.tools.ant.AntAssert.assertNotContains;
import static org.apache.tools.ant.AntAssert.assertContains;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.BuildFileRule;
import org.apache.tools.ant.MagicNames;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.launcher.CommandLauncher;
import org.apache.tools.ant.taskdefs.optional.junit.JUnitTask.ForkMode;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.util.JavaEnvUtils;
import org.apache.tools.ant.util.LoaderUtils;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
@@ -395,4 +410,193 @@ public class JUnitTaskTest {
}
@Test(expected = BuildException.class)
public void testModulePathNeedsFork() throws Exception {
final Project project = new Project();
project.init();
JUnitTask task = new JUnitTask();
task.setProject(project);
final Path p = new Path(project);
p.setPath("modules");
task.createModulepath().add(p);
task.addTest(new JUnitTest("org.apache.tools.ant.taskdefs.optional.junit.TestTest"));
task.execute();
}
@Test(expected = BuildException.class)
public void testUpgradeModulePathNeedsFork() throws Exception {
final Project project = new Project();
project.init();
JUnitTask task = new JUnitTask();
task.setProject(project);
final Path p = new Path(project);
p.setPath("modules");
task.createUpgrademodulepath().add(p);
task.addTest(new JUnitTest("org.apache.tools.ant.taskdefs.optional.junit.TestTest"));
task.execute();
}
@Test
public void testJunitOnCpArguments() throws Exception {
final File tmp = new File(System.getProperty("java.io.tmpdir")); //NOI18N
final File workDir = new File(tmp, String.format("%s_testJCP%d", //NOI18N
getClass().getName(),
System.currentTimeMillis()/1000));
workDir.mkdirs();
try {
final File modulesDir = new File(workDir,"modules"); //NOI18N
modulesDir.mkdirs();
final Project project = new Project();
project.init();
project.setBaseDir(workDir);
final MockCommandLauncher mockProcLauncher = new MockCommandLauncher();
project.addReference(
MagicNames.ANT_VM_LAUNCHER_REF_ID,
mockProcLauncher);
JUnitTask task = new JUnitTask();
task.setDir(workDir);
task.setFork(true);
task.setProject(project);
final File junit = LoaderUtils.getResourceSource(
JUnitTask.class.getClassLoader(),
"junit/framework/Test.class"); //NOI18N
final Path cp = new Path(project);
cp.setPath(junit.getAbsolutePath());
task.createClasspath().add(cp);
final Path mp = new Path(project);
mp.setPath(modulesDir.getName());
task.createModulepath().add(mp);
task.addTest(new JUnitTest("org.apache.tools.ant.taskdefs.optional.junit.TestTest"));
task.execute();
assertNotNull(mockProcLauncher.cmd);
String resCp = null;
String resMp = null;
Set<String> resExports = new TreeSet<>();
for (int i = 1; i< mockProcLauncher.cmd.length; i++) {
if ("-classpath".equals(mockProcLauncher.cmd[i])) { //NOI18N
resCp = mockProcLauncher.cmd[++i];
} else if ("-modulepath".equals(mockProcLauncher.cmd[i])) { //NOI18N
resMp = mockProcLauncher.cmd[++i];
} else if (mockProcLauncher.cmd[i].startsWith("-XaddExports:")) { //NOI18N
resExports.add(mockProcLauncher.cmd[i]);
} else if (JUnitTestRunner.class.getName().equals(mockProcLauncher.cmd[i])) {
break;
}
}
assertTrue("No exports", resExports.isEmpty());
assertEquals("Expected classpath", cp.toString(), resCp);
assertEquals("Expected modulepath", mp.toString(), resMp);
} finally {
delete(workDir);
}
}
@Test
public void testJunitOnMpArguments() throws Exception {
final File tmp = new File(System.getProperty("java.io.tmpdir")); //NOI18N
final File workDir = new File(tmp, String.format("%s_testJMP%d", //NOI18N
getClass().getName(),
System.currentTimeMillis()/1000));
workDir.mkdirs();
try {
final File modulesDir = new File(workDir,"modules"); //NOI18N
modulesDir.mkdirs();
final Project project = new Project();
project.init();
project.setBaseDir(workDir);
final MockCommandLauncher mockProcLauncher = new MockCommandLauncher();
project.addReference(
MagicNames.ANT_VM_LAUNCHER_REF_ID,
mockProcLauncher);
JUnitTask task = new JUnitTask();
task.setDir(workDir);
task.setFork(true);
task.setProject(project);
final File junit = LoaderUtils.getResourceSource(
JUnitTask.class.getClassLoader(),
"junit/framework/Test.class"); //NOI18N
final Path mp = new Path(project);
mp.add(new Path(project, junit.getAbsolutePath()));
mp.add(new Path(project, modulesDir.getName()));
task.createModulepath().add(mp);
task.addTest(new JUnitTest("org.apache.tools.ant.taskdefs.optional.junit.TestTest")); //NOI18N
task.execute();
assertNotNull(mockProcLauncher.cmd);
String resCp = null;
String resMp = null;
Set<String> resExports = new TreeSet<>();
for (int i = 1; i< mockProcLauncher.cmd.length; i++) {
if ("-classpath".equals(mockProcLauncher.cmd[i])) { //NOI18N
resCp = mockProcLauncher.cmd[++i];
} else if ("-modulepath".equals(mockProcLauncher.cmd[i])) { //NOI18N
resMp = mockProcLauncher.cmd[++i];
} else if (mockProcLauncher.cmd[i].startsWith("-XaddExports:")) { //NOI18N
resExports.add(mockProcLauncher.cmd[i]);
} else if (JUnitTestRunner.class.getName().equals(mockProcLauncher.cmd[i])) {
break;
}
}
assertTrue("No exports", resExports.isEmpty());
assertNull("No classpath", resCp);
assertEquals("Expected modulepath", mp.toString(), resMp);
} finally {
delete(workDir);
}
}
private void delete(File f) {
if (f.isDirectory()) {
final File[] clds = f.listFiles();
if (clds != null) {
for (File cld : clds) {
delete(cld);
}
}
}
f.delete();
}
private static final class MockCommandLauncher extends CommandLauncher {
private String[] cmd;
@Override
public Process exec(Project project, String[] cmd, String[] env, File workingDir) throws IOException {
this.cmd = Arrays.copyOf(cmd, cmd.length);
return new MockProcess();
}
private static class MockProcess extends Process {
@Override
public OutputStream getOutputStream() {
return new ByteArrayOutputStream();
}
@Override
public InputStream getInputStream() {
return new ByteArrayInputStream(new byte[0]);
}
@Override
public InputStream getErrorStream() {
return new ByteArrayInputStream(new byte[0]);
}
@Override
public int waitFor() throws InterruptedException {
return exitValue();
}
@Override
public int exitValue() {
return 0;
}
@Override
public void destroy() {
}
}
}
}

Loading…
Cancel
Save