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