View Javadoc

1   //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2   // This file is part of J2MEUnit, a Java 2 Micro Edition unit testing framework.
3   //
4   // J2MEUnit is free software distributed under the Common Public License (CPL).
5   // It may be redistributed and/or modified under the terms of the CPL. You 
6   // should have received a copy of the license along with J2MEUnit. It is also 
7   // available from the website of the Open Source Initiative at 
8   // http://www.opensource.org.
9   //
10  // J2MEUnit is distributed in the hope that it will be useful, but WITHOUT ANY 
11  // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
12  // FOR A PARTICULAR PURPOSE.
13  //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
14  package cldcunit.runner;
15  
16  import java.io.PrintStream;
17  import java.util.Enumeration;
18  
19  import javax.microedition.lcdui.Command;
20  import javax.microedition.lcdui.CommandListener;
21  import javax.microedition.lcdui.Display;
22  import javax.microedition.lcdui.Displayable;
23  import javax.microedition.lcdui.Form;
24  import javax.microedition.lcdui.Gauge;
25  import javax.microedition.lcdui.List;
26  import javax.microedition.lcdui.StringItem;
27  import javax.microedition.midlet.MIDlet;
28  import javax.microedition.midlet.MIDletStateChangeException;
29  
30  import junit.framework.AssertionFailedError;
31  import junit.framework.Test;
32  import junit.framework.TestCase;
33  import junit.framework.TestFailure;
34  import junit.framework.TestListener;
35  import junit.framework.TestResult;
36  import junit.framework.TestSuite;
37  
38  /*******************************************************************************
39   * A TestRunner that runs as a MIDlet. It can be used in two ways:
40   * 
41   * <ul>
42   * <li> By subclassing and implementing startApp() which then calls start() with
43   * the test classes to run as the parameter, or </li>
44   * <li> By invoking it directly after setting the property J2MEUnitTestClasses
45   * with the names of the test classes (can be set in the JAD file) </li>
46   * </ul>
47   * 
48   * 
49   * @author $author$
50   * @version $Revision: 1514 $
51   */
52  public class TestRunner extends MIDlet implements TestListener, CommandListener {
53  	// ~ Static fields/initializers
54  	// ----------------------------------------------
55  
56  	protected static TestRunner theInstance = null;
57  
58  	// ~ Instance fields
59  	// ---------------------------------------------------------
60  
61  	protected Gauge aProgressBar;
62  
63  	protected List aResultsList;
64  
65  	protected PrintStream aWriter = System.out;
66  
67  	protected StringItem aErrorInfo;
68  
69  	protected StringItem aFailureInfo;
70  
71  	protected TestResult aResult;
72  
73  	protected boolean bScreenOutput = true;
74  
75  	protected boolean bTextOutput = true;
76  
77  	protected int nCount;
78  
79  	// v.s.
80  	private Command cmd_exit;
81  
82  	// ~ Constructors
83  	// ------------------------------------------------------------
84  
85  	/***************************************************************************
86  	 * Creates a new TestRunner object.
87  	 */
88  	public TestRunner() {
89  		if (theInstance != null)
90  			throw new RuntimeException("Only one MIDlet instance allowed!");
91  
92  		theInstance = this;
93  	}
94  
95  	// ~ Methods
96  	// -----------------------------------------------------------------
97  
98  	/***************************************************************************
99  	 * To return the current TestRunner instance. This is needed to determine
100 	 * the current Display and is only valid after the constructor of TestRunner
101 	 * has been invoked.
102 	 * 
103 	 * @return
104 	 */
105 	public static TestRunner getInstance() {
106 		return theInstance;
107 	}
108 
109 	/***************************************************************************
110 	 * To set the output mode(s) for this TestRunner.
111 	 * 
112 	 * @param bScreen
113 	 *            If TRUE, output will be written to an LCDUI screen
114 	 * @param bText
115 	 *            If TRUE, output will be written to a PrintStream (System.out
116 	 *            by default)
117 	 */
118 	public void setOutputMode(boolean bScreen, boolean bText) {
119 		bScreenOutput = bScreen;
120 		bTextOutput = bText;
121 	}
122 
123 	/***************************************************************************
124 	 * Set the output stream.
125 	 * 
126 	 * @param aStream
127 	 *            A PrintStream to be used for output.
128 	 */
129 	public void setWriter(PrintStream aStream) {
130 		aWriter = aStream;
131 	}
132 
133 	/***************************************************************************
134 	 * Get the output stream (defaults to System.out).
135 	 * 
136 	 * @return A PrintStream for output
137 	 */
138 	public PrintStream getWriter() {
139 		return aWriter;
140 	}
141 
142 	/***************************************************************************
143 	 * TestListener.addError() - will print 'E' to System.out.
144 	 * 
145 	 * @param test
146 	 *            The test that failed
147 	 * @param t
148 	 *            The Exception that caused the error
149 	 */
150 	public synchronized void addError(Test test, Throwable t) {
151 		System.out.print("E");
152 	}
153 
154 	/***************************************************************************
155 	 * TestListener.addError() - will print 'F' to System.out.
156 	 * 
157 	 * @param test
158 	 *            The test that failed
159 	 * @param e
160 	 *            The AssertionFailedError that caused the failure
161 	 */
162 	public synchronized void addFailure(Test test, AssertionFailedError e) {
163 		System.out.print("F");
164 	}
165 
166 	/***************************************************************************
167 	 * Add a string to the result output.
168 	 * 
169 	 * @param sText
170 	 *            The text to add
171 	 */
172 	public void addToResultsList(String sText) {
173 		if (bScreenOutput)
174 			getResultsList().append(sText, null);
175 
176 		if (bTextOutput)
177 			aWriter.println(sText);
178 	}
179 
180 	/***************************************************************************
181 	 * Add a string to the result output.
182 	 * 
183 	 * @param t
184 	 *            The text to add
185 	 */
186 	public void addToResultsList(Throwable t) {
187 		if (bScreenOutput) {
188 			String sMsg = ((t.getMessage() != null) ? t.getMessage() : t.getClass().getName());
189 			getResultsList().append(sMsg, null);
190 		}
191 
192 		if (bTextOutput)
193 			t.printStackTrace();
194 	}
195 
196 	/***************************************************************************
197 	 * TestListener.endTest()
198 	 * 
199 	 * @param test
200 	 *            The test that finished
201 	 */
202 	public void endTest(Test test) {
203 		if (aProgressBar != null) {
204 			aProgressBar.setValue(aProgressBar.getValue() + 1);
205 			aFailureInfo.setText(Integer.toString(aResult.failureCount()));
206 			aErrorInfo.setText(Integer.toString(aResult.errorCount()));
207 		}
208 	}
209 
210 	/***************************************************************************
211 	 * TestListener.endTestStep()
212 	 * 
213 	 * @param test
214 	 *            The test of which a step has finished
215 	 */
216 	public void endTestStep(Test test) {
217 		if (aProgressBar != null)
218 			aProgressBar.setValue(aProgressBar.getValue() + 1);
219 	}
220 
221 	/***************************************************************************
222 	 * Prints errors and failures to the standard output
223 	 * 
224 	 * @param result
225 	 *            The test results
226 	 */
227 	public synchronized void print(TestResult result) {
228 		printHeader(result);
229 		printErrors(result);
230 		printFailures(result);
231 		printFooter();
232 	}
233 
234 	/***************************************************************************
235 	 * Prints the errors to the standard output
236 	 * 
237 	 * @param result
238 	 *            The test result containing the errors
239 	 */
240 	public void printErrors(TestResult result) {
241 		if (result.errorCount() != 0) {
242 			if (result.errorCount() == 1)
243 				addToResultsList("There was " + result.errorCount() + " error:");
244 			else
245 				addToResultsList("There were " + result.errorCount() + " errors:");
246 
247 			int i = 1;
248 
249 			for (Enumeration e = result.errors(); e.hasMoreElements(); i++) {
250 				TestFailure failure = (TestFailure) e.nextElement();
251 
252 				addToResultsList(i + ") " + failure.failedTest());
253 
254 				if (failure.thrownException() != null)
255 					addToResultsList(failure.thrownException());
256 			}
257 		}
258 	}
259 
260 	/***************************************************************************
261 	 * Prints failures to the standard output
262 	 * 
263 	 * @param result
264 	 *            The test result containing the failures
265 	 */
266 	public void printFailures(TestResult result) {
267 		if (result.failureCount() != 0) {
268 			if (result.failureCount() == 1)
269 				addToResultsList("There was " + result.failureCount() + " failure:");
270 			else
271 				addToResultsList("There were " + result.failureCount() + " failures:");
272 
273 			int i = 1;
274 
275 			for (Enumeration e = result.failures(); e.hasMoreElements(); i++) {
276 				TestFailure failure = (TestFailure) e.nextElement();
277 
278 				addToResultsList(i + ") " + failure.failedTest());
279 
280 				if (failure.thrownException() != null)
281 					addToResultsList(failure.thrownException());
282 			}
283 		}
284 	}
285 
286 	/***************************************************************************
287 	 * Prints the footer
288 	 */
289 	public void printFooter() {
290 		addToResultsList("(c) CLDCUnit + J2MEUnit");
291 	}
292 
293 	/***************************************************************************
294 	 * Prints the header of the report
295 	 * 
296 	 * @param result
297 	 *            DOCUMENT ME!
298 	 */
299 	public void printHeader(TestResult result) {
300 		if (result.wasSuccessful()) {
301 			addToResultsList("OK");
302 			addToResultsList(" (" + result.runCount() + " tests)");
303 		} else {
304 			addToResultsList("FAILURES");
305 			addToResultsList("Test Results:");
306 			addToResultsList("Run: " + result.runCount());
307 			addToResultsList("Failures: " + result.failureCount());
308 			addToResultsList("Errors: " + result.errorCount());
309 		}
310 	}
311 
312 	/***************************************************************************
313 	 * To display the result of the test in a javax.microedition.lcdui.List
314 	 * screen.
315 	 */
316 	public void showResult() {
317 		if (bScreenOutput)
318 			Display.getDisplay(this).setCurrent(getResultsList());
319 	}
320 
321 	/***************************************************************************
322 	 * TestListener.startTest() - will print '.' to te System.out.
323 	 * 
324 	 * @param test
325 	 *            The test that started
326 	 */
327 	public synchronized void startTest(Test test) {
328 		System.out.println();
329 		System.out.print(this.aResult.runCount() + " start (" + test.getClass().getName() + ")");
330 	}
331 
332 	/***************************************************************************
333 	 * Returns the javax.microedition.lcdui.List instance to append the result
334 	 * data to. Will be created if it doesn't exist.
335 	 * 
336 	 * @return A javax.microedition.lcdui.List object
337 	 */
338 	protected List getResultsList() {
339 		if (aResultsList == null) {
340 			aResultsList = new List("J2ME Unit", List.IMPLICIT);
341 			// v.s.
342 			aResultsList.addCommand(cmd_exit);
343 			aResultsList.setCommandListener(this);
344 		}
345 
346 		return aResultsList;
347 	}
348 
349 	/***************************************************************************
350 	 * Builds a test suite from all test case classes in a string array.
351 	 * 
352 	 * @param rTestCaseClasses
353 	 *            A string array containing the test case class names
354 	 * 
355 	 * @return A test suite containing all tests
356 	 */
357 	protected Test createTestSuite(String[] rTestCaseClasses) {
358 		if (rTestCaseClasses.length < 1) {
359 			System.out.println("Usage: TestRunner testCaseName1 [... testCaseNameN]");
360 			System.exit(-1);
361 		}
362 
363 		TestSuite aSuite = new TestSuite();
364 
365 		for (int i = 0; i < rTestCaseClasses.length; i++) {
366 			try {
367 				String sClass = rTestCaseClasses[i];
368 				Class cClass = Class.forName(sClass);
369 				try {
370 					TestCase test = (TestCase) cClass.newInstance();
371 					Test suite = test.getHelper().getSuite();
372 					if (suite != null) {
373 						aSuite.addTest(test.getHelper().getSuite());
374 					} else {
375 						aSuite.addTest(new TestSuite(cClass));
376 					}
377 				} catch (Exception e) {
378 					aSuite.addTest(new TestSuite(cClass));
379 				}
380 
381 			} catch (Throwable e) {
382 				// TODO call addError(...);
383 				System.out.println("Access to TestCase " + rTestCaseClasses[i] + " failed: " + e.getMessage() + " - "
384 						+ e.getClass().getName());
385 			}
386 		}
387 
388 		return aSuite;
389 	}
390 
391 	protected TestSuite createTestSuite(TestCase[] testCaseClasses) {
392 		TestSuite aSuite = new TestSuite();
393 
394 		for (int i = 0; i < testCaseClasses.length; i++) {
395 			try {
396 				TestCase test = testCaseClasses[i];
397 				try {
398 					Test suite = test.getHelper().getSuite();
399 					if (suite != null) {
400 						aSuite.addTest(test.getHelper().getSuite());
401 					} else {
402 						aSuite.addTest(new TestSuite(test.getClass()));
403 					}
404 				} catch (Exception e) {
405 					aSuite.addTest(new TestSuite(test.getClass()));
406 				}
407 
408 			} catch (Throwable e) {
409 				// TODO call addError(...);
410 				System.out.println("Access to TestCase " + testCaseClasses[i] + " failed: " + e.getMessage() + " - "
411 						+ e.getClass().getName());
412 			}
413 		}
414 
415 		return aSuite;
416 	}
417 
418 	/***************************************************************************
419 	 * Empty implementation of MIDlet.destroyApp.
420 	 * 
421 	 * @param bUnconditional
422 	 *            Not needed here
423 	 */
424 	protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
425 		// v.s.
426 		notifyDestroyed();
427 		theInstance = null;
428 	}
429 
430 	/***************************************************************************
431 	 * Will run all tests in the given test suite.
432 	 * 
433 	 * @param suite
434 	 *            The test suite to run
435 	 */
436 	protected void doRun(Test suite) {
437 		aResult = new TestResult();
438 		aResult.addListener(this);
439 
440 		long startTime = System.currentTimeMillis();
441 		suite.run(aResult);
442 
443 		long endTime = System.currentTimeMillis();
444 		long runTime = endTime - startTime;
445 		addToResultsList("Time: " + runTime + "ms");
446 		print(aResult);
447 	}
448 
449 	/***************************************************************************
450 	 * Empty implementation of MIDlet.pauseApp.
451 	 */
452 	protected void pauseApp() {
453 	}
454 
455 	/***************************************************************************
456 	 * Starts a test run. processes the command line arguments and creates a
457 	 * test suite from it. The test suite will then be run in a separate thread
458 	 * to allow the progress bar to be updated. After all tests have run the
459 	 * result screen will be displayed.
460 	 * 
461 	 * <p>
462 	 * The only thing that a sub
463 	 * </p>
464 	 * 
465 	 * @param rTestCaseClasses
466 	 *            The names of the test case classes
467 	 */
468 	protected void start(String[] rTestCaseClasses) {
469 		final Test aTestSuite = createTestSuite(rTestCaseClasses);
470 		start(aTestSuite);
471 	}
472 
473 	protected void start(TestCase[] testCaseClasses) {
474 		final Test aTestSuite = createTestSuite(testCaseClasses);
475 		start(aTestSuite);
476 	}
477 
478 	private void start(final Test aTestSuite) {
479 
480 		final Display rDisplay = Display.getDisplay(this);
481 		Form aForm = new Form("TestRunner");
482 
483 		nCount = aTestSuite.countTestCases();
484 		if (nCount == 0) {
485 			aForm.append("No tests found.");
486 			rDisplay.setCurrent(aForm);
487 			return;
488 		}
489 
490 		aProgressBar = new Gauge(null, false, nCount, 0);
491 		aFailureInfo = new StringItem("Failures:", "0");
492 		aErrorInfo = new StringItem("Errors:", "0");
493 
494 		aForm.append("Testing...");
495 		aForm.append(aProgressBar);
496 		aForm.append(aFailureInfo);
497 		aForm.append(aErrorInfo);
498 
499 		// v.s.
500 		cmd_exit = new Command("Exit", Command.EXIT, 0);
501 		aForm.addCommand(cmd_exit);
502 		aForm.setCommandListener(this);
503 
504 		rDisplay.setCurrent(aForm);
505 
506 		new Thread() {
507 			public void run() {
508 				try {
509 					doRun(aTestSuite);
510 					showResult();
511 				} catch (Exception e) {
512 					System.out.println("Exception while running test: " + e);
513 					e.printStackTrace();
514 				}
515 			}
516 		}.start();
517 	}
518 
519 	/***************************************************************************
520 	 * Default implementation that reads the attribute J2MEUnitTestClasses with
521 	 * getAppProperty and invokes the method start() with the result string.
522 	 * Therefore, if this string contains the space-separated list of test
523 	 * classes to run it is not necessary to subclass midletui.TestRunner.
524 	 */
525 	protected void startApp() throws MIDletStateChangeException {
526 		try {
527 			String sTestClasses = getAppProperty("J2MEUnitTestClasses");
528 
529 			System.out.println("Testing: " + sTestClasses);
530 			start(new String[] { sTestClasses });
531 		} catch (Exception e) {
532 			System.out.println("Exception while setting up tests: " + e);
533 			e.printStackTrace();
534 		}
535 	}
536 
537 	// v.s.
538 	public void commandAction(Command c, Displayable d) {
539 		if (c == cmd_exit) {
540 			try {
541 				destroyApp(true);
542 			} catch (MIDletStateChangeException e) {
543 			}
544 		}
545 
546 	}
547 }