git clone of logicmail with some fixes/features added
1/*-
2 * Copyright (c) 2010, Derek Konigsberg
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the project nor the names of its
15 * contributors may be used to endorse or promote products derived
16 * from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29 * OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package j2meunit.rimui;
33
34import j2meunit.framework.AssertionFailedError;
35import j2meunit.framework.Test;
36import j2meunit.framework.TestCase;
37import j2meunit.framework.TestFailure;
38import j2meunit.framework.TestListener;
39import j2meunit.framework.TestResult;
40import j2meunit.framework.TestSuite;
41import java.util.Enumeration;
42import java.util.Hashtable;
43import java.util.Vector;
44
45import net.rim.device.api.system.Bitmap;
46import net.rim.device.api.system.Characters;
47import net.rim.device.api.ui.Field;
48import net.rim.device.api.ui.Font;
49import net.rim.device.api.ui.Graphics;
50import net.rim.device.api.ui.MenuItem;
51import net.rim.device.api.ui.UiApplication;
52import net.rim.device.api.ui.component.GaugeField;
53import net.rim.device.api.ui.component.LabelField;
54import net.rim.device.api.ui.component.Menu;
55import net.rim.device.api.ui.component.SeparatorField;
56import net.rim.device.api.ui.component.TreeField;
57import net.rim.device.api.ui.component.TreeFieldCallback;
58import net.rim.device.api.ui.container.MainScreen;
59
60/**
61 * J2MEUnit Test Console Screen for the BlackBerry UI.
62 *
63 * @author Derek Konigsberg
64 */
65public class TestConsoleScreen extends MainScreen implements TestListener {
66 private LabelField titleLabel;
67 private LabelField statusLabel;
68 private LabelField failureLabel;
69 private LabelField errorLabel;
70 private LabelField timeLabel;
71 private GaugeField progressGauge;
72 private TreeField testTreeField;
73
74 private TestSuite mainTestSuite;
75 private Hashtable testTreeItems;
76 private TestResult testResults;
77 private Test selectedTest;
78 private long elapsedTime;
79
80 private Bitmap testInitialBitmap = Bitmap.getBitmapResource("test_initial.png");
81 private Bitmap testPassedBitmap = Bitmap.getBitmapResource("test_passed.png");
82 private Bitmap testFailedBitmap = Bitmap.getBitmapResource("test_failed.png");
83
84 private static class TestTreeItem {
85 public TestTreeItem(Test test) {
86 this.test = test;
87 if(test instanceof TestCase) {
88 name = ((TestCase)test).getName();
89 }
90 else if(test instanceof TestSuite) {
91 name = ((TestSuite)test).toString();
92 }
93 else {
94 name = "Test";
95 }
96 this.hasRun = false;
97 this.hasPassed = true;
98 this.id = 0;
99 }
100 public Test test;
101 public String name;
102 public boolean hasRun;
103 public boolean hasPassed;
104 public int id;
105 }
106
107 /**
108 * Creates a new instance of TestConsoleScreen
109 */
110 public TestConsoleScreen(String[] testCaseClasses) {
111 initializeFields();
112 testTreeItems = new Hashtable();
113 mainTestSuite = createTestSuite(testCaseClasses);
114 elapsedTime = -1;
115 populateTestTree(mainTestSuite, 0);
116 }
117
118 private void initializeFields() {
119 Font labelFont = Font.getDefault().derive(Font.PLAIN, 12);
120 Font treeFont = Font.getDefault().derive(Font.PLAIN, 14);
121
122 titleLabel = new LabelField("J2MEUnit " + j2meunit.util.Version.id());
123 titleLabel.setFont(labelFont);
124 statusLabel = new LabelField("Status: Idle");
125 statusLabel.setFont(labelFont);
126 failureLabel = new LabelField("Failures: 0");
127 failureLabel.setFont(labelFont);
128 errorLabel = new LabelField("Errors: 0");
129 errorLabel.setFont(labelFont);
130 timeLabel = new LabelField("Time: n/a");
131 timeLabel.setFont(labelFont);
132 progressGauge = new GaugeField(null, 0, 100, 0, GaugeField.NO_TEXT);
133 progressGauge.setFont(labelFont);
134
135 testTreeField = new TreeField(new TreeFieldCallback() {
136 public void drawTreeItem(TreeField treeField, Graphics graphics, int node, int y, int width, int indent) {
137 testTreeField_DrawTreeItem(treeField, graphics, node, y, width, indent);
138 }
139 }, Field.FOCUSABLE);
140 testTreeField.setEmptyString("No tests", 0);
141 testTreeField.setDefaultExpanded(true);
142 testTreeField.setIndentWidth(20);
143 testTreeField.setFont(treeFont);
144 testTreeField.setRowHeight(treeFont.getHeight() + 2);
145
146 add(titleLabel);
147 add(statusLabel);
148 add(failureLabel);
149 add(errorLabel);
150 add(timeLabel);
151 add(progressGauge);
152 add(new SeparatorField());
153 add(testTreeField);
154 }
155
156 private void populateTestTree(TestSuite suite, int parentId) {
157 // Get the tests
158 Vector tests = new Vector();
159 for(Enumeration e = suite.tests(); e.hasMoreElements(); ) {
160 tests.addElement(e.nextElement());
161 }
162
163 // Iterate backwards so the tree is populated in the correct order
164 int size = tests.size();
165 for(int i = size - 1; i >= 0; i--) {
166 Test test = (Test)tests.elementAt(i);
167 TestTreeItem item = new TestTreeItem(test);
168 int id = testTreeField.addChildNode(parentId, item);
169 item.id = id;
170 testTreeItems.put(test.toString(), item);
171 if(test instanceof TestSuite) {
172 populateTestTree((TestSuite)test, id);
173 }
174 }
175 }
176
177 /** Standard Escape-key handler */
178 public boolean keyChar(char key, int status, int time) {
179 boolean retval = false;
180 switch (key) {
181 case 'n':
182 retval = nextSibling();
183 break;
184 case 'p':
185 retval = prevSibling();
186 break;
187 case Characters.ENTER:
188 retval = openResultsForCurrentNode();
189 break;
190 case Characters.ESCAPE:
191 System.exit(1);
192 break;
193 default:
194 retval = super.keyChar(key, status, time);
195 }
196 return retval;
197 }
198
199 private boolean nextSibling() {
200 int id = testTreeField.getCurrentNode();
201 if(id == -1) { return false; }
202 int nextId = testTreeField.getNextSibling(id);
203 if(nextId == -1) { return false; }
204
205 testTreeField.setCurrentNode(nextId);
206 return true;
207 }
208
209 private boolean prevSibling() {
210 int id = testTreeField.getCurrentNode();
211 if(id == -1) { return false; }
212 int prevId = testTreeField.getPreviousSibling(id);
213 if(prevId == -1) { return false; }
214
215 testTreeField.setCurrentNode(prevId);
216 return true;
217 }
218
219 private boolean openResultsForCurrentNode() {
220 int node = testTreeField.getCurrentNode();
221 if(node > 0) {
222 TestTreeItem item = (TestTreeItem)testTreeField.getCookie(node);
223 if(item.hasRun && item.test instanceof TestCase) {
224 showTestResults();
225 return true;
226 }
227 }
228 return false;
229 }
230
231 private void testTreeField_DrawTreeItem(TreeField treeField, Graphics graphics, int node, int y, int width, int indent) {
232 Object cookie = testTreeField.getCookie(node);
233
234 if(cookie instanceof TestTreeItem) {
235 TestTreeItem item = (TestTreeItem)cookie;
236 int height = testTreeField.getRowHeight();
237
238 Bitmap iconBitmap = getItemIcon(item);
239
240 graphics.drawBitmap(indent, y, 16, 16, iconBitmap, 0, 0);
241
242 graphics.drawText(item.name, indent + height + 2, y, Graphics.ELLIPSIS, width - height - 2);
243 }
244 }
245
246 private Bitmap getItemIcon(TestTreeItem item) {
247 Bitmap iconBitmap;
248 if(item.hasRun) {
249 if(item.hasPassed) {
250 iconBitmap = testPassedBitmap;
251 }
252 else {
253 iconBitmap = testFailedBitmap;
254 }
255 }
256 else {
257 iconBitmap = testInitialBitmap;
258 }
259 return iconBitmap;
260 }
261
262 private MenuItem resultsItem = new MenuItem("View results", 300100, 1010) {
263 public void run() {
264 showTestResults();
265 }
266 };
267 private MenuItem runAllItem = new MenuItem("Run all", 400110, 1020) {
268 public void run() {
269 runAllTests();
270 }
271 };
272 private MenuItem runSelectedItem = new MenuItem("Run selected", 400120, 1030) {
273 public void run() {
274 runSelectedTests();
275 }
276 };
277 private MenuItem exitItem = new MenuItem("Exit", 60000100, 9000) {
278 public void run() {
279 System.exit(0);
280 }
281 };
282
283 protected void makeMenu(Menu menu, int instance) {
284 int node = testTreeField.getCurrentNode();
285 if(node > 0) {
286 TestTreeItem item = (TestTreeItem)testTreeField.getCookie(node);
287 if(item.hasRun && item.test instanceof TestCase) {
288 menu.add(resultsItem);
289 }
290 }
291 menu.add(runAllItem);
292 menu.add(runSelectedItem);
293 menu.add(exitItem);
294 }
295
296 public synchronized void addError(Test test, Throwable throwable) {
297 synchronized(UiApplication.getEventLock()) {
298 errorLabel.setText("Errors: " + testResults.errorCount());
299 }
300 }
301
302 public synchronized void addFailure(Test test, AssertionFailedError assertionFailedError) {
303 synchronized(UiApplication.getEventLock()) {
304 failureLabel.setText("Failures: " + testResults.failureCount());
305 }
306 }
307
308 public void endTest(Test test) {
309 synchronized(UiApplication.getEventLock()) {
310 progressGauge.setValue(progressGauge.getValue() + 1);
311 }
312 updateTreeErrors();
313 updateTestBranch(test);
314 updateTestTree();
315 }
316
317 public void endTestStep(Test test) {
318 synchronized(UiApplication.getEventLock()) {
319 progressGauge.setValue(progressGauge.getValue() + 1);
320 }
321 updateTreeErrors();
322 updateTestBranch(test);
323 updateTestTree();
324 }
325
326 public synchronized void startTest(Test test) {
327 TestTreeItem item = (TestTreeItem)testTreeItems.get(test.toString());
328 item.hasRun = true;
329 }
330
331 private void updateTestBranch(Test test) {
332 TestTreeItem item = (TestTreeItem)testTreeItems.get(test.toString());
333 if(item == null) return;
334 boolean hasAllRun = true;
335 boolean hasAllPassed = true;
336 int parentId = testTreeField.getParent(item.id);
337 if(parentId <= 0) return;
338 int node = testTreeField.getFirstChild(parentId);
339 if(node == -1) return;
340 while(node != -1) {
341 item = (TestTreeItem)testTreeField.getCookie(node);
342 if(!item.hasRun) hasAllRun = false;
343 if(!item.hasPassed) hasAllPassed = false;
344 node = testTreeField.getNextSibling(node);
345 }
346 item = (TestTreeItem)testTreeField.getCookie(parentId);
347 item.hasRun = hasAllRun;
348 item.hasPassed = hasAllPassed;
349
350 if(testTreeField.getParent(item.id) >= 0) {
351 updateTestBranch(item.test);
352 }
353 }
354
355 private void updateTreeErrors() {
356 // Update the test data structures
357 TestTreeItem item;
358 Enumeration e;
359 TestFailure failure;
360 for(e = testResults.failures(); e.hasMoreElements(); ) {
361 failure = (TestFailure)e.nextElement();
362 item = (TestTreeItem)testTreeItems.get(failure.failedTest().toString());
363 item.hasPassed = false;
364 }
365 for(e = testResults.errors(); e.hasMoreElements(); ) {
366 failure = (TestFailure)e.nextElement();
367 item = (TestTreeItem)testTreeItems.get(failure.failedTest().toString());
368 item.hasPassed = false;
369 }
370 }
371
372 private synchronized void updateTestTree() {
373 // Repaint the tree
374 UiApplication.getUiApplication().invokeAndWait(new Runnable() {
375 public void run() {
376 testTreeField.setDirty(true);
377 invalidate();
378 doPaint();
379 }
380 });
381 }
382
383 private void showTestResults() {
384 int node = testTreeField.getCurrentNode();
385 if(node <= 0) return;
386 TestTreeItem item = (TestTreeItem)testTreeField.getCookie(node);
387 if(!(item.test instanceof TestCase)) return;
388 TestCase testCase = (TestCase)item.test;
389
390 Enumeration e;
391 TestFailure testFailure = null;
392 TestFailure testError = null;
393
394 for(e = testResults.failures(); e.hasMoreElements(); ) {
395 TestFailure failure = (TestFailure)e.nextElement();
396 if(failure.failedTest() == testCase) {
397 testFailure = failure;
398 break;
399 }
400 }
401
402 for(e = testResults.errors(); e.hasMoreElements(); ) {
403 TestFailure error = (TestFailure)e.nextElement();
404 if(error.failedTest() == testCase) {
405 testError = error;
406 break;
407 }
408 }
409
410 UiApplication.getUiApplication().pushModalScreen(
411 new TestResultsScreen(testCase, testFailure, testError));
412 }
413
414 /**
415 * Builds a test suite from all test case classes in a string array.
416 *
417 * @param testCaseClasses A string array containing the test case class names
418 * @return A test suite containing all tests
419 */
420 protected TestSuite createTestSuite(String[] testCaseClasses) {
421 TestSuite testSuite = new TestSuite();
422 if (testCaseClasses.length < 1) {
423 return testSuite;
424 }
425
426 for (int i = 0; i < testCaseClasses.length; i++) {
427 try {
428 String className = testCaseClasses[i];
429 TestCase testCase =
430 (TestCase)Class.forName(className).newInstance();
431 testSuite.addTest(testCase.suite());
432 } catch (Throwable t) {
433 System.out.println("Access to TestCase " + testCaseClasses[i] +
434 " failed: " + t.getMessage() + " - " +
435 t.getClass().getName());
436 }
437 }
438
439 return testSuite;
440 }
441
442 /**
443 * Run all tests in the given test suite.
444 *
445 * @param suite The test suite to run
446 */
447 protected void doRun(Test suite) {
448 testResults = new TestResult();
449 testResults.addListener(this);
450
451 long startTime = System.currentTimeMillis();
452
453 // Mark the suite's tree item
454 TestTreeItem item = (TestTreeItem)testTreeItems.get(suite.toString());
455 if(item != null)
456 item.hasRun = true;
457
458 // Run the suite
459 suite.run(testResults);
460
461 long endTime = System.currentTimeMillis();
462 elapsedTime = endTime - startTime;
463 }
464
465 private void runAllTests() {
466 selectedTest = mainTestSuite;
467 runTests();
468 }
469
470 private void runSelectedTests() {
471 int nodeId = testTreeField.getCurrentNode();
472 if(nodeId == -1) return;
473 selectedTest = ((TestTreeItem)testTreeField.getCookie(nodeId)).test;
474 runTests();
475 }
476
477 private void runTests() {
478 statusLabel.setText("Status: Running...");
479 progressGauge.reset(null, 0, selectedTest.countTestCases(), 0);
480 new Thread() {
481 public void run() {
482 Thread.yield();
483 try {
484 doRun(selectedTest);
485 updateResults();
486 } catch (Throwable t) {
487 System.out.println("Exception while running test: " + t);
488 t.printStackTrace();
489 }
490 }
491 }.start();
492 }
493
494 private synchronized void updateResults() {
495 UiApplication.getUiApplication().invokeAndWait(new Runnable() {
496 public void run() {
497 statusLabel.setText("Status: Idle");
498 if(elapsedTime >= 0) {
499 timeLabel.setText("Time: " + elapsedTime + "ms");
500 }
501 }
502 });
503 }
504}