package junit.swingui; | |
import java.awt.*; | |
import java.awt.event.*; | |
import java.io.*; | |
import java.net.URL; | |
import java.util.*; | |
import javax.swing.*; | |
import javax.swing.event.*; | |
import junit.framework.*; | |
import junit.runner.*; | |
/** | |
* A Swing based user interface to run tests. | |
* Enter the name of a class which either provides a static | |
* suite method or is a subclass of TestCase. | |
* <pre> | |
* Synopsis: java junit.swingui.TestRunner [-noloading] [TestCase] | |
* </pre> | |
* TestRunner takes as an optional argument the name of the testcase class to be run. | |
*/ | |
public class TestRunner extends BaseTestRunner implements TestRunContext { | |
private static final int GAP= 4; | |
private static final int HISTORY_LENGTH= 5; | |
protected JFrame fFrame; | |
private Thread fRunner; | |
private TestResult fTestResult; | |
private JComboBox fSuiteCombo; | |
private ProgressBar fProgressIndicator; | |
private DefaultListModel fFailures; | |
private JLabel fLogo; | |
private CounterPanel fCounterPanel; | |
private JButton fRun; | |
private JButton fQuitButton; | |
private JButton fRerunButton; | |
private StatusLine fStatusLine; | |
private FailureDetailView fFailureView; | |
private JTabbedPane fTestViewTab; | |
private JCheckBox fUseLoadingRunner; | |
private Vector fTestRunViews= new Vector(); // view associated with tab in tabbed pane | |
private static final String TESTCOLLECTOR_KEY= "TestCollectorClass"; | |
private static final String FAILUREDETAILVIEW_KEY= "FailureViewClass"; | |
public TestRunner() { | |
} | |
public static void main(String[] args) { | |
new TestRunner().start(args); | |
} | |
public static void run(Class test) { | |
String args[]= { test.getName() }; | |
main(args); | |
} | |
public void testFailed(final int status, final Test test, final Throwable t) { | |
SwingUtilities.invokeLater( | |
new Runnable() { | |
public void run() { | |
switch (status) { | |
case TestRunListener.STATUS_ERROR: | |
fCounterPanel.setErrorValue(fTestResult.errorCount()); | |
appendFailure(test, t); | |
break; | |
case TestRunListener.STATUS_FAILURE: | |
fCounterPanel.setFailureValue(fTestResult.failureCount()); | |
appendFailure(test, t); | |
break; | |
} | |
} | |
} | |
); | |
} | |
public void testStarted(String testName) { | |
postInfo("Running: "+testName); | |
} | |
public void testEnded(String stringName) { | |
synchUI(); | |
SwingUtilities.invokeLater( | |
new Runnable() { | |
public void run() { | |
if (fTestResult != null) { | |
fCounterPanel.setRunValue(fTestResult.runCount()); | |
fProgressIndicator.step(fTestResult.runCount(), fTestResult.wasSuccessful()); | |
} | |
} | |
} | |
); | |
} | |
public void setSuite(String suiteName) { | |
fSuiteCombo.getEditor().setItem(suiteName); | |
} | |
private void addToHistory(final String suite) { | |
for (int i= 0; i < fSuiteCombo.getItemCount(); i++) { | |
if (suite.equals(fSuiteCombo.getItemAt(i))) { | |
fSuiteCombo.removeItemAt(i); | |
fSuiteCombo.insertItemAt(suite, 0); | |
fSuiteCombo.setSelectedIndex(0); | |
return; | |
} | |
} | |
fSuiteCombo.insertItemAt(suite, 0); | |
fSuiteCombo.setSelectedIndex(0); | |
pruneHistory(); | |
} | |
private void pruneHistory() { | |
int historyLength= getPreference("maxhistory", HISTORY_LENGTH); | |
if (historyLength < 1) | |
historyLength= 1; | |
for (int i= fSuiteCombo.getItemCount()-1; i > historyLength-1; i--) | |
fSuiteCombo.removeItemAt(i); | |
} | |
private void appendFailure(Test test, Throwable t) { | |
fFailures.addElement(new TestFailure(test, t)); | |
if (fFailures.size() == 1) | |
revealFailure(test); | |
} | |
private void revealFailure(Test test) { | |
for (Enumeration e= fTestRunViews.elements(); e.hasMoreElements(); ) { | |
TestRunView v= (TestRunView) e.nextElement(); | |
v.revealFailure(test); | |
} | |
} | |
protected void aboutToStart(final Test testSuite) { | |
for (Enumeration e= fTestRunViews.elements(); e.hasMoreElements(); ) { | |
TestRunView v= (TestRunView) e.nextElement(); | |
v.aboutToStart(testSuite, fTestResult); | |
} | |
} | |
protected void runFinished(final Test testSuite) { | |
SwingUtilities.invokeLater( | |
new Runnable() { | |
public void run() { | |
for (Enumeration e= fTestRunViews.elements(); e.hasMoreElements(); ) { | |
TestRunView v= (TestRunView) e.nextElement(); | |
v.runFinished(testSuite, fTestResult); | |
} | |
} | |
} | |
); | |
} | |
protected CounterPanel createCounterPanel() { | |
return new CounterPanel(); | |
} | |
protected JPanel createFailedPanel() { | |
JPanel failedPanel= new JPanel(new GridLayout(0, 1, 0, 2)); | |
fRerunButton= new JButton("Run"); | |
fRerunButton.setEnabled(false); | |
fRerunButton.addActionListener( | |
new ActionListener() { | |
public void actionPerformed(ActionEvent e) { | |
rerun(); | |
} | |
} | |
); | |
failedPanel.add(fRerunButton); | |
return failedPanel; | |
} | |
protected FailureDetailView createFailureDetailView() { | |
String className= BaseTestRunner.getPreference(FAILUREDETAILVIEW_KEY); | |
if (className != null) { | |
Class viewClass= null; | |
try { | |
viewClass= Class.forName(className); | |
return (FailureDetailView)viewClass.newInstance(); | |
} catch(Exception e) { | |
JOptionPane.showMessageDialog(fFrame, "Could not create Failure DetailView - using default view"); | |
} | |
} | |
return new DefaultFailureDetailView(); | |
} | |
/** | |
* Creates the JUnit menu. Clients override this | |
* method to add additional menu items. | |
*/ | |
protected JMenu createJUnitMenu() { | |
JMenu menu= new JMenu("JUnit"); | |
menu.setMnemonic('J'); | |
JMenuItem mi1= new JMenuItem("About..."); | |
mi1.addActionListener( | |
new ActionListener() { | |
public void actionPerformed(ActionEvent event) { | |
about(); | |
} | |
} | |
); | |
mi1.setMnemonic('A'); | |
menu.add(mi1); | |
menu.addSeparator(); | |
JMenuItem mi2= new JMenuItem(" Exit "); | |
mi2.addActionListener( | |
new ActionListener() { | |
public void actionPerformed(ActionEvent event) { | |
terminate(); | |
} | |
} | |
); | |
mi2.setMnemonic('x'); | |
menu.add(mi2); | |
return menu; | |
} | |
protected JFrame createFrame() { | |
JFrame frame= new JFrame("JUnit"); | |
Image icon= loadFrameIcon(); | |
if (icon != null) | |
frame.setIconImage(icon); | |
frame.getContentPane().setLayout(new BorderLayout(0, 0)); | |
frame.addWindowListener( | |
new WindowAdapter() { | |
public void windowClosing(WindowEvent e) { | |
terminate(); | |
} | |
} | |
); | |
return frame; | |
} | |
protected JLabel createLogo() { | |
JLabel label; | |
Icon icon= getIconResource(BaseTestRunner.class, "logo.gif"); | |
if (icon != null) | |
label= new JLabel(icon); | |
else | |
label= new JLabel("JV"); | |
label.setToolTipText("JUnit Version "+Version.id()); | |
return label; | |
} | |
protected void createMenus(JMenuBar mb) { | |
mb.add(createJUnitMenu()); | |
} | |
protected JCheckBox createUseLoaderCheckBox() { | |
boolean useLoader= useReloadingTestSuiteLoader(); | |
JCheckBox box= new JCheckBox("Reload classes every run", useLoader); | |
box.setToolTipText("Use a custom class loader to reload the classes for every run"); | |
if (inVAJava()) | |
box.setVisible(false); | |
return box; | |
} | |
protected JButton createQuitButton() { | |
// spaces required to avoid layout flicker | |
// Exit is shorter than Stop that shows in the same column | |
JButton quit= new JButton(" Exit "); | |
quit.addActionListener( | |
new ActionListener() { | |
public void actionPerformed(ActionEvent e) { | |
terminate(); | |
} | |
} | |
); | |
return quit; | |
} | |
protected JButton createRunButton() { | |
JButton run= new JButton("Run"); | |
run.setEnabled(true); | |
run.addActionListener( | |
new ActionListener() { | |
public void actionPerformed(ActionEvent e) { | |
runSuite(); | |
} | |
} | |
); | |
return run; | |
} | |
protected Component createBrowseButton() { | |
JButton browse= new JButton("..."); | |
browse.setToolTipText("Select a Test class"); | |
browse.addActionListener( | |
new ActionListener() { | |
public void actionPerformed(ActionEvent e) { | |
browseTestClasses(); | |
} | |
} | |
); | |
return browse; | |
} | |
protected StatusLine createStatusLine() { | |
return new StatusLine(380); | |
} | |
protected JComboBox createSuiteCombo() { | |
JComboBox combo= new JComboBox(); | |
combo.setEditable(true); | |
combo.setLightWeightPopupEnabled(false); | |
combo.getEditor().getEditorComponent().addKeyListener( | |
new KeyAdapter() { | |
public void keyTyped(KeyEvent e) { | |
textChanged(); | |
if (e.getKeyChar() == KeyEvent.VK_ENTER) | |
runSuite(); | |
} | |
} | |
); | |
try { | |
loadHistory(combo); | |
} catch (IOException e) { | |
// fails the first time | |
} | |
combo.addItemListener( | |
new ItemListener() { | |
public void itemStateChanged(ItemEvent event) { | |
if (event.getStateChange() == ItemEvent.SELECTED) { | |
textChanged(); | |
} | |
} | |
} | |
); | |
return combo; | |
} | |
protected JTabbedPane createTestRunViews() { | |
JTabbedPane pane= new JTabbedPane(JTabbedPane.BOTTOM); | |
FailureRunView lv= new FailureRunView(this); | |
fTestRunViews.addElement(lv); | |
lv.addTab(pane); | |
TestHierarchyRunView tv= new TestHierarchyRunView(this); | |
fTestRunViews.addElement(tv); | |
tv.addTab(pane); | |
pane.addChangeListener( | |
new ChangeListener() { | |
public void stateChanged(ChangeEvent e) { | |
testViewChanged(); | |
} | |
} | |
); | |
return pane; | |
} | |
public void testViewChanged() { | |
TestRunView view= (TestRunView)fTestRunViews.elementAt(fTestViewTab.getSelectedIndex()); | |
view.activate(); | |
} | |
protected TestResult createTestResult() { | |
return new TestResult(); | |
} | |
protected JFrame createUI(String suiteName) { | |
JFrame frame= createFrame(); | |
JMenuBar mb= new JMenuBar(); | |
createMenus(mb); | |
frame.setJMenuBar(mb); | |
JLabel suiteLabel= new JLabel("Test class name:"); | |
fSuiteCombo= createSuiteCombo(); | |
fRun= createRunButton(); | |
frame.getRootPane().setDefaultButton(fRun); | |
Component browseButton= createBrowseButton(); | |
fUseLoadingRunner= createUseLoaderCheckBox(); | |
fProgressIndicator= new ProgressBar(); | |
fCounterPanel= createCounterPanel(); | |
fFailures= new DefaultListModel(); | |
fTestViewTab= createTestRunViews(); | |
JPanel failedPanel= createFailedPanel(); | |
fFailureView= createFailureDetailView(); | |
JScrollPane tracePane= new JScrollPane(fFailureView.getComponent(), JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); | |
fStatusLine= createStatusLine(); | |
fQuitButton= createQuitButton(); | |
fLogo= createLogo(); | |
JPanel panel= new JPanel(new GridBagLayout()); | |
addGrid(panel, suiteLabel, 0, 0, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); | |
addGrid(panel, fSuiteCombo, 0, 1, 1, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); | |
addGrid(panel, browseButton, 1, 1, 1, GridBagConstraints.NONE, 0.0, GridBagConstraints.WEST); | |
addGrid(panel, fRun, 2, 1, 1, GridBagConstraints.HORIZONTAL, 0.0, GridBagConstraints.CENTER); | |
addGrid(panel, fUseLoadingRunner, 0, 2, 3, GridBagConstraints.NONE, 1.0, GridBagConstraints.WEST); | |
//addGrid(panel, new JSeparator(), 0, 3, 3, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); | |
addGrid(panel, fProgressIndicator, 0, 3, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); | |
addGrid(panel, fLogo, 2, 3, 1, GridBagConstraints.NONE, 0.0, GridBagConstraints.NORTH); | |
addGrid(panel, fCounterPanel, 0, 4, 2, GridBagConstraints.NONE, 0.0, GridBagConstraints.WEST); | |
addGrid(panel, new JSeparator(), 0, 5, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); | |
addGrid(panel, new JLabel("Results:"), 0, 6, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); | |
JSplitPane splitter= new JSplitPane(JSplitPane.VERTICAL_SPLIT, fTestViewTab, tracePane); | |
addGrid(panel, splitter, 0, 7, 2, GridBagConstraints.BOTH, 1.0, GridBagConstraints.WEST); | |
addGrid(panel, failedPanel, 2, 7, 1, GridBagConstraints.HORIZONTAL, 0.0, GridBagConstraints.NORTH/*CENTER*/); | |
addGrid(panel, fStatusLine, 0, 9, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.CENTER); | |
addGrid(panel, fQuitButton, 2, 9, 1, GridBagConstraints.HORIZONTAL, 0.0, GridBagConstraints.CENTER); | |
frame.setContentPane(panel); | |
frame.pack(); | |
frame.setLocation(200, 200); | |
return frame; | |
} | |
private void addGrid(JPanel p, Component co, int x, int y, int w, int fill, double wx, int anchor) { | |
GridBagConstraints c= new GridBagConstraints(); | |
c.gridx= x; c.gridy= y; | |
c.gridwidth= w; | |
c.anchor= anchor; | |
c.weightx= wx; | |
c.fill= fill; | |
if (fill == GridBagConstraints.BOTH || fill == GridBagConstraints.VERTICAL) | |
c.weighty= 1.0; | |
c.insets= new Insets(y == 0 ? 10 : 0, x == 0 ? 10 : GAP, GAP, GAP); | |
p.add(co, c); | |
} | |
protected String getSuiteText() { | |
if (fSuiteCombo == null) | |
return ""; | |
return (String)fSuiteCombo.getEditor().getItem(); | |
} | |
public ListModel getFailures() { | |
return fFailures; | |
} | |
public void insertUpdate(DocumentEvent event) { | |
textChanged(); | |
} | |
public void browseTestClasses() { | |
TestCollector collector= createTestCollector(); | |
TestSelector selector= new TestSelector(fFrame, collector); | |
if (selector.isEmpty()) { | |
JOptionPane.showMessageDialog(fFrame, "No Test Cases found.\nCheck that the configured \'TestCollector\' is supported on this platform."); | |
return; | |
} | |
selector.show(); | |
String className= selector.getSelectedItem(); | |
if (className != null) | |
setSuite(className); | |
} | |
TestCollector createTestCollector() { | |
String className= BaseTestRunner.getPreference(TESTCOLLECTOR_KEY); | |
if (className != null) { | |
Class collectorClass= null; | |
try { | |
collectorClass= Class.forName(className); | |
return (TestCollector)collectorClass.newInstance(); | |
} catch(Exception e) { | |
JOptionPane.showMessageDialog(fFrame, "Could not create TestCollector - using default collector"); | |
} | |
} | |
return new SimpleTestCollector(); | |
} | |
private Image loadFrameIcon() { | |
ImageIcon icon= (ImageIcon)getIconResource(BaseTestRunner.class, "smalllogo.gif"); | |
if (icon != null) | |
return icon.getImage(); | |
return null; | |
} | |
private void loadHistory(JComboBox combo) throws IOException { | |
BufferedReader br= new BufferedReader(new FileReader(getSettingsFile())); | |
int itemCount= 0; | |
try { | |
String line; | |
while ((line= br.readLine()) != null) { | |
combo.addItem(line); | |
itemCount++; | |
} | |
if (itemCount > 0) | |
combo.setSelectedIndex(0); | |
} finally { | |
br.close(); | |
} | |
} | |
private File getSettingsFile() { | |
String home= System.getProperty("user.home"); | |
return new File(home,".junitsession"); | |
} | |
private void postInfo(final String message) { | |
SwingUtilities.invokeLater( | |
new Runnable() { | |
public void run() { | |
showInfo(message); | |
} | |
} | |
); | |
} | |
private void postStatus(final String status) { | |
SwingUtilities.invokeLater( | |
new Runnable() { | |
public void run() { | |
showStatus(status); | |
} | |
} | |
); | |
} | |
public void removeUpdate(DocumentEvent event) { | |
textChanged(); | |
} | |
private void rerun() { | |
TestRunView view= (TestRunView)fTestRunViews.elementAt(fTestViewTab.getSelectedIndex()); | |
Test rerunTest= view.getSelectedTest(); | |
if (rerunTest != null) | |
rerunTest(rerunTest); | |
} | |
private void rerunTest(Test test) { | |
if (!(test instanceof TestCase)) { | |
showInfo("Could not reload "+ test.toString()); | |
return; | |
} | |
Test reloadedTest= null; | |
TestCase rerunTest= (TestCase)test; | |
try { | |
Class reloadedTestClass= getLoader().reload(test.getClass()); | |
reloadedTest= TestSuite.createTest(reloadedTestClass, rerunTest.getName()); | |
} catch(Exception e) { | |
showInfo("Could not reload "+ test.toString()); | |
return; | |
} | |
TestResult result= new TestResult(); | |
reloadedTest.run(result); | |
String message= reloadedTest.toString(); | |
if(result.wasSuccessful()) | |
showInfo(message+" was successful"); | |
else if (result.errorCount() == 1) | |
showStatus(message+" had an error"); | |
else | |
showStatus(message+" had a failure"); | |
} | |
protected void reset() { | |
fCounterPanel.reset(); | |
fProgressIndicator.reset(); | |
fRerunButton.setEnabled(false); | |
fFailureView.clear(); | |
fFailures.clear(); | |
} | |
protected void runFailed(String message) { | |
showStatus(message); | |
fRun.setText("Run"); | |
fRunner= null; | |
} | |
synchronized public void runSuite() { | |
if (fRunner != null) { | |
fTestResult.stop(); | |
} else { | |
setLoading(shouldReload()); | |
reset(); | |
showInfo("Load Test Case..."); | |
final String suiteName= getSuiteText(); | |
final Test testSuite= getTest(suiteName); | |
if (testSuite != null) { | |
addToHistory(suiteName); | |
doRunTest(testSuite); | |
} | |
} | |
} | |
private boolean shouldReload() { | |
return !inVAJava() && fUseLoadingRunner.isSelected(); | |
} | |
synchronized protected void runTest(final Test testSuite) { | |
if (fRunner != null) { | |
fTestResult.stop(); | |
} else { | |
reset(); | |
if (testSuite != null) { | |
doRunTest(testSuite); | |
} | |
} | |
} | |
private void doRunTest(final Test testSuite) { | |
setButtonLabel(fRun, "Stop"); | |
fRunner= new Thread("TestRunner-Thread") { | |
public void run() { | |
TestRunner.this.start(testSuite); | |
postInfo("Running..."); | |
long startTime= System.currentTimeMillis(); | |
testSuite.run(fTestResult); | |
if (fTestResult.shouldStop()) { | |
postStatus("Stopped"); | |
} else { | |
long endTime= System.currentTimeMillis(); | |
long runTime= endTime-startTime; | |
postInfo("Finished: " + elapsedTimeAsString(runTime) + " seconds"); | |
} | |
runFinished(testSuite); | |
setButtonLabel(fRun, "Run"); | |
fRunner= null; | |
System.gc(); | |
} | |
}; | |
// make sure that the test result is created before we start the | |
// test runner thread so that listeners can register for it. | |
fTestResult= createTestResult(); | |
fTestResult.addListener(TestRunner.this); | |
aboutToStart(testSuite); | |
fRunner.start(); | |
} | |
private void saveHistory() throws IOException { | |
BufferedWriter bw= new BufferedWriter(new FileWriter(getSettingsFile())); | |
try { | |
for (int i= 0; i < fSuiteCombo.getItemCount(); i++) { | |
String testsuite= fSuiteCombo.getItemAt(i).toString(); | |
bw.write(testsuite, 0, testsuite.length()); | |
bw.newLine(); | |
} | |
} finally { | |
bw.close(); | |
} | |
} | |
private void setButtonLabel(final JButton button, final String label) { | |
SwingUtilities.invokeLater( | |
new Runnable() { | |
public void run() { | |
button.setText(label); | |
} | |
} | |
); | |
} | |
public void handleTestSelected(Test test) { | |
fRerunButton.setEnabled(test != null && (test instanceof TestCase)); | |
showFailureDetail(test); | |
} | |
private void showFailureDetail(Test test) { | |
if (test != null) { | |
ListModel failures= getFailures(); | |
for (int i= 0; i < failures.getSize(); i++) { | |
TestFailure failure= (TestFailure)failures.getElementAt(i); | |
if (failure.failedTest() == test) { | |
fFailureView.showFailure(failure); | |
return; | |
} | |
} | |
} | |
fFailureView.clear(); | |
} | |
private void showInfo(String message) { | |
fStatusLine.showInfo(message); | |
} | |
private void showStatus(String status) { | |
fStatusLine.showError(status); | |
} | |
/** | |
* Starts the TestRunner | |
*/ | |
public void start(String[] args) { | |
String suiteName= processArguments(args); | |
fFrame= createUI(suiteName); | |
fFrame.pack(); | |
fFrame.setVisible(true); | |
if (suiteName != null) { | |
setSuite(suiteName); | |
runSuite(); | |
} | |
} | |
private void start(final Test test) { | |
SwingUtilities.invokeLater( | |
new Runnable() { | |
public void run() { | |
int total= test.countTestCases(); | |
fProgressIndicator.start(total); | |
fCounterPanel.setTotal(total); | |
} | |
} | |
); | |
} | |
/** | |
* Wait until all the events are processed in the event thread | |
*/ | |
private void synchUI() { | |
try { | |
SwingUtilities.invokeAndWait( | |
new Runnable() { | |
public void run() {} | |
} | |
); | |
} | |
catch (Exception e) { | |
} | |
} | |
/** | |
* Terminates the TestRunner | |
*/ | |
public void terminate() { | |
fFrame.dispose(); | |
try { | |
saveHistory(); | |
} catch (IOException e) { | |
System.out.println("Couldn't save test run history"); | |
} | |
System.exit(0); | |
} | |
public void textChanged() { | |
fRun.setEnabled(getSuiteText().length() > 0); | |
clearStatus(); | |
} | |
protected void clearStatus() { | |
fStatusLine.clear(); | |
} | |
public static Icon getIconResource(Class clazz, String name) { | |
URL url= clazz.getResource(name); | |
if (url == null) { | |
System.err.println("Warning: could not load \""+name+"\" icon"); | |
return null; | |
} | |
return new ImageIcon(url); | |
} | |
private void about() { | |
AboutDialog about= new AboutDialog(fFrame); | |
about.show(); | |
} | |
} |