View Javadoc

1   /*
2    * Copyright  2000-2004 The Apache Software Foundation
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   *
16   */
17  
18  package org.woopi.ant.taskdefs.junit;
19  import java.io.BufferedWriter;
20  import java.io.File;
21  import java.io.FileOutputStream;
22  import java.io.FileWriter;
23  import java.io.IOException;
24  import java.io.OutputStream;
25  import java.io.PrintWriter;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Enumeration;
29  import java.util.HashMap;
30  import java.util.Hashtable;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Properties;
35  import java.util.Vector;
36  import org.apache.tools.ant.AntClassLoader;
37  import org.apache.tools.ant.BuildException;
38  import org.apache.tools.ant.Project;
39  import org.apache.tools.ant.Task;
40  import org.apache.tools.ant.taskdefs.Execute;
41  import org.apache.tools.ant.taskdefs.ExecuteWatchdog;
42  import org.apache.tools.ant.taskdefs.LogOutputStream;
43  import org.apache.tools.ant.taskdefs.LogStreamHandler;
44  import org.apache.tools.ant.types.Assertions;
45  import org.apache.tools.ant.types.Commandline;
46  import org.apache.tools.ant.types.CommandlineJava;
47  import org.apache.tools.ant.types.EnumeratedAttribute;
48  import org.apache.tools.ant.types.Environment;
49  import org.apache.tools.ant.types.Path;
50  import org.apache.tools.ant.types.Permissions;
51  import org.apache.tools.ant.types.PropertySet;
52  import org.apache.tools.ant.util.FileUtils;
53  import org.apache.tools.ant.util.LoaderUtils;
54  import junit.framework.AssertionFailedError;
55  import junit.framework.Test;
56  import junit.framework.TestResult;
57  
58  /***
59   * Runs JUnit tests.
60   *
61   * <p> JUnit is a framework to create unit test. It has been initially
62   * created by Erich Gamma and Kent Beck.  JUnit can be found at <a
63   * href="http://www.junit.org">http://www.junit.org</a>.
64   *
65   * <p> <code>JUnitTask</code> can run a single specific
66   * <code>JUnitTest</code> using the <code>test</code> element.</p>
67   * For example, the following target <code><pre>
68   *   &lt;target name="test-int-chars" depends="jar-test"&gt;
69   *       &lt;echo message="testing international characters"/&gt;
70   *       &lt;junit printsummary="no" haltonfailure="yes" fork="false"&gt;
71   *           &lt;classpath refid="classpath"/&gt;
72   *           &lt;formatter type="plain" usefile="false" /&gt;
73   *           &lt;test name="org.apache.ecs.InternationalCharTest" /&gt;
74   *       &lt;/junit&gt;
75   *   &lt;/target&gt;
76   * </pre></code>
77   * <p>runs a single junit test
78   * (<code>org.apache.ecs.InternationalCharTest</code>) in the current
79   * VM using the path with id <code>classpath</code> as classpath and
80   * presents the results formatted using the standard
81   * <code>plain</code> formatter on the command line.</p>
82   *
83   * <p> This task can also run batches of tests.  The
84   * <code>batchtest</code> element creates a <code>BatchTest</code>
85   * based on a fileset.  This allows, for example, all classes found in
86   * directory to be run as testcases.</p>
87   *
88   * <p>For example,</p><code><pre>
89   * &lt;target name="run-tests" depends="dump-info,compile-tests" if="junit.present"&gt;
90   *   &lt;junit printsummary="no" haltonfailure="yes" fork="${junit.fork}"&gt;
91   *     &lt;jvmarg value="-classic"/&gt;
92   *     &lt;classpath refid="tests-classpath"/&gt;
93   *     &lt;sysproperty key="build.tests" value="${build.tests}"/&gt;
94   *     &lt;formatter type="brief" usefile="false" /&gt;
95   *     &lt;batchtest&gt;
96   *       &lt;fileset dir="${tests.dir}"&gt;
97   *         &lt;include name="**&#047;*Test*" /&gt;
98   *       &lt;/fileset&gt;
99   *     &lt;/batchtest&gt;
100  *   &lt;/junit&gt;
101  * &lt;/target&gt;
102  * </pre></code>
103  * <p>this target finds any classes with a <code>test</code> directory
104  * anywhere in their path (under the top <code>${tests.dir}</code>, of
105  * course) and creates <code>JUnitTest</code>'s for each one.</p>
106  *
107  * <p> Of course, <code>&lt;junit&gt;</code> and
108  * <code>&lt;batch&gt;</code> elements can be combined for more
109  * complex tests. For an example, see the ant <code>build.xml</code>
110  * target <code>run-tests</code> (the second example is an edited
111  * version).</p>
112  *
113  * <p> To spawn a new Java VM to prevent interferences between
114  * different testcases, you need to enable <code>fork</code>.  A
115  * number of attributes and elements allow you to set up how this JVM
116  * runs.
117  *
118  * @version $Revision: 1.83.2.12 $
119  *
120  * @since Ant 1.2
121  *
122  * @see JUnitTest
123  * @see BatchTest
124  */
125 public class JUnitTask extends Task {
126 
127     private CommandlineJava commandline;
128     private Vector tests = new Vector();
129     private Vector batchTests = new Vector();
130     private Vector formatters = new Vector();
131     private File dir = null;
132 
133     private Integer timeout = null;
134     private boolean summary = false;
135     private boolean reloading = true;
136     private String summaryValue = "";
137     private JUnitTestRunner runner = null;
138 
139     private boolean newEnvironment = false;
140     private Environment env = new Environment();
141 
142     private boolean includeAntRuntime = true;
143     private Path antRuntimeClasses = null;
144 
145     private boolean showOutput = false;
146     private File tmpDir;
147     private AntClassLoader classLoader = null;
148     private Permissions perm = null;
149     private ForkMode forkMode = new ForkMode("perTest");
150 
151     private static final int STRING_BUFFER_SIZE = 128;
152 
153     /***
154      * If true, force ant to re-classload all classes for each JUnit TestCase
155      *
156      * @param value force class reloading for each test case
157      */
158     public void setReloading(boolean value) {
159         reloading = value;
160     }
161 
162     /***
163      * If true, smartly filter the stack frames of
164      * JUnit errors and failures before reporting them.
165      *
166      * <p>This property is applied on all BatchTest (batchtest) and
167      * JUnitTest (test) however it can possibly be overridden by their
168      * own properties.</p>
169      * @param value <tt>false</tt> if it should not filter, otherwise
170      * <tt>true<tt>
171      *
172      * @since Ant 1.5
173      */
174     public void setFiltertrace(boolean value) {
175         Enumeration e = allTests();
176         while (e.hasMoreElements()) {
177             BaseTest test = (BaseTest) e.nextElement();
178             test.setFiltertrace(value);
179         }
180     }
181 
182     /***
183      * If true, stop the build process when there is an error in a test.
184      * This property is applied on all BatchTest (batchtest) and JUnitTest
185      * (test) however it can possibly be overridden by their own
186      * properties.
187      * @param value <tt>true</tt> if it should halt, otherwise
188      * <tt>false</tt>
189      *
190      * @since Ant 1.2
191      */
192     public void setHaltonerror(boolean value) {
193         Enumeration e = allTests();
194         while (e.hasMoreElements()) {
195             BaseTest test = (BaseTest) e.nextElement();
196             test.setHaltonerror(value);
197         }
198     }
199 
200     /***
201      * Property to set to "true" if there is a error in a test.
202      *
203      * <p>This property is applied on all BatchTest (batchtest) and
204      * JUnitTest (test), however, it can possibly be overriden by
205      * their own properties.</p>
206      * @param propertyName the name of the property to set in the
207      * event of an error.
208      *
209      * @since Ant 1.4
210      */
211     public void setErrorProperty(String propertyName) {
212         Enumeration e = allTests();
213         while (e.hasMoreElements()) {
214             BaseTest test = (BaseTest) e.nextElement();
215             test.setErrorProperty(propertyName);
216         }
217     }
218 
219     /***
220      * If true, stop the build process if a test fails
221      * (errors are considered failures as well).
222      * This property is applied on all BatchTest (batchtest) and
223      * JUnitTest (test) however it can possibly be overridden by their
224      * own properties.
225      * @param value <tt>true</tt> if it should halt, otherwise
226      * <tt>false</tt>
227      *
228      * @since Ant 1.2
229      */
230     public void setHaltonfailure(boolean value) {
231         Enumeration e = allTests();
232         while (e.hasMoreElements()) {
233             BaseTest test = (BaseTest) e.nextElement();
234             test.setHaltonfailure(value);
235         }
236     }
237 
238     /***
239      * Property to set to "true" if there is a failure in a test.
240      *
241      * <p>This property is applied on all BatchTest (batchtest) and
242      * JUnitTest (test), however, it can possibly be overriden by
243      * their own properties.</p>
244      * @param propertyName the name of the property to set in the
245      * event of an failure.
246      *
247      * @since Ant 1.4
248      */
249     public void setFailureProperty(String propertyName) {
250         Enumeration e = allTests();
251         while (e.hasMoreElements()) {
252             BaseTest test = (BaseTest) e.nextElement();
253             test.setFailureProperty(propertyName);
254         }
255     }
256 
257     /***
258      * If true, JVM should be forked for each test.
259      *
260      * <p>It avoids interference between testcases and possibly avoids
261      * hanging the build.  this property is applied on all BatchTest
262      * (batchtest) and JUnitTest (test) however it can possibly be
263      * overridden by their own properties.</p>
264      * @param value <tt>true</tt> if a JVM should be forked, otherwise
265      * <tt>false</tt>
266      * @see #setTimeout
267      *
268      * @since Ant 1.2
269      */
270     public void setFork(boolean value) {
271         Enumeration e = allTests();
272         while (e.hasMoreElements()) {
273             BaseTest test = (BaseTest) e.nextElement();
274             test.setFork(value);
275         }
276     }
277 
278     /***
279      * Set the behavior when {@link #setFork fork} fork has been enabled.
280      *
281      * <p>Possible values are "once", "perTest" and "perBatch".  If
282      * set to "once", only a single Java VM will be forked for all
283      * tests, with "perTest" (the default) each test will run in a
284      * fresh Java VM and "perBatch" will run all tests from the same
285      * &lt;batchtest&gt; in the same Java VM.</p>
286      *
287      * <p>This attribute will be ignored if tests run in the same VM
288      * as Ant.</p>
289      *
290      * <p>Only tests with the same configuration of haltonerror,
291      * haltonfailure, errorproperty, failureproperty and filtertrace
292      * can share a forked Java VM, so even if you set the value to
293      * "once", Ant may need to fork mutliple VMs.</p>
294      *
295      * @since Ant 1.6.2
296      */
297     public void setForkMode(ForkMode mode) {
298         this.forkMode = mode;
299     }
300 
301 
302     /*
303       example how to add a property to the task
304     
305     public void setHurz(HurzAttribute value) {
306       System.out.println("setHurz called");
307     }
308 
309     public static class HurzAttribute extends EnumeratedAttribute {
310         public String[] getValues() {
311             return new String[] {"hurz1", "hurz2"};
312         }
313 
314     }
315     */
316 
317     /***
318      * If true, print one-line statistics for each test, or "withOutAndErr"
319      * to also show standard output and error.
320      *
321      * Can take the values on, off, and withOutAndErr.
322      * @param value <tt>true</tt> to print a summary,
323      * <tt>withOutAndErr</tt> to include the test&apos;s output as
324      * well, <tt>false</tt> otherwise.
325      * @see SummaryJUnitResultFormatter
326      *
327      * @since Ant 1.2
328      */
329     public void setPrintsummary(SummaryAttribute value) {
330         summaryValue = value.getValue();
331         summary = value.asBoolean();
332     }
333 
334     /***
335      * Print summary enumeration values.
336      */
337     public static class SummaryAttribute extends EnumeratedAttribute {
338         /***
339          * list the possible values
340          * @return  array of allowed values
341          */
342         public String[] getValues() {
343             return new String[] {"true", "yes", "false", "no",
344                                  "on", "off", "withOutAndErr"};
345         }
346 
347         /***
348          * gives the boolean equivalent of the authorized values
349          * @return boolean equivalent of the value
350          */
351         public boolean asBoolean() {
352             String value = getValue();
353             return "true".equals(value)
354                 || "on".equals(value)
355                 || "yes".equals(value)
356                 || "withOutAndErr".equals(value);
357         }
358     }
359 
360     /***
361      * Set the timeout value (in milliseconds).
362      *
363      * <p>If the test is running for more than this value, the test
364      * will be canceled. (works only when in 'fork' mode).</p>
365      * @param value the maximum time (in milliseconds) allowed before
366      * declaring the test as 'timed-out'
367      * @see #setFork(boolean)
368      *
369      * @since Ant 1.2
370      */
371     public void setTimeout(Integer value) {
372         timeout = value;
373     }
374 
375     /***
376      * Set the maximum memory to be used by all forked JVMs.
377      * @param   max     the value as defined by <tt>-mx</tt> or <tt>-Xmx</tt>
378      *                  in the java command line options.
379      *
380      * @since Ant 1.2
381      */
382     public void setMaxmemory(String max) {
383         getCommandline().setMaxmemory(max);
384     }
385 
386     /***
387      * The command used to invoke the Java Virtual Machine,
388      * default is 'java'. The command is resolved by
389      * java.lang.Runtime.exec(). Ignored if fork is disabled.
390      *
391      * @param   value   the new VM to use instead of <tt>java</tt>
392      * @see #setFork(boolean)
393      *
394      * @since Ant 1.2
395      */
396     public void setJvm(String value) {
397         getCommandline().setVm(value);
398     }
399 
400     /***
401      * Adds a JVM argument; ignored if not forking.
402      *
403      * @return create a new JVM argument so that any argument can be
404      * passed to the JVM.
405      * @see #setFork(boolean)
406      *
407      * @since Ant 1.2
408      */
409     public Commandline.Argument createJvmarg() {
410         return getCommandline().createVmArgument();
411     }
412 
413     /***
414      * The directory to invoke the VM in. Ignored if no JVM is forked.
415      * @param   dir     the directory to invoke the JVM from.
416      * @see #setFork(boolean)
417      *
418      * @since Ant 1.2
419      */
420     public void setDir(File dir) {
421         this.dir = dir;
422     }
423 
424     /***
425      * Adds a system property that tests can access.
426      * This might be useful to tranfer Ant properties to the
427      * testcases when JVM forking is not enabled.
428      *
429      * @since Ant 1.3
430      * @deprecated since ant 1.6
431      * @param sysp environment variable to add
432      */
433     public void addSysproperty(Environment.Variable sysp) {
434 
435         getCommandline().addSysproperty(sysp);
436     }
437 
438     /***
439      * Adds a system property that tests can access.
440      * This might be useful to tranfer Ant properties to the
441      * testcases when JVM forking is not enabled.
442      * @param sysp new environment variable to add
443      * @since Ant 1.6
444      */
445     public void addConfiguredSysproperty(Environment.Variable sysp) {
446         // get a build exception if there is a missing key or value
447         // see bugzilla report 21684
448         String testString = sysp.getContent();
449         getProject().log("sysproperty added : " + testString, Project.MSG_DEBUG);
450         getCommandline().addSysproperty(sysp);
451     }
452 
453     /***
454      * Adds a set of properties that will be used as system properties
455      * that tests can access.
456      *
457      * This might be useful to tranfer Ant properties to the
458      * testcases when JVM forking is not enabled.
459      *
460      * @param sysp set of properties to be added
461      * @since Ant 1.6
462      */
463     public void addSyspropertyset(PropertySet sysp) {
464         getCommandline().addSyspropertyset(sysp);
465     }
466 
467     /***
468      * Adds path to classpath used for tests.
469      *
470      * @return reference to the classpath in the embedded java command line
471      * @since Ant 1.2
472      */
473     public Path createClasspath() {
474         return getCommandline().createClasspath(getProject()).createPath();
475     }
476 
477     /***
478      * Adds a path to the bootclasspath.
479      * @return reference to the bootclasspath in the embedded java command line
480      * @since Ant 1.6
481      */
482     public Path createBootclasspath() {
483         return getCommandline().createBootclasspath(getProject()).createPath();
484     }
485 
486     /***
487      * Adds an environment variable; used when forking.
488      *
489      * <p>Will be ignored if we are not forking a new VM.</p>
490      * @param var environment variable to be added
491      * @since Ant 1.5
492      */
493     public void addEnv(Environment.Variable var) {
494         env.addVariable(var);
495     }
496 
497     /***
498      * If true, use a new environment when forked.
499      *
500      * <p>Will be ignored if we are not forking a new VM.</p>
501      *
502      * @param newenv boolean indicating if setting a new environment is wished
503      * @since Ant 1.5
504      */
505     public void setNewenvironment(boolean newenv) {
506         newEnvironment = newenv;
507     }
508 
509     /***
510      * Add a new single testcase.
511      * @param   test    a new single testcase
512      * @see JUnitTest
513      *
514      * @since Ant 1.2
515      */
516     public void addTest(JUnitTest test) {
517         tests.addElement(test);
518     }
519 
520     /***
521      * Adds a set of tests based on pattern matching.
522      *
523      * @return  a new instance of a batch test.
524      * @see BatchTest
525      *
526      * @since Ant 1.2
527      */
528     public BatchTest createBatchTest() {
529         BatchTest test = new BatchTest(getProject());
530         batchTests.addElement(test);
531         return test;
532     }
533 
534     /***
535      * Add a new formatter to all tests of this task.
536      *
537      * @param fe formatter element
538      * @since Ant 1.2
539      */
540     public void addFormatter(FormatterElement fe) {
541         formatters.addElement(fe);
542     }
543 
544     /***
545      * If true, include ant.jar, optional.jar and junit.jar in the forked VM.
546      *
547      * @param b include ant run time yes or no
548      * @since Ant 1.5
549      */
550     public void setIncludeantruntime(boolean b) {
551         includeAntRuntime = b;
552     }
553 
554     /***
555      * If true, send any output generated by tests to Ant's logging system
556      * as well as to the formatters.
557      * By default only the formatters receive the output.
558      *
559      * <p>Output will always be passed to the formatters and not by
560      * shown by default.  This option should for example be set for
561      * tests that are interactive and prompt the user to do
562      * something.</p>
563      *
564      * @param showOutput if true, send output to Ant's logging system too
565      * @since Ant 1.5
566      */
567     public void setShowOutput(boolean showOutput) {
568         this.showOutput = showOutput;
569     }
570 
571     /***
572      * Assertions to enable in this program (if fork=true)
573      * @since Ant 1.6
574      * @param asserts assertion set
575      */
576     public void addAssertions(Assertions asserts) {
577         if (getCommandline().getAssertions() != null) {
578             throw new BuildException("Only one assertion declaration is allowed");
579         }
580         getCommandline().setAssertions(asserts);
581     }
582 
583     /***
584      * Sets the permissions for the application run inside the same JVM.
585      * @since Ant 1.6
586      * @return .
587      */
588     public Permissions createPermissions() {
589         if (perm == null) {
590             perm = new Permissions();
591         }
592         return perm;
593     }
594 
595     /***
596      * Creates a new JUnitRunner and enables fork of a new Java VM.
597      *
598      * @throws Exception under ??? circumstances
599      * @since Ant 1.2
600      */
601     public JUnitTask() throws Exception {
602         getCommandline()
603             .setClassname("org.woopi.ant.taskdefs.junit.JUnitTestRunner");
604     }
605 
606     /***
607      * Where Ant should place temporary files.
608      *
609      * @param tmpDir location where temporary files should go to
610      * @since Ant 1.6
611      */
612     public void setTempdir(File tmpDir) {
613         if (tmpDir!=null) {
614             if (!tmpDir.exists() || !tmpDir.isDirectory()) {
615                 throw new BuildException(tmpDir.toString()
616                                          +" is not a valid temp directory");
617             }
618         }
619         this.tmpDir = tmpDir;
620     }
621 
622     /***
623      * Adds the jars or directories containing Ant, this task and
624      * JUnit to the classpath - this should make the forked JVM work
625      * without having to specify them directly.
626      *
627      * @since Ant 1.4
628      */
629     public void init() {
630         antRuntimeClasses = new Path(getProject());
631         addClasspathEntry("/junit/framework/TestCase.class");
632         addClasspathEntry("/org/apache/tools/ant/launch/AntMain.class");
633         addClasspathEntry("/org/apache/tools/ant/Task.class");
634         addClasspathEntry("/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.class");
635     }
636 
637     /***
638      * Runs the testcase.
639      *
640      * @throws BuildException in case of test failures or errors
641      * @since Ant 1.2
642      */
643     public void execute() throws BuildException {
644         List testLists = new ArrayList();
645 
646         boolean forkPerTest = forkMode.getValue().equals(ForkMode.PER_TEST);
647         if (forkPerTest || forkMode.getValue().equals(ForkMode.ONCE)) {
648             testLists.addAll(executeOrQueue(getIndividualTests(),
649                                             forkPerTest));
650         } else { /* forkMode.getValue().equals(ForkMode.PER_BATCH) */
651             final int count = batchTests.size();
652             for (int i = 0; i < count; i++) {
653                 BatchTest batchtest = (BatchTest) batchTests.elementAt(i);
654                 testLists.addAll(executeOrQueue(batchtest.elements(), false));
655             }
656             testLists.addAll(executeOrQueue(tests.elements(), forkPerTest));
657         }
658 
659         Iterator iter = testLists.iterator();
660         while (iter.hasNext()) {
661             List l = (List) iter.next();
662             if (l.size() == 1) {
663                 execute((JUnitTest) l.get(0));
664             } else {
665                 execute(l);
666             }            
667         }
668     }
669 
670     /***
671      * Run the tests.
672      * @param arg one JunitTest
673      * @throws BuildException in case of test failures or errors
674      */
675     protected void execute(JUnitTest arg) throws BuildException {
676         JUnitTest test = (JUnitTest) arg.clone();
677         // set the default values if not specified
678         //@todo should be moved to the test class instead.
679         if (test.getTodir() == null) {
680             test.setTodir(getProject().resolveFile("."));
681         }
682 
683         if (test.getOutfile() == null) {
684             test.setOutfile("TEST-" + test.getName());
685         }
686 
687         // execute the test and get the return code
688         int exitValue = JUnitTestRunner.ERRORS;
689         boolean wasKilled = false;
690         if (!test.getFork()) {
691             exitValue = executeInVM(test);
692         } else {
693             ExecuteWatchdog watchdog = createWatchdog();
694             exitValue = executeAsForked(test, watchdog, null);
695             // null watchdog means no timeout, you'd better not check with null
696             if (watchdog != null) {
697                 wasKilled = watchdog.killedProcess();
698             }
699         }
700         actOnTestResult(exitValue, wasKilled, test, "Test " + test.getName());
701     }
702 
703     /***
704      * Execute a list of tests in a single forked Java VM.
705      */
706     protected void execute(List tests) throws BuildException {
707         JUnitTest test = null;
708         // Create a temporary file to pass the test cases to run to 
709         // the runner (one test case per line)
710         File casesFile = createTempPropertiesFile("junittestcases");
711         PrintWriter writer = null;
712         try {
713             writer = 
714                 new PrintWriter(new BufferedWriter(new FileWriter(casesFile)));
715             Iterator iter = tests.iterator();
716             while (iter.hasNext()) {
717                 test = (JUnitTest) iter.next();
718                 writer.print(test.getName()); 
719                 if (test.getTodir() == null) {
720                     writer.print("," + getProject().resolveFile("."));
721                 } else {
722                     writer.print("," + test.getTodir());
723                 }
724 
725                 if (test.getOutfile() == null) {
726                     writer.println("," + "TEST-" + test.getName());
727                 } else {
728                     writer.println("," + test.getOutfile());
729                 }
730             }
731             writer.flush();
732             writer.close();
733             writer = null;
734 
735             // execute the test and get the return code
736             int exitValue = JUnitTestRunner.ERRORS;
737             boolean wasKilled = false;
738             ExecuteWatchdog watchdog = createWatchdog();
739             exitValue = executeAsForked(test, watchdog, casesFile);
740             // null watchdog means no timeout, you'd better not check
741             // with null
742             if (watchdog != null) {
743                 wasKilled = watchdog.killedProcess();
744             }
745             actOnTestResult(exitValue, wasKilled, test, "Tests");
746         } catch(IOException e) {
747             log(e.toString(), Project.MSG_ERR);
748             throw new BuildException(e);
749         } finally {
750             if (writer != null) {
751                 writer.close();
752             }
753             
754             try {
755                 casesFile.delete();
756             } catch (Exception e) {
757                 log(e.toString(), Project.MSG_ERR);
758             }
759         }
760     }
761 
762     /***
763      * Execute a testcase by forking a new JVM. The command will block until
764      * it finishes. To know if the process was destroyed or not, use the
765      * <tt>killedProcess()</tt> method of the watchdog class.
766      * @param  test       the testcase to execute.
767      * @param  watchdog   the watchdog in charge of cancelling the test if it
768      * exceeds a certain amount of time. Can be <tt>null</tt>, in this case
769      * the test could probably hang forever.
770      * @throws BuildException in case of error creating a temporary property file,
771      * or if the junit process can not be forked
772      */
773     private int executeAsForked(JUnitTest test, ExecuteWatchdog watchdog, 
774                                 File casesFile)
775         throws BuildException {
776 
777         if (perm != null) {
778             log("Permissions ignored when running in forked mode!",
779                 Project.MSG_WARN);
780         }
781 
782         CommandlineJava cmd = (CommandlineJava) getCommandline().clone();
783 
784         cmd.setClassname("org.woopi.ant.taskdefs.junit.JUnitTestRunner");
785         if (casesFile == null) {
786             cmd.createArgument().setValue(test.getName());
787         } else {
788             log("Running multiple tests in the same VM", Project.MSG_VERBOSE);
789             cmd.createArgument().setValue("testsfile=" + casesFile);
790         }
791         
792         cmd.createArgument().setValue("filtertrace=" + test.getFiltertrace());
793         cmd.createArgument().setValue("haltOnError=" + test.getHaltonerror());
794         cmd.createArgument().setValue("haltOnFailure="
795                                       + test.getHaltonfailure());
796         if (includeAntRuntime) {
797             Vector v = Execute.getProcEnvironment();
798             Enumeration e = v.elements();
799             while (e.hasMoreElements()) {
800                 String s = (String) e.nextElement();
801                 if (s.startsWith("CLASSPATH=")) {
802                     cmd.createClasspath(getProject()).createPath()
803                         .append(new Path(getProject(),
804                                          s.substring(10 // "CLASSPATH=".length()
805                                                      )));
806                 }
807             }
808             log("Implicitly adding " + antRuntimeClasses + " to CLASSPATH",
809                 Project.MSG_VERBOSE);
810             cmd.createClasspath(getProject()).createPath()
811                 .append(antRuntimeClasses);
812         }
813 
814         if (summary) {
815             log("Running " + test.getName(), Project.MSG_INFO);
816             cmd.createArgument()
817                 .setValue("formatter"
818                 + "=org.woopi.ant.taskdefs.junit.SummaryJUnitResultFormatter");
819         }
820 
821         cmd.createArgument().setValue("showoutput="
822                                       + String.valueOf(showOutput));
823 
824         StringBuffer formatterArg = new StringBuffer(STRING_BUFFER_SIZE);
825         final FormatterElement[] feArray = mergeFormatters(test);
826         for (int i = 0; i < feArray.length; i++) {
827             FormatterElement fe = feArray[i];
828             if (fe.shouldUse(this)) {
829                 formatterArg.append("formatter=");
830                 formatterArg.append(fe.getClassname());
831                 File outFile = getOutput(fe, test);
832                 if (outFile != null) {
833                     formatterArg.append(",");
834                     formatterArg.append(outFile);
835                 }
836                 cmd.createArgument().setValue(formatterArg.toString());
837                 formatterArg = new StringBuffer();
838             }
839         }
840 
841 
842         File propsFile = createTempPropertiesFile("junit");
843         cmd.createArgument().setValue("propsfile="
844                                       + propsFile.getAbsolutePath());
845         Hashtable p = getProject().getProperties();
846         Properties props = new Properties();
847         for (Enumeration e = p.keys(); e.hasMoreElements();) {
848             Object key = e.nextElement();
849             props.put(key, p.get(key));
850         }
851         try {
852             FileOutputStream outstream = new FileOutputStream(propsFile);
853             props.store(outstream, "Ant JUnitTask generated properties file");
854             outstream.close();
855         } catch (java.io.IOException e) {
856             propsFile.delete();
857             throw new BuildException("Error creating temporary properties "
858                                      + "file.", e, getLocation());
859         }
860 
861         Execute execute = new Execute(new LogStreamHandler(this,
862                                                            Project.MSG_INFO,
863                                                            Project.MSG_WARN),
864                                       watchdog);
865         execute.setCommandline(cmd.getCommandline());
866         execute.setAntRun(getProject());
867         if (dir != null) {
868             execute.setWorkingDirectory(dir);
869         }
870 
871         String[] environment = env.getVariables();
872         if (environment != null) {
873             for (int i = 0; i < environment.length; i++) {
874                 log("Setting environment variable: " + environment[i],
875                     Project.MSG_VERBOSE);
876             }
877         }
878         execute.setNewenvironment(newEnvironment);
879         execute.setEnvironment(environment);
880 
881         log(cmd.describeCommand(), Project.MSG_VERBOSE);
882         int retVal;
883         try {
884             retVal = execute.execute();
885         } catch (IOException e) {
886             throw new BuildException("Process fork failed.", e, getLocation());
887         } finally {
888             if (watchdog != null && watchdog.killedProcess()) {
889                 logTimeout(feArray, test);
890             }
891 
892             if (!propsFile.delete()) {
893                 throw new BuildException("Could not delete temporary "
894                                          + "properties file.");
895             }
896         }
897 
898         return retVal;
899     }
900 
901     /***
902      * Create a temporary file to pass the properties to a new process.
903      * Will auto-delete on (graceful) exit.
904      * The file will be in the project basedir unless tmpDir declares
905      * something else.
906      * @param prefix
907      * @return
908      */
909     private File createTempPropertiesFile(String prefix) {
910         File propsFile =
911             FileUtils.newFileUtils().createTempFile(prefix, ".properties",
912                 tmpDir != null ? tmpDir : getProject().getBaseDir());
913         propsFile.deleteOnExit();
914         return propsFile;
915     }
916 
917 
918     /***
919      * Pass output sent to System.out to the TestRunner so it can
920      * collect ot for the formatters.
921      *
922      * @param output output coming from System.out
923      * @since Ant 1.5
924      */
925     protected void handleOutput(String output) {
926         if (runner != null) {
927             runner.handleOutput(output);
928             if (showOutput) {
929                 super.handleOutput(output);
930             }
931         } else {
932             super.handleOutput(output);
933         }
934     }
935 
936     /***
937      * @see Task#handleInput(byte[], int, int)
938      *
939      * @since Ant 1.6
940      */
941     protected int handleInput(byte[] buffer, int offset, int length)
942         throws IOException {
943         if (runner != null) {
944             return runner.handleInput(buffer, offset, length);
945         } else {
946             return super.handleInput(buffer, offset, length);
947         }
948     }
949 
950 
951     /***
952      * Pass output sent to System.out to the TestRunner so it can
953      * collect ot for the formatters.
954      *
955      * @param output output coming from System.out
956      * @since Ant 1.5.2
957      */
958     protected void handleFlush(String output) {
959         if (runner != null) {
960             runner.handleFlush(output);
961             if (showOutput) {
962                 super.handleFlush(output);
963             }
964         } else {
965             super.handleFlush(output);
966         }
967     }
968 
969     /***
970      * Pass output sent to System.err to the TestRunner so it can
971      * collect it for the formatters.
972      *
973      * @param output output coming from System.err
974      * @since Ant 1.5
975      */
976     public void handleErrorOutput(String output) {
977         if (runner != null) {
978             runner.handleErrorOutput(output);
979             if (showOutput) {
980                 super.handleErrorOutput(output);
981             }
982         } else {
983             super.handleErrorOutput(output);
984         }
985     }
986 
987 
988     /***
989      * Pass output sent to System.err to the TestRunner so it can
990      * collect it for the formatters.
991      *
992      * @param output coming from System.err
993      * @since Ant 1.5.2
994      */
995     public void handleErrorFlush(String output) {
996         if (runner != null) {
997             runner.handleErrorFlush(output);
998             if (showOutput) {
999                 super.handleErrorFlush(output);
1000             }
1001         } else {
1002             super.handleErrorFlush(output);
1003         }
1004     }
1005 
1006     // in VM is not very nice since it could probably hang the
1007     // whole build. IMHO this method should be avoided and it would be best
1008     // to remove it in future versions. TBD. (SBa)
1009 
1010     /***
1011      * Execute inside VM.
1012      * @param arg one JUnitTest
1013      * @throws BuildException under unspecified circumstances
1014      */
1015     private int executeInVM(JUnitTest arg) throws BuildException {
1016         JUnitTest test = (JUnitTest) arg.clone();
1017         test.setProperties(getProject().getProperties());
1018         if (dir != null) {
1019             log("dir attribute ignored if running in the same VM",
1020                 Project.MSG_WARN);
1021         }
1022 
1023         if (newEnvironment || null != env.getVariables()) {
1024             log("Changes to environment variables are ignored if running in "
1025                 + "the same VM.", Project.MSG_WARN);
1026         }
1027 
1028         if (getCommandline().getBootclasspath() != null) {
1029             log("bootclasspath is ignored if running in the same VM.",
1030                 Project.MSG_WARN);
1031         }
1032 
1033         CommandlineJava.SysProperties sysProperties =
1034                 getCommandline().getSystemProperties();
1035         if (sysProperties != null) {
1036             sysProperties.setSystem();
1037         }
1038 
1039         try {
1040             log("Using System properties " + System.getProperties(),
1041                 Project.MSG_VERBOSE);
1042             createClassLoader();
1043             if (classLoader != null) {
1044                 classLoader.setThreadContextLoader();
1045             }
1046             runner = new JUnitTestRunner(test, test.getHaltonerror(),
1047                                          test.getFiltertrace(),
1048                                          test.getHaltonfailure(), classLoader);
1049             if (summary) {
1050                 log("Running " + test.getName(), Project.MSG_INFO);
1051 
1052                 SummaryJUnitResultFormatter f =
1053                     new SummaryJUnitResultFormatter();
1054                 f.setWithOutAndErr("withoutanderr"
1055                                    .equalsIgnoreCase(summaryValue));
1056                 f.setOutput(getDefaultOutput());
1057                 runner.addFormatter(f);
1058             }
1059 
1060             runner.setPermissions(perm);
1061 
1062             final FormatterElement[] feArray = mergeFormatters(test);
1063             for (int i = 0; i < feArray.length; i++) {
1064                 FormatterElement fe = feArray[i];
1065                 if (fe.shouldUse(this)) {
1066                     File outFile = getOutput(fe, test);
1067                     if (outFile != null) {
1068                         fe.setOutfile(outFile);
1069                     } else {
1070                         fe.setOutput(getDefaultOutput());
1071                     }
1072                     runner.addFormatter(fe.createFormatter(classLoader));
1073                 }
1074             }
1075 
1076             runner.run();
1077             return runner.getRetCode();
1078         } finally {
1079             if (sysProperties != null) {
1080                 sysProperties.restoreSystem();
1081             }
1082             if (classLoader != null) {
1083                 classLoader.resetThreadContextLoader();
1084             }
1085         }
1086     }
1087 
1088     /***
1089      * @return <tt>null</tt> if there is a timeout value, otherwise the
1090      * watchdog instance.
1091      *
1092      * @throws BuildException under unspecified circumstances
1093      * @since Ant 1.2
1094      */
1095     protected ExecuteWatchdog createWatchdog() throws BuildException {
1096         if (timeout == null) {
1097             return null;
1098         }
1099         return new ExecuteWatchdog((long) timeout.intValue());
1100     }
1101 
1102     /***
1103      * Get the default output for a formatter.
1104      *
1105      * @return default output stream for a formatter
1106      * @since Ant 1.3
1107      */
1108     protected OutputStream getDefaultOutput() {
1109         return new LogOutputStream(this, Project.MSG_INFO);
1110     }
1111 
1112     /***
1113      * Merge all individual tests from the batchtest with all individual tests
1114      * and return an enumeration over all <tt>JUnitTest</tt>.
1115      *
1116      * @return enumeration over individual tests
1117      * @since Ant 1.3
1118      */
1119     protected Enumeration getIndividualTests() {
1120         final int count = batchTests.size();
1121         final Enumeration[] enums = new Enumeration[ count + 1];
1122         for (int i = 0; i < count; i++) {
1123             BatchTest batchtest = (BatchTest) batchTests.elementAt(i);
1124             enums[i] = batchtest.elements();
1125         }
1126         enums[enums.length - 1] = tests.elements();
1127         return Enumerations.fromCompound(enums);
1128     }
1129 
1130     /***
1131      * return an enumeration listing each test, then each batchtest
1132      * @return enumeration
1133      * @since Ant 1.3
1134      */
1135     protected Enumeration allTests() {
1136         Enumeration[] enums = {tests.elements(), batchTests.elements()};
1137         return Enumerations.fromCompound(enums);
1138     }
1139 
1140     /***
1141      * @param test junit test
1142      * @return array of FormatterElement
1143      * @since Ant 1.3
1144      */
1145     private FormatterElement[] mergeFormatters(JUnitTest test) {
1146         Vector feVector = (Vector) formatters.clone();
1147         test.addFormattersTo(feVector);
1148         FormatterElement[] feArray = new FormatterElement[feVector.size()];
1149         feVector.copyInto(feArray);
1150         return feArray;
1151     }
1152 
1153     /***
1154      * If the formatter sends output to a file, return that file.
1155      * null otherwise.
1156      * @param fe  formatter element
1157      * @param test one JUnit test
1158      * @return file reference
1159      * @since Ant 1.3
1160      */
1161     protected File getOutput(FormatterElement fe, JUnitTest test) {
1162         if (fe.getUseFile()) {
1163             String filename = test.getOutfile() + fe.getExtension();
1164             File destFile = new File(test.getTodir(), filename);
1165             String absFilename = destFile.getAbsolutePath();
1166             return getProject().resolveFile(absFilename);
1167         }
1168         return null;
1169     }
1170 
1171     /***
1172      * Search for the given resource and add the directory or archive
1173      * that contains it to the classpath.
1174      *
1175      * <p>Doesn't work for archives in JDK 1.1 as the URL returned by
1176      * getResource doesn't contain the name of the archive.</p>
1177      *
1178      * @param resource resource that one wants to lookup
1179      * @since Ant 1.4
1180      */
1181     protected void addClasspathEntry(String resource) {
1182         /*
1183          * pre Ant 1.6 this method used to call getClass().getResource
1184          * while Ant 1.6 will call ClassLoader.getResource().
1185          *
1186          * The difference is that Class.getResource expects a leading
1187          * slash for "absolute" resources and will strip it before
1188          * delegating to ClassLoader.getResource - so we now have to
1189          * emulate Class's behavior.
1190          */
1191         if (resource.startsWith("/")) {
1192             resource = resource.substring(1);
1193         } else {
1194             resource = "org/apache/tools/ant/taskdefs/optional/junit/"
1195                 + resource;
1196         }
1197 
1198         File f = LoaderUtils.getResourceSource(getClass().getClassLoader(),
1199                                                resource);
1200         if (f != null) {
1201             log("Found " + f.getAbsolutePath(), Project.MSG_DEBUG);
1202             antRuntimeClasses.createPath().setLocation(f);
1203         } else {
1204             log("Couldn\'t find " + resource, Project.MSG_DEBUG);
1205         }
1206     }
1207 
1208     /***
1209      * Take care that some output is produced in report files if the
1210      * watchdog kills the test.
1211      *
1212      * @since Ant 1.5.2
1213      */
1214 
1215     private void logTimeout(FormatterElement[] feArray, JUnitTest test) {
1216         createClassLoader();
1217         test.setCounts(1, 0, 1);
1218         test.setProperties(getProject().getProperties());
1219         for (int i = 0; i < feArray.length; i++) {
1220             FormatterElement fe = feArray[i];
1221             File outFile = getOutput(fe, test);
1222             JUnitResultFormatter formatter = fe.createFormatter(classLoader);
1223             if (outFile != null && formatter != null) {
1224                 try {
1225                     OutputStream out = new FileOutputStream(outFile);
1226                     addTimeout(test, formatter, out);
1227                 } catch (IOException e) {
1228                     // ignore
1229                 }
1230             }
1231         }
1232         if (summary) {
1233             SummaryJUnitResultFormatter f = new SummaryJUnitResultFormatter();
1234             f.setWithOutAndErr("withoutanderr".equalsIgnoreCase(summaryValue));
1235             addTimeout(test, f, getDefaultOutput());
1236         }
1237     }
1238 
1239     /***
1240      * Adds the actual timeout to the formatter.
1241      * Only used from the logTimeout method.
1242      * @since Ant 1.6
1243      */
1244     private void addTimeout(JUnitTest test, JUnitResultFormatter formatter,
1245                             OutputStream out) {
1246         formatter.setOutput(out);
1247         formatter.startTestSuite(test);
1248 
1249         //the trick to integrating test output to the formatter, is to
1250         //create a special test class that asserts a timout occurred,
1251         //and tell the formatter that it raised.  
1252         Test t = new Test() {
1253             public int countTestCases() { return 1; }
1254             public void run(TestResult r) {
1255                 throw new AssertionFailedError("Timeout occurred");
1256             }
1257         };
1258         formatter.startTest(t);
1259         formatter.addError(t, new AssertionFailedError("Timeout occurred"));
1260         formatter.endTestSuite(test);
1261     }
1262 
1263     /***
1264      * Creates and configures an AntClassLoader instance from the
1265      * nested classpath element.
1266      *
1267      * @since Ant 1.6
1268      */
1269     private void createClassLoader() {
1270         Path userClasspath = getCommandline().getClasspath();
1271         if (userClasspath != null) {
1272             if (reloading || classLoader == null) {
1273                 Path classpath = (Path) userClasspath.clone();
1274                 if (includeAntRuntime) {
1275                     log("Implicitly adding " + antRuntimeClasses
1276                         + " to CLASSPATH", Project.MSG_VERBOSE);
1277                     classpath.append(antRuntimeClasses);
1278                 }
1279                 classLoader = getProject().createClassLoader(classpath);
1280                 log("Using CLASSPATH " + classLoader.getClasspath(),
1281                 Project.MSG_VERBOSE);
1282                 classLoader.setParentFirst(false);
1283                 classLoader.addJavaLibraries();
1284                 log("Using CLASSPATH " + classLoader.getClasspath(), Project.MSG_VERBOSE);
1285                 // make sure the test will be accepted as a TestCase
1286                 classLoader.addSystemPackageRoot("junit");
1287                 // will cause trouble in JDK 1.1 if omitted
1288                 classLoader.addSystemPackageRoot("org.apache.tools.ant");
1289             }
1290         }
1291     }
1292 
1293     /***
1294      * @since Ant 1.6.2
1295      */
1296     protected CommandlineJava getCommandline() {
1297         if (commandline == null) {
1298             commandline = new CommandlineJava();
1299         }
1300         return commandline;
1301     }
1302 
1303     /***
1304      * Forked test support
1305      * @since Ant 1.6.2
1306      */
1307     private final class ForkedTestConfiguration {
1308         private boolean filterTrace;
1309         private boolean haltOnError;
1310         private boolean haltOnFailure;
1311         private String errorProperty;
1312         private String failureProperty;
1313 
1314         /***
1315          * constructor for forked test configuration
1316          * @param filterTrace
1317          * @param haltOnError
1318          * @param haltOnFailure
1319          * @param errorProperty
1320          * @param failureProperty
1321          */
1322         ForkedTestConfiguration(boolean filterTrace, boolean haltOnError,
1323                                 boolean haltOnFailure, String errorProperty,
1324                                 String failureProperty) {
1325             this.filterTrace = filterTrace;
1326             this.haltOnError = haltOnError;
1327             this.haltOnFailure = haltOnFailure;
1328             this.errorProperty = errorProperty;
1329             this.failureProperty = failureProperty;
1330         }
1331 
1332         /***
1333          * configure from a test; sets member variables to attributes of the test
1334          * @param test
1335          */
1336         ForkedTestConfiguration(JUnitTest test) {
1337             this(test.getFiltertrace(),
1338                     test.getHaltonerror(),
1339                     test.getHaltonfailure(),
1340                     test.getErrorProperty(),
1341                     test.getFailureProperty());
1342         }
1343 
1344         /***
1345          * equality test checks all the member variables
1346          * @param other
1347          * @return true if everything is equal
1348          */
1349         public boolean equals(Object other) {
1350             if (other == null 
1351                 || other.getClass() != ForkedTestConfiguration.class) {
1352                 return false;
1353             }
1354             ForkedTestConfiguration o = (ForkedTestConfiguration) other;
1355             return filterTrace == o.filterTrace 
1356                 && haltOnError == o.haltOnError
1357                 && haltOnFailure == o.haltOnFailure
1358                 && ((errorProperty == null && o.errorProperty == null)
1359                     || 
1360                     (errorProperty != null 
1361                      && errorProperty.equals(o.errorProperty)))
1362                 && ((failureProperty == null && o.failureProperty == null)
1363                     || 
1364                     (failureProperty != null 
1365                      && failureProperty.equals(o.failureProperty)));
1366         }
1367 
1368         /***
1369          * hashcode is based only on the boolean members, and returns a value
1370          * in the range 0-7.
1371          * @return
1372          */
1373         public int hashCode() {
1374             return (filterTrace ? 1 : 0) 
1375                 + (haltOnError ? 2 : 0)
1376                 + (haltOnFailure ? 4 : 0);
1377         }
1378     }
1379 
1380     /***
1381      * These are the different forking options
1382      * @since 1.6.2
1383      */
1384     public static final class ForkMode extends EnumeratedAttribute {
1385 
1386         /***
1387          * fork once only
1388          */
1389         public static final String ONCE = "once";
1390         /***
1391          * fork once per test class
1392          */
1393         public static final String PER_TEST = "perTest";
1394         /***
1395          * fork once per batch of tests
1396          */
1397         public static final String PER_BATCH = "perBatch";
1398 
1399         public ForkMode() {
1400             super();
1401         }
1402 
1403         public ForkMode(String value) {
1404             super();
1405             setValue(value);
1406         }
1407 
1408         public String[] getValues() {
1409             return new String[] {ONCE, PER_TEST, PER_BATCH};
1410         }
1411     }
1412 
1413     /***
1414      * Executes all tests that don't need to be forked (or all tests
1415      * if the runIndividual argument is true.  Returns a collection of
1416      * lists of tests that share the same VM configuration and haven't
1417      * been executed yet.
1418      *
1419      * @since 1.6.2
1420      */
1421     protected Collection executeOrQueue(Enumeration testList,
1422                                         boolean runIndividual) {
1423         Map testConfigurations = new HashMap();
1424         while (testList.hasMoreElements()) {
1425             JUnitTest test = (JUnitTest) testList.nextElement();
1426             if (test.shouldRun(getProject())) {
1427                 if (runIndividual || !test.getFork()) {
1428                     execute(test);
1429                 } else {
1430                     ForkedTestConfiguration c =
1431                         new ForkedTestConfiguration(test);
1432                     List l = (List) testConfigurations.get(c);
1433                     if (l == null) {
1434                         l = new ArrayList();
1435                         testConfigurations.put(c, l);
1436                     }
1437                     l.add(test);
1438                 }
1439             }
1440         }
1441         return testConfigurations.values();
1442     }
1443 
1444     /***
1445      * Logs information about failed tests, potentially stops
1446      * processing (by throwing a BuildException) if a failure/error
1447      * occured or sets a property.
1448      *
1449      * @since Ant 1.6.2
1450      */
1451     protected void actOnTestResult(int exitValue, boolean wasKilled,
1452                                    JUnitTest test, String name) {
1453         // if there is an error/failure and that it should halt, stop
1454         // everything otherwise just log a statement
1455         boolean errorOccurredHere =
1456             exitValue == JUnitTestRunner.ERRORS || wasKilled;
1457         boolean failureOccurredHere =
1458             exitValue != JUnitTestRunner.SUCCESS || wasKilled;
1459         if (errorOccurredHere || failureOccurredHere) {
1460             if ((errorOccurredHere && test.getHaltonerror())
1461                 || (failureOccurredHere && test.getHaltonfailure())) {
1462                 throw new BuildException(name + " failed"
1463                     + (wasKilled ? " (timeout)" : ""), getLocation());
1464             } else {
1465                 log(name + " FAILED"
1466                     + (wasKilled ? " (timeout)" : ""), Project.MSG_ERR);
1467                 if (errorOccurredHere && test.getErrorProperty() != null) {
1468                     getProject().setNewProperty(test.getErrorProperty(), "true");
1469                 }
1470                 if (failureOccurredHere && test.getFailureProperty() != null) {
1471                     getProject().setNewProperty(test.getFailureProperty(), "true");
1472                 }
1473             }
1474         }
1475     }
1476 
1477 }