1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 * <target name="test-int-chars" depends="jar-test">
69 * <echo message="testing international characters"/>
70 * <junit printsummary="no" haltonfailure="yes" fork="false">
71 * <classpath refid="classpath"/>
72 * <formatter type="plain" usefile="false" />
73 * <test name="org.apache.ecs.InternationalCharTest" />
74 * </junit>
75 * </target>
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 * <target name="run-tests" depends="dump-info,compile-tests" if="junit.present">
90 * <junit printsummary="no" haltonfailure="yes" fork="${junit.fork}">
91 * <jvmarg value="-classic"/>
92 * <classpath refid="tests-classpath"/>
93 * <sysproperty key="build.tests" value="${build.tests}"/>
94 * <formatter type="brief" usefile="false" />
95 * <batchtest>
96 * <fileset dir="${tests.dir}">
97 * <include name="**/*Test*" />
98 * </fileset>
99 * </batchtest>
100 * </junit>
101 * </target>
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><junit></code> and
108 * <code><batch></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 * <batchtest> 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
304
305
306
307
308
309
310
311
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'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
447
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 {
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
678
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
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
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
709
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
736 int exitValue = JUnitTestRunner.ERRORS;
737 boolean wasKilled = false;
738 ExecuteWatchdog watchdog = createWatchdog();
739 exitValue = executeAsForked(test, watchdog, casesFile);
740
741
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
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
1007
1008
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
1184
1185
1186
1187
1188
1189
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
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
1250
1251
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
1286 classLoader.addSystemPackageRoot("junit");
1287
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
1454
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 }