001: /* BaseTestRunner.java */
002:
003: package org.quilt.runner;
004:
005: import java.io.ByteArrayOutputStream;
006: import java.io.File;
007: import java.io.FileInputStream;
008: import java.io.IOException;
009: import java.io.OutputStream;
010: import java.io.PrintStream;
011:
012: import java.lang.reflect.Method;
013: import java.util.Enumeration;
014: import java.util.Properties;
015: import java.util.Vector;
016:
017: import org.apache.tools.ant.AntClassLoader;
018: import org.apache.tools.ant.BuildException;
019: import org.apache.tools.ant.Project;
020:
021: import junit.framework.*;
022:
023: import org.quilt.cl.*;
024: import org.quilt.framework.QuiltTest;
025: import org.quilt.reports.*;
026:
027: /**
028: * Stand-along Quilt test runner, fully compatible with Ant's JUnit
029: * options. Accepts options from Ant via QuiltTask; can also be run
030: * from the command line using TestRunner.
031: *
032: * @see QuiltTask
033: * @see QuiltTest
034: * @see TestRunner
035: */
036:
037: public class BaseTestRunner extends Runner {
038:
039: /** The QuiltTest we are currently running. */
040: private QuiltTest qt;
041:
042: /** The JUnit test suite for this QuiltTest. Built by the constructor.*/
043: private Test suite = null;
044:
045: /** Exception caught in constructor. */
046: private Exception exception = null;
047:
048: /** Status code returned by main() */
049: private int retCode = SUCCESS;
050:
051: /** JUnit test result - which has the run() method. */
052: private TestResult res;
053:
054: // RUN PARAMETERS ///////////////////////////////////////////////
055:
056: /** Formatters for this particular test. */
057: private Vector formatters = new Vector();
058:
059: /** Do we stop on errors? */
060: // private boolean haltOnError = false;
061: /** Do we stop on test failures? */
062: // private boolean haltOnFailure = false;
063: /**
064: * Do we send output to System.out/.err as well as to the formatters.
065: * XXX BUG or inconsistency in documentation.
066: */
067: // private boolean showOutput = false;
068: /** Error output during the test */
069: private PrintStream systemError;
070:
071: /** Output written during the test */
072: private PrintStream systemOut;
073:
074: // CONSTRUCTORS /////////////////////////////////////////////////
075:
076: /**
077: * Constructor used by command line test runner. And so also
078: * used when Ant forks the test runner. XXX No longer true.
079: *
080: * @param test Data structure holding parameters for a single
081: * test suite.
082: */
083: public BaseTestRunner(QuiltTest test) {
084: this (test, null);
085: }
086:
087: /**
088: * Constructor used when not using Quilt class loader. XXX Not true.
089: * Uses BaseTestRunner.retCode to signal whether construction
090: * successful or not. If the operation fails, the exception
091: * involved is passed back in BaseTestRunner.exception.
092: *
093: * @param test Data structure holding parameters for a single
094: * test suite.
095: * @param loader Class loader passed from parent.
096: */
097: public BaseTestRunner(QuiltTest test, ClassLoader loader) {
098: qt = test;
099:
100: try {
101: Class testClass = null;
102: if (loader == null) {
103: testClass = Class.forName(qt.getName());
104: } else {
105: testClass = loader.loadClass(qt.getName());
106: if (!(loader instanceof QuiltClassLoader)) {
107: // trick JVM into initializing class statics
108: AntClassLoader.initializeClass(testClass);
109: }
110: }
111:
112: Method suiteMethod = null;
113: try {
114: // check if there is a no-arg "suite" method in the class
115: suiteMethod = testClass
116: .getMethod("suite", new Class[0]);
117: } catch (Exception e) {
118: // not found
119: }
120: if (suiteMethod != null) {
121: // we somehow have a suiteMethod; try to use it to
122: // extract the suite
123: suite = (Test) suiteMethod.invoke(null, new Class[0]);
124: } else {
125: // use the JUnit TestSuite constructor to extract a
126: // test suite
127: suite = new TestSuite(testClass);
128: }
129:
130: } catch (Exception e) {
131: retCode = ERRORS;
132: exception = e;
133: }
134: }
135:
136: // /**
137: // * Constructor using Quilt class loader. Uses
138: // * BaseTestRunner.retCode to signal whether construction
139: // * successful or not. If the operation fails, the exception
140: // * involved is passed back in BaseTestRunner.exception.
141: // *
142: // * @param test Data structure holding parameters for a single
143: // * test suite.
144: // * @param loader QuiltClassLoader passed from parent.
145: // */
146: // public BaseTestRunner(QuiltTest test, QuiltClassLoader loader) {
147: // this.qt = test;
148:
149: // try {
150: // Class testClass = null;
151: // if (loader == null) {
152: // testClass = Class.forName(test.getName());
153: // } else {
154: // testClass = loader.loadClass(test.getName());
155: // // trick JVM into initializing class statics
156: // AntClassLoader.initializeClass(testClass);
157: // }
158: //
159: // Method suiteMethod = null;
160: // try {
161: // // check if there is a no-arg "suite" method in the class
162: // suiteMethod = testClass.getMethod("suite", new Class[0]);
163: // } catch (Exception e) {
164: // // not found
165: // }
166: // if (suiteMethod != null){
167: // // if there is a suite method available, then try
168: // // to extract the suite from it. If there is an error
169: // // here it will be caught below and reported.
170: // suite = (Test) suiteMethod.invoke(null, new Class[0]);
171: // } else {
172: // // use the JUnit TestSuite constructor to extract a
173: // // test suite
174: // suite = new TestSuite(testClass);
175: // }
176: //
177: // } catch (Exception e) {
178: // retCode = ERRORS;
179: // exception = e;
180: // }
181: // } // GEEP
182:
183: public void run() {
184: res = new TestResult();
185: res.addListener(this );
186: for (int i = 0; i < formatters.size(); i++) {
187: res.addListener((TestListener) formatters.elementAt(i));
188: }
189:
190: long start = System.currentTimeMillis();
191:
192: fireStartTestSuite();
193: if (exception != null) { // had an exception in the constructor
194: for (int i = 0; i < formatters.size(); i++) {
195: ((TestListener) formatters.elementAt(i)).addError(null,
196: exception);
197: }
198: qt.setCounts(1, 0, 1);
199: qt.setRunTime(0);
200: } else {
201:
202: ByteArrayOutputStream errStrm = new ByteArrayOutputStream();
203: systemError = new PrintStream(errStrm);
204:
205: ByteArrayOutputStream outStrm = new ByteArrayOutputStream();
206: systemOut = new PrintStream(outStrm);
207:
208: PrintStream savedOut = null;
209: PrintStream savedErr = null;
210:
211: if (qt.getFork()) {
212: savedOut = System.out;
213: savedErr = System.err;
214: if (!qt.getShowOutput()) {
215: System.setOut(systemOut);
216: System.setErr(systemError);
217: } else {
218: System
219: .setOut(new PrintStream(
220: new TeeOutputStream(
221: new OutputStream[] {
222: savedOut, systemOut })));
223: System.setErr(new PrintStream(
224: new TeeOutputStream(new OutputStream[] {
225: savedErr, systemError })));
226: }
227: }
228:
229: try {
230: suite.run(res);
231: } finally {
232: if (savedOut != null) {
233: System.setOut(savedOut);
234: }
235: if (savedErr != null) {
236: System.setErr(savedErr);
237: }
238:
239: systemError.close();
240: systemError = null;
241: systemOut.close();
242: systemOut = null;
243: sendOutAndErr(new String(outStrm.toByteArray()),
244: new String(errStrm.toByteArray()));
245:
246: qt.setCounts(res.runCount(), res.failureCount(), res
247: .errorCount());
248: qt.setRunTime(System.currentTimeMillis() - start);
249: }
250: }
251: fireEndTestSuite();
252:
253: if (retCode != SUCCESS || res.errorCount() != 0) {
254: retCode = ERRORS;
255: } else if (res.failureCount() != 0) {
256: retCode = FAILURES;
257: }
258: }
259:
260: /**
261: * Get status code from run.
262: *
263: * @return Status codes from RunnerConst
264: */
265: public int getRetCode() {
266: return retCode;
267: }
268:
269: /////////////////////////////////////////////////////////////////
270: // INTERFACE TESTLISTENER
271: //
272: // NEEDS TO BE CHECKED CAREFULLY
273: /////////////////////////////////////////////////////////////////
274:
275: /** Called at start of test run. */
276: public void startTest(Test t) {
277: }
278:
279: /** Called at end of test suite. */
280: public void endTest(Test test) {
281: }
282:
283: /** A test failure (or error) has occurred. */
284: public void addFailure(Test test, Throwable t) {
285: if (qt.getHaltOnFailure()) {
286: res.stop();
287: }
288: }
289:
290: /** A test failure (or error) has occurred. */
291: public void addFailure(Test test, AssertionFailedError t) {
292: addFailure(test, (Throwable) t);
293: }
294:
295: /** An unexpected error occurred. */
296: public void addError(Test test, Throwable t) {
297: if (qt.getHaltOnError()) {
298: res.stop();
299: }
300: }
301:
302: /** Handle a block of output. */
303: public void handleOutput(String line) {
304: if (systemOut != null) {
305: systemOut.println(line);
306: }
307: }
308:
309: /** Process an error message. */
310: public void handleErrorOutput(String line) {
311: if (systemError != null) {
312: systemError.println(line);
313: }
314: }
315:
316: /** Flush standard output. */
317: public void handleFlush(String line) {
318: if (systemOut != null) {
319: systemOut.print(line);
320: }
321: }
322:
323: /** Flush error output. */
324: public void handleErrorFlush(String line) {
325: if (systemError != null) {
326: systemError.print(line);
327: }
328: }
329:
330: /**
331: * Whether to duplicate test output to standard output and error
332: * output streams.
333: */
334: private void sendOutAndErr(String out, String err) {
335: for (int i = 0; i < formatters.size(); i++) {
336: Formatter formatter = ((Formatter) formatters.elementAt(i));
337:
338: formatter.setSystemOutput(out);
339: formatter.setSystemError(err);
340: }
341: }
342:
343: /** Notifies each formatter at start of processing test suite. */
344: private void fireStartTestSuite() {
345: for (int i = 0; i < formatters.size(); i++) {
346: ((Formatter) formatters.elementAt(i)).startTestSuite(qt);
347: // actually has nothing to do with fireStart but it's
348: // convenient to drop it in here
349: ((Formatter) formatters.elementAt(i)).setFiltertrace(qt
350: .getFiltertrace());
351: }
352: }
353:
354: /** Called at end of test suite, notifies each formatter. */
355: private void fireEndTestSuite() {
356: for (int i = 0; i < formatters.size(); i++) {
357: ((Formatter) formatters.elementAt(i)).endTestSuite(qt);
358: }
359: }
360:
361: /** Add a result formatter */
362: public void addFormatter(Formatter f) {
363: formatters.addElement(f);
364: }
365:
366: // OTHER METHODS ////////////////////////////////////////////////
367: private class TeeOutputStream extends OutputStream {
368:
369: private OutputStream[] outs;
370:
371: private TeeOutputStream(OutputStream[] outs) {
372: this .outs = outs;
373: }
374:
375: public void write(int b) throws IOException {
376: for (int i = 0; i < outs.length; i++) {
377: outs[i].write(b);
378: }
379: }
380: }
381: }
|