package junit.swingui;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.Enumeration;
import java.util.Vector;
import javax.swing.DefaultListModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.ListModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestFailure;
import junit.framework.TestResult;
import junit.framework.TestSuite;
import junit.runner.BaseTestRunner;
import junit.runner.FailureDetailView;
import junit.runner.JUnit3AdaptedBaseTestRunner;
import junit.runner.SimpleTestCollector;
import junit.runner.TestCollector;
import junit.runner.TestRunListener;
import junit.runner.Version;
/**
* 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 JUnit3AdaptedBaseTestRunner 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(SwingConstants.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();
fStatusLine= createStatusLine();
if (inMac())
fProgressIndicator= new MacProgressBar(fStatusLine);
else
fProgressIndicator= new ProgressBar();
fCounterPanel= createCounterPanel();
fFailures= new DefaultListModel();
fTestViewTab= createTestRunViews();
JPanel failedPanel= createFailedPanel();
fFailureView= createFailureDetailView();
JScrollPane tracePane= new JScrollPane(fFailureView.getComponent(), ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
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();
}
protected Object instanciateClass(String fullClassName, Object param) {
try {
Class clazz= Class.forName(fullClassName);
if (param == null) {
return clazz.newInstance();
} else {
Class[] clazzParam= {param.getClass()};
Constructor clazzConstructor= clazz.getConstructor(clazzParam);
Object[] objectParam= {param};
return clazzConstructor.newInstance(objectParam);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
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();
}
}