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> <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> <h4>formatter</h4>


<p>The results of the tests can be printed in different <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 The two nested formatters are for displaying (for the user) and for updating the collector
class. class.
</p> </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> </body>
</html> </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.Hashtable;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Vector; import java.util.Vector;
@@ -510,6 +511,26 @@ public class JUnitTask extends Task {
return getCommandline().createBootclasspath(getProject()).createPath(); 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. * Adds an environment variable; used when forking.
* *
@@ -749,7 +770,7 @@ public class JUnitTask extends Task {
loader.loadClass("junit.framework.Test"); // sanity check loader.loadClass("junit.framework.Test"); // sanity check
} catch (final ClassNotFoundException e) { } catch (final ClassNotFoundException e) {
throw new BuildException( 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", + "if not in Ant's own classpath",
e, task.getLocation()); e, task.getLocation());
} }
@@ -777,10 +798,14 @@ public class JUnitTask extends Task {
if (splitJUnit) { if (splitJUnit) {
final Path path = new Path(getProject()); final Path path = new Path(getProject());
path.add(antRuntimeClasses); path.add(antRuntimeClasses);
final Path extra = getCommandline().getClasspath();
Path extra = getCommandline().getClasspath();
if (extra != null) { if (extra != null) {
path.add(extra); path.add(extra);
} }
extra = getCommandline().getModulepath();
if (extra != null && !hasJunit(path)) {
path.add(expandModulePath(extra));
}
mirrorLoader = (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() { mirrorLoader = (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() { public Object run() {
return new SplitClassLoader(myLoader, path, getProject(), return new SplitClassLoader(myLoader, path, getProject(),
@@ -818,7 +843,7 @@ public class JUnitTask extends Task {
@Override @Override
public void execute() throws BuildException { public void execute() throws BuildException {
checkMethodLists(); checkMethodLists();
checkModules();
setupJUnitDelegate(); setupJUnitDelegate();


final List<List> testLists = new ArrayList<List>(); 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 an enumeration listing each test, then each batchtest
* @return enumeration * @return enumeration
@@ -1892,16 +1986,23 @@ public class JUnitTask extends Task {
*/ */
private void createClassLoader() { private void createClassLoader() {
final Path userClasspath = getCommandline().getClasspath(); final Path userClasspath = getCommandline().getClasspath();
if (userClasspath != null) {
final Path userModulepath = getCommandline().getModulepath();
if (userClasspath != null || userModulepath != null) {
if (reloading || classLoader == null) { if (reloading || classLoader == null) {
deleteClassLoader(); 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) { if (includeAntRuntime) {
log("Implicitly adding " + antRuntimeClasses log("Implicitly adding " + antRuntimeClasses
+ " to CLASSPATH", Project.MSG_VERBOSE); + " to CLASSPATH", Project.MSG_VERBOSE);
classpath.append(antRuntimeClasses);
path.append(antRuntimeClasses);
} }
classLoader = getProject().createClassLoader(classpath);
classLoader = getProject().createClassLoader(path);
if (getClass().getClassLoader() != null if (getClass().getClassLoader() != null
&& getClass().getClassLoader() != Project.class.getClassLoader()) { && getClass().getClassLoader() != Project.class.getClassLoader()) {
classLoader.setParent(getClass().getClassLoader()); classLoader.setParent(getClass().getClassLoader());
@@ -2280,4 +2381,24 @@ public class JUnitTask extends Task {
w.newLine(); w.newLine();
s.println(text); 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 static org.apache.tools.ant.AntAssert.assertContains;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; 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.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath; import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory; import javax.xml.xpath.XPathFactory;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.BuildFileRule; 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.JavaEnvUtils;
import org.apache.tools.ant.util.LoaderUtils;
import org.junit.Assume; import org.junit.Assume;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; 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