ant-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jgl...@apache.org
Subject svn commit: r906782 - in /ant/core/branches/run-single-test-method: docs/manual/OptionalTasks/ lib/optional/ src/main/org/apache/tools/ant/taskdefs/optional/junit/ src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/
Date Fri, 05 Feb 2010 02:51:41 GMT
Author: jglick
Date: Fri Feb  5 02:51:40 2010
New Revision: 906782

URL: http://svn.apache.org/viewvc?rev=906782&view=rev
Log:
Attempting to forward-port patch made against 1.7.1.

Added:
    ant/core/branches/run-single-test-method/lib/optional/junit-4.8.1.jar   (with props)
    ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnit4TestMethodAdapter.java   (with props)
    ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultParser.java   (with props)
    ant/core/branches/run-single-test-method/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/BatchTestTest.java   (with props)
Removed:
    ant/core/branches/run-single-test-method/lib/optional/junit-3.8.2.jar
Modified:
    ant/core/branches/run-single-test-method/docs/manual/OptionalTasks/junit.html
    ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/   (props changed)
    ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java
    ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/Constants.java
    ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java
    ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirror.java
    ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirrorImpl.java
    ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java
    ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java
    ant/core/branches/run-single-test-method/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunnerTest.java

Modified: ant/core/branches/run-single-test-method/docs/manual/OptionalTasks/junit.html
URL: http://svn.apache.org/viewvc/ant/core/branches/run-single-test-method/docs/manual/OptionalTasks/junit.html?rev=906782&r1=906781&r2=906782&view=diff
==============================================================================
--- ant/core/branches/run-single-test-method/docs/manual/OptionalTasks/junit.html (original)
+++ ant/core/branches/run-single-test-method/docs/manual/OptionalTasks/junit.html Fri Feb  5 02:51:40 2010
@@ -351,7 +351,7 @@
 
 <p><em>since Ant 1.6.</em></p>
 
-<h4>formatter</h4>
+<h4><a name="formatter">formatter</a></h4>
 
 <p>The results of the tests can be printed in different
 formats. Output will always be sent to a file, unless you set the
@@ -440,6 +440,26 @@
     <td align="center">Yes</td>
   </tr>
   <tr>
+    <td valign="top">methods</td>
+    <td valign="top">Comma-separated list of names of test case methods to execute.
+      <em>Since Ant 1.7.1</em>
+      <p>The <code>methods</code> attribute can be useful in the following scenarios:</p>
+      <ul>
+        <li>A test method has failed and you want to re-run the test method
+            to test a fix or re-run the test under the Java debugger without
+            having to wait for the other (possibly long running) test methods
+            to complete.</li>
+        <li>One or more test methods are running slower than expected and you
+            want to re-run them under a Java profiler (without the overhead
+            of running the profiler whilst other test methods are being
+            executed).</li>
+      </ul>
+      <p>If the <code>methods</code> attribute is used but no test method
+      is specified, then no test method from the suite will be executed.</p>
+    </td>
+    <td align="center">No; default is to run all test methods in the suite.</td>
+  </tr>  
+  <tr>
     <td valign="top">fork</td>
     <td valign="top">Run the tests in a separate VM.
       Overrides value set in <code>&lt;junit&gt;</code>.</td>
@@ -570,6 +590,17 @@
     <td align="center" valign="top">No; default is the current directory.</td>
   </tr>
   <tr>
+    <td valign="top">rerunfailed</td>
+    <td valign="top">Run only those test methods that failed during the previous
+      execution. <em>Since Ant 1.7.1</em>
+      <p>Results of the previous execution are taken from XML report files
+      generated by the XML <a href="#formatter">formatter</a>.<br />
+      The report files are searched for in the target directory for reports,
+      i.e. in the directory specified by the <code>todir</code> attribute.</p>
+      </td>
+    <td align="center" valign="top">No; default is <code>off</code>.</td>
+  </tr>
+  <tr>
     <td valign="top">if</td>
     <td valign="top">Only run tests <a href="../properties.html#if+unless">if the named property is set</a>.</td>
     <td align="center" valign="top">No</td>

Added: ant/core/branches/run-single-test-method/lib/optional/junit-4.8.1.jar
URL: http://svn.apache.org/viewvc/ant/core/branches/run-single-test-method/lib/optional/junit-4.8.1.jar?rev=906782&view=auto
==============================================================================
Binary file - no diff available.

Propchange: ant/core/branches/run-single-test-method/lib/optional/junit-4.8.1.jar
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Propchange: ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Fri Feb  5 02:51:40 2010
@@ -0,0 +1,4 @@
+JUnitTask.java.orig
+BatchTest.java.orig
+JUnitTestRunner.java.orig
+JUnitTest.java.orig

Modified: ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java
URL: http://svn.apache.org/viewvc/ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java?rev=906782&r1=906781&r2=906782&view=diff
==============================================================================
--- ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java (original)
+++ ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java Fri Feb  5 02:51:40 2010
@@ -19,15 +19,23 @@
 package org.apache.tools.ant.taskdefs.optional.junit;
 
 
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Vector;
 import org.apache.tools.ant.Project;
 import org.apache.tools.ant.types.FileSet;
 import org.apache.tools.ant.types.Resource;
 import org.apache.tools.ant.types.ResourceCollection;
 import org.apache.tools.ant.types.resources.Resources;
+import org.xml.sax.SAXException;
 
 /**
  * <p> Create then run <code>JUnitTest</code>'s based on the list of files
@@ -48,6 +56,9 @@
     /** the list of filesets containing the testcase filename rules */
     private Resources resources = new Resources();
 
+    /** only run tests that failed during the previous run */
+    private boolean failedOnly = false;
+
     /**
      * create a new batchtest instance
      * @param project     the project it depends on.
@@ -58,6 +69,18 @@
     }
 
     /**
+     * Sets a flag that only tests that failed during the previous session
+     * should be executed.
+     * The results of the previous test execution are taken from XML report
+     * files so an XML report file with output to file should be used
+     * when this option is used.
+     * @since Ant 1.7.1
+     */
+    public void setRerunfailed(boolean rerunFailed) {
+        this.failedOnly = rerunFailed;
+    }
+
+    /**
      * Add a new fileset instance to this batchtest. Whatever the fileset is,
      * only filename that are <tt>.java</tt> or <tt>.class</tt> will be
      * considered as 'candidates'.
@@ -124,15 +147,203 @@
      */
     private JUnitTest[] createAllJUnitTest() {
         String[] filenames = getFilenames();
-        JUnitTest[] tests = new JUnitTest[filenames.length];
-        for (int i = 0; i < tests.length; i++) {
-            String classname = javaToClass(filenames[i]);
-            tests[i] = createJUnitTest(classname);
+        JUnitTest[] tests;
+        if (!failedOnly) {
+            tests = new JUnitTest[filenames.length];
+            for (int i = 0; i < tests.length; i++) {
+                String classname = javaToClass(filenames[i]);
+                tests[i] = createJUnitTest(classname);
+            }
+        } else {
+            List testsList = new ArrayList(10);
+            for (int i = 0; i < filenames.length; i++) {
+                String classname = javaToClass(filenames[i]);
+                File resultsFile = getXMLResultsFile(classname);
+                if (resultsFile == null) {
+                    continue;
+                }
+                List testListLines = parseResultsFile(resultsFile);
+                if ((testListLines != null) && !testListLines.isEmpty()) {
+                    final int count = testListLines.size();
+                    for (int j = 0; j < count; j++) {
+                        String line = (String) testListLines.get(j);
+                        JUnitTest test;
+                        try {
+                            test = parseTestListLine(line); //may return null
+                        } catch (IllegalArgumentException ex) {
+                            project.log(
+                                    "File " + resultsFile
+                                    + " contained invalid description of tests: "
+                                    + ex.getMessage(),
+                                    Project.MSG_WARN);
+                            break;
+                        }
+                        if (test != null) {
+                            testsList.add(test);
+                        }
+                    }
+                }
+            }
+            if (!testsList.isEmpty()) {
+                tests = (JUnitTest[])
+                        testsList.toArray(new JUnitTest[testsList.size()]);
+            } else {
+                tests = new JUnitTest[0];
+            }
         }
         return tests;
     }
 
     /**
+     * Finds an XML report file for the given class name.
+     * @param testClassName name of a test class for which a result file should
+     *                      be found
+     * @return XML report file for the given class name,
+     *         or <code>null</code> if the report file does not exist
+     *         or if it is not a plain file
+     */
+    private File getXMLResultsFile(String testClassName) {
+        File file = new File(getTodir(), "TEST-" + testClassName + ".xml");
+        if (!file.isFile()) {
+            project.log("XML results file " + file + " does not exist",
+                        Project.MSG_VERBOSE);
+            return null;
+        }
+        return file;
+    }
+
+    /**
+     * Parses the given XML report file.
+     * @param file XML report file to be parsed
+     * @return list of test specification lines,
+     *         or <code>null</code> if the file does not exist
+     *         or if some error occured while reading it or during parsing
+     */
+    private List parseResultsFile(File file) {
+        if (!file.canRead()) {
+            project.log("Cannot read XML results file " + file,
+                        Project.MSG_WARN);
+            return null;
+        }
+        project.log("Parsing file " + file, Project.MSG_VERBOSE);
+
+        Reader reader = null;
+        try {
+            reader = new BufferedReader(
+                new InputStreamReader(new FileInputStream(file), "UTF8"));
+            return XMLResultParser.parseResultsFile(reader);
+        } catch (IOException ex1) {
+            project.log("Error while reading XML results file " + file,
+                        ex1,
+                        Project.MSG_ERR);
+            return null;
+        } catch (SAXException ex2) {
+            project.log("Error while parsing XML results file " + file,
+                        ex2,
+                        Project.MSG_ERR);
+            return null;
+        } finally {
+            if (reader != null) {
+                try {
+                    reader.close();
+                } catch (IOException ex) {
+                    //give up
+                }
+            }
+        }
+    }
+
+    /**
+     * Parses one line of a test list file and creates <code>JUnitTest</code>
+     * instances for the tests corresponding to the specification on the line.
+     * @param line string describing a test
+     * @return <code>JUnitTest</code> that would execute test methods that are
+     *         specified by the given line;
+     *         or <code>null</code> if an empty test would be created
+     * @exception java.lang.IllegalArgumentException
+     *            if the line is malformed
+     */
+    private JUnitTest parseTestListLine(String line)
+                                            throws IllegalArgumentException {
+        String className;
+        String methodsList;
+        int colonIndex = line.indexOf(':');
+        if (colonIndex == -1) {
+            // no specification of test methods
+            className = line.trim();
+            methodsList = null;
+        } else {
+            className = line.substring(0, colonIndex).trim();
+            methodsList = line.substring(colonIndex + 1);
+        }
+
+        checkClassNameIsValid(className);
+        if ("junit.framework.JUnit4TestCaseFacade".equals(className)) {
+            // This is an artificial test case failure.
+            // It is used e.g. to report that a test class did not have any
+            // test methods.
+            return null;
+        }
+
+        if (methodsList == null) {
+            return createJUnitTest(className);
+        } else {
+            String[] methods = JUnitTest.parseTestMethodNamesList(methodsList);
+            return (methods.length != 0) ? createJUnitTest(className, methods)
+                                         : null;
+        }
+    }
+
+    /**
+     * Checks whether the passed string is a valid class name.
+     * @param className class name to be checked
+     * @throws java.lang.IllegalArgumentException
+     *         if the name is not a valid class name
+     */
+    private void checkClassNameIsValid(String className) throws IllegalArgumentException {
+        final String errMsg = "Invalid class name";
+        int dotIndex = className.indexOf('.');
+        if (dotIndex == -1) {
+            checkJavaIdentifier(className, errMsg);
+        } else {
+            int previousDotIndex = -1;
+            do {
+                checkJavaIdentifier(className.substring(previousDotIndex + 1, dotIndex),
+                                    errMsg);
+                previousDotIndex = dotIndex;
+                dotIndex = className.indexOf('.', previousDotIndex + 1);
+            } while (dotIndex != -1);
+            checkJavaIdentifier(className.substring(previousDotIndex + 1),
+                                errMsg);
+        }
+    }
+
+    /**
+     * Checks whether the passed string is a valid Java identifier.
+     * @param name name to be checked
+     * @param errMsg message to used in the thrown exception if the name
+     *               is found invalid
+     * @throws java.lang.IllegalArgumentException
+     *         if the name is not a valid Java identifier
+     */
+    private static void checkJavaIdentifier(String name,
+                                            String errMsg)
+                                    throws IllegalArgumentException {
+        int length = name.length();
+        if ((length == 0) || !Character.isJavaIdentifierStart(name.charAt(0))) {
+            throw new IllegalArgumentException(errMsg + ": " + name);
+        }
+        if (length > 1) {
+            final char[] chars = name.substring(1).toCharArray();
+            for (int i = 0; i < chars.length; i++) {
+                if (!Character.isJavaIdentifierPart(chars[i])) {
+                    throw new IllegalArgumentException(errMsg + ": " + name);
+                }
+            }
+        }
+    }
+
+    /**
      * 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
@@ -182,8 +393,23 @@
      * @return the <tt>JUnitTest</tt> over the given classname.
      */
     private JUnitTest createJUnitTest(String classname) {
+       return createJUnitTest(classname, null);
+   }
+
+   /**
+    * Create a <tt>JUnitTest</tt> that has the same property as this
+    * <tt>BatchTest</tt> instance.
+    * @param classname the name of the class that should be run as a
+    * <tt>JUnitTest</tt>. It must be a fully qualified name.
+    * @param methods array of names of test methods within the class
+    *                to be executed, or <code>null</code> if all test methods
+    *                in the test suite given by the class should be executed
+    * @return the <tt>JUnitTest</tt> over the given classname and test methods.
+    */
+   private JUnitTest createJUnitTest(String classname, String[] methods) {
         JUnitTest test = new JUnitTest();
         test.setName(classname);
+        test.setMethods(methods);
         test.setHaltonerror(this.haltOnError);
         test.setHaltonfailure(this.haltOnFail);
         test.setFiltertrace(this.filtertrace);

Modified: ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/Constants.java
URL: http://svn.apache.org/viewvc/ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/Constants.java?rev=906782&r1=906781&r2=906782&view=diff
==============================================================================
--- ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/Constants.java (original)
+++ ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/Constants.java Fri Feb  5 02:51:40 2010
@@ -23,6 +23,7 @@
  */
 public class Constants {
 
+    static final String METHOD_NAMES = "methods=";
     static final String HALT_ON_ERROR = "haltOnError=";
     static final String HALT_ON_FAILURE = "haltOnFailure=";
     static final String FILTERTRACE = "filtertrace=";

Added: ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnit4TestMethodAdapter.java
URL: http://svn.apache.org/viewvc/ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnit4TestMethodAdapter.java?rev=906782&view=auto
==============================================================================
--- ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnit4TestMethodAdapter.java (added)
+++ ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnit4TestMethodAdapter.java Fri Feb  5 02:51:40 2010
@@ -0,0 +1,250 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Sun Microsystems, Inc. Portions Copyright 2008 Sun
+ * Microsystems, Inc. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+
+package org.apache.tools.ant.taskdefs.optional.junit;
+
+import java.util.Iterator;
+import java.util.List;
+import junit.framework.JUnit4TestAdapterCache;
+import junit.framework.Test;
+import junit.framework.TestResult;
+import org.junit.runner.Description;
+import org.junit.runner.Request;
+import org.junit.runner.Runner;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+import org.junit.runner.notification.RunNotifier;
+
+/**
+ * Adapter between JUnit 3.8.x API and JUnit 4.x API for execution of tests
+ * and listening of events (test start, test finish, test failure).
+ * The constructor is passed a JUnit 4 test class and a list of name of methods
+ * in it that should be executed. Method {@link #run run(TestResult)} executes
+ * the given JUnit-4-style test methods and notifies the given {@code TestResult}
+ * object using its old (JUnit 3.8.x style) API.
+ *
+ * @author  Marian Petras
+ */
+public class JUnit4TestMethodAdapter implements Test {
+
+    private final Class testClass;
+    private final String[] methodNames;
+    private final Runner runner;
+    private final Cache cache;
+
+    /**
+     * Creates a new adapter for the given class and a method within the class.
+     * 
+     * @param testClass test class containing the method to be executed
+     * @param methodNames names of the test methods that are to be executed
+     * @exception  java.lang.IllegalArgumentException
+     *             if any of the arguments is {@code null}
+     *             or if any of the given method names is {@code null} or empty
+     */
+    public JUnit4TestMethodAdapter(final Class testClass,
+                                   final String[] methodNames) {
+        if (testClass == null) {
+            throw new IllegalArgumentException("testClass is <null>");
+        }
+        if (methodNames == null) {
+            throw new IllegalArgumentException("methodNames is <null>");
+        }
+        for (int i = 0; i < methodNames.length; i++) {
+            if (methodNames[i] == null) {
+                throw new IllegalArgumentException("method name #" + i + " is <null>");
+            }
+            if (methodNames[i].length() == 0) {
+                throw new IllegalArgumentException("method name #" + i + " is empty");
+            }
+        }
+        this.testClass = testClass;
+        this.methodNames = methodNames;
+        this.cache = Cache.instance;
+
+        // Warning: If 'testClass' is an old-style (pre-JUnit-4) class,
+        // then all its test methods will be executed by the returned runner!
+        Request request;
+        if (methodNames.length == 1) {
+            request = Request.method(testClass, methodNames[0]);
+        } else {
+            request = Request.aClass(testClass).filterWith(
+                            new MultipleMethodsFilter(testClass, methodNames));
+        }
+        runner = request.getRunner();
+    }
+
+    public int countTestCases() {
+        return runner.testCount();
+    }
+
+    public Description getDescription() {
+        return runner.getDescription();
+    }
+
+    public List/*<Test>*/ getTests() {
+        return cache.asTestList(getDescription());
+    }
+
+    public Class getTestClass() {
+        return testClass;
+    }
+    
+    public void run(final TestResult result) {
+        runner.run(cache.getNotifier(result));
+    }
+
+    public String toString() {
+        String testClassName = testClass.getName();
+        StringBuilder buf = new StringBuilder(testClassName.length()
+                                              + 12 * methodNames.length)
+                            .append(':');
+        if (methodNames.length != 0) {
+            buf.append(methodNames[0]);
+            for (int i = 1; i < methodNames.length; i++) {
+                buf.append(',')
+                   .append(methodNames[i]);
+            }
+        }
+        return buf.toString();
+    }
+
+    private static final class MultipleMethodsFilter extends Filter {
+
+        private final Description methodsListDescription;
+        private final Class testClass;
+        private final String[] methodNames;
+
+        private MultipleMethodsFilter(Class testClass, String[] methodNames) {
+            if (testClass == null) {
+                throw new IllegalArgumentException("testClass is <null>");
+            }
+            if (methodNames == null) {
+                throw new IllegalArgumentException("methodNames is <null>");
+            }
+            methodsListDescription = Description.createSuiteDescription(testClass);
+            for (int i = 0; i < methodNames.length; i++) {
+                methodsListDescription.addChild(
+                        Description.createTestDescription(testClass, methodNames[i]));
+            }
+            this.testClass = testClass;
+            this.methodNames = methodNames;
+        }
+
+        public boolean shouldRun(Description description) {
+            if (methodNames.length == 0) {
+                return false;
+            }
+            if (description.isTest()) {
+                Iterator/*<Description>*/ it = methodsListDescription.getChildren().iterator();
+                while (it.hasNext()) {
+                    Description methodDescription = (Description) it.next();
+                    if (methodDescription.equals(description)) {
+                        return true;
+                    }
+                }
+            } else {
+                Iterator/*<Description>*/ it = description.getChildren().iterator();
+                while (it.hasNext()) {
+                    Description each = (Description) it.next();
+                    if (shouldRun(each)) {
+                        return true;
+                    }
+                }
+            }
+            return false;					
+        }
+
+        public String describe() {
+            StringBuilder buf = new StringBuilder(40);
+            if (methodNames.length == 0) {
+                buf.append("No methods");
+            } else {
+                buf.append(methodNames.length == 1 ? "Method" : "Methods");
+                buf.append(' ');
+                buf.append(methodNames[0]);
+                for (int i = 1; i < methodNames.length; i++) {
+                    buf.append(',').append(methodNames[i]);
+                }
+            }
+            buf.append('(').append(testClass.getName()).append(')');
+            return buf.toString();
+        }
+
+    }
+
+    /**
+     * Effectively a copy of {@code JUnit4TestAdapterCache}, except that its
+     * method {@code getNotifier()} does not require an argument
+     * of type {@code JUnit4TestAdapter}.
+     */
+    private static final class Cache extends JUnit4TestAdapterCache {
+
+	private static final Cache instance = new Cache();
+
+	public static JUnit4TestAdapterCache getDefault() {
+            return instance;
+	}
+	
+	public RunNotifier getNotifier(final TestResult result) {
+            RunNotifier notifier = new RunNotifier();
+            notifier.addListener(new RunListener() {
+                    public void testFailure(Failure failure) throws Exception {
+                        result.addError(asTest(failure.getDescription()),
+                                        failure.getException());
+                    }
+
+                    public void testFinished(Description description)
+                                    throws Exception {
+                        result.endTest(asTest(description));
+                    }
+
+                    public void testStarted(Description description)
+                                    throws Exception {
+                        result.startTest(asTest(description));
+                    }
+            });
+            return notifier;
+	}
+
+    }
+
+}
\ No newline at end of file

Propchange: ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnit4TestMethodAdapter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java
URL: http://svn.apache.org/viewvc/ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java?rev=906782&r1=906781&r2=906782&view=diff
==============================================================================
--- ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java (original)
+++ ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java Fri Feb  5 02:51:40 2010
@@ -729,6 +729,7 @@
                 new SplitClassLoader(myLoader, path, getProject(),
                                      new String[] {
                                          "BriefJUnitResultFormatter",
+                                         "JUnit4TestMethodAdapter",
                                          "JUnitResultFormatter",
                                          "JUnitTaskMirrorImpl",
                                          "JUnitTestRunner",
@@ -752,6 +753,8 @@
      * @since Ant 1.2
      */
     public void execute() throws BuildException {
+        checkMethodLists();
+
         setupJUnitDelegate();
 
         List testLists = new ArrayList();
@@ -836,6 +839,9 @@
             while (iter.hasNext()) {
                 test = (JUnitTest) iter.next();
                 printDual(writer, logWriter, test.getName());
+                if (test.getMethods() != null) {
+                    printDual(writer, logWriter, ":" + test.getMethodsString().replace(',', '+'));
+                }
                 if (test.getTodir() == null) {
                     printDual(writer, logWriter,
                               "," + getProject().resolveFile("."));
@@ -907,6 +913,9 @@
         cmd.setClassname("org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner");
         if (casesFile == null) {
             cmd.createArgument().setValue(test.getName());
+            if (test.getMethods() != null) {
+                cmd.createArgument().setValue(Constants.METHOD_NAMES + test.getMethodsString());
+            }
         } else {
             log("Running multiple tests in the same VM", Project.MSG_VERBOSE);
             cmd.createArgument().setValue(Constants.TESTSFILE + casesFile);
@@ -1307,7 +1316,7 @@
             if (classLoader != null) {
                 classLoader.setThreadContextLoader();
             }
-            runner = delegate.newJUnitTestRunner(test, test.getHaltonerror(),
+            runner = delegate.newJUnitTestRunner(test, test.getMethods(), test.getHaltonerror(),
                                          test.getFiltertrace(),
                                          test.getHaltonfailure(), false,
                                          true, classLoader);
@@ -1393,6 +1402,29 @@
     }
 
     /**
+     * Verifies all <code>test</code> elements having the <code>methods</code>
+     * attribute specified and having the <code>if</code>-condition resolved
+     * to true, that the value of the <code>methods</code> attribute is valid.
+     * @exception BuildException if some of the tests matching the described
+     *                           conditions has invalid value of the
+     *                           <code>methods</code> attribute
+     * @since Ant 1.7.1
+     */
+    private void checkMethodLists() throws BuildException {
+        if (tests.isEmpty()) {
+            return;
+        }
+
+        Enumeration testsEnum = tests.elements();
+        while (testsEnum.hasMoreElements()) {
+            JUnitTest test = (JUnitTest) testsEnum.nextElement();
+            if (test.hasMethodsSpecified() && test.shouldRun(getProject())) {
+                test.resolveMethods();
+            }
+        }
+    }
+
+    /**
      * return an enumeration listing each test, then each batchtest
      * @return enumeration
      * @since Ant 1.3

Modified: ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirror.java
URL: http://svn.apache.org/viewvc/ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirror.java?rev=906782&r1=906781&r2=906782&view=diff
==============================================================================
--- ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirror.java (original)
+++ ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirror.java Fri Feb  5 02:51:40 2010
@@ -55,6 +55,7 @@
     /**
      * Create a new test runner for a test.
      * @param test the test to run.
+     * @param methods names of the test methods to be run.
      * @param haltOnError if true halt the tests if an error occurs.
      * @param filterTrace if true filter the stack traces.
      * @param haltOnFailure if true halt the test if a failure occurs.
@@ -63,7 +64,7 @@
      * @param classLoader      the classloader to use to create the runner.
      * @return the test runner.
      */
-    JUnitTestRunnerMirror newJUnitTestRunner(JUnitTest test, boolean haltOnError,
+    JUnitTestRunnerMirror newJUnitTestRunner(JUnitTest test, String[] methods, boolean haltOnError,
             boolean filterTrace, boolean haltOnFailure, boolean showOutput,
             boolean logTestListenerEvents, AntClassLoader classLoader);
 

Modified: ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirrorImpl.java
URL: http://svn.apache.org/viewvc/ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirrorImpl.java?rev=906782&r1=906781&r2=906782&view=diff
==============================================================================
--- ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirrorImpl.java (original)
+++ ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTaskMirrorImpl.java Fri Feb  5 02:51:40 2010
@@ -60,9 +60,10 @@
 
     /** {@inheritDoc}. */
     public JUnitTaskMirror.JUnitTestRunnerMirror newJUnitTestRunner(JUnitTest test,
+            String[] methods,
             boolean haltOnError, boolean filterTrace, boolean haltOnFailure,
             boolean showOutput, boolean logTestListenerEvents, AntClassLoader classLoader) {
-        return new JUnitTestRunner(test, haltOnError, filterTrace, haltOnFailure,
+        return new JUnitTestRunner(test, methods, haltOnError, filterTrace, haltOnFailure,
                 showOutput, logTestListenerEvents, classLoader);
     }
 

Modified: ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java
URL: http://svn.apache.org/viewvc/ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java?rev=906782&r1=906781&r2=906782&view=diff
==============================================================================
--- ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java (original)
+++ ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java Fri Feb  5 02:51:40 2010
@@ -22,6 +22,7 @@
 import java.util.Hashtable;
 import java.util.Properties;
 import java.util.Vector;
+import org.apache.tools.ant.BuildException;
 import org.apache.tools.ant.Project;
 import org.apache.tools.ant.PropertyHelper;
 
@@ -41,6 +42,19 @@
     /** the name of the test case */
     private String name = null;
 
+    /**
+     * whether the list of test methods has been specified
+     * @see #setMethods(java.lang.String)
+     * @see #setMethods(java.lang.String[])
+     */
+    private boolean methodsSpecified = false;
+
+    /** comma-separated list of names of test methods to execute */
+    private String methodsList = null;
+
+    /** the names of test methods to execute */
+    private String[] methods = null;
+    
     /** the name of the result file */
     private String outfile = null;
 
@@ -73,11 +87,53 @@
      * @param filtertrace if true filter stack traces.
      */
     public JUnitTest(String name, boolean haltOnError, boolean haltOnFailure,
-                     boolean filtertrace) {
+            boolean filtertrace) {
+        this(name, haltOnError, haltOnFailure, filtertrace, null);
+    }    
+    
+    /**
+     * Constructor with options.
+     * @param name the name of the test.
+     * @param haltOnError if true halt the tests if there is an error.
+     * @param haltOnFailure if true halt the tests if there is a failure.
+     * @param filtertrace if true filter stack traces.
+     * @param methods if true run only test methods that failed during the
+     *                previous run of the test suite
+     * @since Ant 1.7.1
+     */
+    public JUnitTest(String name, boolean haltOnError, boolean haltOnFailure,
+                     boolean filtertrace, String[] methods) {
         this.name  = name;
         this.haltOnError = haltOnError;
         this.haltOnFail = haltOnFailure;
         this.filtertrace = filtertrace;
+        this.methods = methods;
+        this.methodsSpecified = (methods != null);
+    }
+
+    /**
+     * Sets names of individual test methods to be executed.
+     * @param value comma-separated list of names of individual test methods
+     *              to be executed,
+     *              or <code>null</code> if all test methods should be executed
+     * @since Ant 1.7.1
+     */
+    public void setMethods(String value) {
+        methodsList = value;
+        methodsSpecified = (value != null);
+        methods = null;
+    }
+
+    /**
+     * Sets names of individual test methods to be executed.
+     * @param value non-empty array of names of test methods to be executed
+     * @see #setMethods(String)
+     * @since Ant 1.7.1
+     */
+    void setMethods(String[] value) {
+        methods = value;
+        methodsSpecified = (value != null);
+        methodsList = null;
     }
 
     /**
@@ -97,6 +153,189 @@
     }
 
     /**
+     * Informs whether a list of test methods has been specified in this test.
+     * @return <code>true</code> if test methods to be executed have been
+     *         specified, <code>false</code> otherwise
+     * @see #setMethods(java.lang.String)
+     * @see #setMethods(java.lang.String[])
+     * @since Ant 1.7.1
+     */
+    boolean hasMethodsSpecified() {
+        return methodsSpecified;
+    }
+
+    /**
+     * Get names of individual test methods to be executed.
+     *
+     * @return array of names of the individual test methods to be executed,
+     *         or <code>null</code> if all test methods in the suite
+     *         defined by the test class will be executed
+     * @since Ant 1.7.1
+     */
+    String[] getMethods() {
+        if (methodsSpecified && (methods == null)) {
+            resolveMethods();
+        }
+        return methods;
+    }
+
+    /**
+     * Gets a comma-separated list of names of methods that are to be executed
+     * by this test.
+     * @return the comma-separated list of test method names, or an empty
+     *         string of no method is to be executed, or <code>null</code>
+     *         if no method is specified
+     * @since Ant 1.7.1
+     */
+    String getMethodsString() {
+        if ((methodsList == null) && methodsSpecified) {
+            if (methods.length == 0) {
+                methodsList = "";
+            } else if (methods.length == 1) {
+                methodsList = methods[0];
+            } else {
+                StringBuffer buf = new StringBuffer(methods.length * 16);
+                buf.append(methods[0]);
+                for (int i = 1; i < methods.length; i++) {
+                    buf.append(',').append(methods[i]);
+                }
+                methodsList = buf.toString();
+            }
+        }
+        return methodsList;
+    }
+
+    /**
+     * Computes the value of the {@link #methods} field from the value
+     * of the {@link #methodsList} field, if it has not been computed yet.
+     * @exception BuildException if the value of the {@link #methodsList} field
+     *                           was invalid
+     * @since Ant 1.7.1
+     */
+    void resolveMethods() {
+        if ((methods == null) && methodsSpecified) {
+            try {
+                methods = parseTestMethodNamesList(methodsList);
+            } catch (IllegalArgumentException ex) {
+                throw new BuildException(
+                        "Invalid specification of test methods: \""
+                            + methodsList
+                            + "\"; expected: comma-separated list of valid Java identifiers",
+                        ex);
+            }
+        }
+    }
+
+    /**
+     * Parses a comma-separated list of method names and check their validity.
+     * @param methodNames comma-separated list of method names to be parsed
+     * @return array of individual test method names
+     * @exception  java.lang.IllegalArgumentException
+     *             if the given string is <code>null</code> or if it is not
+     *             a comma-separated list of valid Java identifiers;
+     *             an empty string is acceptable and is handled as an empty
+     *             list
+     * @since Ant 1.7.1
+     */
+    static String[] parseTestMethodNamesList(String methodNames)
+                                            throws IllegalArgumentException {
+        if (methodNames == null) {
+            throw new IllegalArgumentException("methodNames is <null>");
+        }
+
+        methodNames = methodNames.trim();
+
+        int length = methodNames.length();
+        if (length == 0) {
+            return new String[0];
+        }
+
+        /* strip the trailing comma, if any */
+        if (methodNames.charAt(length - 1) == ',') {
+            methodNames = methodNames.substring(0, length - 1).trim();
+            length = methodNames.length();
+            if (length == 0) {
+                throw new IllegalArgumentException("Empty method name");
+            }
+        }
+
+        final char[] chars = methodNames.toCharArray();
+        /* easy detection of one particular case of illegal string: */
+        if (chars[0] == ',') {
+            throw new IllegalArgumentException("Empty method name");
+        }
+        /* count number of method names: */
+        int wordCount = 1;
+        for (int i = 1; i < chars.length; i++) {
+            if (chars[i] == ',') {
+                wordCount++;
+            }
+        }
+        /* prepare the resulting array: */
+        String[] result = new String[wordCount];
+        /* parse the string: */
+        final int stateBeforeWord = 1;
+        final int stateInsideWord = 2;
+        final int stateAfterWord = 3;
+        //
+        int state = stateBeforeWord;
+        int wordStartIndex = -1;
+        int wordIndex = 0;
+        for (int i = 0; i < chars.length; i++) {
+            char c = chars[i];
+            switch (state) {
+                case stateBeforeWord:
+                    if (c == ',') {
+                        throw new IllegalArgumentException("Empty method name");
+                    } else if (c == ' ') {
+                        // remain in the same state
+                    } else if (Character.isJavaIdentifierStart(c)) {
+                        wordStartIndex = i;
+                        state = stateInsideWord;
+                    } else {
+                        throw new IllegalArgumentException("Illegal start of method name: " + c);
+                    }
+                    break;
+                case stateInsideWord:
+                    if (c == ',') {
+                        result[wordIndex++] = new String(methodNames.substring(wordStartIndex, i));
+                        state = stateBeforeWord;
+                    } else if (c == ' ') {
+                        result[wordIndex++] = new String(methodNames.substring(wordStartIndex, i));
+                        state = stateAfterWord;
+                    } else if (Character.isJavaIdentifierPart(c)) {
+                        // remain in the same state
+                    } else {
+                        throw new IllegalArgumentException("Illegal character in method name: " + c);
+                    }
+                    break;
+                case stateAfterWord:
+                    if (c == ',') {
+                        state = stateBeforeWord;
+                    } else if (c == ' ') {
+                        // remain in the same state
+                    } else {
+                        throw new IllegalArgumentException("Space in method name");
+                    }
+                    break;
+                default:
+                    // this should never happen
+            }
+        }
+        switch (state) {
+            case stateBeforeWord:
+            case stateAfterWord:
+                break;
+            case stateInsideWord:
+                result[wordIndex++] = new String(methodNames.substring(wordStartIndex, chars.length));
+                break;
+            default:
+                // this should never happen
+        }
+        return result;
+    }
+
+    /**
      * Get the name of the test class.
      * @return the name of the test.
      */

Modified: ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java
URL: http://svn.apache.org/viewvc/ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java?rev=906782&r1=906781&r2=906782&view=diff
==============================================================================
--- ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java (original)
+++ ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java Fri Feb  5 02:51:40 2010
@@ -163,6 +163,9 @@
      */
     private static String crashFile = null;
 
+    /** Names of test methods to execute */
+    private String[] methods = null;
+    
     /**
      * Constructor for fork=true or when the user hasn't specified a
      * classpath.
@@ -205,7 +208,26 @@
     public JUnitTestRunner(JUnitTest test, boolean haltOnError,
                            boolean filtertrace, boolean haltOnFailure,
                            boolean showOutput, boolean logTestListenerEvents) {
-        this(test, haltOnError, filtertrace, haltOnFailure, showOutput,
+        this(test, null, haltOnError, filtertrace, haltOnFailure, showOutput,
+             logTestListenerEvents, null);
+    }
+
+    /**
+     * Constructor for fork=true or when the user hasn't specified a
+     * classpath.
+     * @param test the test to run.
+     * @param methods names of methods of the test to be executed.
+     * @param haltOnError whether to stop the run if an error is found.
+     * @param filtertrace whether to filter junit.*.* stack frames out of exceptions
+     * @param haltOnFailure whether to stop the run if failure is found.
+     * @param showOutput    whether to send output to System.out/.err as well as formatters.
+     * @param logTestListenerEvents whether to print TestListener events.
+     * @since Ant 1.7
+     */
+    public JUnitTestRunner(JUnitTest test, String[] methods, boolean haltOnError,
+                           boolean filtertrace, boolean haltOnFailure,
+                           boolean showOutput, boolean logTestListenerEvents) {
+        this(test, methods, haltOnError, filtertrace, haltOnFailure, showOutput,
              logTestListenerEvents, null);
     }
 
@@ -254,12 +276,26 @@
                            boolean filtertrace, boolean haltOnFailure,
                            boolean showOutput, boolean logTestListenerEvents,
                            ClassLoader loader) {
+        this(test, null, haltOnError, filtertrace, haltOnFailure, showOutput, 
+             logTestListenerEvents, loader);
+    }
+
+
+    /**
+     * Constructor to use when the user has specified a classpath.
+     * @since Ant 1.7
+     */
+    public JUnitTestRunner(JUnitTest test, String[] methods, boolean haltOnError,
+                           boolean filtertrace, boolean haltOnFailure,
+                           boolean showOutput, boolean logTestListenerEvents,
+                           ClassLoader loader) {
         JUnitTestRunner.filtertrace = filtertrace;
         this.junitTest = test;
         this.haltOnError = haltOnError;
         this.haltOnFailure = haltOnFailure;
         this.showOutput = showOutput;
         this.logTestListenerEvents = logTestListenerEvents;
+        this.methods = methods;
         this.loader = loader;
     }
 
@@ -340,9 +376,12 @@
                                               loader);
                 }
 
+                final boolean testMethodsSpecified = (methods != null);
+
                 // check for a static suite method first, even when using
                 // JUnit 4
                 Method suiteMethod = null;
+                if (!testMethodsSpecified) {
                 try {
                     // check if there is a suite method
                     suiteMethod = testClass.getMethod("suite", new Class[0]);
@@ -350,6 +389,7 @@
                     // no appropriate suite method found. We don't report any
                     // error here since it might be perfectly normal.
                 }
+                }
 
                 if (suiteMethod != null) {
                     // if there is a suite method available, then try
@@ -359,7 +399,23 @@
 
                 } else {
                     Class junit4TestAdapterClass = null;
+                    boolean useSingleMethodAdapter = false;
 
+                    if (junit.framework.TestCase.class.isAssignableFrom(testClass)) {
+                        // Do not use JUnit 4 API for running JUnit 3.x
+                        // tests - it is not able to run individual test
+                        // methods.
+                        //
+                        // Technical details:
+                        // org.junit.runner.Request.method(Class, String).getRunner()
+                        // would return a runner which always executes all
+                        // test methods. The reason is that the Runner would be
+                        // an instance of class
+                        // org.junit.internal.runners.OldTestClassRunner
+                        // that does not implement interface Filterable - so it
+                        // is unable to filter out test methods not matching
+                        // the requested name.
+                    } else {
                     // Check for JDK 5 first. Will *not* help on JDK 1.4
                     // if only junit-4.0.jar in CP because in that case
                     // linkage of whole task will already have failed! But
@@ -373,10 +429,29 @@
                         if (loader == null) {
                             junit4TestAdapterClass =
                                 Class.forName(JUNIT_4_TEST_ADAPTER);
+                            if (testMethodsSpecified) {
+                                /*
+                                 * We cannot try to load the JUnit4TestAdapter
+                                 * before trying to load JUnit4TestMethodAdapter
+                                 * because it might fail with
+                                 * NoClassDefFoundException, instead of plain
+                                 * ClassNotFoundException.
+                                 */
+                                junit4TestAdapterClass = Class.forName(
+                                    "org.apache.tools.ant.taskdefs.optional.junit.JUnit4TestMethodAdapter");
+                                useSingleMethodAdapter = true;
+                            }
                         } else {
                             junit4TestAdapterClass =
                                 Class.forName(JUNIT_4_TEST_ADAPTER,
                                               true, loader);
+                            if (testMethodsSpecified) {
+                                junit4TestAdapterClass =
+                                    Class.forName(
+                                        "org.apache.tools.ant.taskdefs.optional.junit.JUnit4TestMethodAdapter",
+                                        true, loader);
+                                useSingleMethodAdapter = true;
+                            }
                         }
                     } catch (ClassNotFoundException e) {
                         // OK, fall back to JUnit 3.
@@ -385,17 +460,38 @@
 
                     if (junit4) {
                         // Let's use it!
+                        Class[] formalParams;
+                        Object[] actualParams;
+                        if (useSingleMethodAdapter) {
+                            formalParams = new Class[] {Class.class, String[].class};
+                            actualParams = new Object[] {testClass, methods};
+                        } else {
+                            formalParams = new Class[] {Class.class};
+                            actualParams = new Object[] {testClass};
+                        }
                         suite =
                             (Test) junit4TestAdapterClass
-                            .getConstructor(new Class[] {Class.class}).
-                            newInstance(new Object[] {testClass});
+                            .getConstructor(formalParams).
+                            newInstance(actualParams);
                     } else {
                         // Use JUnit 3.
 
                         // try to extract a test suite automatically this
                         // will generate warnings if the class is no
                         // suitable Test
-                        suite = new TestSuite(testClass);
+                        if (!testMethodsSpecified) {
+                            suite = new TestSuite(testClass);
+                        } else if (methods.length == 1) {
+                            suite = TestSuite.createTest(testClass, methods[0]);
+                        } else {
+                            TestSuite testSuite = new TestSuite(testClass.getName());
+                            for (int i = 0; i < methods.length; i++) {
+                                testSuite.addTest(
+                                    TestSuite.createTest(testClass, methods[i]));
+                            }
+                            suite = testSuite;
+                        }
+                    }
                     }
 
                 }
@@ -670,11 +766,16 @@
      * <tr><td>logtestlistenerevents</td><td>log TestListener events to
      * System.out.</td><td>false</td></tr>
      *
+     * <tr><td>methods</td><td>Comma-separated list of names of individual
+     * test methods to execute.
+     * </td><td>null</td></tr>
+     *
      * </table>
      * @param args the command line arguments.
      * @throws IOException on error.
      */
     public static void main(String[] args) throws IOException {
+        String[] methods = null;
         boolean haltError = false;
         boolean haltFail = false;
         boolean stackfilter = true;
@@ -696,7 +797,15 @@
         }
 
         for (int i = 1; i < args.length; i++) {
-            if (args[i].startsWith(Constants.HALT_ON_ERROR)) {
+            if (args[i].startsWith(Constants.METHOD_NAMES)) {
+                try {
+                    String methodsList = args[i].substring(Constants.METHOD_NAMES.length());
+                    methods = JUnitTest.parseTestMethodNamesList(methodsList);
+                } catch (IllegalArgumentException ex) {
+                    System.err.println("Invalid specification of test method names: " + args[i]);
+                    System.exit(ERRORS);
+                }
+            } else if (args[i].startsWith(Constants.HALT_ON_ERROR)) {
                 haltError = Project.toBoolean(args[i].substring(Constants.HALT_ON_ERROR.length()));
             } else if (args[i].startsWith(Constants.HALT_ON_FAILURE)) {
                 haltFail = Project.toBoolean(args[i].substring(Constants.HALT_ON_FAILURE.length()));
@@ -744,18 +853,30 @@
                 java.io.BufferedReader reader =
                     new java.io.BufferedReader(new java.io.FileReader(args[0]));
                 String testCaseName;
+                String[] testMethodNames;
                 int code = 0;
                 boolean errorOccurred = false;
                 boolean failureOccurred = false;
                 String line = null;
                 while ((line = reader.readLine()) != null) {
                     StringTokenizer st = new StringTokenizer(line, ",");
-                    testCaseName = st.nextToken();
+                    String testListSpec = st.nextToken();
+                    int colonIndex = testListSpec.indexOf(':');
+                    if (colonIndex == -1) {
+                        testCaseName = testListSpec;
+                        testMethodNames = null;
+                    } else {
+                        testCaseName = testListSpec.substring(0, colonIndex);
+                        testMethodNames = JUnitTest.parseTestMethodNamesList(
+                                                    testListSpec
+                                                    .substring(colonIndex + 1)
+                                                    .replace('+', ','));
+                    }
                     JUnitTest t = new JUnitTest(testCaseName);
                     t.setTodir(new File(st.nextToken()));
                     t.setOutfile(st.nextToken());
                     t.setProperties(props);
-                    code = launch(t, haltError, stackfilter, haltFail,
+                    code = launch(t, testMethodNames, haltError, stackfilter, haltFail,
                                   showOut, outputToFormat,
                                   logTestListenerEvents);
                     errorOccurred = (code == ERRORS);
@@ -783,7 +904,7 @@
             JUnitTest t = new JUnitTest(args[0]);
             t.setProperties(props);
             returnCode = launch(
-                t, haltError, stackfilter, haltFail,
+                t, methods, haltError, stackfilter, haltFail,
                 showOut, outputToFormat, logTestListenerEvents);
         }
 
@@ -917,12 +1038,12 @@
     /**
      * @since Ant 1.6.2
      */
-    private static int launch(JUnitTest t, boolean haltError,
+    private static int launch(JUnitTest t, String[] methods, boolean haltError,
                               boolean stackfilter, boolean haltFail,
                               boolean showOut, boolean outputToFormat,
                               boolean logTestListenerEvents) {
         JUnitTestRunner runner =
-            new JUnitTestRunner(t, haltError, stackfilter, haltFail, showOut,
+            new JUnitTestRunner(t, methods, haltError, stackfilter, haltFail, showOut,
                                 logTestListenerEvents, null);
         runner.forked = true;
         runner.outputToFormatters = outputToFormat;

Added: ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultParser.java
URL: http://svn.apache.org/viewvc/ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultParser.java?rev=906782&view=auto
==============================================================================
--- ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultParser.java (added)
+++ ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultParser.java Fri Feb  5 02:51:40 2010
@@ -0,0 +1,265 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
+ * Microsystems, Inc. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+
+package org.apache.tools.ant.taskdefs.optional.junit;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+/**
+ * Parser of XML-output generated by the JUnit XML formatter.
+ *
+ * @author Marian Petras
+ */
+final class XMLResultParser extends DefaultHandler {
+
+    /** */
+    private static final int STATE_OUT_OF_SCOPE = 1;
+    /** */
+    private static final int STATE_TESTSUITE = 2;
+    /** */
+    private static final int STATE_PROPERTIES = 3;
+    /** */
+    private static final int STATE_PROPERTY = 4;
+    /** */
+    private static final int STATE_TESTCASE = 8;
+    /** */
+    private static final int STATE_FAILURE = 12;
+    /** */
+    private static final int STATE_ERROR = 13;
+    /** */
+    private static final int STATE_OUTPUT_STD = 16;
+    /** */
+    private static final int STATE_OUTPUT_ERR = 17;
+    
+    /** */
+    private int state = STATE_OUT_OF_SCOPE;
+    /** */
+    int unknownElemNestLevel = 0;
+    
+    /** */
+    private final XMLReader xmlReader;
+    /** */
+    private String lastErrClassName = null;
+    /** */
+    private List lastErrMethodNames = null;
+    /** */
+    private String className;
+    /** */
+    private String methodName;
+
+    /** */
+    private List result = null;
+    
+    /**
+     * Parses an XML file provided by a given reader.
+     * @param reader reader to read the XML file from
+     * @return list of Strings, each describing a single <code>JUnitTest</code>
+     * @exception java.io.IOException
+     *            in case of troubles with reading the file
+     * @exception org.xml.sax.SAXException
+     *            if initialization of the parser failed
+     */
+    static List parseResultsFile(Reader reader) throws IOException, SAXException {
+        XMLResultParser parser = new XMLResultParser();
+        parser.xmlReader.parse(new InputSource(reader));
+        return (parser.result != null) ? parser.result
+                                       : Collections.EMPTY_LIST;
+    }
+    
+    /** Creates a new instance of XMLResultParser */
+    private XMLResultParser() throws SAXException {
+        xmlReader = XMLReaderFactory.createXMLReader();
+        xmlReader.setContentHandler(this);
+    }
+    
+    /**
+     */
+    public void startElement(String uri,
+                             String localName,
+                             String qName,
+                             Attributes attrs) throws SAXException {
+        switch (state) {
+            case STATE_PROPERTIES:
+                if (qName.equals("property")) {
+                    state = STATE_PROPERTY;
+                } else {
+                    startUnknownElem();
+                }
+                break;
+            case STATE_TESTSUITE:
+                if (qName.equals("testcase")) {
+                    className = attrs.getValue("classname");
+                    methodName = attrs.getValue("name");
+                    state = STATE_TESTCASE;
+                } else if (qName.equals("system-out")) {
+                    state = STATE_OUTPUT_STD;
+                } else if (qName.equals("system-err")) {
+                    state = STATE_OUTPUT_ERR;
+                } else if (qName.equals("properties")) {
+                    state = STATE_PROPERTIES;
+                } else {
+                    startUnknownElem();
+                }
+                break;
+            case STATE_TESTCASE:
+                if (qName.equals("failure")) {
+                    state = STATE_FAILURE;
+                } else if (qName.equals("error")) {
+                    state = STATE_ERROR;
+                } else {
+                    startUnknownElem();
+                }
+                if (state >= 0) {     //i.e. the element is "failure" or "error"
+                    if ((className != null) && (methodName != null)) {
+                        if (className.equals(lastErrClassName)) {
+                            lastErrMethodNames.add(methodName);
+                        } else {
+                            maybeSaveLastErrJUnitTest();
+                        }
+                        
+                        lastErrClassName = className;
+                        if (lastErrMethodNames == null) {
+                            lastErrMethodNames = new ArrayList(5);
+                        }
+                        lastErrMethodNames.add(methodName);
+                    }
+                }
+                break;
+            case STATE_OUT_OF_SCOPE:
+                if (qName.equals("testsuite")) {
+                    state = STATE_TESTSUITE;
+                } else {
+                    startUnknownElem();
+                }
+                break;
+            case STATE_PROPERTY:
+            case STATE_FAILURE:
+            case STATE_ERROR:
+            case STATE_OUTPUT_STD:
+            case STATE_OUTPUT_ERR:
+                startUnknownElem();
+                break;
+            default:
+                unknownElemNestLevel++;
+                break;
+        }
+    }
+
+    /**
+     */
+    public void endElement(String uri,
+                           String localName,
+                           String qName) throws SAXException {
+        switch (state) {
+            case STATE_PROPERTIES:
+                state = STATE_TESTSUITE;
+                break;
+            case STATE_TESTSUITE:
+                maybeSaveLastErrJUnitTest();
+                state = STATE_OUT_OF_SCOPE;
+                break;
+            case STATE_TESTCASE:
+                state = STATE_TESTSUITE;
+                break;
+            case STATE_OUT_OF_SCOPE:
+                break;
+            case STATE_PROPERTY:
+                state = STATE_PROPERTIES;
+                break;
+            case STATE_FAILURE:
+            case STATE_ERROR:
+                state = STATE_TESTCASE;
+                break;
+            case STATE_OUTPUT_STD:
+            case STATE_OUTPUT_ERR:
+                state = STATE_TESTSUITE;
+                break;
+            default:
+                if (--unknownElemNestLevel == 0) {
+                    state = -state;
+                }
+                break;
+        }
+    }
+    
+    /**
+     */
+    private void startUnknownElem() {
+        state = -state;
+        unknownElemNestLevel++;
+    }
+    
+    /**
+     */
+    private void maybeSaveLastErrJUnitTest() {
+        if (lastErrClassName == null) {
+            return;
+        }
+
+        StringBuffer buf = new StringBuffer(20);
+        buf.append(lastErrClassName).append(':');
+        buf.append(lastErrMethodNames.get(0));
+        int methodsCount = lastErrMethodNames.size();
+        if (methodsCount > 1) {
+            for (int i = 1; i < methodsCount; i++) {
+                buf.append(',').append(lastErrMethodNames.get(i));
+            }
+        }
+        if (result == null) {
+            result = new ArrayList(10);
+        }
+        result.add(buf.toString());
+
+        lastErrClassName = null;
+        lastErrMethodNames.clear();
+    }
+    
+}

Propchange: ant/core/branches/run-single-test-method/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultParser.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: ant/core/branches/run-single-test-method/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/BatchTestTest.java
URL: http://svn.apache.org/viewvc/ant/core/branches/run-single-test-method/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/BatchTestTest.java?rev=906782&view=auto
==============================================================================
--- ant/core/branches/run-single-test-method/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/BatchTestTest.java (added)
+++ ant/core/branches/run-single-test-method/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/BatchTestTest.java Fri Feb  5 02:51:40 2010
@@ -0,0 +1,129 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.apache.tools.ant.taskdefs.optional.junit;
+
+import junit.framework.ComparisonFailure;
+import junit.framework.TestCase;
+
+/**
+ *
+ * @author  Marian Petras
+ */
+public class BatchTestTest extends TestCase {
+    
+    public BatchTestTest(String testName) {
+        super(testName);
+    }
+
+
+    public void testParseTestMethodNamesList() {
+        try {
+            JUnitTest.parseTestMethodNamesList(null);
+            fail("IllegalArgumentException expected when the param is <null>");
+        } catch (IllegalArgumentException ex) {
+            //this is an expected exception
+        }
+
+        assertEquals(new String[0], JUnitTest.parseTestMethodNamesList(""));
+        assertEquals(new String[0], JUnitTest.parseTestMethodNamesList(" "));
+        assertEquals(new String[0], JUnitTest.parseTestMethodNamesList("  "));
+
+        checkParseCausesIAE(",");
+        checkParseCausesIAE(" ,");
+        checkParseCausesIAE(", ");
+        checkParseCausesIAE(" , ");
+        checkParseCausesIAE(",a");
+        checkParseCausesIAE(" ,a");
+        checkParseCausesIAE("  ,a");
+        checkParseCausesIAE("  , a");
+        checkParseCausesIAE("  ,a  ");
+        checkParseCausesIAE("  ,a  ,");
+        checkParseCausesIAE("ab,,cd");
+        checkParseCausesIAE("ab, ,cd");
+        checkParseCausesIAE("ab,  ,cd");
+        checkParseCausesIAE("ab,  ,cd,");
+        checkParseCausesIAE(",ab,  ,cd,");
+
+        assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList("abc"));
+        assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList("abc "));
+        assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList(" abc"));
+        assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList(" abc "));
+        assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList("abc  "));
+        assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList("abc,"));
+        assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList("abc, "));
+        assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList("abc ,"));
+        assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList("abc , "));
+        assertEquals(new String[] {"abc"}, JUnitTest.parseTestMethodNamesList(" abc  ,"));
+
+        /* legal Java identifiers: */
+        assertEquals(new String[] {"a"}, JUnitTest.parseTestMethodNamesList("a"));
+        assertEquals(new String[] {"a1"}, JUnitTest.parseTestMethodNamesList("a1"));
+        assertEquals(new String[] {"a$"}, JUnitTest.parseTestMethodNamesList("a$"));
+        assertEquals(new String[] {"a$1"}, JUnitTest.parseTestMethodNamesList("a$1"));
+        assertEquals(new String[] {"_bc"}, JUnitTest.parseTestMethodNamesList("_bc"));
+        assertEquals(new String[] {"___"}, JUnitTest.parseTestMethodNamesList("___"));
+
+        /* illegal Java identifiers: */
+        checkParseCausesIAE("1");
+        checkParseCausesIAE("1a");
+        checkParseCausesIAE("1ab");
+        checkParseCausesIAE("1abc");
+        checkParseCausesIAE("1abc d");
+        checkParseCausesIAE("1abc de");
+        checkParseCausesIAE("1abc def");
+        checkParseCausesIAE("1abc def,");
+        checkParseCausesIAE(",1abc def");
+
+        assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList("abc,def"));
+        assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList("abc,def,"));
+        assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList("abc,def "));
+        assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList("abc, def"));
+        assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList("abc, def "));
+        assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList("abc ,def"));
+        assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList("abc ,def "));
+        assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList("abc , def"));
+        assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList("abc , def "));
+        assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList(" abc,def"));
+        assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList(" abc,def "));
+        assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList(" abc, def"));
+        assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList(" abc, def "));
+        assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList(" abc ,def"));
+        assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList(" abc ,def "));
+        assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList(" abc , def"));
+        assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList(" abc , def "));
+        assertEquals(new String[] {"abc", "def"}, JUnitTest.parseTestMethodNamesList(" abc , def ,"));
+    }
+
+    private static void checkParseCausesIAE(String param) {
+        try {
+            JUnitTest.parseTestMethodNamesList(param);
+            fail("IllegalArgumentException expected when the param is \"" + param + '"');
+        } catch (IllegalArgumentException ex) {
+            //this is an expected exception
+        }
+    }
+
+    private static void assertEquals(String[] expected, String[] actual) {
+        assertEquals(null, expected, actual);
+    }
+
+    private static void assertEquals(String message,
+                                     String[] expected,
+                                     String[] actual) {
+        if ((expected == null) && (actual == null)) {
+            return;
+        }
+        if (expected.length != actual.length) {
+            throw new ComparisonFailure(message,
+                                        expected.toString(),
+                                        actual.toString());
+        }
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals(expected[i], actual[i]);
+        }
+    }
+
+}

Propchange: ant/core/branches/run-single-test-method/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/BatchTestTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: ant/core/branches/run-single-test-method/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunnerTest.java
URL: http://svn.apache.org/viewvc/ant/core/branches/run-single-test-method/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunnerTest.java?rev=906782&r1=906781&r2=906782&view=diff
==============================================================================
--- ant/core/branches/run-single-test-method/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunnerTest.java (original)
+++ ant/core/branches/run-single-test-method/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunnerTest.java Fri Feb  5 02:51:40 2010
@@ -33,6 +33,21 @@
         super(name);
     }
 
+    // check that a valid method name generates no errors
+    public void testValidMethod(){
+        TestRunner runner = createRunnerForTestMethod(ValidMethodTestCase.class,"testA");
+        runner.run();
+        assertEquals(runner.getFormatter().getError(), JUnitTestRunner.SUCCESS, runner.getRetCode());
+    }
+
+    // check that having an invalid method name generates an error
+    public void testInvalidMethod(){
+        TestRunner runner = createRunnerForTestMethod(InvalidMethodTestCase.class,"testInvalid");
+        runner.run();
+        String error = runner.getFormatter().getError();
+        assertEquals(error, JUnitTestRunner.ERRORS, runner.getRetCode());
+    }    
+    
     // check that having no suite generates no errors
     public void testNoSuite(){
         TestRunner runner = createRunner(NoSuiteTestCase.class);
@@ -87,14 +102,22 @@
     }
 
     protected TestRunner createRunner(Class clazz){
-        return new TestRunner(new JUnitTest(clazz.getName()), true, true, true);
+        return new TestRunner(new JUnitTest(clazz.getName()), null, 
+                                            true, true, true);
     }
 
+    protected TestRunner createRunnerForTestMethod(Class clazz, String method){
+        return new TestRunner(new JUnitTest(clazz.getName()), new String[] {method},
+                                            true, true, true);
+    }    
+    
     // the test runner that wrap the dummy formatter that interests us
     private final static class TestRunner extends JUnitTestRunner {
         private ResultFormatter formatter = new ResultFormatter();
-        TestRunner(JUnitTest test, boolean haltonerror, boolean filtertrace, boolean haltonfailure){
-            super(test, haltonerror, filtertrace,  haltonfailure, TestRunner.class.getClassLoader());
+        TestRunner(JUnitTest test, String[] methods, boolean haltonerror,
+                   boolean filtertrace, boolean haltonfailure){
+            super(test, methods, haltonerror, filtertrace,  haltonfailure, 
+                  false, false, TestRunner.class.getClassLoader());
             // use the classloader that loaded this class otherwise
             // it will not be able to run inner classes if this test
             // is ran in non-forked mode.
@@ -133,6 +156,24 @@
     public static class NoTestCase {
     }
 
+    public static class InvalidMethodTestCase extends TestCase {
+        public InvalidMethodTestCase(String name){ super(name); }
+        public void testA(){
+            throw new NullPointerException("thrown on purpose");
+        }
+    }
+
+    public static class ValidMethodTestCase extends TestCase {
+        public ValidMethodTestCase(String name){ super(name); }
+        public void testA(){
+            // expected to be executed
+        }
+        public void testB(){
+            // should not be executed
+            throw new NullPointerException("thrown on purpose");
+        }
+    }    
+    
     public static class InvalidTestCase extends TestCase {
         public InvalidTestCase(String name){
             super(name);



Mime
View raw message