1 package junit.swingui;
2
3 import java.awt.*;
4 import java.awt.event.*;
5 import java.io.*;
6 import java.lang.reflect.Constructor;
7 import java.net.URL;
8 import java.util.*;
9
10 import javax.swing.*;
11 import javax.swing.event.*;
12 import junit.framework.*;
13 import junit.runner.*;
14
15 /***
16 * A Swing based user interface to run tests.
17 * Enter the name of a class which either provides a static
18 * suite method or is a subclass of TestCase.
19 * <pre>
20 * Synopsis: java junit.swingui.TestRunner [-noloading] [TestCase]
21 * </pre>
22 * TestRunner takes as an optional argument the name of the testcase class to be run.
23 */
24 public class TestRunner extends BaseTestRunner implements TestRunContext {
25 private static final int GAP= 4;
26 private static final int HISTORY_LENGTH= 5;
27
28 protected JFrame fFrame;
29 private Thread fRunner;
30 private TestResult fTestResult;
31
32 private JComboBox fSuiteCombo;
33 private ProgressBar fProgressIndicator;
34 private DefaultListModel fFailures;
35 private JLabel fLogo;
36 private CounterPanel fCounterPanel;
37 private JButton fRun;
38 private JButton fQuitButton;
39 private JButton fRerunButton;
40 private StatusLine fStatusLine;
41 private FailureDetailView fFailureView;
42 private JTabbedPane fTestViewTab;
43 private JCheckBox fUseLoadingRunner;
44 private Vector fTestRunViews= new Vector();
45
46 private static final String TESTCOLLECTOR_KEY= "TestCollectorClass";
47 private static final String FAILUREDETAILVIEW_KEY= "FailureViewClass";
48
49 public TestRunner() {
50 }
51
52 public static void main(String[] args) {
53 new TestRunner().start(args);
54 }
55
56 public static void run(Class test) {
57 String args[]= { test.getName() };
58 main(args);
59 }
60
61 public void testFailed(final int status, final Test test, final Throwable t) {
62 SwingUtilities.invokeLater(
63 new Runnable() {
64 public void run() {
65 switch (status) {
66 case TestRunListener.STATUS_ERROR:
67 fCounterPanel.setErrorValue(fTestResult.errorCount());
68 appendFailure(test, t);
69 break;
70 case TestRunListener.STATUS_FAILURE:
71 fCounterPanel.setFailureValue(fTestResult.failureCount());
72 appendFailure(test, t);
73 break;
74 }
75 }
76 }
77 );
78 }
79
80 public void testStarted(String testName) {
81 postInfo("Running: "+testName);
82 }
83
84 public void testEnded(String stringName) {
85 synchUI();
86 SwingUtilities.invokeLater(
87 new Runnable() {
88 public void run() {
89 if (fTestResult != null) {
90 fCounterPanel.setRunValue(fTestResult.runCount());
91 fProgressIndicator.step(fTestResult.runCount(), fTestResult.wasSuccessful());
92 }
93 }
94 }
95 );
96 }
97
98 public void setSuite(String suiteName) {
99 fSuiteCombo.getEditor().setItem(suiteName);
100 }
101
102 private void addToHistory(final String suite) {
103 for (int i= 0; i < fSuiteCombo.getItemCount(); i++) {
104 if (suite.equals(fSuiteCombo.getItemAt(i))) {
105 fSuiteCombo.removeItemAt(i);
106 fSuiteCombo.insertItemAt(suite, 0);
107 fSuiteCombo.setSelectedIndex(0);
108 return;
109 }
110 }
111 fSuiteCombo.insertItemAt(suite, 0);
112 fSuiteCombo.setSelectedIndex(0);
113 pruneHistory();
114 }
115
116 private void pruneHistory() {
117 int historyLength= getPreference("maxhistory", HISTORY_LENGTH);
118 if (historyLength < 1)
119 historyLength= 1;
120 for (int i= fSuiteCombo.getItemCount()-1; i > historyLength-1; i--)
121 fSuiteCombo.removeItemAt(i);
122 }
123
124 private void appendFailure(Test test, Throwable t) {
125 fFailures.addElement(new TestFailure(test, t));
126 if (fFailures.size() == 1)
127 revealFailure(test);
128 }
129
130 private void revealFailure(Test test) {
131 for (Enumeration e= fTestRunViews.elements(); e.hasMoreElements(); ) {
132 TestRunView v= (TestRunView) e.nextElement();
133 v.revealFailure(test);
134 }
135 }
136
137 protected void aboutToStart(final Test testSuite) {
138 for (Enumeration e= fTestRunViews.elements(); e.hasMoreElements(); ) {
139 TestRunView v= (TestRunView) e.nextElement();
140 v.aboutToStart(testSuite, fTestResult);
141 }
142 }
143
144 protected void runFinished(final Test testSuite) {
145 SwingUtilities.invokeLater(
146 new Runnable() {
147 public void run() {
148 for (Enumeration e= fTestRunViews.elements(); e.hasMoreElements(); ) {
149 TestRunView v= (TestRunView) e.nextElement();
150 v.runFinished(testSuite, fTestResult);
151 }
152 }
153 }
154 );
155 }
156
157 protected CounterPanel createCounterPanel() {
158 return new CounterPanel();
159 }
160
161 protected JPanel createFailedPanel() {
162 JPanel failedPanel= new JPanel(new GridLayout(0, 1, 0, 2));
163 fRerunButton= new JButton("Run");
164 fRerunButton.setEnabled(false);
165 fRerunButton.addActionListener(
166 new ActionListener() {
167 public void actionPerformed(ActionEvent e) {
168 rerun();
169 }
170 }
171 );
172 failedPanel.add(fRerunButton);
173 return failedPanel;
174 }
175
176 protected FailureDetailView createFailureDetailView() {
177 String className= BaseTestRunner.getPreference(FAILUREDETAILVIEW_KEY);
178 if (className != null) {
179 Class viewClass= null;
180 try {
181 viewClass= Class.forName(className);
182 return (FailureDetailView)viewClass.newInstance();
183 } catch(Exception e) {
184 JOptionPane.showMessageDialog(fFrame, "Could not create Failure DetailView - using default view");
185 }
186 }
187 return new DefaultFailureDetailView();
188 }
189
190 /***
191 * Creates the JUnit menu. Clients override this
192 * method to add additional menu items.
193 */
194 protected JMenu createJUnitMenu() {
195 JMenu menu= new JMenu("JUnit");
196 menu.setMnemonic('J');
197 JMenuItem mi1= new JMenuItem("About...");
198 mi1.addActionListener(
199 new ActionListener() {
200 public void actionPerformed(ActionEvent event) {
201 about();
202 }
203 }
204 );
205 mi1.setMnemonic('A');
206 menu.add(mi1);
207
208 menu.addSeparator();
209 JMenuItem mi2= new JMenuItem(" Exit ");
210 mi2.addActionListener(
211 new ActionListener() {
212 public void actionPerformed(ActionEvent event) {
213 terminate();
214 }
215 }
216 );
217 mi2.setMnemonic('x');
218 menu.add(mi2);
219
220 return menu;
221 }
222
223 protected JFrame createFrame() {
224 JFrame frame= new JFrame("JUnit");
225 Image icon= loadFrameIcon();
226 if (icon != null)
227 frame.setIconImage(icon);
228 frame.getContentPane().setLayout(new BorderLayout(0, 0));
229
230 frame.addWindowListener(
231 new WindowAdapter() {
232 public void windowClosing(WindowEvent e) {
233 terminate();
234 }
235 }
236 );
237 return frame;
238 }
239
240 protected JLabel createLogo() {
241 JLabel label;
242 Icon icon= getIconResource(BaseTestRunner.class, "logo.gif");
243 if (icon != null)
244 label= new JLabel(icon);
245 else
246 label= new JLabel("JV");
247 label.setToolTipText("JUnit Version "+Version.id());
248 return label;
249 }
250
251 protected void createMenus(JMenuBar mb) {
252 mb.add(createJUnitMenu());
253 }
254
255 protected JCheckBox createUseLoaderCheckBox() {
256 boolean useLoader= useReloadingTestSuiteLoader();
257 JCheckBox box= new JCheckBox("Reload classes every run", useLoader);
258 box.setToolTipText("Use a custom class loader to reload the classes for every run");
259 if (inVAJava())
260 box.setVisible(false);
261 return box;
262 }
263
264 protected JButton createQuitButton() {
265
266
267 JButton quit= new JButton(" Exit ");
268 quit.addActionListener(
269 new ActionListener() {
270 public void actionPerformed(ActionEvent e) {
271 terminate();
272 }
273 }
274 );
275 return quit;
276 }
277
278 protected JButton createRunButton() {
279 JButton run= new JButton("Run");
280 run.setEnabled(true);
281 run.addActionListener(
282 new ActionListener() {
283 public void actionPerformed(ActionEvent e) {
284 runSuite();
285 }
286 }
287 );
288 return run;
289 }
290
291 protected Component createBrowseButton() {
292 JButton browse= new JButton("...");
293 browse.setToolTipText("Select a Test class");
294 browse.addActionListener(
295 new ActionListener() {
296 public void actionPerformed(ActionEvent e) {
297 browseTestClasses();
298 }
299 }
300 );
301 return browse;
302 }
303
304 protected StatusLine createStatusLine() {
305 return new StatusLine(380);
306 }
307
308 protected JComboBox createSuiteCombo() {
309 JComboBox combo= new JComboBox();
310 combo.setEditable(true);
311 combo.setLightWeightPopupEnabled(false);
312
313 combo.getEditor().getEditorComponent().addKeyListener(
314 new KeyAdapter() {
315 public void keyTyped(KeyEvent e) {
316 textChanged();
317 if (e.getKeyChar() == KeyEvent.VK_ENTER)
318 runSuite();
319 }
320 }
321 );
322 try {
323 loadHistory(combo);
324 } catch (IOException e) {
325
326 }
327 combo.addItemListener(
328 new ItemListener() {
329 public void itemStateChanged(ItemEvent event) {
330 if (event.getStateChange() == ItemEvent.SELECTED) {
331 textChanged();
332 }
333 }
334 }
335 );
336 return combo;
337 }
338
339 protected JTabbedPane createTestRunViews() {
340 JTabbedPane pane= new JTabbedPane(JTabbedPane.BOTTOM);
341
342 FailureRunView lv= new FailureRunView(this);
343 fTestRunViews.addElement(lv);
344 lv.addTab(pane);
345
346 TestHierarchyRunView tv= new TestHierarchyRunView(this);
347 fTestRunViews.addElement(tv);
348 tv.addTab(pane);
349
350 pane.addChangeListener(
351 new ChangeListener() {
352 public void stateChanged(ChangeEvent e) {
353 testViewChanged();
354 }
355 }
356 );
357 return pane;
358 }
359
360 public void testViewChanged() {
361 TestRunView view= (TestRunView)fTestRunViews.elementAt(fTestViewTab.getSelectedIndex());
362 view.activate();
363 }
364
365 protected TestResult createTestResult() {
366 return new TestResult();
367 }
368
369 protected JFrame createUI(String suiteName) {
370 JFrame frame= createFrame();
371 JMenuBar mb= new JMenuBar();
372 createMenus(mb);
373 frame.setJMenuBar(mb);
374
375 JLabel suiteLabel= new JLabel("Test class name:");
376 fSuiteCombo= createSuiteCombo();
377 fRun= createRunButton();
378 frame.getRootPane().setDefaultButton(fRun);
379 Component browseButton= createBrowseButton();
380
381 fUseLoadingRunner= createUseLoaderCheckBox();
382
383 fStatusLine= createStatusLine();
384 if (inMac())
385 fProgressIndicator= new MacProgressBar(fStatusLine);
386 else
387 fProgressIndicator= new ProgressBar();
388 fCounterPanel= createCounterPanel();
389
390 fFailures= new DefaultListModel();
391
392 fTestViewTab= createTestRunViews();
393 JPanel failedPanel= createFailedPanel();
394
395 fFailureView= createFailureDetailView();
396 JScrollPane tracePane= new JScrollPane(fFailureView.getComponent(), JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
397
398
399
400 fQuitButton= createQuitButton();
401 fLogo= createLogo();
402
403 JPanel panel= new JPanel(new GridBagLayout());
404
405 addGrid(panel, suiteLabel, 0, 0, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST);
406 addGrid(panel, fSuiteCombo, 0, 1, 1, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST);
407 addGrid(panel, browseButton, 1, 1, 1, GridBagConstraints.NONE, 0.0, GridBagConstraints.WEST);
408 addGrid(panel, fRun, 2, 1, 1, GridBagConstraints.HORIZONTAL, 0.0, GridBagConstraints.CENTER);
409
410 addGrid(panel, fUseLoadingRunner, 0, 2, 3, GridBagConstraints.NONE, 1.0, GridBagConstraints.WEST);
411
412
413
414 addGrid(panel, fProgressIndicator, 0, 3, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST);
415 addGrid(panel, fLogo, 2, 3, 1, GridBagConstraints.NONE, 0.0, GridBagConstraints.NORTH);
416
417 addGrid(panel, fCounterPanel, 0, 4, 2, GridBagConstraints.NONE, 0.0, GridBagConstraints.WEST);
418 addGrid(panel, new JSeparator(), 0, 5, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST);
419 addGrid(panel, new JLabel("Results:"), 0, 6, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST);
420
421 JSplitPane splitter= new JSplitPane(JSplitPane.VERTICAL_SPLIT, fTestViewTab, tracePane);
422 addGrid(panel, splitter, 0, 7, 2, GridBagConstraints.BOTH, 1.0, GridBagConstraints.WEST);
423
424 addGrid(panel, failedPanel, 2, 7, 1, GridBagConstraints.HORIZONTAL, 0.0, GridBagConstraints.NORTH
425
426 addGrid(panel, fStatusLine, 0, 9, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.CENTER);
427 addGrid(panel, fQuitButton, 2, 9, 1, GridBagConstraints.HORIZONTAL, 0.0, GridBagConstraints.CENTER);
428
429 frame.setContentPane(panel);
430 frame.pack();
431 frame.setLocation(200, 200);
432 return frame;
433 }
434
435 private void addGrid(JPanel p, Component co, int x, int y, int w, int fill, double wx, int anchor) {
436 GridBagConstraints c= new GridBagConstraints();
437 c.gridx= x; c.gridy= y;
438 c.gridwidth= w;
439 c.anchor= anchor;
440 c.weightx= wx;
441 c.fill= fill;
442 if (fill == GridBagConstraints.BOTH || fill == GridBagConstraints.VERTICAL)
443 c.weighty= 1.0;
444 c.insets= new Insets(y == 0 ? 10 : 0, x == 0 ? 10 : GAP, GAP, GAP);
445 p.add(co, c);
446 }
447
448 protected String getSuiteText() {
449 if (fSuiteCombo == null)
450 return "";
451 return (String)fSuiteCombo.getEditor().getItem();
452 }
453
454 public ListModel getFailures() {
455 return fFailures;
456 }
457
458 public void insertUpdate(DocumentEvent event) {
459 textChanged();
460 }
461
462 protected Object instanciateClass(String fullClassName, Object param) {
463 try {
464 Class clazz= Class.forName(fullClassName);
465 if (param == null) {
466 return clazz.newInstance();
467 } else {
468 Class[] clazzParam= {param.getClass()};
469 Constructor clazzConstructor= clazz.getConstructor(clazzParam);
470 Object[] objectParam= {param};
471 return clazzConstructor.newInstance(objectParam);
472 }
473 } catch (Exception e) {
474 e.printStackTrace();
475 }
476 return null;
477 }
478
479 public void browseTestClasses() {
480 TestCollector collector= createTestCollector();
481 TestSelector selector= new TestSelector(fFrame, collector);
482 if (selector.isEmpty()) {
483 JOptionPane.showMessageDialog(fFrame, "No Test Cases found.\nCheck that the configured \'TestCollector\' is supported on this platform.");
484 return;
485 }
486 selector.show();
487 String className= selector.getSelectedItem();
488 if (className != null)
489 setSuite(className);
490 }
491
492 TestCollector createTestCollector() {
493 String className= BaseTestRunner.getPreference(TESTCOLLECTOR_KEY);
494 if (className != null) {
495 Class collectorClass= null;
496 try {
497 collectorClass= Class.forName(className);
498 return (TestCollector)collectorClass.newInstance();
499 } catch(Exception e) {
500 JOptionPane.showMessageDialog(fFrame, "Could not create TestCollector - using default collector");
501 }
502 }
503 return new SimpleTestCollector();
504 }
505
506 private Image loadFrameIcon() {
507 ImageIcon icon= (ImageIcon)getIconResource(BaseTestRunner.class, "smalllogo.gif");
508 if (icon != null)
509 return icon.getImage();
510 return null;
511 }
512
513 private void loadHistory(JComboBox combo) throws IOException {
514 BufferedReader br= new BufferedReader(new FileReader(getSettingsFile()));
515 int itemCount= 0;
516 try {
517 String line;
518 while ((line= br.readLine()) != null) {
519 combo.addItem(line);
520 itemCount++;
521 }
522 if (itemCount > 0)
523 combo.setSelectedIndex(0);
524
525 } finally {
526 br.close();
527 }
528 }
529
530 private File getSettingsFile() {
531 String home= System.getProperty("user.home");
532 return new File(home,".junitsession");
533 }
534
535 private void postInfo(final String message) {
536 SwingUtilities.invokeLater(
537 new Runnable() {
538 public void run() {
539 showInfo(message);
540 }
541 }
542 );
543 }
544
545 private void postStatus(final String status) {
546 SwingUtilities.invokeLater(
547 new Runnable() {
548 public void run() {
549 showStatus(status);
550 }
551 }
552 );
553 }
554
555 public void removeUpdate(DocumentEvent event) {
556 textChanged();
557 }
558
559 private void rerun() {
560 TestRunView view= (TestRunView)fTestRunViews.elementAt(fTestViewTab.getSelectedIndex());
561 Test rerunTest= view.getSelectedTest();
562 if (rerunTest != null)
563 rerunTest(rerunTest);
564 }
565
566 private void rerunTest(Test test) {
567 if (!(test instanceof TestCase)) {
568 showInfo("Could not reload "+ test.toString());
569 return;
570 }
571 Test reloadedTest= null;
572 TestCase rerunTest= (TestCase)test;
573
574 try {
575 Class reloadedTestClass= getLoader().reload(test.getClass());
576 reloadedTest= TestSuite.createTest(reloadedTestClass, rerunTest.getName());
577 } catch(Exception e) {
578 showInfo("Could not reload "+ test.toString());
579 return;
580 }
581 TestResult result= new TestResult();
582 reloadedTest.run(result);
583
584 String message= reloadedTest.toString();
585 if(result.wasSuccessful())
586 showInfo(message+" was successful");
587 else if (result.errorCount() == 1)
588 showStatus(message+" had an error");
589 else
590 showStatus(message+" had a failure");
591 }
592
593 protected void reset() {
594 fCounterPanel.reset();
595 fProgressIndicator.reset();
596 fRerunButton.setEnabled(false);
597 fFailureView.clear();
598 fFailures.clear();
599 }
600
601 protected void runFailed(String message) {
602 showStatus(message);
603 fRun.setText("Run");
604 fRunner= null;
605 }
606
607 synchronized public void runSuite() {
608 if (fRunner != null) {
609 fTestResult.stop();
610 } else {
611 setLoading(shouldReload());
612 reset();
613 showInfo("Load Test Case...");
614 final String suiteName= getSuiteText();
615 final Test testSuite= getTest(suiteName);
616 if (testSuite != null) {
617 addToHistory(suiteName);
618 doRunTest(testSuite);
619 }
620 }
621 }
622
623 private boolean shouldReload() {
624 return !inVAJava() && fUseLoadingRunner.isSelected();
625 }
626
627
628 synchronized protected void runTest(final Test testSuite) {
629 if (fRunner != null) {
630 fTestResult.stop();
631 } else {
632 reset();
633 if (testSuite != null) {
634 doRunTest(testSuite);
635 }
636 }
637 }
638
639 private void doRunTest(final Test testSuite) {
640 setButtonLabel(fRun, "Stop");
641 fRunner= new Thread("TestRunner-Thread") {
642 public void run() {
643 TestRunner.this.start(testSuite);
644 postInfo("Running...");
645
646 long startTime= System.currentTimeMillis();
647 testSuite.run(fTestResult);
648
649 if (fTestResult.shouldStop()) {
650 postStatus("Stopped");
651 } else {
652 long endTime= System.currentTimeMillis();
653 long runTime= endTime-startTime;
654 postInfo("Finished: " + elapsedTimeAsString(runTime) + " seconds");
655 }
656 runFinished(testSuite);
657 setButtonLabel(fRun, "Run");
658 fRunner= null;
659 System.gc();
660 }
661 };
662
663
664 fTestResult= createTestResult();
665 fTestResult.addListener(TestRunner.this);
666 aboutToStart(testSuite);
667
668 fRunner.start();
669 }
670
671 private void saveHistory() throws IOException {
672 BufferedWriter bw= new BufferedWriter(new FileWriter(getSettingsFile()));
673 try {
674 for (int i= 0; i < fSuiteCombo.getItemCount(); i++) {
675 String testsuite= fSuiteCombo.getItemAt(i).toString();
676 bw.write(testsuite, 0, testsuite.length());
677 bw.newLine();
678 }
679 } finally {
680 bw.close();
681 }
682 }
683
684 private void setButtonLabel(final JButton button, final String label) {
685 SwingUtilities.invokeLater(
686 new Runnable() {
687 public void run() {
688 button.setText(label);
689 }
690 }
691 );
692 }
693
694 public void handleTestSelected(Test test) {
695 fRerunButton.setEnabled(test != null && (test instanceof TestCase));
696 showFailureDetail(test);
697 }
698
699 private void showFailureDetail(Test test) {
700 if (test != null) {
701 ListModel failures= getFailures();
702 for (int i= 0; i < failures.getSize(); i++) {
703 TestFailure failure= (TestFailure)failures.getElementAt(i);
704 if (failure.failedTest() == test) {
705 fFailureView.showFailure(failure);
706 return;
707 }
708 }
709 }
710 fFailureView.clear();
711 }
712
713 private void showInfo(String message) {
714 fStatusLine.showInfo(message);
715 }
716
717 private void showStatus(String status) {
718 fStatusLine.showError(status);
719 }
720
721 /***
722 * Starts the TestRunner
723 */
724 public void start(String[] args) {
725 String suiteName= processArguments(args);
726 fFrame= createUI(suiteName);
727 fFrame.pack();
728 fFrame.setVisible(true);
729
730 if (suiteName != null) {
731 setSuite(suiteName);
732 runSuite();
733 }
734 }
735
736 private void start(final Test test) {
737 SwingUtilities.invokeLater(
738 new Runnable() {
739 public void run() {
740 int total= test.countTestCases();
741 fProgressIndicator.start(total);
742 fCounterPanel.setTotal(total);
743 }
744 }
745 );
746 }
747
748 /***
749 * Wait until all the events are processed in the event thread
750 */
751 private void synchUI() {
752 try {
753 SwingUtilities.invokeAndWait(
754 new Runnable() {
755 public void run() {}
756 }
757 );
758 }
759 catch (Exception e) {
760 }
761 }
762
763 /***
764 * Terminates the TestRunner
765 */
766 public void terminate() {
767 fFrame.dispose();
768 try {
769 saveHistory();
770 } catch (IOException e) {
771 System.out.println("Couldn't save test run history");
772 }
773 System.exit(0);
774 }
775
776 public void textChanged() {
777 fRun.setEnabled(getSuiteText().length() > 0);
778 clearStatus();
779 }
780
781 protected void clearStatus() {
782 fStatusLine.clear();
783 }
784
785 public static Icon getIconResource(Class clazz, String name) {
786 URL url= clazz.getResource(name);
787 if (url == null) {
788 System.err.println("Warning: could not load \""+name+"\" icon");
789 return null;
790 }
791 return new ImageIcon(url);
792 }
793
794 private void about() {
795 AboutDialog about= new AboutDialog(fFrame);
796 about.show();
797 }
798 }