Hi,
This is a patch for the <junit> task, that adds an option to use the traditional JUnit GUI instead of Ant's built-in text/xml/html/etc. formatters. You know, this kind of thing:
http://junit.sourceforge.net/doc/testinfected/IMG00001.GIF
This is a scratch I've always wanted to itch. The text/html/etc. <junit> output formats are great in continuous-integration situations, but when doing a code/compile/test cycle, I think the "immediate" feedback of the GUI is preferable. You have to love the green-bar :D
This patch adds a uimode="true" attribute to <junit> (see the preliminary doco below).
I have tested this patch with JDK1.2.2 on Win2k, with JUnit 3.8.1 and 3.0
Patch Contents ============== (see attached patch.txt and patch.tar.gz, created using patch.xml)
NOTES: - I've used "@since Ant 1.6FIXME", not sure what the correct value should be
Modified files:
o JUnitTask.java - added UI mode functionality - "extract method" refactor in executeAsForked() creating executeAsForkedCommon(), so that the setup performed on "cmd" could be reused - Removed some deprecated warnings by using appropriate JDK1.2 methods (this is okay now, right?) - misc whitespace cleaning o JUnitTestRunner.java - an "extract method" refactor so that logic can be re-used - fix illegal javadoc o BaseTest.java o JUnitTest.java o BatchTest.java - move shouldRun() from JUnitTest to superclass BaseTest - added abstract method getTestNames() to BaseTest and implemented in subclasses
Added File:
o BatchSuite.java - A JUnit test that initializes its suite from a file Preliminary Doco ================ (if the patch is accepted I'll submit a follow-up patch to junit.html)
--START DOCS--
Note: UI Mode -------------
When UI mode is activated (using the "uimode" attribute), any associated <formatter> tags are ignored, and one of JUnit's own GUI viewers is used instead.
When in UI mode, the following options are ignored: printsummary, fork, halton*, errorproperty, failureproperty, filtertrace, timeout, showoutput, todir, and outfile.
All tests will be run in forked mode, irrespective of the value of "fork". If more than one test is run at once (eg, as in a <batchtest> or multiple <test>s) then "includeAntRuntime" will be force to be "true".
The <uiarg> nested element can be used when UI mode is active (otherwise it is ignored by <junit>).
uimode When set, UI mode is activated as described above. Valid values include "swing", "awt" or the fully-qualified classname of a JUnit "runner". When "true", "yes" or "on", then "swing" is used. (In versions of JUnit that don't have the "swing" viewer, then "swing" and "awt" mean the same thing) If "false" or not set (the default), then UI mode is not active.
<uiarg>
Adds arguments to the of the JUnit's commandline. <uiarg> allows all attributes described in Command-line Arguments.
This is useful, for example, to pass "-noloading" to JUnit:
<junit uimode="yes"> <uiarg value="-noloading"/> ... </junit>
--END DOCS--
Thanks,
=Matt
? src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchSuite.java Index: src/main/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java =================================================================== RCS file: /home/cvspublic/ant/src/main/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java,v retrieving revision 1.9 diff -u -r1.9 BaseTest.java --- src/main/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java 7 Mar 2003 11:23:06 -0000 1.9 +++ src/main/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java 14 Jun 2003 10:53:53 -0000 @@ -56,6 +56,7 @@ import java.io.File; import java.util.Vector; +import org.apache.tools.ant.Project; /** * Baseclass for BatchTest and JUnitTest. @@ -153,5 +154,22 @@ public void setErrorProperty(String errorProperty) { this.errorProperty = errorProperty; + } + + /** + * @return the list of all the test class names represented + * by this Test + */ + public abstract String[] getTestNames(); + + public boolean shouldRun(Project p) { + if (ifProperty != null && p.getProperty(ifProperty) == null) { + return false; + } else if (unlessProperty != null && + p.getProperty(unlessProperty) != null) { + return false; + } + + return true; } } Index: src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java =================================================================== RCS file: /home/cvspublic/ant/src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java,v retrieving revision 1.14 diff -u -r1.14 BatchTest.java --- src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java 7 Mar 2003 11:23:06 -0000 1.14 +++ src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java 14 Jun 2003 10:53:53 -0000 @@ -142,6 +142,18 @@ } /** + * @see BaseTest#getTestNames + */ + public String[] getTestNames() { + String[] filenames = getFilenames(); + String[] names = new String[filenames.length]; + for (int i = 0; i < names.length; i++) { + names[i] = javaToClass(filenames[i]); + } + return names; + } + + /** * Iterate over all filesets and return the filename of all files * that end with <tt>.java</tt> or <tt>.class</tt>. This is to avoid * wrapping a <tt>JUnitTest</tt> over an xml file for example. A Testcase Index: src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java =================================================================== RCS file: /home/cvspublic/ant/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java,v retrieving revision 1.67 diff -u -r1.67 JUnitTask.java --- src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java 28 May 2003 13:12:03 -0000 1.67 +++ src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java 14 Jun 2003 10:53:57 -0000 @@ -54,8 +54,10 @@ package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; +import java.io.FileWriter; import java.io.IOException; import java.io.OutputStream; import java.util.Enumeration; @@ -178,6 +180,38 @@ private boolean showOutput = false; private File tmpDir; + private String uiMode = null; + private Vector uiArguments = new Vector(); + + + /** + * Activates UI mode. + * + * <p>Valid modes are "swing", "awt" or the fully qualified classname + * of a subclass of JUnit's junit.runner.BaseTestRunner</p> + * + * <p>If the mode is "true", "yes" or "on", then "swing" mode is used.</p> + * + * <p>If "false" or not set, then UI mode is not activated.</p> + * + * @param value the mode to use + */ + public void setUiMode(String value) { + uiMode = value; + } + + /** + * If UI mode is active, adds arguments to the the "runner" (useful for + * specifing "-noloading", etc.) + * + * @param arg the argument to add + * + * @since Ant 1.6FIXME + */ + public void addUiArg(Commandline.Argument arg) { + uiArguments.addElement(arg); + } + /** * If true, smartly filter the stack frames of * JUnit errors and failures before reporting them. @@ -554,15 +588,29 @@ * @since Ant 1.2 */ public void execute() throws BuildException { - Enumeration list = getIndividualTests(); - while (list.hasMoreElements()) { - JUnitTest test = (JUnitTest) list.nextElement(); - if (test.shouldRun(getProject())) { - execute(test); + boolean useJUnitTestRunner = + (uiMode == null) + || "".equals(uiMode) + || "false".equals(uiMode) + || "no".equals(uiMode) + || "off".equals(uiMode); + + if (useJUnitTestRunner) { + // run each test individually using JUnitTestRunner + Enumeration list = getIndividualTests(); + while (list.hasMoreElements()) { + JUnitTest test = (JUnitTest) list.nextElement(); + if (test.shouldRun(getProject())) { + execute(test); + } } + } else { + // run tests using JUnit's own test runners + executeInUiMode(); } } + /** * Run the tests. */ @@ -634,12 +682,6 @@ cmd.createArgument().setValue("haltOnError=" + test.getHaltonerror()); cmd.createArgument().setValue("haltOnFailure=" + test.getHaltonfailure()); - if (includeAntRuntime) { - log("Implicitly adding " + antRuntimeClasses + " to CLASSPATH", - Project.MSG_VERBOSE); - cmd.createClasspath(getProject()).createPath() - .append(antRuntimeClasses); - } if (summary) { log("Running " + test.getName(), Project.MSG_INFO); @@ -683,7 +725,7 @@ } try { FileOutputStream outstream = new FileOutputStream(propsFile); - props.save(outstream, "Ant JUnitTask generated properties file"); + props.store(outstream, "Ant JUnitTask generated properties file"); outstream.close(); } catch (java.io.IOException e) { propsFile.delete(); @@ -691,6 +733,45 @@ + "file.", e, getLocation()); } + try { + return executeAsForkedCommon(cmd, watchdog, false); + } finally { + if (!propsFile.delete()) { + throw new BuildException("Could not delete temporary " + + "properties file."); + } + if (watchdog != null && watchdog.killedProcess()) { + logTimeout(feArray, test); + } + } + } + + /** + * Execute the given CommandlineJava by forking a new JVM. + * The command will block until it finishes. To know if the + * process was destroyed or not, use the <tt>killedProcess()</tt> + * method of the watchdog class. + * + * This method performs some additional initialization of the + * java command, including setting the working dir, environment + * variables and including the ant runtime + * + * @param cmd the java command to execute + * @param watchdog the watchdog in charge of cancelling the test if it + * exceeds a certain amount of time. Can be <tt>null</tt>, in this case + * the test could probably hang forever. + * @param forceAntRuntime if true, the ant runtime will be included + * unconditionally + */ + private int executeAsForkedCommon(CommandlineJava cmd, ExecuteWatchdog watchdog, + boolean forceAntRuntime) { + if (includeAntRuntime || forceAntRuntime) { + log("Implicitly adding " + antRuntimeClasses + " to CLASSPATH", + Project.MSG_VERBOSE); + cmd.createClasspath(getProject()).createPath() + .append(antRuntimeClasses); + } + Execute execute = new Execute(new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_WARN), @@ -712,23 +793,12 @@ execute.setEnvironment(environment); log(cmd.describeCommand(), Project.MSG_VERBOSE); - int retVal; + try { - retVal = execute.execute(); + return execute.execute(); } catch (IOException e) { throw new BuildException("Process fork failed.", e, getLocation()); - } finally { - if (watchdog != null && watchdog.killedProcess()) { - logTimeout(feArray, test); - } - - if (!propsFile.delete()) { - throw new BuildException("Could not delete temporary " - + "properties file."); - } } - - return retVal; } @@ -751,10 +821,10 @@ /** * @see Task#handleInput(byte[], int, int) - * + * * @since Ant 1.6 */ - protected int handleInput(byte[] buffer, int offset, int length) + protected int handleInput(byte[] buffer, int offset, int length) throws IOException { if (runner != null) { return runner.handleInput(buffer, offset, length); @@ -762,8 +832,8 @@ return super.handleInput(buffer, offset, length); } } - - + + /** * Pass output sent to System.out to the TestRunner so it can * collect ot for the formatters. @@ -837,7 +907,7 @@ } if (commandline.getBootclasspath() != null) { - log("bootclasspath is ignored if running in the same VM.", + log("bootclasspath is ignored if running in the same VM.", Project.MSG_WARN); } @@ -895,6 +965,150 @@ } /** + * Runs all the test in "UI" mode. That is, the tests are run using + * JUnit's built-in UIs, and not with Ant's JUnitTestRunner. + */ + protected void executeInUiMode() { + log("Running JUnit using " + uiMode, Project.MSG_INFO); + + String runnerClass = determineUiModeRunnerClass(); + + log("Using runner class " + runnerClass, Project.MSG_VERBOSE); + + Enumeration tests = allTests(); + // merge all <test> and <batchtest> tests into one list + Vector mergedTests = new Vector(); + boolean alreadyWarnedAboutFork = false; + + while (tests.hasMoreElements()) { + BaseTest test = (BaseTest) tests.nextElement(); + if (!test.shouldRun(getProject())) { + continue; + } + + String[] testNames = test.getTestNames(); + + for (int i = 0; i < testNames.length; i++) { + String testName = testNames[i]; + mergedTests.addElement(testName); + } + + if (!test.getFork() && !alreadyWarnedAboutFork) { + // we can't run in-VM since JUnit does a System.exit() + log("fork is ignored when running in uimode.", + Project.MSG_WARN); + alreadyWarnedAboutFork = true; + } + } + + String[] testNames = new String[mergedTests.size()]; + mergedTests.copyInto(testNames); + runJUnitUI(runnerClass, testNames); + } + + private String determineUiModeRunnerClass() { + // in JUnit 3.0 the GUI runner is junit.ui.TestRunner + // but by JUnit 3.8 (or earlier) that runner is gone, and + // replaced with junit.swingui.TestRunner and junit.awtui.TestRunner + + String awtRunner = "junit.awtui.TestRunner"; + String swingRunner = "junit.swingui.TestRunner"; + + try { + // if we can find the old one, we have to use it + Class clazz = Class.forName("junit.ui.TestRunner"); + swingRunner = clazz.getName(); + awtRunner = clazz.getName(); + } catch (ClassNotFoundException e) { + // just use the defaults + } + + String runnerClass; + if (Project.toBoolean(uiMode) || "swing".equals(uiMode)) { + runnerClass = swingRunner; + } else if ("awt".equals(uiMode)) { + runnerClass = awtRunner; + } else { + // assume it is a fully qualified class name + runnerClass = uiMode; + } + return runnerClass; + } + + /** + * Invoke a JUnit UI on a set of classes (in a forked JVM). + * This method will block until the UI is manually closed. + * + * @param runnerClass the fully qualified classname of the JUnit "runner" + * @param testNames the list of test classes to run + */ + private void runJUnitUI(String runnerClass, String[] testNames) { + CommandlineJava cmd = (CommandlineJava) commandline.clone(); + cmd.setClassname(runnerClass); + + File batchFile = null; + boolean forceAntRuntime = false; + + try { + if (testNames.length == 1) { + cmd.createArgument().setValue(testNames[0]); + } else { + try { + batchFile = createBatchFileList(testNames); + } catch (IOException e) { + throw new BuildException("could not create temp file", e); + } + + cmd.createVmArgument().setValue( + "-Dorg.apache.tools.ant.taskdefs.optional.junit.BATCHFILE=" + + batchFile.getAbsolutePath()); + cmd.createArgument().setValue("org.apache.tools.ant.taskdefs.optional.junit.BatchSuite"); + + Enumeration extraArgs = uiArguments.elements(); + while (extraArgs.hasMoreElements()) { + Commandline.Argument arg = (Commandline.Argument) extraArgs.nextElement(); + cmd.getJavaCommand().addArguments(arg.getParts()); + } + + // we need BatchSuite in the new VM's classpath, so we need to + // include the ant runtime + forceAntRuntime = true; + + if (!includeAntRuntime) { + log("includeAntRuntime=false is ignored when running in uimode", Project.MSG_WARN); + } + } + + executeAsForkedCommon(cmd, null, forceAntRuntime); + } finally { + if (batchFile != null) { + batchFile.delete(); + } + } + } + + /** + * Create a file storing the names of the given tests + * @param testNames the test to store + * @return the temporary file where the names are stored + * @throws IOException an error creating/writing the file. + */ + private File createBatchFileList(String[] testNames) throws IOException { + File file = File.createTempFile("junitlist", ".txt"); + BufferedWriter out = new BufferedWriter(new FileWriter(file)); + try { + for (int i = 0; i < testNames.length; i++) { + String testName = testNames[i]; + out.write(testName); + out.newLine(); + } + } finally { + out.close(); + } + return file; + } + + /** * @return <tt>null</tt> if there is a timeout value, otherwise the * watchdog instance. * @@ -904,7 +1118,7 @@ if (timeout == null){ return null; } - return new ExecuteWatchdog(timeout.intValue()); + return new ExecuteWatchdog(timeout.longValue()); } /** @@ -978,7 +1192,7 @@ * @since Ant 1.4 */ protected void addClasspathEntry(String resource) { - /* + /* * pre Ant 1.6 this method used to call getClass().getResource * while Ant 1.6 will call ClassLoader.getResource(). * @@ -993,7 +1207,7 @@ resource = "org/apache/tools/ant/taskdefs/optional/junit/" + resource; } - + File f = LoaderUtils.getResourceSource(getClass().getClassLoader(), resource); if (f != null) { @@ -1054,7 +1268,7 @@ if (userClasspath != null) { Path classpath = (Path) userClasspath.clone(); if (includeAntRuntime) { - log("Implicitly adding " + antRuntimeClasses + log("Implicitly adding " + antRuntimeClasses + " to CLASSPATH", Project.MSG_VERBOSE); classpath.append(antRuntimeClasses); } Index: src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java =================================================================== RCS file: /home/cvspublic/ant/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java,v retrieving revision 1.15 diff -u -r1.15 JUnitTest.java --- src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java 7 Mar 2003 11:23:06 -0000 1.15 +++ src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java 14 Jun 2003 10:53:57 -0000 @@ -58,7 +58,6 @@ import java.util.Hashtable; import java.util.Properties; import java.util.Vector; -import org.apache.tools.ant.Project; /** * <p> Run a single JUnit test. @@ -129,6 +128,14 @@ } /** + * @see BaseTest#getTestNames + */ + public String[] getTestNames() { + String[] names = {getName()}; + return names; + } + + /** * Get the name of the output file * * @return the name of the output file. @@ -173,17 +180,6 @@ Object key = enum.nextElement(); props.put(key, p.get(key)); } - } - - public boolean shouldRun(Project p) { - if (ifProperty != null && p.getProperty(ifProperty) == null) { - return false; - } else if (unlessProperty != null && - p.getProperty(unlessProperty) != null) { - return false; - } - - return true; } public FormatterElement[] getFormatters() { Index: src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java =================================================================== RCS file: /home/cvspublic/ant/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java,v retrieving revision 1.35 diff -u -r1.35 JUnitTestRunner.java --- src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java 18 Apr 2003 23:40:26 -0000 1.35 +++ src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java 14 Jun 2003 10:53:59 -0000 @@ -64,6 +64,7 @@ import java.io.StringReader; import java.io.StringWriter; import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; import java.util.Enumeration; import java.util.Hashtable; import java.util.Properties; @@ -230,41 +231,60 @@ this.showOutput = showOutput; try { - Class testClass = null; - if (loader == null) { - testClass = Class.forName(test.getName()); - } else { - testClass = loader.loadClass(test.getName()); - AntClassLoader.initializeClass(testClass); - } - - Method suiteMethod = null; - try { - // check if there is a suite method - suiteMethod = testClass.getMethod("suite", new Class[0]); - } catch (Exception e) { - // no appropriate suite method found. We don't report any - // error here since it might be perfectly normal. We don't - // know exactly what is the cause, but we're doing exactly - // the same as JUnit TestRunner do. We swallow the exceptions. - } - if (suiteMethod != null){ - // if there is a suite method available, then try - // to extract the suite from it. If there is an error - // here it will be caught below and reported. - suite = (Test) suiteMethod.invoke(null, new Class[0]); - } else { - // try to extract a test suite automatically - // this will generate warnings if the class is no suitable Test - suite = new TestSuite(testClass); - } - + suite = extractTestFromClassname(test.getName(), loader); + } catch (Exception e) { retCode = ERRORS; exception = e; } } + /** + * Creates a JUnit test from a classname. + * + * @param className the fully qualified name of the class to load + * @param loader the classloader to use to find the class, or <code>null</code> + * to use the system classloader. + * @return the test (or test suite) extracted from the class + * @throws ClassNotFoundException could not extract the test info + * @throws IllegalAccessException could not extract the test info + * @throws InvocationTargetException could not extract the test info + */ + public static Test extractTestFromClassname(String className, ClassLoader loader) + throws ClassNotFoundException, IllegalAccessException, InvocationTargetException { + // This performs the same logic as junit.runner.BaseTestRunner#getTest(String) + // but that method is not static, so we can't reuse it's logic. + + Class testClass = null; + if (loader == null) { + testClass = Class.forName(className); + } else { + testClass = loader.loadClass(className); + AntClassLoader.initializeClass(testClass); + } + + Method suiteMethod = null; + try { + // check if there is a suite method + suiteMethod = testClass.getMethod("suite", new Class[0]); + } catch (Exception e) { + // no appropriate suite method found. We don't report any + // error here since it might be perfectly normal. We don't + // know exactly what is the cause, but we're doing exactly + // the same as JUnit TestRunner do. We swallow the exceptions. + } + if (suiteMethod != null){ + // if there is a suite method available, then try + // to extract the suite from it. If there is an error + // here it will be caught below and reported. + return (Test) suiteMethod.invoke(null, new Class[0]); + } else { + // try to extract a test suite automatically + // this will generate warnings if the class is no suitable Test + return new TestSuite(testClass); + } + } + public void run() { res = new TestResult(); res.addListener(this); @@ -406,7 +426,7 @@ } /** - * @see Task#handleInput(byte[], int, int) + * @see org.apache.tools.ant.Task#handleInput(byte[], int, int) * * @since Ant 1.6 */
patch.tar.gz
Description: GNU Zip compressed data
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]