Index: src/main/org/apache/tools/ant/AntClassLoader.java =================================================================== --- src/main/org/apache/tools/ant/AntClassLoader.java (revision 380826) +++ src/main/org/apache/tools/ant/AntClassLoader.java (working copy) @@ -1,5 +1,5 @@ /* - * Copyright 2000-2005 The Apache Software Foundation + * Copyright 2000-2006 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1490,4 +1490,8 @@ } } + public String toString() { + return "AntClassLoader[" + getClasspath() + "]"; + } + } Index: src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirror.java =================================================================== --- src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirror.java (revision 0) +++ src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirror.java (revision 0) @@ -0,0 +1,108 @@ +/* + * Copyright 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.io.IOException; +import java.io.OutputStream; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.types.Permissions; + +/** + * Handles the portions of {@link JUnitTask} which need to directly access + * actual JUnit classes, so that junit.jar need not be on Ant's startup classpath. + * Neither JUnitTask.java nor JUnitTaskMirror.java nor their transitive static + * deps may import any junit.** classes! + * Specifically, need to not refer to + * - JUnitResultFormatter or its subclasses + * - JUnitVersionHelper + * - JUnitTestRunner + * Cf. {@link JUnitTask.SplitLoader#isSplit} + * + * @author refactoring tricks by Jesse Glick, real code by others + * @since 1.7 + */ +public interface JUnitTaskMirror { + + void addVmExit(JUnitTest test, JUnitResultFormatterMirror formatter, + OutputStream out, final String message); + + JUnitTestRunnerMirror newJUnitTestRunner(JUnitTest test, boolean haltOnError, + boolean filterTrace, boolean haltOnFailure, boolean showOutput, + boolean logTestListenerEvents, AntClassLoader classLoader); + + SummaryJUnitResultFormatterMirror newSummaryJUnitResultFormatter(); + + public interface JUnitResultFormatterMirror { + + void setOutput(OutputStream outputStream); + + } + + public interface SummaryJUnitResultFormatterMirror extends JUnitResultFormatterMirror { + + void setWithOutAndErr(boolean value); + + } + + public interface JUnitTestRunnerMirror { + + /** + * Used in formatter arguments as a placeholder for the basename + * of the output file (which gets replaced by a test specific + * output file name later). + * + * @since Ant 1.6.3 + */ + String IGNORED_FILE_NAME = "IGNORETHIS"; + + /** + * No problems with this test. + */ + int SUCCESS = 0; + + /** + * Some tests failed. + */ + int FAILURES = 1; + + /** + * An error occurred. + */ + int ERRORS = 2; + + void setPermissions(Permissions perm); + + void run(); + + void addFormatter(JUnitResultFormatterMirror formatter); + + int getRetCode(); + + void handleErrorFlush(String output); + + void handleErrorOutput(String output); + + void handleOutput(String output); + + int handleInput(byte[] buffer, int offset, int length) throws IOException; + + void handleFlush(String output); + + } + +} Property changes on: src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirror.java ___________________________________________________________________ Name: svn:eol-style + native Index: src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java =================================================================== --- src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java (revision 380826) +++ src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java (working copy) @@ -24,6 +24,7 @@ import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; +import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; @@ -34,9 +35,6 @@ import java.util.Map; import java.util.Properties; import java.util.Vector; -import junit.framework.AssertionFailedError; -import junit.framework.Test; -import junit.framework.TestResult; import org.apache.tools.ant.AntClassLoader; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; @@ -134,7 +132,7 @@ private boolean summary = false; private boolean reloading = true; private String summaryValue = ""; - private JUnitTestRunner runner = null; + private JUnitTaskMirror.JUnitTestRunnerMirror runner = null; private boolean newEnvironment = false; private Environment env = new Environment(); @@ -148,6 +146,9 @@ private Permissions perm = null; private ForkMode forkMode = new ForkMode("perTest"); + private boolean splitJunit = false; + private JUnitTaskMirror delegate; + private static final int STRING_BUFFER_SIZE = 128; /** * @since Ant 1.7 @@ -632,12 +633,81 @@ */ public void init() { antRuntimeClasses = new Path(getProject()); - addClasspathEntry("/junit/framework/TestCase.class"); + splitJunit = !addClasspathEntry("/junit/framework/TestCase.class"); addClasspathEntry("/org/apache/tools/ant/launch/AntMain.class"); addClasspathEntry("/org/apache/tools/ant/Task.class"); addClasspathEntry("/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.class"); } + private static JUnitTaskMirror createMirror(JUnitTask task, ClassLoader loader) { + try { + loader.loadClass("junit.framework.Test"); // sanity check + } catch (ClassNotFoundException e) { + throw new BuildException( + "The for must include junit.jar if not in Ant's own classpath", + e, task.getLocation()); + } + try { + Class c = loader.loadClass(JUnitTaskMirror.class.getName() + "Impl"); + if (c.getClassLoader() != loader) { + throw new BuildException("Overdelegating loader", task.getLocation()); + } + Constructor cons = c.getConstructor(new Class[] {JUnitTask.class}); + return (JUnitTaskMirror) cons.newInstance(new Object[] {task}); + } catch (Exception e) { + throw new BuildException(e, task.getLocation()); + } + } + + private final class SplitLoader extends AntClassLoader { + + public SplitLoader(ClassLoader parent, Path path) { + super(parent, getProject(), path, true); + } + + // forceLoadClass is not convenient here since it would not + // properly deal with inner classes of these classes. + protected synchronized Class loadClass(String classname, boolean resolve) + throws ClassNotFoundException { + Class theClass = findLoadedClass(classname); + if (theClass != null) { + return theClass; + } + if (isSplit(classname)) { + theClass = findClass(classname); + if (resolve) { + resolveClass(theClass); + } + return theClass; + } else { + return super.loadClass(classname, resolve); + } + } + + private final String[] SPLIT_CLASSES = { + "BriefJUnitResultFormatter", + "JUnitResultFormatter", + "JUnitTaskMirrorImpl", + "JUnitTestRunner", + "JUnitVersionHelper", + "OutErrSummaryJUnitResultFormatter", + "PlainJUnitResultFormatter", + "SummaryJUnitResultFormatter", + "XMLJUnitResultFormatter", + }; + + private boolean isSplit(String classname) { + String simplename = classname.substring(classname.lastIndexOf('.') + 1); + for (int i = 0; i < SPLIT_CLASSES.length; i++) { + if (simplename.equals(SPLIT_CLASSES[i]) || simplename.startsWith(SPLIT_CLASSES[i] + '$')) { + return true; + } + } + return false; + } + + } + /** * Runs the testcase. * @@ -645,6 +715,18 @@ * @since Ant 1.2 */ public void execute() throws BuildException { + ClassLoader myLoader = JUnitTask.class.getClassLoader(); + ClassLoader mirrorLoader; + if (splitJunit) { + Path path = new Path(getProject()); + path.add(antRuntimeClasses); + path.add(getCommandline().getClasspath()); + mirrorLoader = new SplitLoader(myLoader, path); + } else { + mirrorLoader = myLoader; + } + delegate = createMirror(this, mirrorLoader); + List testLists = new ArrayList(); boolean forkPerTest = forkMode.getValue().equals(ForkMode.PER_TEST); @@ -672,6 +754,10 @@ } } finally { deleteClassLoader(); + if (mirrorLoader instanceof SplitLoader) { + ((SplitLoader) mirrorLoader).cleanup(); + } + delegate = null; } } @@ -1061,18 +1147,22 @@ try { log("Using System properties " + System.getProperties(), Project.MSG_VERBOSE); - createClassLoader(); + if (splitJunit) { + classLoader = (AntClassLoader) delegate.getClass().getClassLoader(); + } else { + createClassLoader(); + } if (classLoader != null) { classLoader.setThreadContextLoader(); } - runner = new JUnitTestRunner(test, test.getHaltonerror(), + runner = delegate.newJUnitTestRunner(test, test.getHaltonerror(), test.getFiltertrace(), test.getHaltonfailure(), false, true, classLoader); if (summary) { - SummaryJUnitResultFormatter f = - new SummaryJUnitResultFormatter(); + JUnitTaskMirror.SummaryJUnitResultFormatterMirror f = + delegate.newSummaryJUnitResultFormatter(); f.setWithOutAndErr("withoutanderr" .equalsIgnoreCase(summaryValue)); f.setOutput(getDefaultOutput()); @@ -1186,7 +1276,7 @@ if (fe.getUseFile()) { String base = test.getOutfile(); if (base == null) { - base = JUnitTestRunner.IGNORED_FILE_NAME; + base = JUnitTaskMirror.JUnitTestRunnerMirror.IGNORED_FILE_NAME; } String filename = base + fe.getExtension(); File destFile = new File(test.getTodir(), filename); @@ -1204,9 +1294,10 @@ * getResource doesn't contain the name of the archive.

* * @param resource resource that one wants to lookup + * @return true if something was in fact added * @since Ant 1.4 */ - protected void addClasspathEntry(String resource) { + protected boolean addClasspathEntry(String resource) { /* * pre Ant 1.6 this method used to call getClass().getResource * while Ant 1.6 will call ClassLoader.getResource(). @@ -1228,8 +1319,10 @@ if (f != null) { log("Found " + f.getAbsolutePath(), Project.MSG_DEBUG); antRuntimeClasses.createPath().setLocation(f); + return true; } else { log("Couldn\'t find " + resource, Project.MSG_DEBUG); + return false; } } @@ -1269,7 +1362,7 @@ for (int i = 0; i < feArray.length; i++) { FormatterElement fe = feArray[i]; File outFile = getOutput(fe, test); - JUnitResultFormatter formatter = fe.createFormatter(classLoader); + JUnitTaskMirror.JUnitResultFormatterMirror formatter = fe.createFormatter(classLoader); if (outFile != null && formatter != null) { try { OutputStream out = new FileOutputStream(outFile); @@ -1280,7 +1373,7 @@ } } if (summary) { - SummaryJUnitResultFormatter f = new SummaryJUnitResultFormatter(); + JUnitTaskMirror.SummaryJUnitResultFormatterMirror f = delegate.newSummaryJUnitResultFormatter(); f.setWithOutAndErr("withoutanderr".equalsIgnoreCase(summaryValue)); addVmExit(test, f, getDefaultOutput(), message); } @@ -1291,23 +1384,9 @@ * Only used from the logVmExit method. * @since Ant 1.7 */ - private void addVmExit(JUnitTest test, JUnitResultFormatter formatter, + private void addVmExit(JUnitTest test, JUnitTaskMirror.JUnitResultFormatterMirror formatter, OutputStream out, final String message) { - formatter.setOutput(out); - formatter.startTestSuite(test); - - //the trick to integrating test output to the formatter, is to - //create a special test class that asserts an error - //and tell the formatter that it raised. - Test t = new Test() { - public int countTestCases() { return 1; } - public void run(TestResult r) { - throw new AssertionFailedError(message); - } - }; - formatter.startTest(t); - formatter.addError(t, new AssertionFailedError(message)); - formatter.endTestSuite(test); + delegate.addVmExit(test, formatter, out, message); } /** @@ -1535,9 +1614,9 @@ // everything otherwise just log a statement boolean fatal = result.timedOut || result.crashed; boolean errorOccurredHere = - result.exitCode == JUnitTestRunner.ERRORS || fatal; + result.exitCode == JUnitTaskMirror.JUnitTestRunnerMirror.ERRORS || fatal; boolean failureOccurredHere = - result.exitCode != JUnitTestRunner.SUCCESS || fatal; + result.exitCode != JUnitTaskMirror.JUnitTestRunnerMirror.SUCCESS || fatal; if (errorOccurredHere || failureOccurredHere) { if ((errorOccurredHere && test.getHaltonerror()) || (failureOccurredHere && test.getHaltonfailure())) { @@ -1559,7 +1638,7 @@ } protected class TestResultHolder { - public int exitCode = JUnitTestRunner.ERRORS; + public int exitCode = JUnitTaskMirror.JUnitTestRunnerMirror.ERRORS; public boolean timedOut = false; public boolean crashed = false; } Index: src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java =================================================================== --- src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java (revision 380826) +++ src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java (working copy) @@ -1,5 +1,5 @@ /* - * Copyright 2001-2002,2004 The Apache Software Foundation + * Copyright 2001-2002,2004,2006 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ * testrun. * */ -public interface JUnitResultFormatter extends TestListener { +public interface JUnitResultFormatter extends TestListener, JUnitTaskMirror.JUnitResultFormatterMirror { /** * The whole testsuite started. */ Index: src/main/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java =================================================================== --- src/main/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java (revision 380826) +++ src/main/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java (working copy) @@ -1,5 +1,5 @@ /* - * Copyright 2001-2004 The Apache Software Foundation + * Copyright 2001-2004,2006 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -188,14 +188,14 @@ /** * @since Ant 1.2 */ - JUnitResultFormatter createFormatter() throws BuildException { + JUnitTaskMirror.JUnitResultFormatterMirror createFormatter() throws BuildException { return createFormatter(null); } /** * @since Ant 1.6 */ - JUnitResultFormatter createFormatter(ClassLoader loader) + JUnitTaskMirror.JUnitResultFormatterMirror createFormatter(ClassLoader loader) throws BuildException { if (classname == null) { @@ -210,7 +210,9 @@ f = Class.forName(classname, true, loader); } } catch (ClassNotFoundException e) { - throw new BuildException(e); + throw new BuildException("Using loader " + loader + " on class " + classname + ": " + e, e); + } catch (NoClassDefFoundError e) { + throw new BuildException("Using loader " + loader + " on class " + classname + ": " + e, e); } Object o = null; @@ -222,13 +224,12 @@ throw new BuildException(e); } - if (!(o instanceof JUnitResultFormatter)) { + if (!(o instanceof JUnitTaskMirror.JUnitResultFormatterMirror)) { throw new BuildException(classname + " is not a JUnitResultFormatter"); } + JUnitTaskMirror.JUnitResultFormatterMirror r = (JUnitTaskMirror.JUnitResultFormatterMirror) o; - JUnitResultFormatter r = (JUnitResultFormatter) o; - if (useFile && outFile != null) { try { out = new FileOutputStream(outFile); Index: src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java =================================================================== --- src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java (revision 380826) +++ src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java (working copy) @@ -1,5 +1,5 @@ /* - * Copyright 2000-2005 The Apache Software Foundation + * Copyright 2000-2006 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,33 +62,9 @@ * @since Ant 1.2 */ -public class JUnitTestRunner implements TestListener { +public class JUnitTestRunner implements TestListener, JUnitTaskMirror.JUnitTestRunnerMirror { /** - * No problems with this test. - */ - public static final int SUCCESS = 0; - - /** - * Some tests failed. - */ - public static final int FAILURES = 1; - - /** - * An error occurred. - */ - public static final int ERRORS = 2; - - /** - * Used in formatter arguments as a placeholder for the basename - * of the output file (which gets replaced by a test specific - * output file name later). - * - * @since Ant 1.6.3 - */ - public static final String IGNORED_FILE_NAME = "IGNORETHIS"; - - /** * Holds the registered formatters. */ private Vector formatters = new Vector(); @@ -441,7 +417,7 @@ perm = permissions; } - protected void handleOutput(String output) { + public void handleOutput(String output) { if (!logTestListenerEvents && output.startsWith(JUnitTask.TESTLISTENER_PREFIX)) ; // ignore else if (systemOut != null) { @@ -454,24 +430,24 @@ * * @since Ant 1.6 */ - protected int handleInput(byte[] buffer, int offset, int length) + public int handleInput(byte[] buffer, int offset, int length) throws IOException { return -1; } - protected void handleErrorOutput(String output) { + public void handleErrorOutput(String output) { if (systemError != null) { systemError.print(output); } } - protected void handleFlush(String output) { + public void handleFlush(String output) { if (systemOut != null) { systemOut.print(output); } } - protected void handleErrorFlush(String output) { + public void handleErrorFlush(String output) { if (systemError != null) { systemError.print(output); } @@ -505,6 +481,10 @@ formatters.addElement(f); } + public void addFormatter(JUnitTaskMirror.JUnitResultFormatterMirror f) { + formatters.addElement((JUnitResultFormatter) f); + } + /** * Entry point for standalone (forked) mode. * @@ -645,7 +625,7 @@ test.getOutfile() + fe.getExtension()); fe.setOutfile(destFile); } - runner.addFormatter(fe.createFormatter()); + runner.addFormatter((JUnitResultFormatter) fe.createFormatter()); } } Index: src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirrorImpl.java =================================================================== --- src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirrorImpl.java (revision 0) +++ src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirrorImpl.java (revision 0) @@ -0,0 +1,69 @@ +/* + * Copyright 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs.optional.junit; + +import java.io.OutputStream; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestResult; +import org.apache.tools.ant.AntClassLoader; + +/** + * Implementation of the part of the junit task which can directly refer to junit.* classes. + * @see JUnitTaskMirror + */ +public final class JUnitTaskMirrorImpl implements JUnitTaskMirror { + + private final JUnitTask task; + + public JUnitTaskMirrorImpl(JUnitTask task) { + this.task = task; + } + + public void addVmExit(JUnitTest test, JUnitResultFormatterMirror _formatter, + OutputStream out, final String message) { + JUnitResultFormatter formatter = (JUnitResultFormatter) _formatter; + formatter.setOutput(out); + formatter.startTestSuite(test); + + //the trick to integrating test output to the formatter, is to + //create a special test class that asserts an error + //and tell the formatter that it raised. + Test t = new Test() { + public int countTestCases() { return 1; } + public void run(TestResult r) { + throw new AssertionFailedError(message); + } + }; + formatter.startTest(t); + formatter.addError(t, new AssertionFailedError(message)); + formatter.endTestSuite(test); + } + + public JUnitTaskMirror.JUnitTestRunnerMirror newJUnitTestRunner(JUnitTest test, + boolean haltOnError, boolean filterTrace, boolean haltOnFailure, + boolean showOutput, boolean logTestListenerEvents, AntClassLoader classLoader) { + return new JUnitTestRunner(test, haltOnError, filterTrace, haltOnFailure, + showOutput, logTestListenerEvents, classLoader); + } + + public JUnitTaskMirror.SummaryJUnitResultFormatterMirror newSummaryJUnitResultFormatter() { + return new SummaryJUnitResultFormatter(); + } + +} Property changes on: src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirrorImpl.java ___________________________________________________________________ Name: svn:eol-style + native Index: src/main/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java =================================================================== --- src/main/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java (revision 380826) +++ src/main/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java (working copy) @@ -29,7 +29,7 @@ * */ -public class SummaryJUnitResultFormatter implements JUnitResultFormatter { +public class SummaryJUnitResultFormatter implements JUnitResultFormatter, JUnitTaskMirror.SummaryJUnitResultFormatterMirror { /** * Formatter for timings.