diff options
author | Brett Chabot <brettchabot@google.com> | 2009-06-17 17:21:33 -0700 |
---|---|---|
committer | Brett Chabot <brettchabot@google.com> | 2009-06-17 17:21:33 -0700 |
commit | 58a8b0aba2dec5695628a2bf25a3fae42c2c3533 (patch) | |
tree | 7c3e88d9efbf280f4849710858b6a67c740920be /src | |
parent | d550fde3ba6ac3e9b5abb87b7d722a4d30f8d57b (diff) | |
download | junit-58a8b0aba2dec5695628a2bf25a3fae42c2c3533.tar.gz |
Add junit 3.8.2 source to external/junit.
Diffstat (limited to 'src')
57 files changed, 5678 insertions, 0 deletions
diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000..e171a01 --- /dev/null +++ b/src/META-INF/MANIFEST.MF @@ -0,0 +1,4 @@ +Manifest-Version: 1.0
+Ant-Version: Apache Ant 1.6.5
+Created-By: 1.4.2_10-b03 (Sun Microsystems Inc.)
+
diff --git a/src/junit/awtui/AboutDialog.java b/src/junit/awtui/AboutDialog.java new file mode 100644 index 0000000..38f57bb --- /dev/null +++ b/src/junit/awtui/AboutDialog.java @@ -0,0 +1,77 @@ +package junit.awtui; + +import java.awt.Button; +import java.awt.Dialog; +import java.awt.Font; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Label; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import junit.runner.Version; + +class AboutDialog extends Dialog { + public AboutDialog(Frame parent) { + super(parent); + + setResizable(false); + setLayout(new GridBagLayout()); + setSize(330, 138); + setTitle("About"); + + Button button= new Button("Close"); + button.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + dispose(); + } + } + ); + + Label label1= new Label("JUnit"); + label1.setFont(new Font("dialog", Font.PLAIN, 36)); + + Label label2= new Label("JUnit "+Version.id()+ " by Kent Beck and Erich Gamma"); + label2.setFont(new Font("dialog", Font.PLAIN, 14)); + + Logo logo= new Logo(); + + GridBagConstraints constraintsLabel1= new GridBagConstraints(); + constraintsLabel1.gridx = 3; constraintsLabel1.gridy = 0; + constraintsLabel1.gridwidth = 1; constraintsLabel1.gridheight = 1; + constraintsLabel1.anchor = GridBagConstraints.CENTER; + add(label1, constraintsLabel1); + + GridBagConstraints constraintsLabel2= new GridBagConstraints(); + constraintsLabel2.gridx = 2; constraintsLabel2.gridy = 1; + constraintsLabel2.gridwidth = 2; constraintsLabel2.gridheight = 1; + constraintsLabel2.anchor = GridBagConstraints.CENTER; + add(label2, constraintsLabel2); + + GridBagConstraints constraintsButton1= new GridBagConstraints(); + constraintsButton1.gridx = 2; constraintsButton1.gridy = 2; + constraintsButton1.gridwidth = 2; constraintsButton1.gridheight = 1; + constraintsButton1.anchor = GridBagConstraints.CENTER; + constraintsButton1.insets= new Insets(8, 0, 8, 0); + add(button, constraintsButton1); + + GridBagConstraints constraintsLogo1= new GridBagConstraints(); + constraintsLogo1.gridx = 2; constraintsLogo1.gridy = 0; + constraintsLogo1.gridwidth = 1; constraintsLogo1.gridheight = 1; + constraintsLogo1.anchor = GridBagConstraints.CENTER; + add(logo, constraintsLogo1); + + addWindowListener( + new WindowAdapter() { + public void windowClosing(WindowEvent e) { + dispose(); + } + } + ); + } +}
\ No newline at end of file diff --git a/src/junit/awtui/Logo.java b/src/junit/awtui/Logo.java new file mode 100644 index 0000000..4cc3b4f --- /dev/null +++ b/src/junit/awtui/Logo.java @@ -0,0 +1,58 @@ +package junit.awtui; + +import java.awt.Canvas; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.MediaTracker; +import java.awt.SystemColor; +import java.awt.Toolkit; +import java.awt.image.ImageProducer; +import java.net.URL; + +import junit.runner.BaseTestRunner; + +public class Logo extends Canvas { + private Image fImage; + private int fWidth; + private int fHeight; + + public Logo() { + fImage= loadImage("logo.gif"); + MediaTracker tracker= new MediaTracker(this); + tracker.addImage(fImage, 0); + try { + tracker.waitForAll(); + } catch (Exception e) { + } + + if (fImage != null) { + fWidth= fImage.getWidth(this); + fHeight= fImage.getHeight(this); + } else { + fWidth= 20; + fHeight= 20; + } + setSize(fWidth, fHeight); + } + + public Image loadImage(String name) { + Toolkit toolkit= Toolkit.getDefaultToolkit(); + try { + URL url= BaseTestRunner.class.getResource(name); + return toolkit.createImage((ImageProducer) url.getContent()); + } catch (Exception ex) { + } + return null; + } + + public void paint(Graphics g) { + paintBackground(g); + if (fImage != null) + g.drawImage(fImage, 0, 0, fWidth, fHeight, this); + } + + public void paintBackground( java.awt.Graphics g) { + g.setColor(SystemColor.control); + g.fillRect(0, 0, getBounds().width, getBounds().height); + } +}
\ No newline at end of file diff --git a/src/junit/awtui/ProgressBar.java b/src/junit/awtui/ProgressBar.java new file mode 100644 index 0000000..03e7ec2 --- /dev/null +++ b/src/junit/awtui/ProgressBar.java @@ -0,0 +1,88 @@ +package junit.awtui; + +import java.awt.Canvas; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.SystemColor; + +public class ProgressBar extends Canvas { + public boolean fError= false; + public int fTotal= 0; + public int fProgress= 0; + public int fProgressX= 0; + + public ProgressBar() { + super(); + setSize(20, 30); + } + + private Color getStatusColor() { + if (fError) + return Color.red; + return Color.green; + } + + public void paint(Graphics g) { + paintBackground(g); + paintStatus(g); + } + + public void paintBackground(Graphics g) { + g.setColor(SystemColor.control); + Rectangle r= getBounds(); + g.fillRect(0, 0, r.width, r.height); + g.setColor(Color.darkGray); + g.drawLine(0, 0, r.width-1, 0); + g.drawLine(0, 0, 0, r.height-1); + g.setColor(Color.white); + g.drawLine(r.width-1, 0, r.width-1, r.height-1); + g.drawLine(0, r.height-1, r.width-1, r.height-1); + } + + public void paintStatus(Graphics g) { + g.setColor(getStatusColor()); + Rectangle r= new Rectangle(0, 0, fProgressX, getBounds().height); + g.fillRect(1, 1, r.width-1, r.height-2); + } + + private void paintStep(int startX, int endX) { + repaint(startX, 1, endX-startX, getBounds().height-2); + } + + public void reset() { + fProgressX= 1; + fProgress= 0; + fError= false; + paint(getGraphics()); + } + + public int scale(int value) { + if (fTotal > 0) + return Math.max(1, value*(getBounds().width-1)/fTotal); + return value; + } + + public void setBounds(int x, int y, int w, int h) { + super.setBounds(x, y, w, h); + fProgressX= scale(fProgress); + } + + public void start(int total) { + fTotal= total; + reset(); + } + + public void step(boolean successful) { + fProgress++; + int x= fProgressX; + + fProgressX= scale(fProgress); + + if (!fError && !successful) { + fError= true; + x= 1; + } + paintStep(x, fProgressX); + } +}
\ No newline at end of file diff --git a/src/junit/awtui/TestRunner.java b/src/junit/awtui/TestRunner.java new file mode 100644 index 0000000..cb735d5 --- /dev/null +++ b/src/junit/awtui/TestRunner.java @@ -0,0 +1,571 @@ +package junit.awtui; + +import java.awt.BorderLayout; +import java.awt.Button; +import java.awt.Checkbox; +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.Image; +import java.awt.Insets; +import java.awt.Label; +import java.awt.List; +import java.awt.Menu; +import java.awt.MenuBar; +import java.awt.MenuItem; +import java.awt.Panel; +import java.awt.SystemColor; +import java.awt.TextArea; +import java.awt.TextField; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.TextEvent; +import java.awt.event.TextListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.image.ImageProducer; +import java.util.Vector; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestResult; +import junit.framework.TestSuite; +import junit.runner.BaseTestRunner; +import junit.runner.TestRunListener; + +/** + * An AWT 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.awtui.TestRunner [-noloading] [TestCase] + * </pre> + * TestRunner takes as an optional argument the name of the testcase class to be run. + */ + public class TestRunner extends BaseTestRunner { + protected Frame fFrame; + protected Vector fExceptions; + protected Vector fFailedTests; + protected Thread fRunner; + protected TestResult fTestResult; + + protected TextArea fTraceArea; + protected TextField fSuiteField; + protected Button fRun; + protected ProgressBar fProgressIndicator; + protected List fFailureList; + protected Logo fLogo; + protected Label fNumberOfErrors; + protected Label fNumberOfFailures; + protected Label fNumberOfRuns; + protected Button fQuitButton; + protected Button fRerunButton; + protected TextField fStatusLine; + protected Checkbox fUseLoadingRunner; + + protected static final Font PLAIN_FONT= new Font("dialog", Font.PLAIN, 12); + private static final int GAP= 4; + + public TestRunner() { + } + + private void about() { + AboutDialog about= new AboutDialog(fFrame); + about.setModal(true); + about.setLocation(300, 300); + about.setVisible(true); + } + + public void testStarted(String testName) { + showInfo("Running: "+testName); + } + + public void testEnded(String testName) { + setLabelValue(fNumberOfRuns, fTestResult.runCount()); + synchronized(this) { + fProgressIndicator.step(fTestResult.wasSuccessful()); + } + } + + public void testFailed(int status, Test test, Throwable t) { + switch (status) { + case TestRunListener.STATUS_ERROR: + fNumberOfErrors.setText(Integer.toString(fTestResult.errorCount())); + appendFailure("Error", test, t); + break; + case TestRunListener.STATUS_FAILURE: + fNumberOfFailures.setText(Integer.toString(fTestResult.failureCount())); + appendFailure("Failure", test, t); + break; + } + } + + protected void addGrid(Panel 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 ? GAP : 0, x == 0 ? GAP : 0, GAP, GAP); + p.add(co, c); + } + + private void appendFailure(String kind, Test test, Throwable t) { + kind+= ": " + test; + String msg= t.getMessage(); + if (msg != null) { + kind+= ":" + truncate(msg); + } + fFailureList.add(kind); + fExceptions.addElement(t); + fFailedTests.addElement(test); + if (fFailureList.getItemCount() == 1) { + fFailureList.select(0); + failureSelected(); + } + } + /** + * Creates the JUnit menu. Clients override this + * method to add additional menu items. + */ + protected Menu createJUnitMenu() { + Menu menu= new Menu("JUnit"); + MenuItem mi= new MenuItem("About..."); + mi.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent event) { + about(); + } + } + ); + menu.add(mi); + + menu.addSeparator(); + mi= new MenuItem("Exit"); + mi.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent event) { + System.exit(0); + } + } + ); + menu.add(mi); + return menu; + } + + protected void createMenus(MenuBar mb) { + mb.add(createJUnitMenu()); + } + protected TestResult createTestResult() { + return new TestResult(); + } + + protected Frame createUI(String suiteName) { + Frame frame= new Frame("JUnit"); + Image icon= loadFrameIcon(); + if (icon != null) + frame.setIconImage(icon); + + frame.setLayout(new BorderLayout(0, 0)); + frame.setBackground(SystemColor.control); + final Frame finalFrame= frame; + + frame.addWindowListener( + new WindowAdapter() { + public void windowClosing(WindowEvent e) { + finalFrame.dispose(); + System.exit(0); + } + } + ); + + MenuBar mb = new MenuBar(); + createMenus(mb); + frame.setMenuBar(mb); + + //---- first section + Label suiteLabel= new Label("Test class name:"); + + fSuiteField= new TextField(suiteName != null ? suiteName : ""); + fSuiteField.selectAll(); + fSuiteField.requestFocus(); + fSuiteField.setFont(PLAIN_FONT); + fSuiteField.setColumns(40); + fSuiteField.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + runSuite(); + } + } + ); + fSuiteField.addTextListener( + new TextListener() { + public void textValueChanged(TextEvent e) { + fRun.setEnabled(fSuiteField.getText().length() > 0); + fStatusLine.setText(""); + } + } + ); + fRun= new Button("Run"); + fRun.setEnabled(false); + fRun.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + runSuite(); + } + } + ); + boolean useLoader= useReloadingTestSuiteLoader(); + fUseLoadingRunner= new Checkbox("Reload classes every run", useLoader); + if (inVAJava()) + fUseLoadingRunner.setVisible(false); + + //---- second section + fProgressIndicator= new ProgressBar(); + + //---- third section + fNumberOfErrors= new Label("0000", Label.RIGHT); + fNumberOfErrors.setText("0"); + fNumberOfErrors.setFont(PLAIN_FONT); + + fNumberOfFailures= new Label("0000", Label.RIGHT); + fNumberOfFailures.setText("0"); + fNumberOfFailures.setFont(PLAIN_FONT); + + fNumberOfRuns= new Label("0000", Label.RIGHT); + fNumberOfRuns.setText("0"); + fNumberOfRuns.setFont(PLAIN_FONT); + + Panel numbersPanel= createCounterPanel(); + + //---- fourth section + Label failureLabel= new Label("Errors and Failures:"); + + fFailureList= new List(5); + fFailureList.addItemListener( + new ItemListener() { + public void itemStateChanged(ItemEvent e) { + failureSelected(); + } + } + ); + fRerunButton= new Button("Run"); + fRerunButton.setEnabled(false); + fRerunButton.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + rerun(); + } + } + ); + + Panel failedPanel= new Panel(new GridLayout(0, 1, 0, 2)); + failedPanel.add(fRerunButton); + + fTraceArea= new TextArea(); + fTraceArea.setRows(5); + fTraceArea.setColumns(60); + + //---- fifth section + fStatusLine= new TextField(); + fStatusLine.setFont(PLAIN_FONT); + fStatusLine.setEditable(false); + fStatusLine.setForeground(Color.red); + + fQuitButton= new Button("Exit"); + fQuitButton.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + System.exit(0); + } + } + ); + + // --------- + fLogo= new Logo(); + + //---- overall layout + Panel panel= new Panel(new GridBagLayout()); + + addGrid(panel, suiteLabel, 0, 0, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); + + addGrid(panel, fSuiteField, 0, 1, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); + addGrid(panel, fRun, 2, 1, 1, GridBagConstraints.HORIZONTAL, 0.0, GridBagConstraints.CENTER); + addGrid(panel, fUseLoadingRunner, 0, 2, 2, GridBagConstraints.NONE, 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, numbersPanel, 0, 4, 2, GridBagConstraints.NONE, 0.0, GridBagConstraints.WEST); + + addGrid(panel, failureLabel, 0, 5, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); + addGrid(panel, fFailureList, 0, 6, 2, GridBagConstraints.BOTH, 1.0, GridBagConstraints.WEST); + addGrid(panel, failedPanel, 2, 6, 1, GridBagConstraints.HORIZONTAL, 0.0, GridBagConstraints.CENTER); + addGrid(panel, fTraceArea, 0, 7, 2, GridBagConstraints.BOTH, 1.0, GridBagConstraints.WEST); + + addGrid(panel, fStatusLine, 0, 8, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.CENTER); + addGrid(panel, fQuitButton, 2, 8, 1, GridBagConstraints.HORIZONTAL, 0.0, GridBagConstraints.CENTER); + + frame.add(panel, BorderLayout.CENTER); + frame.pack(); + return frame; + } + + protected Panel createCounterPanel() { + Panel numbersPanel= new Panel(new GridBagLayout()); + addToCounterPanel( + numbersPanel, + new Label("Runs:"), + 0, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0) + ); + addToCounterPanel( + numbersPanel, + fNumberOfRuns, + 1, 0, 1, 1, 0.33, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(0, 8, 0, 40) + ); + addToCounterPanel( + numbersPanel, + new Label("Errors:"), + 2, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.NONE, + new Insets(0, 8, 0, 0) + ); + addToCounterPanel( + numbersPanel, + fNumberOfErrors, + 3, 0, 1, 1, 0.33, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(0, 8, 0, 40) + ); + addToCounterPanel( + numbersPanel, + new Label("Failures:"), + 4, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.NONE, + new Insets(0, 8, 0, 0) + ); + addToCounterPanel( + numbersPanel, + fNumberOfFailures, + 5, 0, 1, 1, 0.33, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(0, 8, 0, 0) + ); + return numbersPanel; + } + + private void addToCounterPanel(Panel counter, Component comp, + int gridx, int gridy, int gridwidth, int gridheight, + double weightx, double weighty, + int anchor, int fill, + Insets insets) { + + GridBagConstraints constraints= new GridBagConstraints(); + constraints.gridx= gridx; + constraints.gridy= gridy; + constraints.gridwidth= gridwidth; + constraints.gridheight= gridheight; + constraints.weightx= weightx; + constraints.weighty= weighty; + constraints.anchor= anchor; + constraints.fill= fill; + constraints.insets= insets; + counter.add(comp, constraints); + } + + + public void failureSelected() { + fRerunButton.setEnabled(isErrorSelected()); + showErrorTrace(); + } + + private boolean isErrorSelected() { + return fFailureList.getSelectedIndex() != -1; + } + + private Image loadFrameIcon() { + Toolkit toolkit= Toolkit.getDefaultToolkit(); + try { + java.net.URL url= BaseTestRunner.class.getResource("smalllogo.gif"); + return toolkit.createImage((ImageProducer) url.getContent()); + } catch (Exception ex) { + } + return null; + } + + public Thread getRunner() { + return fRunner; + } + + public static void main(String[] args) { + new TestRunner().start(args); + } + + public static void run(Class test) { + String args[]= { test.getName() }; + main(args); + } + + public void rerun() { + int index= fFailureList.getSelectedIndex(); + if (index == -1) + return; + + Test test= (Test)fFailedTests.elementAt(index); + rerunTest(test); + } + + 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() { + setLabelValue(fNumberOfErrors, 0); + setLabelValue(fNumberOfFailures, 0); + setLabelValue(fNumberOfRuns, 0); + fProgressIndicator.reset(); + fRerunButton.setEnabled(false); + fFailureList.removeAll(); + fExceptions= new Vector(10); + fFailedTests= new Vector(10); + fTraceArea.setText(""); + + } + + protected void runFailed(String message) { + showStatus(message); + fRun.setLabel("Run"); + fRunner= null; + } + + synchronized public void runSuite() { + if (fRunner != null && fTestResult != null) { + fTestResult.stop(); + } else { + setLoading(shouldReload()); + fRun.setLabel("Stop"); + showInfo("Initializing..."); + reset(); + + showInfo("Load Test Case..."); + + final Test testSuite= getTest(fSuiteField.getText()); + if (testSuite != null) { + fRunner= new Thread() { + public void run() { + fTestResult= createTestResult(); + fTestResult.addListener(TestRunner.this); + fProgressIndicator.start(testSuite.countTestCases()); + showInfo("Running..."); + + long startTime= System.currentTimeMillis(); + testSuite.run(fTestResult); + + if (fTestResult.shouldStop()) { + showStatus("Stopped"); + } else { + long endTime= System.currentTimeMillis(); + long runTime= endTime-startTime; + showInfo("Finished: " + elapsedTimeAsString(runTime) + " seconds"); + } + fTestResult= null; + fRun.setLabel("Run"); + fRunner= null; + System.gc(); + } + }; + fRunner.start(); + } + } + } + + private boolean shouldReload() { + return !inVAJava() && fUseLoadingRunner.getState(); + } + + private void setLabelValue(Label label, int value) { + label.setText(Integer.toString(value)); + label.invalidate(); + label.getParent().validate(); + + } + + public void setSuiteName(String suite) { + fSuiteField.setText(suite); + } + + private void showErrorTrace() { + int index= fFailureList.getSelectedIndex(); + if (index == -1) + return; + + Throwable t= (Throwable) fExceptions.elementAt(index); + fTraceArea.setText(getFilteredTrace(t)); + } + + + private void showInfo(String message) { + fStatusLine.setFont(PLAIN_FONT); + fStatusLine.setForeground(Color.black); + fStatusLine.setText(message); + } + + protected void clearStatus() { + showStatus(""); + } + + private void showStatus(String status) { + fStatusLine.setFont(PLAIN_FONT); + fStatusLine.setForeground(Color.red); + fStatusLine.setText(status); + } + /** + * Starts the TestRunner + */ + public void start(String[] args) { + String suiteName= processArguments(args); + fFrame= createUI(suiteName); + fFrame.setLocation(200, 200); + fFrame.setVisible(true); + + if (suiteName != null) { + setSuiteName(suiteName); + runSuite(); + } + } +}
\ No newline at end of file diff --git a/src/junit/extensions/ActiveTestSuite.java b/src/junit/extensions/ActiveTestSuite.java new file mode 100644 index 0000000..9fc4edd --- /dev/null +++ b/src/junit/extensions/ActiveTestSuite.java @@ -0,0 +1,66 @@ +package junit.extensions; + +import junit.framework.Test; +import junit.framework.TestResult; +import junit.framework.TestSuite; + +/** + * A TestSuite for active Tests. It runs each + * test in a separate thread and waits until all + * threads have terminated. + * -- Aarhus Radisson Scandinavian Center 11th floor + */ +public class ActiveTestSuite extends TestSuite { + private volatile int fActiveTestDeathCount; + + public ActiveTestSuite() { + } + + public ActiveTestSuite(Class theClass) { + super(theClass); + } + + public ActiveTestSuite(String name) { + super (name); + } + + public ActiveTestSuite(Class theClass, String name) { + super(theClass, name); + } + + public void run(TestResult result) { + fActiveTestDeathCount= 0; + super.run(result); + waitUntilFinished(); + } + + public void runTest(final Test test, final TestResult result) { + Thread t= new Thread() { + public void run() { + try { + // inlined due to limitation in VA/Java + //ActiveTestSuite.super.runTest(test, result); + test.run(result); + } finally { + ActiveTestSuite.this.runFinished(); + } + } + }; + t.start(); + } + + synchronized void waitUntilFinished() { + while (fActiveTestDeathCount < testCount()) { + try { + wait(); + } catch (InterruptedException e) { + return; // ignore + } + } + } + + synchronized public void runFinished() { + fActiveTestDeathCount++; + notifyAll(); + } +}
\ No newline at end of file diff --git a/src/junit/extensions/ExceptionTestCase.java b/src/junit/extensions/ExceptionTestCase.java new file mode 100644 index 0000000..7004085 --- /dev/null +++ b/src/junit/extensions/ExceptionTestCase.java @@ -0,0 +1,46 @@ +package junit.extensions; + +import junit.framework.TestCase; + +/** + * A TestCase that expects an Exception of class fExpected to be thrown. + * The other way to check that an expected exception is thrown is: + * <pre> + * try { + * shouldThrow(); + * } + * catch (SpecialException e) { + * return; + * } + * fail("Expected SpecialException"); + * </pre> + * + * To use ExceptionTestCase, create a TestCase like: + * <pre> + * new ExceptionTestCase("testShouldThrow", SpecialException.class); + * </pre> + */ +public class ExceptionTestCase extends TestCase { + Class fExpected; + + public ExceptionTestCase(String name, Class exception) { + super(name); + fExpected= exception; + } + /** + * Execute the test method expecting that an Exception of + * class fExpected or one of its subclasses will be thrown + */ + protected void runTest() throws Throwable { + try { + super.runTest(); + } + catch (Exception e) { + if (fExpected.isAssignableFrom(e.getClass())) + return; + else + throw e; + } + fail("Expected exception " + fExpected); + } +}
\ No newline at end of file diff --git a/src/junit/extensions/RepeatedTest.java b/src/junit/extensions/RepeatedTest.java new file mode 100644 index 0000000..be5c439 --- /dev/null +++ b/src/junit/extensions/RepeatedTest.java @@ -0,0 +1,32 @@ +package junit.extensions; + +import junit.framework.Test; +import junit.framework.TestResult; + +/** + * A Decorator that runs a test repeatedly. + * + */ +public class RepeatedTest extends TestDecorator { + private int fTimesRepeat; + + public RepeatedTest(Test test, int repeat) { + super(test); + if (repeat < 0) + throw new IllegalArgumentException("Repetition count must be > 0"); + fTimesRepeat= repeat; + } + public int countTestCases() { + return super.countTestCases()*fTimesRepeat; + } + public void run(TestResult result) { + for (int i= 0; i < fTimesRepeat; i++) { + if (result.shouldStop()) + break; + super.run(result); + } + } + public String toString() { + return super.toString()+"(repeated)"; + } +}
\ No newline at end of file diff --git a/src/junit/extensions/TestDecorator.java b/src/junit/extensions/TestDecorator.java new file mode 100644 index 0000000..2111e8f --- /dev/null +++ b/src/junit/extensions/TestDecorator.java @@ -0,0 +1,40 @@ +package junit.extensions; + +import junit.framework.Assert; +import junit.framework.Test; +import junit.framework.TestResult; + +/** + * A Decorator for Tests. Use TestDecorator as the base class + * for defining new test decorators. Test decorator subclasses + * can be introduced to add behaviour before or after a test + * is run. + * + */ +public class TestDecorator extends Assert implements Test { + protected Test fTest; + + public TestDecorator(Test test) { + fTest= test; + } + /** + * The basic run behaviour. + */ + public void basicRun(TestResult result) { + fTest.run(result); + } + public int countTestCases() { + return fTest.countTestCases(); + } + public void run(TestResult result) { + basicRun(result); + } + + public String toString() { + return fTest.toString(); + } + + public Test getTest() { + return fTest; + } +}
\ No newline at end of file diff --git a/src/junit/extensions/TestSetup.java b/src/junit/extensions/TestSetup.java new file mode 100644 index 0000000..9ee8b05 --- /dev/null +++ b/src/junit/extensions/TestSetup.java @@ -0,0 +1,39 @@ +package junit.extensions; + +import junit.framework.Protectable; +import junit.framework.Test; +import junit.framework.TestResult; + +/** + * A Decorator to set up and tear down additional fixture state. + * Subclass TestSetup and insert it into your tests when you want + * to set up additional state once before the tests are run. + */ +public class TestSetup extends TestDecorator { + + public TestSetup(Test test) { + super(test); + } + public void run(final TestResult result) { + Protectable p= new Protectable() { + public void protect() throws Exception { + setUp(); + basicRun(result); + tearDown(); + } + }; + result.runProtected(this, p); + } + /** + * Sets up the fixture. Override to set up additional fixture + * state. + */ + protected void setUp() throws Exception { + } + /** + * Tears down the fixture. Override to tear down the additional + * fixture state. + */ + protected void tearDown() throws Exception { + } +}
\ No newline at end of file diff --git a/src/junit/framework/Assert.java b/src/junit/framework/Assert.java new file mode 100644 index 0000000..ec6e838 --- /dev/null +++ b/src/junit/framework/Assert.java @@ -0,0 +1,289 @@ +package junit.framework; + +/** + * A set of assert methods. Messages are only displayed when an assert fails. + */ + +public class Assert { + /** + * Protect constructor since it is a static only class + */ + protected Assert() { + } + + /** + * Asserts that a condition is true. If it isn't it throws + * an AssertionFailedError with the given message. + */ + static public void assertTrue(String message, boolean condition) { + if (!condition) + fail(message); + } + /** + * Asserts that a condition is true. If it isn't it throws + * an AssertionFailedError. + */ + static public void assertTrue(boolean condition) { + assertTrue(null, condition); + } + /** + * Asserts that a condition is false. If it isn't it throws + * an AssertionFailedError with the given message. + */ + static public void assertFalse(String message, boolean condition) { + assertTrue(message, !condition); + } + /** + * Asserts that a condition is false. If it isn't it throws + * an AssertionFailedError. + */ + static public void assertFalse(boolean condition) { + assertFalse(null, condition); + } + /** + * Fails a test with the given message. + */ + static public void fail(String message) { + throw new AssertionFailedError(message); + } + /** + * Fails a test with no message. + */ + static public void fail() { + fail(null); + } + /** + * Asserts that two objects are equal. If they are not + * an AssertionFailedError is thrown with the given message. + */ + static public void assertEquals(String message, Object expected, Object actual) { + if (expected == null && actual == null) + return; + if (expected != null && expected.equals(actual)) + return; + failNotEquals(message, expected, actual); + } + /** + * Asserts that two objects are equal. If they are not + * an AssertionFailedError is thrown. + */ + static public void assertEquals(Object expected, Object actual) { + assertEquals(null, expected, actual); + } + /** + * Asserts that two Strings are equal. + */ + static public void assertEquals(String message, String expected, String actual) { + if (expected == null && actual == null) + return; + if (expected != null && expected.equals(actual)) + return; + throw new ComparisonFailure(message, expected, actual); + } + /** + * Asserts that two Strings are equal. + */ + static public void assertEquals(String expected, String actual) { + assertEquals(null, expected, actual); + } + /** + * Asserts that two doubles are equal concerning a delta. If they are not + * an AssertionFailedError is thrown with the given message. If the expected + * value is infinity then the delta value is ignored. + */ + static public void assertEquals(String message, double expected, double actual, double delta) { + if (Double.compare(expected, actual) == 0) + return; + if (!(Math.abs(expected-actual) <= delta)) + failNotEquals(message, new Double(expected), new Double(actual)); + } + /** + * Asserts that two doubles are equal concerning a delta. If the expected + * value is infinity then the delta value is ignored. + */ + static public void assertEquals(double expected, double actual, double delta) { + assertEquals(null, expected, actual, delta); + } + /** + * Asserts that two floats are equal concerning a delta. If they are not + * an AssertionFailedError is thrown with the given message. If the expected + * value is infinity then the delta value is ignored. + */ + static public void assertEquals(String message, float expected, float actual, float delta) { + // handle infinity specially since subtracting to infinite values gives NaN and the + // the following test fails + if (Float.isInfinite(expected)) { + if (!(expected == actual)) + failNotEquals(message, new Float(expected), new Float(actual)); + } else if (!(Math.abs(expected-actual) <= delta)) + failNotEquals(message, new Float(expected), new Float(actual)); + } + /** + * Asserts that two floats are equal concerning a delta. If the expected + * value is infinity then the delta value is ignored. + */ + static public void assertEquals(float expected, float actual, float delta) { + assertEquals(null, expected, actual, delta); + } + /** + * Asserts that two longs are equal. If they are not + * an AssertionFailedError is thrown with the given message. + */ + static public void assertEquals(String message, long expected, long actual) { + assertEquals(message, new Long(expected), new Long(actual)); + } + /** + * Asserts that two longs are equal. + */ + static public void assertEquals(long expected, long actual) { + assertEquals(null, expected, actual); + } + /** + * Asserts that two booleans are equal. If they are not + * an AssertionFailedError is thrown with the given message. + */ + static public void assertEquals(String message, boolean expected, boolean actual) { + assertEquals(message, Boolean.valueOf(expected), Boolean.valueOf(actual)); + } + /** + * Asserts that two booleans are equal. + */ + static public void assertEquals(boolean expected, boolean actual) { + assertEquals(null, expected, actual); + } + /** + * Asserts that two bytes are equal. If they are not + * an AssertionFailedError is thrown with the given message. + */ + static public void assertEquals(String message, byte expected, byte actual) { + assertEquals(message, new Byte(expected), new Byte(actual)); + } + /** + * Asserts that two bytes are equal. + */ + static public void assertEquals(byte expected, byte actual) { + assertEquals(null, expected, actual); + } + /** + * Asserts that two chars are equal. If they are not + * an AssertionFailedError is thrown with the given message. + */ + static public void assertEquals(String message, char expected, char actual) { + assertEquals(message, new Character(expected), new Character(actual)); + } + /** + * Asserts that two chars are equal. + */ + static public void assertEquals(char expected, char actual) { + assertEquals(null, expected, actual); + } + /** + * Asserts that two shorts are equal. If they are not + * an AssertionFailedError is thrown with the given message. + */ + static public void assertEquals(String message, short expected, short actual) { + assertEquals(message, new Short(expected), new Short(actual)); + } + /** + * Asserts that two shorts are equal. + */ + static public void assertEquals(short expected, short actual) { + assertEquals(null, expected, actual); + } + /** + * Asserts that two ints are equal. If they are not + * an AssertionFailedError is thrown with the given message. + */ + static public void assertEquals(String message, int expected, int actual) { + assertEquals(message, new Integer(expected), new Integer(actual)); + } + /** + * Asserts that two ints are equal. + */ + static public void assertEquals(int expected, int actual) { + assertEquals(null, expected, actual); + } + /** + * Asserts that an object isn't null. + */ + static public void assertNotNull(Object object) { + assertNotNull(null, object); + } + /** + * Asserts that an object isn't null. If it is + * an AssertionFailedError is thrown with the given message. + */ + static public void assertNotNull(String message, Object object) { + assertTrue(message, object != null); + } + /** + * Asserts that an object is null. + */ + static public void assertNull(Object object) { + assertNull(null, object); + } + /** + * Asserts that an object is null. If it is not + * an AssertionFailedError is thrown with the given message. + */ + static public void assertNull(String message, Object object) { + assertTrue(message, object == null); + } + /** + * Asserts that two objects refer to the same object. If they are not + * an AssertionFailedError is thrown with the given message. + */ + static public void assertSame(String message, Object expected, Object actual) { + if (expected == actual) + return; + failNotSame(message, expected, actual); + } + /** + * Asserts that two objects refer to the same object. If they are not + * the same an AssertionFailedError is thrown. + */ + static public void assertSame(Object expected, Object actual) { + assertSame(null, expected, actual); + } + /** + * Asserts that two objects do not refer to the same object. If they do + * refer to the same object an AssertionFailedError is thrown with the + * given message. + */ + static public void assertNotSame(String message, Object expected, Object actual) { + if (expected == actual) + failSame(message); + } + /** + * Asserts that two objects do not refer to the same object. If they do + * refer to the same object an AssertionFailedError is thrown. + */ + static public void assertNotSame(Object expected, Object actual) { + assertNotSame(null, expected, actual); + } + + static public void failSame(String message) { + String formatted= ""; + if (message != null) + formatted= message+" "; + fail(formatted+"expected not same"); + } + + static public void failNotSame(String message, Object expected, Object actual) { + String formatted= ""; + if (message != null) + formatted= message+" "; + fail(formatted+"expected same:<"+expected+"> was not:<"+actual+">"); + } + + static public void failNotEquals(String message, Object expected, Object actual) { + fail(format(message, expected, actual)); + } + + static String format(String message, Object expected, Object actual) { + String formatted= ""; + if (message != null) + formatted= message+" "; + return formatted+"expected:<"+expected+"> but was:<"+actual+">"; + } +} diff --git a/src/junit/framework/AssertionFailedError.java b/src/junit/framework/AssertionFailedError.java new file mode 100644 index 0000000..b041f06 --- /dev/null +++ b/src/junit/framework/AssertionFailedError.java @@ -0,0 +1,15 @@ +package junit.framework; + +/** + * Thrown when an assertion failed. + */ +public class AssertionFailedError extends Error { + + private static final long serialVersionUID= 1L; + + public AssertionFailedError () { + } + public AssertionFailedError (String message) { + super (message); + } +}
\ No newline at end of file diff --git a/src/junit/framework/ComparisonCompactor.java b/src/junit/framework/ComparisonCompactor.java new file mode 100644 index 0000000..bbc3ba1 --- /dev/null +++ b/src/junit/framework/ComparisonCompactor.java @@ -0,0 +1,72 @@ +package junit.framework; + +public class ComparisonCompactor { + + private static final String ELLIPSIS= "..."; + private static final String DELTA_END= "]"; + private static final String DELTA_START= "["; + + private int fContextLength; + private String fExpected; + private String fActual; + private int fPrefix; + private int fSuffix; + + public ComparisonCompactor(int contextLength, String expected, String actual) { + fContextLength= contextLength; + fExpected= expected; + fActual= actual; + } + + public String compact(String message) { + if (fExpected == null || fActual == null || areStringsEqual()) + return Assert.format(message, fExpected, fActual); + + findCommonPrefix(); + findCommonSuffix(); + String expected= compactString(fExpected); + String actual= compactString(fActual); + return Assert.format(message, expected, actual); + } + + private String compactString(String source) { + String result= DELTA_START + source.substring(fPrefix, source.length() - fSuffix + 1) + DELTA_END; + if (fPrefix > 0) + result= computeCommonPrefix() + result; + if (fSuffix > 0) + result= result + computeCommonSuffix(); + return result; + } + + private void findCommonPrefix() { + fPrefix= 0; + int end= Math.min(fExpected.length(), fActual.length()); + for (; fPrefix < end; fPrefix++) { + if (fExpected.charAt(fPrefix) != fActual.charAt(fPrefix)) + break; + } + } + + private void findCommonSuffix() { + int expectedSuffix= fExpected.length() - 1; + int actualSuffix= fActual.length() - 1; + for (; actualSuffix >= fPrefix && expectedSuffix >= fPrefix; actualSuffix--, expectedSuffix--) { + if (fExpected.charAt(expectedSuffix) != fActual.charAt(actualSuffix)) + break; + } + fSuffix= fExpected.length() - expectedSuffix; + } + + private String computeCommonPrefix() { + return (fPrefix > fContextLength ? ELLIPSIS : "") + fExpected.substring(Math.max(0, fPrefix - fContextLength), fPrefix); + } + + private String computeCommonSuffix() { + int end= Math.min(fExpected.length() - fSuffix + 1 + fContextLength, fExpected.length()); + return fExpected.substring(fExpected.length() - fSuffix + 1, end) + (fExpected.length() - fSuffix + 1 < fExpected.length() - fContextLength ? ELLIPSIS : ""); + } + + private boolean areStringsEqual() { + return fExpected.equals(fActual); + } +} diff --git a/src/junit/framework/ComparisonFailure.java b/src/junit/framework/ComparisonFailure.java new file mode 100644 index 0000000..c31068f --- /dev/null +++ b/src/junit/framework/ComparisonFailure.java @@ -0,0 +1,51 @@ +package junit.framework; + +/** + * Thrown when an assert equals for Strings failed. + * + * Inspired by a patch from Alex Chaffee mailto:alex@purpletech.com + */ +public class ComparisonFailure extends AssertionFailedError { + private static final int MAX_CONTEXT_LENGTH= 20; + private static final long serialVersionUID= 1L; + + private String fExpected; + private String fActual; + + /** + * Constructs a comparison failure. + * @param message the identifying message or null + * @param expected the expected string value + * @param actual the actual string value + */ + public ComparisonFailure (String message, String expected, String actual) { + super (message); + fExpected= expected; + fActual= actual; + } + + /** + * Returns "..." in place of common prefix and "..." in + * place of common suffix between expected and actual. + * + * @see java.lang.Throwable#getMessage() + */ + public String getMessage() { + return new ComparisonCompactor(MAX_CONTEXT_LENGTH, fExpected, fActual).compact(super.getMessage()); + } + + /** + * Gets the actual string value + * @return the actual string value + */ + public String getActual() { + return fActual; + } + /** + * Gets the expected string value + * @return the expected string value + */ + public String getExpected() { + return fExpected; + } +}
\ No newline at end of file diff --git a/src/junit/framework/Protectable.java b/src/junit/framework/Protectable.java new file mode 100644 index 0000000..e143237 --- /dev/null +++ b/src/junit/framework/Protectable.java @@ -0,0 +1,14 @@ +package junit.framework; + +/** + * A <em>Protectable</em> can be run and can throw a Throwable. + * + * @see TestResult + */ +public interface Protectable { + + /** + * Run the the following method protected. + */ + public abstract void protect() throws Throwable; +}
\ No newline at end of file diff --git a/src/junit/framework/Test.java b/src/junit/framework/Test.java new file mode 100644 index 0000000..a016ee8 --- /dev/null +++ b/src/junit/framework/Test.java @@ -0,0 +1,17 @@ +package junit.framework; + +/** + * A <em>Test</em> can be run and collect its results. + * + * @see TestResult + */ +public interface Test { + /** + * Counts the number of test cases that will be run by this test. + */ + public abstract int countTestCases(); + /** + * Runs a test and collects its result in a TestResult instance. + */ + public abstract void run(TestResult result); +}
\ No newline at end of file diff --git a/src/junit/framework/TestCase.java b/src/junit/framework/TestCase.java new file mode 100644 index 0000000..e54b16b --- /dev/null +++ b/src/junit/framework/TestCase.java @@ -0,0 +1,207 @@ +package junit.framework; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * A test case defines the fixture to run multiple tests. To define a test case<br> + * 1) implement a subclass of TestCase<br> + * 2) define instance variables that store the state of the fixture<br> + * 3) initialize the fixture state by overriding <code>setUp</code><br> + * 4) clean-up after a test by overriding <code>tearDown</code>.<br> + * Each test runs in its own fixture so there + * can be no side effects among test runs. + * Here is an example: + * <pre> + * public class MathTest extends TestCase { + * protected double fValue1; + * protected double fValue2; + * + * protected void setUp() { + * fValue1= 2.0; + * fValue2= 3.0; + * } + * } + * </pre> + * + * For each test implement a method which interacts + * with the fixture. Verify the expected results with assertions specified + * by calling <code>assertTrue</code> with a boolean. + * <pre> + * public void testAdd() { + * double result= fValue1 + fValue2; + * assertTrue(result == 5.0); + * } + * </pre> + * Once the methods are defined you can run them. The framework supports + * both a static type safe and more dynamic way to run a test. + * In the static way you override the runTest method and define the method to + * be invoked. A convenient way to do so is with an anonymous inner class. + * <pre> + * TestCase test= new MathTest("add") { + * public void runTest() { + * testAdd(); + * } + * }; + * test.run(); + * </pre> + * The dynamic way uses reflection to implement <code>runTest</code>. It dynamically finds + * and invokes a method. + * In this case the name of the test case has to correspond to the test method + * to be run. + * <pre> + * TestCase test= new MathTest("testAdd"); + * test.run(); + * </pre> + * The tests to be run can be collected into a TestSuite. JUnit provides + * different <i>test runners</i> which can run a test suite and collect the results. + * A test runner either expects a static method <code>suite</code> as the entry + * point to get a test to run or it will extract the suite automatically. + * <pre> + * public static Test suite() { + * suite.addTest(new MathTest("testAdd")); + * suite.addTest(new MathTest("testDivideByZero")); + * return suite; + * } + * </pre> + * @see TestResult + * @see TestSuite + */ + +public abstract class TestCase extends Assert implements Test { + /** + * the name of the test case + */ + private String fName; + + /** + * No-arg constructor to enable serialization. This method + * is not intended to be used by mere mortals without calling setName(). + */ + public TestCase() { + fName= null; + } + /** + * Constructs a test case with the given name. + */ + public TestCase(String name) { + fName= name; + } + /** + * Counts the number of test cases executed by run(TestResult result). + */ + public int countTestCases() { + return 1; + } + /** + * Creates a default TestResult object + * + * @see TestResult + */ + protected TestResult createResult() { + return new TestResult(); + } + /** + * A convenience method to run this test, collecting the results with a + * default TestResult object. + * + * @see TestResult + */ + public TestResult run() { + TestResult result= createResult(); + run(result); + return result; + } + /** + * Runs the test case and collects the results in TestResult. + */ + public void run(TestResult result) { + result.run(this); + } + /** + * Runs the bare test sequence. + * @exception Throwable if any exception is thrown + */ + public void runBare() throws Throwable { + Throwable exception= null; + setUp(); + try { + runTest(); + } catch (Throwable running) { + exception= running; + } + finally { + try { + tearDown(); + } catch (Throwable tearingDown) { + if (exception == null) exception= tearingDown; + } + } + if (exception != null) throw exception; + } + /** + * Override to run the test and assert its state. + * @exception Throwable if any exception is thrown + */ + protected void runTest() throws Throwable { + assertNotNull(fName); // Some VMs crash when calling getMethod(null,null); + Method runMethod= null; + try { + // use getMethod to get all public inherited + // methods. getDeclaredMethods returns all + // methods of this class but excludes the + // inherited ones. + runMethod= getClass().getMethod(fName, (Class[])null); + } catch (NoSuchMethodException e) { + fail("Method \""+fName+"\" not found"); + } + if (!Modifier.isPublic(runMethod.getModifiers())) { + fail("Method \""+fName+"\" should be public"); + } + + try { + runMethod.invoke(this, (Object[])new Class[0]); + } + catch (InvocationTargetException e) { + e.fillInStackTrace(); + throw e.getTargetException(); + } + catch (IllegalAccessException e) { + e.fillInStackTrace(); + throw e; + } + } + /** + * Sets up the fixture, for example, open a network connection. + * This method is called before a test is executed. + */ + protected void setUp() throws Exception { + } + /** + * Tears down the fixture, for example, close a network connection. + * This method is called after a test is executed. + */ + protected void tearDown() throws Exception { + } + /** + * Returns a string representation of the test case + */ + public String toString() { + return getName() + "(" + getClass().getName() + ")"; + } + /** + * Gets the name of a TestCase + * @return returns a String + */ + public String getName() { + return fName; + } + /** + * Sets the name of a TestCase + * @param name The name to set + */ + public void setName(String name) { + fName= name; + } +} diff --git a/src/junit/framework/TestFailure.java b/src/junit/framework/TestFailure.java new file mode 100644 index 0000000..aff6a5a --- /dev/null +++ b/src/junit/framework/TestFailure.java @@ -0,0 +1,57 @@ +package junit.framework; + +import java.io.PrintWriter; +import java.io.StringWriter; + + +/** + * A <code>TestFailure</code> collects a failed test together with + * the caught exception. + * @see TestResult + */ +public class TestFailure extends Object { + protected Test fFailedTest; + protected Throwable fThrownException; + + + /** + * Constructs a TestFailure with the given test and exception. + */ + public TestFailure(Test failedTest, Throwable thrownException) { + fFailedTest= failedTest; + fThrownException= thrownException; + } + /** + * Gets the failed test. + */ + public Test failedTest() { + return fFailedTest; + } + /** + * Gets the thrown exception. + */ + public Throwable thrownException() { + return fThrownException; + } + /** + * Returns a short description of the failure. + */ + public String toString() { + StringBuffer buffer= new StringBuffer(); + buffer.append(fFailedTest+": "+fThrownException.getMessage()); + return buffer.toString(); + } + public String trace() { + StringWriter stringWriter= new StringWriter(); + PrintWriter writer= new PrintWriter(stringWriter); + thrownException().printStackTrace(writer); + StringBuffer buffer= stringWriter.getBuffer(); + return buffer.toString(); + } + public String exceptionMessage() { + return thrownException().getMessage(); + } + public boolean isFailure() { + return thrownException() instanceof AssertionFailedError; + } +}
\ No newline at end of file diff --git a/src/junit/framework/TestListener.java b/src/junit/framework/TestListener.java new file mode 100644 index 0000000..9b69443 --- /dev/null +++ b/src/junit/framework/TestListener.java @@ -0,0 +1,23 @@ +package junit.framework; + +/** + * A Listener for test progress + */ +public interface TestListener { + /** + * An error occurred. + */ + public void addError(Test test, Throwable t); + /** + * A failure occurred. + */ + public void addFailure(Test test, AssertionFailedError t); + /** + * A test ended. + */ + public void endTest(Test test); + /** + * A test started. + */ + public void startTest(Test test); +}
\ No newline at end of file diff --git a/src/junit/framework/TestResult.java b/src/junit/framework/TestResult.java new file mode 100644 index 0000000..8535ce0 --- /dev/null +++ b/src/junit/framework/TestResult.java @@ -0,0 +1,166 @@ +package junit.framework; + +import java.util.Enumeration; +import java.util.Vector; + +/** + * A <code>TestResult</code> collects the results of executing + * a test case. It is an instance of the Collecting Parameter pattern. + * The test framework distinguishes between <i>failures</i> and <i>errors</i>. + * A failure is anticipated and checked for with assertions. Errors are + * unanticipated problems like an <code>ArrayIndexOutOfBoundsException</code>. + * + * @see Test + */ +public class TestResult extends Object { + protected Vector fFailures; + protected Vector fErrors; + protected Vector fListeners; + protected int fRunTests; + private boolean fStop; + + public TestResult() { + fFailures= new Vector(); + fErrors= new Vector(); + fListeners= new Vector(); + fRunTests= 0; + fStop= false; + } + /** + * Adds an error to the list of errors. The passed in exception + * caused the error. + */ + public synchronized void addError(Test test, Throwable t) { + fErrors.addElement(new TestFailure(test, t)); + for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) { + ((TestListener)e.nextElement()).addError(test, t); + } + } + /** + * Adds a failure to the list of failures. The passed in exception + * caused the failure. + */ + public synchronized void addFailure(Test test, AssertionFailedError t) { + fFailures.addElement(new TestFailure(test, t)); + for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) { + ((TestListener)e.nextElement()).addFailure(test, t); + } + } + /** + * Registers a TestListener + */ + public synchronized void addListener(TestListener listener) { + fListeners.addElement(listener); + } + /** + * Unregisters a TestListener + */ + public synchronized void removeListener(TestListener listener) { + fListeners.removeElement(listener); + } + /** + * Returns a copy of the listeners. + */ + private synchronized Vector cloneListeners() { + return (Vector)fListeners.clone(); + } + /** + * Informs the result that a test was completed. + */ + public void endTest(Test test) { + for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) { + ((TestListener)e.nextElement()).endTest(test); + } + } + /** + * Gets the number of detected errors. + */ + public synchronized int errorCount() { + return fErrors.size(); + } + /** + * Returns an Enumeration for the errors + */ + public synchronized Enumeration errors() { + return fErrors.elements(); + } + /** + * Gets the number of detected failures. + */ + public synchronized int failureCount() { + return fFailures.size(); + } + /** + * Returns an Enumeration for the failures + */ + public synchronized Enumeration failures() { + return fFailures.elements(); + } + /** + * Runs a TestCase. + */ + protected void run(final TestCase test) { + startTest(test); + Protectable p= new Protectable() { + public void protect() throws Throwable { + test.runBare(); + } + }; + runProtected(test, p); + + endTest(test); + } + /** + * Gets the number of run tests. + */ + public synchronized int runCount() { + return fRunTests; + } + /** + * Runs a TestCase. + */ + public void runProtected(final Test test, Protectable p) { + try { + p.protect(); + } + catch (AssertionFailedError e) { + addFailure(test, e); + } + catch (ThreadDeath e) { // don't catch ThreadDeath by accident + throw e; + } + catch (Throwable e) { + addError(test, e); + } + } + /** + * Checks whether the test run should stop + */ + public synchronized boolean shouldStop() { + return fStop; + } + /** + * Informs the result that a test will be started. + */ + public void startTest(Test test) { + final int count= test.countTestCases(); + synchronized(this) { + fRunTests+= count; + } + for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) { + ((TestListener)e.nextElement()).startTest(test); + } + } + /** + * Marks that the test run should stop. + */ + public synchronized void stop() { + fStop= true; + } + /** + * Returns whether the entire test was successful or not. + */ + public synchronized boolean wasSuccessful() { + return failureCount() == 0 && errorCount() == 0; + } +}
\ No newline at end of file diff --git a/src/junit/framework/TestSuite.java b/src/junit/framework/TestSuite.java new file mode 100644 index 0000000..5a96bc2 --- /dev/null +++ b/src/junit/framework/TestSuite.java @@ -0,0 +1,293 @@ +package junit.framework; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Enumeration; +import java.util.Vector; + +/** + * A <code>TestSuite</code> is a <code>Composite</code> of Tests. + * It runs a collection of test cases. Here is an example using + * the dynamic test definition. + * <pre> + * TestSuite suite= new TestSuite(); + * suite.addTest(new MathTest("testAdd")); + * suite.addTest(new MathTest("testDivideByZero")); + * </pre> + * Alternatively, a TestSuite can extract the tests to be run automatically. + * To do so you pass the class of your TestCase class to the + * TestSuite constructor. + * <pre> + * TestSuite suite= new TestSuite(MathTest.class); + * </pre> + * This constructor creates a suite with all the methods + * starting with "test" that take no arguments. + * <p> + * A final option is to do the same for a large array of test classes. + * <pre> + * Class[] testClasses = { MathTest.class, AnotherTest.class } + * TestSuite suite= new TestSuite(testClasses); + * </pre> + * + * @see Test + */ +public class TestSuite implements Test { + + /** + * ...as the moon sets over the early morning Merlin, Oregon + * mountains, our intrepid adventurers type... + */ + static public Test createTest(Class theClass, String name) { + Constructor constructor; + try { + constructor= getTestConstructor(theClass); + } catch (NoSuchMethodException e) { + return warning("Class "+theClass.getName()+" has no public constructor TestCase(String name) or TestCase()"); + } + Object test; + try { + if (constructor.getParameterTypes().length == 0) { + test= constructor.newInstance(new Object[0]); + if (test instanceof TestCase) + ((TestCase) test).setName(name); + } else { + test= constructor.newInstance(new Object[]{name}); + } + } catch (InstantiationException e) { + return(warning("Cannot instantiate test case: "+name+" ("+exceptionToString(e)+")")); + } catch (InvocationTargetException e) { + return(warning("Exception in constructor: "+name+" ("+exceptionToString(e.getTargetException())+")")); + } catch (IllegalAccessException e) { + return(warning("Cannot access test case: "+name+" ("+exceptionToString(e)+")")); + } + return (Test) test; + } + + /** + * Gets a constructor which takes a single String as + * its argument or a no arg constructor. + */ + public static Constructor getTestConstructor(Class theClass) throws NoSuchMethodException { + Class[] args= { String.class }; + try { + return theClass.getConstructor(args); + } catch (NoSuchMethodException e) { + // fall through + } + return theClass.getConstructor(new Class[0]); + } + + /** + * Returns a test which will fail and log a warning message. + */ + public static Test warning(final String message) { + return new TestCase("warning") { + protected void runTest() { + fail(message); + } + }; + } + + /** + * Converts the stack trace into a string + */ + private static String exceptionToString(Throwable t) { + StringWriter stringWriter= new StringWriter(); + PrintWriter writer= new PrintWriter(stringWriter); + t.printStackTrace(writer); + return stringWriter.toString(); + + } + private String fName; + + private Vector fTests= new Vector(10); + + /** + * Constructs an empty TestSuite. + */ + public TestSuite() { + } + + /** + * Constructs a TestSuite from the given class. Adds all the methods + * starting with "test" as test cases to the suite. + * Parts of this method was written at 2337 meters in the Hueffihuette, + * Kanton Uri + */ + public TestSuite(final Class theClass) { + fName= theClass.getName(); + try { + getTestConstructor(theClass); // Avoid generating multiple error messages + } catch (NoSuchMethodException e) { + addTest(warning("Class "+theClass.getName()+" has no public constructor TestCase(String name) or TestCase()")); + return; + } + + if (!Modifier.isPublic(theClass.getModifiers())) { + addTest(warning("Class "+theClass.getName()+" is not public")); + return; + } + + Class superClass= theClass; + Vector names= new Vector(); + while (Test.class.isAssignableFrom(superClass)) { + Method[] methods= superClass.getDeclaredMethods(); + for (int i= 0; i < methods.length; i++) { + addTestMethod(methods[i], names, theClass); + } + superClass= superClass.getSuperclass(); + } + if (fTests.size() == 0) + addTest(warning("No tests found in "+theClass.getName())); + } + + /** + * Constructs a TestSuite from the given class with the given name. + * @see TestSuite#TestSuite(Class) + */ + public TestSuite(Class theClass, String name) { + this(theClass); + setName(name); + } + + /** + * Constructs an empty TestSuite. + */ + public TestSuite(String name) { + setName(name); + } + + /** + * Constructs a TestSuite from the given array of classes. + * @param classes + */ + public TestSuite (Class[] classes) { + for (int i= 0; i < classes.length; i++) + addTest(new TestSuite(classes[i])); + } + + /** + * Constructs a TestSuite from the given array of classes with the given name. + * @see TestSuite#TestSuite(Class[]) + */ + public TestSuite(Class[] classes, String name) { + this(classes); + setName(name); + } + + /** + * Adds a test to the suite. + */ + public void addTest(Test test) { + fTests.addElement(test); + } + + /** + * Adds the tests from the given class to the suite + */ + public void addTestSuite(Class testClass) { + addTest(new TestSuite(testClass)); + } + + /** + * Counts the number of test cases that will be run by this test. + */ + public int countTestCases() { + int count= 0; + for (Enumeration e= tests(); e.hasMoreElements(); ) { + Test test= (Test)e.nextElement(); + count= count + test.countTestCases(); + } + return count; + } + + /** + * Returns the name of the suite. Not all + * test suites have a name and this method + * can return null. + */ + public String getName() { + return fName; + } + + /** + * Runs the tests and collects their result in a TestResult. + */ + public void run(TestResult result) { + for (Enumeration e= tests(); e.hasMoreElements(); ) { + if (result.shouldStop() ) + break; + Test test= (Test)e.nextElement(); + runTest(test, result); + } + } + + public void runTest(Test test, TestResult result) { + test.run(result); + } + + /** + * Sets the name of the suite. + * @param name The name to set + */ + public void setName(String name) { + fName= name; + } + + /** + * Returns the test at the given index + */ + public Test testAt(int index) { + return (Test)fTests.elementAt(index); + } + + /** + * Returns the number of tests in this suite + */ + public int testCount() { + return fTests.size(); + } + + /** + * Returns the tests as an enumeration + */ + public Enumeration tests() { + return fTests.elements(); + } + + /** + */ + public String toString() { + if (getName() != null) + return getName(); + return super.toString(); + } + + private void addTestMethod(Method m, Vector names, Class theClass) { + String name= m.getName(); + if (names.contains(name)) + return; + if (! isPublicTestMethod(m)) { + if (isTestMethod(m)) + addTest(warning("Test method isn't public: "+m.getName())); + return; + } + names.addElement(name); + addTest(createTest(theClass, name)); + } + + private boolean isPublicTestMethod(Method m) { + return isTestMethod(m) && Modifier.isPublic(m.getModifiers()); + } + + private boolean isTestMethod(Method m) { + String name= m.getName(); + Class[] parameters= m.getParameterTypes(); + Class returnType= m.getReturnType(); + return parameters.length == 0 && name.startsWith("test") && returnType.equals(Void.TYPE); + } +}
\ No newline at end of file diff --git a/src/junit/runner/BaseTestRunner.java b/src/junit/runner/BaseTestRunner.java new file mode 100644 index 0000000..1518f7c --- /dev/null +++ b/src/junit/runner/BaseTestRunner.java @@ -0,0 +1,343 @@ +package junit.runner; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.StringReader; +import java.io.StringWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.text.NumberFormat; +import java.util.Properties; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestListener; +import junit.framework.TestSuite; + +/** + * Base class for all test runners. + * This class was born live on stage in Sardinia during XP2000. + */ +public abstract class BaseTestRunner implements TestListener { + public static final String SUITE_METHODNAME= "suite"; + + private static Properties fPreferences; + static int fgMaxMessageLength= 500; + static boolean fgFilterStack= true; + boolean fLoading= true; + + /* + * Implementation of TestListener + */ + public synchronized void startTest(Test test) { + testStarted(test.toString()); + } + + protected static void setPreferences(Properties preferences) { + fPreferences= preferences; + } + + protected static Properties getPreferences() { + if (fPreferences == null) { + fPreferences= new Properties(); + fPreferences.put("loading", "true"); + fPreferences.put("filterstack", "true"); + readPreferences(); + } + return fPreferences; + } + + public static void savePreferences() throws IOException { + FileOutputStream fos= new FileOutputStream(getPreferencesFile()); + try { + // calling of the deprecated save method to enable compiling under 1.1.7 + getPreferences().save(fos, ""); + } finally { + fos.close(); + } + } + + public static void setPreference(String key, String value) { + getPreferences().put(key, value); + } + + public synchronized void endTest(Test test) { + testEnded(test.toString()); + } + + public synchronized void addError(final Test test, final Throwable t) { + testFailed(TestRunListener.STATUS_ERROR, test, t); + } + + public synchronized void addFailure(final Test test, final AssertionFailedError t) { + testFailed(TestRunListener.STATUS_FAILURE, test, t); + } + + // TestRunListener implementation + + public abstract void testStarted(String testName); + + public abstract void testEnded(String testName); + + public abstract void testFailed(int status, Test test, Throwable t); + + /** + * Returns the Test corresponding to the given suite. This is + * a template method, subclasses override runFailed(), clearStatus(). + */ + public Test getTest(String suiteClassName) { + if (suiteClassName.length() <= 0) { + clearStatus(); + return null; + } + Class testClass= null; + try { + testClass= loadSuiteClass(suiteClassName); + } catch (ClassNotFoundException e) { + String clazz= e.getMessage(); + if (clazz == null) + clazz= suiteClassName; + runFailed("Class not found \""+clazz+"\""); + return null; + } catch(Exception e) { + runFailed("Error: "+e.toString()); + return null; + } + Method suiteMethod= null; + try { + suiteMethod= testClass.getMethod(SUITE_METHODNAME, new Class[0]); + } catch(Exception e) { + // try to extract a test suite automatically + clearStatus(); + return new TestSuite(testClass); + } + if (! Modifier.isStatic(suiteMethod.getModifiers())) { + runFailed("Suite() method must be static"); + return null; + } + Test test= null; + try { + test= (Test)suiteMethod.invoke(null, (Object[])new Class[0]); // static method + if (test == null) + return test; + } + catch (InvocationTargetException e) { + runFailed("Failed to invoke suite():" + e.getTargetException().toString()); + return null; + } + catch (IllegalAccessException e) { + runFailed("Failed to invoke suite():" + e.toString()); + return null; + } + + clearStatus(); + return test; + } + + /** + * Returns the formatted string of the elapsed time. + */ + public String elapsedTimeAsString(long runTime) { + return NumberFormat.getInstance().format((double)runTime/1000); + } + + /** + * Processes the command line arguments and + * returns the name of the suite class to run or null + */ + protected String processArguments(String[] args) { + String suiteName= null; + for (int i= 0; i < args.length; i++) { + if (args[i].equals("-noloading")) { + setLoading(false); + } else if (args[i].equals("-nofilterstack")) { + fgFilterStack= false; + } else if (args[i].equals("-c")) { + if (args.length > i+1) + suiteName= extractClassName(args[i+1]); + else + System.out.println("Missing Test class name"); + i++; + } else { + suiteName= args[i]; + } + } + return suiteName; + } + + /** + * Sets the loading behaviour of the test runner + */ + public void setLoading(boolean enable) { + fLoading= enable; + } + /** + * Extract the class name from a String in VA/Java style + */ + public String extractClassName(String className) { + if(className.startsWith("Default package for")) + return className.substring(className.lastIndexOf(".")+1); + return className; + } + + /** + * Truncates a String to the maximum length. + */ + public static String truncate(String s) { + if (fgMaxMessageLength != -1 && s.length() > fgMaxMessageLength) + s= s.substring(0, fgMaxMessageLength)+"..."; + return s; + } + + /** + * Override to define how to handle a failed loading of + * a test suite. + */ + protected abstract void runFailed(String message); + + /** + * Returns the loaded Class for a suite name. + */ + protected Class loadSuiteClass(String suiteClassName) throws ClassNotFoundException { + return getLoader().load(suiteClassName); + } + + /** + * Clears the status message. + */ + protected void clearStatus() { // Belongs in the GUI TestRunner class + } + + /** + * Returns the loader to be used. + */ + public TestSuiteLoader getLoader() { + if (useReloadingTestSuiteLoader()) + return new ReloadingTestSuiteLoader(); + return new StandardTestSuiteLoader(); + } + + protected boolean useReloadingTestSuiteLoader() { + return getPreference("loading").equals("true") && !inVAJava() && fLoading; + } + + private static File getPreferencesFile() { + String home= System.getProperty("user.home"); + return new File(home, "junit.properties"); + } + + private static void readPreferences() { + InputStream is= null; + try { + is= new FileInputStream(getPreferencesFile()); + setPreferences(new Properties(getPreferences())); + getPreferences().load(is); + } catch (IOException e) { + try { + if (is != null) + is.close(); + } catch (IOException e1) { + } + } + } + + public static String getPreference(String key) { + return getPreferences().getProperty(key); + } + + public static int getPreference(String key, int dflt) { + String value= getPreference(key); + int intValue= dflt; + if (value == null) + return intValue; + try { + intValue= Integer.parseInt(value); + } catch (NumberFormatException ne) { + } + return intValue; + } + + public static boolean inVAJava() { + try { + Class.forName("com.ibm.uvm.tools.DebugSupport"); + } + catch (Exception e) { + return false; + } + return true; + } + + public static boolean inMac() { + return System.getProperty("mrj.version") != null; + } + + + /** + * Returns a filtered stack trace + */ + public static String getFilteredTrace(Throwable t) { + StringWriter stringWriter= new StringWriter(); + PrintWriter writer= new PrintWriter(stringWriter); + t.printStackTrace(writer); + StringBuffer buffer= stringWriter.getBuffer(); + String trace= buffer.toString(); + return BaseTestRunner.getFilteredTrace(trace); + } + + /** + * Filters stack frames from internal JUnit classes + */ + public static String getFilteredTrace(String stack) { + if (showStackRaw()) + return stack; + + StringWriter sw= new StringWriter(); + PrintWriter pw= new PrintWriter(sw); + StringReader sr= new StringReader(stack); + BufferedReader br= new BufferedReader(sr); + + String line; + try { + while ((line= br.readLine()) != null) { + if (!filterLine(line)) + pw.println(line); + } + } catch (Exception IOException) { + return stack; // return the stack unfiltered + } + return sw.toString(); + } + + protected static boolean showStackRaw() { + return !getPreference("filterstack").equals("true") || fgFilterStack == false; + } + + static boolean filterLine(String line) { + String[] patterns= new String[] { + "junit.framework.TestCase", + "junit.framework.TestResult", + "junit.framework.TestSuite", + "junit.framework.Assert.", // don't filter AssertionFailure + "junit.swingui.TestRunner", + "junit.awtui.TestRunner", + "junit.textui.TestRunner", + "java.lang.reflect.Method.invoke(" + }; + for (int i= 0; i < patterns.length; i++) { + if (line.indexOf(patterns[i]) > 0) + return true; + } + return false; + } + + static { + fgMaxMessageLength= getPreference("maxmessage", fgMaxMessageLength); + } + +} diff --git a/src/junit/runner/ClassPathTestCollector.java b/src/junit/runner/ClassPathTestCollector.java new file mode 100644 index 0000000..0dbb98e --- /dev/null +++ b/src/junit/runner/ClassPathTestCollector.java @@ -0,0 +1,83 @@ +package junit.runner; + +import java.io.File; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.StringTokenizer; +import java.util.Vector; + +/** + * An implementation of a TestCollector that consults the + * class path. It considers all classes on the class path + * excluding classes in JARs. It leaves it up to subclasses + * to decide whether a class is a runnable Test. + * + * @see TestCollector + */ +public abstract class ClassPathTestCollector implements TestCollector { + + static final int SUFFIX_LENGTH= ".class".length(); + + public ClassPathTestCollector() { + } + + public Enumeration collectTests() { + String classPath= System.getProperty("java.class.path"); + Hashtable result = collectFilesInPath(classPath); + return result.elements(); + } + + public Hashtable collectFilesInPath(String classPath) { + Hashtable result= collectFilesInRoots(splitClassPath(classPath)); + return result; + } + + Hashtable collectFilesInRoots(Vector roots) { + Hashtable result= new Hashtable(100); + Enumeration e= roots.elements(); + while (e.hasMoreElements()) + gatherFiles(new File((String)e.nextElement()), "", result); + return result; + } + + void gatherFiles(File classRoot, String classFileName, Hashtable result) { + File thisRoot= new File(classRoot, classFileName); + if (thisRoot.isFile()) { + if (isTestClass(classFileName)) { + String className= classNameFromFile(classFileName); + result.put(className, className); + } + return; + } + String[] contents= thisRoot.list(); + if (contents != null) { + for (int i= 0; i < contents.length; i++) + gatherFiles(classRoot, classFileName+File.separatorChar+contents[i], result); + } + } + + Vector splitClassPath(String classPath) { + Vector result= new Vector(); + String separator= System.getProperty("path.separator"); + StringTokenizer tokenizer= new StringTokenizer(classPath, separator); + while (tokenizer.hasMoreTokens()) + result.addElement(tokenizer.nextToken()); + return result; + } + + protected boolean isTestClass(String classFileName) { + return + classFileName.endsWith(".class") && + classFileName.indexOf('$') < 0 && + classFileName.indexOf("Test") > 0; + } + + protected String classNameFromFile(String classFileName) { + // convert /a/b.class to a.b + String s= classFileName.substring(0, classFileName.length()-SUFFIX_LENGTH); + String s2= s.replace(File.separatorChar, '.'); + if (s2.startsWith(".")) + return s2.substring(1); + return s2; + } +} diff --git a/src/junit/runner/FailureDetailView.java b/src/junit/runner/FailureDetailView.java new file mode 100644 index 0000000..fc9aaf4 --- /dev/null +++ b/src/junit/runner/FailureDetailView.java @@ -0,0 +1,23 @@ +package junit.runner; + +import java.awt.Component; + +import junit.framework.TestFailure; + +/** + * A view to show a details about a failure + */ +public interface FailureDetailView { + /** + * Returns the component used to present the TraceView + */ + public Component getComponent(); + /** + * Shows details of a TestFailure + */ + public void showFailure(TestFailure failure); + /** + * Clears the view + */ + public void clear(); +}
\ No newline at end of file diff --git a/src/junit/runner/LoadingTestCollector.java b/src/junit/runner/LoadingTestCollector.java new file mode 100644 index 0000000..21a3144 --- /dev/null +++ b/src/junit/runner/LoadingTestCollector.java @@ -0,0 +1,70 @@ +package junit.runner; + +import java.lang.reflect.Modifier; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * An implementation of a TestCollector that loads + * all classes on the class path and tests whether + * it is assignable from Test or provides a static suite method. + * @see TestCollector + */ +public class LoadingTestCollector extends ClassPathTestCollector { + + TestCaseClassLoader fLoader; + + public LoadingTestCollector() { + fLoader= new TestCaseClassLoader(); + } + + protected boolean isTestClass(String classFileName) { + try { + if (classFileName.endsWith(".class")) { + Class testClass= classFromFile(classFileName); + return (testClass != null) && isTestClass(testClass); + } + } + catch (ClassNotFoundException expected) { + } + catch (NoClassDefFoundError notFatal) { + } + return false; + } + + Class classFromFile(String classFileName) throws ClassNotFoundException { + String className= classNameFromFile(classFileName); + if (!fLoader.isExcluded(className)) + return fLoader.loadClass(className, false); + return null; + } + + boolean isTestClass(Class testClass) { + if (hasSuiteMethod(testClass)) + return true; + if (Test.class.isAssignableFrom(testClass) && + Modifier.isPublic(testClass.getModifiers()) && + hasPublicConstructor(testClass)) + return true; + return false; + } + + boolean hasSuiteMethod(Class testClass) { + try { + testClass.getMethod(BaseTestRunner.SUITE_METHODNAME, new Class[0]); + } catch(Exception e) { + return false; + } + return true; + } + + boolean hasPublicConstructor(Class testClass) { + try { + TestSuite.getTestConstructor(testClass); + } catch(NoSuchMethodException e) { + return false; + } + return true; + } +} diff --git a/src/junit/runner/ReloadingTestSuiteLoader.java b/src/junit/runner/ReloadingTestSuiteLoader.java new file mode 100644 index 0000000..537504b --- /dev/null +++ b/src/junit/runner/ReloadingTestSuiteLoader.java @@ -0,0 +1,19 @@ +package junit.runner; + +/** + * A TestSuite loader that can reload classes. + */ +public class ReloadingTestSuiteLoader implements TestSuiteLoader { + + public Class load(String suiteClassName) throws ClassNotFoundException { + return createLoader().loadClass(suiteClassName, true); + } + + public Class reload(Class aClass) throws ClassNotFoundException { + return createLoader().loadClass(aClass.getName(), true); + } + + protected TestCaseClassLoader createLoader() { + return new TestCaseClassLoader(); + } +}
\ No newline at end of file diff --git a/src/junit/runner/SimpleTestCollector.java b/src/junit/runner/SimpleTestCollector.java new file mode 100644 index 0000000..9d1956a --- /dev/null +++ b/src/junit/runner/SimpleTestCollector.java @@ -0,0 +1,20 @@ +package junit.runner; + +/** + * An implementation of a TestCollector that considers + * a class to be a test class when it contains the + * pattern "Test" in its name + * @see TestCollector + */ +public class SimpleTestCollector extends ClassPathTestCollector { + + public SimpleTestCollector() { + } + + protected boolean isTestClass(String classFileName) { + return + classFileName.endsWith(".class") && + classFileName.indexOf('$') < 0 && + classFileName.indexOf("Test") > 0; + } +} diff --git a/src/junit/runner/Sorter.java b/src/junit/runner/Sorter.java new file mode 100644 index 0000000..e868992 --- /dev/null +++ b/src/junit/runner/Sorter.java @@ -0,0 +1,36 @@ +package junit.runner; + +import java.util.Vector; + +/** + * A custom quick sort with support to customize the swap behaviour. + * NOTICE: We can't use the the sorting support from the JDK 1.2 collection + * classes because of the JDK 1.1.7 compatibility. + */ +public class Sorter { + public static interface Swapper { + public void swap(Vector values, int left, int right); + } + + public static void sortStrings(Vector values , int left, int right, Swapper swapper) { + int oleft= left; + int oright= right; + String mid= (String)values.elementAt((left + right) / 2); + do { + while (((String)(values.elementAt(left))).compareTo(mid) < 0) + left++; + while (mid.compareTo((String)(values.elementAt(right))) < 0) + right--; + if (left <= right) { + swapper.swap(values, left, right); + left++; + right--; + } + } while (left <= right); + + if (oleft < right) + sortStrings(values, oleft, right, swapper); + if (left < oright) + sortStrings(values, left, oright, swapper); + } +}
\ No newline at end of file diff --git a/src/junit/runner/StandardTestSuiteLoader.java b/src/junit/runner/StandardTestSuiteLoader.java new file mode 100644 index 0000000..54f29c1 --- /dev/null +++ b/src/junit/runner/StandardTestSuiteLoader.java @@ -0,0 +1,19 @@ +package junit.runner; + +/** + * The standard test suite loader. It can only load the same class once. + */ +public class StandardTestSuiteLoader implements TestSuiteLoader { + /** + * Uses the system class loader to load the test class + */ + public Class load(String suiteClassName) throws ClassNotFoundException { + return Class.forName(suiteClassName); + } + /** + * Uses the system class loader to load the test class + */ + public Class reload(Class aClass) throws ClassNotFoundException { + return aClass; + } +}
\ No newline at end of file diff --git a/src/junit/runner/TestCaseClassLoader.java b/src/junit/runner/TestCaseClassLoader.java new file mode 100644 index 0000000..b4bbc24 --- /dev/null +++ b/src/junit/runner/TestCaseClassLoader.java @@ -0,0 +1,240 @@ +package junit.runner; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.Vector; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * A custom class loader which enables the reloading + * of classes for each test run. The class loader + * can be configured with a list of package paths that + * should be excluded from loading. The loading + * of these packages is delegated to the system class + * loader. They will be shared across test runs. + * <p> + * The list of excluded package paths is specified in + * a properties file "excluded.properties" that is located in + * the same place as the TestCaseClassLoader class. + * <p> + * <b>Known limitation:</b> the TestCaseClassLoader cannot load classes + * from jar files. + */ + + +public class TestCaseClassLoader extends ClassLoader { + /** scanned class path */ + private Vector fPathItems; + /** default excluded paths */ + private String[] defaultExclusions= { + "junit.framework.", + "junit.extensions.", + "junit.runner." + }; + /** name of excluded properties file */ + static final String EXCLUDED_FILE= "excluded.properties"; + /** excluded paths */ + private Vector fExcluded; + + /** + * Constructs a TestCaseLoader. It scans the class path + * and the excluded package paths + */ + public TestCaseClassLoader() { + this(System.getProperty("java.class.path")); + } + + /** + * Constructs a TestCaseLoader. It scans the class path + * and the excluded package paths + */ + public TestCaseClassLoader(String classPath) { + scanPath(classPath); + readExcludedPackages(); + } + + private void scanPath(String classPath) { + String separator= System.getProperty("path.separator"); + fPathItems= new Vector(10); + StringTokenizer st= new StringTokenizer(classPath, separator); + while (st.hasMoreTokens()) { + fPathItems.addElement(st.nextToken()); + } + } + + public URL getResource(String name) { + return ClassLoader.getSystemResource(name); + } + + public InputStream getResourceAsStream(String name) { + return ClassLoader.getSystemResourceAsStream(name); + } + + public boolean isExcluded(String name) { + for (int i= 0; i < fExcluded.size(); i++) { + if (name.startsWith((String) fExcluded.elementAt(i))) { + return true; + } + } + return false; + } + + public synchronized Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + + Class c= findLoadedClass(name); + if (c != null) + return c; + // + // Delegate the loading of excluded classes to the + // standard class loader. + // + if (isExcluded(name)) { + try { + c= findSystemClass(name); + return c; + } catch (ClassNotFoundException e) { + // keep searching + } + } + if (c == null) { + byte[] data= lookupClassData(name); + if (data == null) + throw new ClassNotFoundException(); + c= defineClass(name, data, 0, data.length); + } + if (resolve) + resolveClass(c); + return c; + } + + private byte[] lookupClassData(String className) throws ClassNotFoundException { + byte[] data= null; + for (int i= 0; i < fPathItems.size(); i++) { + String path= (String) fPathItems.elementAt(i); + String fileName= className.replace('.', '/')+".class"; + if (isJar(path)) { + data= loadJarData(path, fileName); + } else { + data= loadFileData(path, fileName); + } + if (data != null) + return data; + } + throw new ClassNotFoundException(className); + } + + boolean isJar(String pathEntry) { + return pathEntry.endsWith(".jar") || pathEntry.endsWith(".zip"); + } + + private byte[] loadFileData(String path, String fileName) { + File file= new File(path, fileName); + if (file.exists()) { + return getClassData(file); + } + return null; + } + + private byte[] getClassData(File f) { + FileInputStream stream= null; + try { + stream= new FileInputStream(f); + ByteArrayOutputStream out= new ByteArrayOutputStream(1000); + byte[] b= new byte[1000]; + int n; + while ((n= stream.read(b)) != -1) + out.write(b, 0, n); + stream.close(); + out.close(); + return out.toByteArray(); + + } catch (IOException e) { + } + finally { + if (stream != null) + try { + stream.close(); + } catch (IOException e1) { + } + } + return null; + } + + private byte[] loadJarData(String path, String fileName) { + ZipFile zipFile= null; + InputStream stream= null; + File archive= new File(path); + if (!archive.exists()) + return null; + try { + zipFile= new ZipFile(archive); + } catch(IOException io) { + return null; + } + ZipEntry entry= zipFile.getEntry(fileName); + if (entry == null) + return null; + int size= (int) entry.getSize(); + try { + stream= zipFile.getInputStream(entry); + byte[] data= new byte[size]; + int pos= 0; + while (pos < size) { + int n= stream.read(data, pos, data.length - pos); + pos += n; + } + zipFile.close(); + return data; + } catch (IOException e) { + } finally { + try { + if (stream != null) + stream.close(); + } catch (IOException e) { + } + } + return null; + } + + private void readExcludedPackages() { + fExcluded= new Vector(10); + for (int i= 0; i < defaultExclusions.length; i++) + fExcluded.addElement(defaultExclusions[i]); + + InputStream is= getClass().getResourceAsStream(EXCLUDED_FILE); + if (is == null) + return; + Properties p= new Properties(); + try { + p.load(is); + } + catch (IOException e) { + return; + } finally { + try { + is.close(); + } catch (IOException e) { + } + } + for (Enumeration e= p.propertyNames(); e.hasMoreElements(); ) { + String key= (String)e.nextElement(); + if (key.startsWith("excluded.")) { + String path= p.getProperty(key); + path= path.trim(); + if (path.endsWith("*")) + path= path.substring(0, path.length()-1); + if (path.length() > 0) + fExcluded.addElement(path); + } + } + } +}
\ No newline at end of file diff --git a/src/junit/runner/TestCollector.java b/src/junit/runner/TestCollector.java new file mode 100644 index 0000000..276e7fa --- /dev/null +++ b/src/junit/runner/TestCollector.java @@ -0,0 +1,17 @@ +package junit.runner; + +import java.util.Enumeration; +import junit.swingui.TestSelector; + + +/** + * Collects Test class names to be presented + * by the TestSelector. + * @see TestSelector + */ +public interface TestCollector { + /** + * Returns an enumeration of Strings with qualified class names + */ + public Enumeration collectTests(); +} diff --git a/src/junit/runner/TestRunListener.java b/src/junit/runner/TestRunListener.java new file mode 100644 index 0000000..b11ef07 --- /dev/null +++ b/src/junit/runner/TestRunListener.java @@ -0,0 +1,19 @@ +package junit.runner; +/** + * A listener interface for observing the + * execution of a test run. Unlike TestListener, + * this interface using only primitive objects, + * making it suitable for remote test execution. + */ + public interface TestRunListener { + /* test status constants*/ + public static final int STATUS_ERROR= 1; + public static final int STATUS_FAILURE= 2; + + public void testRunStarted(String testSuiteName, int testCount); + public void testRunEnded(long elapsedTime); + public void testRunStopped(long elapsedTime); + public void testStarted(String testName); + public void testEnded(String testName); + public void testFailed(int status, String testName, String trace); +} diff --git a/src/junit/runner/TestSuiteLoader.java b/src/junit/runner/TestSuiteLoader.java new file mode 100644 index 0000000..2db589e --- /dev/null +++ b/src/junit/runner/TestSuiteLoader.java @@ -0,0 +1,9 @@ +package junit.runner; + +/** + * An interface to define how a test suite should be loaded. + */ +public interface TestSuiteLoader { + abstract public Class load(String suiteClassName) throws ClassNotFoundException; + abstract public Class reload(Class aClass) throws ClassNotFoundException; +}
\ No newline at end of file diff --git a/src/junit/runner/Version.java b/src/junit/runner/Version.java new file mode 100644 index 0000000..7fd76aa --- /dev/null +++ b/src/junit/runner/Version.java @@ -0,0 +1,18 @@ +package junit.runner; + +/** + * This class defines the current version of JUnit + */ +public class Version { + private Version() { + // don't instantiate + } + + public static String id() { + return "3.8.2"; + } + + public static void main(String[] args) { + System.out.println(id()); + } +} diff --git a/src/junit/runner/excluded.properties b/src/junit/runner/excluded.properties new file mode 100644 index 0000000..011ae3c --- /dev/null +++ b/src/junit/runner/excluded.properties @@ -0,0 +1,13 @@ +# +# The list of excluded package paths for the TestCaseClassLoader +# +excluded.0=sun.* +excluded.1=com.sun.* +excluded.2=org.omg.* +excluded.3=javax.* +excluded.4=sunw.* +excluded.5=java.* +excluded.6=org.w3c.dom.* +excluded.7=org.xml.sax.* +excluded.8=net.jini.* +excluded.9=org.apache.commons.logging.*
\ No newline at end of file diff --git a/src/junit/runner/logo.gif b/src/junit/runner/logo.gif Binary files differnew file mode 100644 index 0000000..d0e1547 --- /dev/null +++ b/src/junit/runner/logo.gif diff --git a/src/junit/runner/smalllogo.gif b/src/junit/runner/smalllogo.gif Binary files differnew file mode 100644 index 0000000..7b25eaf --- /dev/null +++ b/src/junit/runner/smalllogo.gif diff --git a/src/junit/swingui/AboutDialog.java b/src/junit/swingui/AboutDialog.java new file mode 100644 index 0000000..c55b420 --- /dev/null +++ b/src/junit/swingui/AboutDialog.java @@ -0,0 +1,93 @@ +package junit.swingui; + +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; + +import junit.runner.BaseTestRunner; +import junit.runner.Version; + +/** + * The AboutDialog. + */ +class AboutDialog extends JDialog { + public AboutDialog(JFrame parent) { + super(parent, true); + + setResizable(false); + getContentPane().setLayout(new GridBagLayout()); + setSize(330, 138); + setTitle("About"); + // setLocationRelativeTo is only available in JDK 1.4 + try { + setLocationRelativeTo(parent); + } catch (NoSuchMethodError e) { + TestSelector.centerWindow(this); + } + + JButton close= new JButton("Close"); + close.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + dispose(); + } + } + ); + getRootPane().setDefaultButton(close); + JLabel label1= new JLabel("JUnit"); + label1.setFont(new Font("dialog", Font.PLAIN, 36)); + + JLabel label2= new JLabel("JUnit "+Version.id()+" by Kent Beck and Erich Gamma"); + label2.setFont(new Font("dialog", Font.PLAIN, 14)); + + JLabel logo= createLogo(); + + GridBagConstraints constraintsLabel1= new GridBagConstraints(); + constraintsLabel1.gridx = 3; constraintsLabel1.gridy = 0; + constraintsLabel1.gridwidth = 1; constraintsLabel1.gridheight = 1; + constraintsLabel1.anchor = GridBagConstraints.CENTER; + getContentPane().add(label1, constraintsLabel1); + + GridBagConstraints constraintsLabel2= new GridBagConstraints(); + constraintsLabel2.gridx = 2; constraintsLabel2.gridy = 1; + constraintsLabel2.gridwidth = 2; constraintsLabel2.gridheight = 1; + constraintsLabel2.anchor = GridBagConstraints.CENTER; + getContentPane().add(label2, constraintsLabel2); + + GridBagConstraints constraintsButton1= new GridBagConstraints(); + constraintsButton1.gridx = 2; constraintsButton1.gridy = 2; + constraintsButton1.gridwidth = 2; constraintsButton1.gridheight = 1; + constraintsButton1.anchor = GridBagConstraints.CENTER; + constraintsButton1.insets= new Insets(8, 0, 8, 0); + getContentPane().add(close, constraintsButton1); + + GridBagConstraints constraintsLogo1= new GridBagConstraints(); + constraintsLogo1.gridx = 2; constraintsLogo1.gridy = 0; + constraintsLogo1.gridwidth = 1; constraintsLogo1.gridheight = 1; + constraintsLogo1.anchor = GridBagConstraints.CENTER; + getContentPane().add(logo, constraintsLogo1); + + addWindowListener( + new WindowAdapter() { + public void windowClosing(WindowEvent e) { + dispose(); + } + } + ); + } + protected JLabel createLogo() { + Icon icon= TestRunner.getIconResource(BaseTestRunner.class, "logo.gif"); + return new JLabel(icon); + } +}
\ No newline at end of file diff --git a/src/junit/swingui/CounterPanel.java b/src/junit/swingui/CounterPanel.java new file mode 100644 index 0000000..cac4427 --- /dev/null +++ b/src/junit/swingui/CounterPanel.java @@ -0,0 +1,118 @@ +package junit.swingui; + +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.SwingConstants; + +/** + * A panel with test run counters + */ +public class CounterPanel extends JPanel { + private JTextField fNumberOfErrors; + private JTextField fNumberOfFailures; + private JTextField fNumberOfRuns; + private Icon fFailureIcon= TestRunner.getIconResource(getClass(), "icons/failure.gif"); + private Icon fErrorIcon= TestRunner.getIconResource(getClass(), "icons/error.gif"); + + private int fTotal; + + public CounterPanel() { + super(new GridBagLayout()); + fNumberOfErrors= createOutputField(5); + fNumberOfFailures= createOutputField(5); + fNumberOfRuns= createOutputField(9); + + addToGrid(new JLabel("Runs:", SwingConstants.CENTER), + 0, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0)); + addToGrid(fNumberOfRuns, + 1, 0, 1, 1, 0.33, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(0, 8, 0, 0)); + + addToGrid(new JLabel("Errors:", fErrorIcon, SwingConstants.LEFT), + 2, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.NONE, + new Insets(0, 8, 0, 0)); + addToGrid(fNumberOfErrors, + 3, 0, 1, 1, 0.33, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(0, 8, 0, 0)); + + addToGrid(new JLabel("Failures:", fFailureIcon, SwingConstants.LEFT), + 4, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.NONE, + new Insets(0, 8, 0, 0)); + addToGrid(fNumberOfFailures, + 5, 0, 1, 1, 0.33, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(0, 8, 0, 0)); + } + + private JTextField createOutputField(int width) { + JTextField field= new JTextField("0", width); + // force a fixed layout to avoid accidental hiding on relayout + field.setMinimumSize(field.getPreferredSize()); + field.setMaximumSize(field.getPreferredSize()); + field.setHorizontalAlignment(SwingConstants.LEFT); + field.setFont(StatusLine.BOLD_FONT); + field.setEditable(false); + field.setBorder(BorderFactory.createEmptyBorder()); + return field; + } + + public void addToGrid(Component comp, + int gridx, int gridy, int gridwidth, int gridheight, + double weightx, double weighty, + int anchor, int fill, + Insets insets) { + + GridBagConstraints constraints= new GridBagConstraints(); + constraints.gridx= gridx; + constraints.gridy= gridy; + constraints.gridwidth= gridwidth; + constraints.gridheight= gridheight; + constraints.weightx= weightx; + constraints.weighty= weighty; + constraints.anchor= anchor; + constraints.fill= fill; + constraints.insets= insets; + add(comp, constraints); + } + + public void reset() { + setLabelValue(fNumberOfErrors, 0); + setLabelValue(fNumberOfFailures, 0); + setLabelValue(fNumberOfRuns, 0); + fTotal= 0; + } + + public void setTotal(int value) { + fTotal= value; + } + + public void setRunValue(int value) { + fNumberOfRuns.setText(Integer.toString(value) + "/" + fTotal); + } + + public void setErrorValue(int value) { + setLabelValue(fNumberOfErrors, value); + } + + public void setFailureValue(int value) { + setLabelValue(fNumberOfFailures, value); + } + + private void setLabelValue(JTextField label, int value) { + label.setText(Integer.toString(value)); + } +}
\ No newline at end of file diff --git a/src/junit/swingui/DefaultFailureDetailView.java b/src/junit/swingui/DefaultFailureDetailView.java new file mode 100644 index 0000000..51e79c7 --- /dev/null +++ b/src/junit/swingui/DefaultFailureDetailView.java @@ -0,0 +1,101 @@ +package junit.swingui; + +import java.awt.Component; +import java.awt.Font; +import java.util.StringTokenizer; +import java.util.Vector; + +import javax.swing.AbstractListModel; +import javax.swing.DefaultListCellRenderer; +import javax.swing.JList; +import javax.swing.ListSelectionModel; + +import junit.framework.TestFailure; +import junit.runner.BaseTestRunner; +import junit.runner.FailureDetailView; + +/** + * A view that shows a stack trace of a failure + */ +public class DefaultFailureDetailView implements FailureDetailView { + JList fList; + + /** + * A ListModel representing the scanned failure stack trace. + */ + static class StackTraceListModel extends AbstractListModel { + private Vector fLines= new Vector(20); + + public Object getElementAt(int index) { + return fLines.elementAt(index); + } + + public int getSize() { + return fLines.size(); + } + + public void setTrace(String trace) { + scan(trace); + fireContentsChanged(this, 0, fLines.size()); + } + + public void clear() { + fLines.removeAllElements(); + fireContentsChanged(this, 0, fLines.size()); + } + + private void scan(String trace) { + fLines.removeAllElements(); + StringTokenizer st= new StringTokenizer(trace, "\n\r", false); + while (st.hasMoreTokens()) + fLines.addElement(st.nextToken()); + } + } + + /** + * Renderer for stack entries + */ + static class StackEntryRenderer extends DefaultListCellRenderer { + + public Component getListCellRendererComponent( + JList list, Object value, int modelIndex, + boolean isSelected, boolean cellHasFocus) { + String text= ((String)value).replace('\t', ' '); + Component c= super.getListCellRendererComponent(list, text, modelIndex, isSelected, cellHasFocus); + setText(text); + setToolTipText(text); + return c; + } + } + + /** + * Returns the component used to present the trace + */ + public Component getComponent() { + if (fList == null) { + fList= new JList(new StackTraceListModel()); + fList.setFont(new Font("Dialog", Font.PLAIN, 12)); + fList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + fList.setVisibleRowCount(5); + fList.setCellRenderer(new StackEntryRenderer()); + } + return fList; + } + + /** + * Shows a TestFailure + */ + public void showFailure(TestFailure failure) { + getModel().setTrace(BaseTestRunner.getFilteredTrace(failure.trace())); + } + /** + * Clears the output. + */ + public void clear() { + getModel().clear(); + } + + private StackTraceListModel getModel() { + return (StackTraceListModel)fList.getModel(); + } +}
\ No newline at end of file diff --git a/src/junit/swingui/FailureRunView.java b/src/junit/swingui/FailureRunView.java new file mode 100644 index 0000000..3ec6126 --- /dev/null +++ b/src/junit/swingui/FailureRunView.java @@ -0,0 +1,122 @@ +package junit.swingui; + +import java.awt.Component; +import java.awt.Font; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.Icon; +import javax.swing.JList; +import javax.swing.JScrollPane; +import javax.swing.JTabbedPane; +import javax.swing.ListModel; +import javax.swing.ListSelectionModel; +import javax.swing.ScrollPaneConstants; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import junit.framework.Test; +import junit.framework.TestFailure; +import junit.framework.TestResult; +import junit.runner.BaseTestRunner; + + +/** + * A view presenting the test failures as a list. + */ +public class FailureRunView implements TestRunView { + JList fFailureList; + TestRunContext fRunContext; + + /** + * Renders TestFailures in a JList + */ + static class FailureListCellRenderer extends DefaultListCellRenderer { + private Icon fFailureIcon; + private Icon fErrorIcon; + + FailureListCellRenderer() { + super(); + loadIcons(); + } + + void loadIcons() { + fFailureIcon= TestRunner.getIconResource(getClass(), "icons/failure.gif"); + fErrorIcon= TestRunner.getIconResource(getClass(), "icons/error.gif"); + } + + public Component getListCellRendererComponent( + JList list, Object value, int modelIndex, + boolean isSelected, boolean cellHasFocus) { + + Component c= super.getListCellRendererComponent(list, value, modelIndex, isSelected, cellHasFocus); + TestFailure failure= (TestFailure)value; + String text= failure.failedTest().toString(); + String msg= failure.exceptionMessage(); + if (msg != null) + text+= ":" + BaseTestRunner.truncate(msg); + + if (failure.isFailure()) { + if (fFailureIcon != null) + setIcon(fFailureIcon); + } else { + if (fErrorIcon != null) + setIcon(fErrorIcon); + } + setText(text); + setToolTipText(text); + return c; + } + } + + public FailureRunView(TestRunContext context) { + fRunContext= context; + fFailureList= new JList(fRunContext.getFailures()); + fFailureList.setFont(new Font("Dialog", Font.PLAIN, 12)); + + fFailureList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + fFailureList.setCellRenderer(new FailureListCellRenderer()); + fFailureList.setVisibleRowCount(5); + + fFailureList.addListSelectionListener( + new ListSelectionListener() { + public void valueChanged(ListSelectionEvent e) { + testSelected(); + } + } + ); + } + + public Test getSelectedTest() { + int index= fFailureList.getSelectedIndex(); + if (index == -1) + return null; + + ListModel model= fFailureList.getModel(); + TestFailure failure= (TestFailure)model.getElementAt(index); + return failure.failedTest(); + } + + public void activate() { + testSelected(); + } + + public void addTab(JTabbedPane pane) { + JScrollPane scrollPane= new JScrollPane(fFailureList, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); + Icon errorIcon= TestRunner.getIconResource(getClass(), "icons/error.gif"); + pane.addTab("Failures", errorIcon, scrollPane, "The list of failed tests"); + } + + public void revealFailure(Test failure) { + fFailureList.setSelectedIndex(0); + } + + public void aboutToStart(Test suite, TestResult result) { + } + + public void runFinished(Test suite, TestResult result) { + } + + protected void testSelected() { + fRunContext.handleTestSelected(getSelectedTest()); + } +} diff --git a/src/junit/swingui/MacProgressBar.java b/src/junit/swingui/MacProgressBar.java new file mode 100644 index 0000000..1de6cfd --- /dev/null +++ b/src/junit/swingui/MacProgressBar.java @@ -0,0 +1,20 @@ +package junit.swingui; + +import javax.swing.JTextField; + +/** + http://java.sun.com/developer/technicalArticles/JavaLP/JavaToMac2/ +*/ +public class MacProgressBar extends ProgressBar { + + private JTextField component; + + public MacProgressBar(JTextField component) { + super(); + this.component= component; + } + + protected void updateBarColor() { + component.setBackground(getStatusColor()); + } +} diff --git a/src/junit/swingui/ProgressBar.java b/src/junit/swingui/ProgressBar.java new file mode 100644 index 0000000..d5de71e --- /dev/null +++ b/src/junit/swingui/ProgressBar.java @@ -0,0 +1,46 @@ +package junit.swingui; + +import java.awt.Color; + +import javax.swing.JProgressBar; + +/** + * A progress bar showing the green/red status + */ +class ProgressBar extends JProgressBar { + boolean fError= false; + + public ProgressBar() { + super(); + setForeground(getStatusColor()); + } + + protected Color getStatusColor() { + if (fError) + return Color.red; + return Color.green; + } + + public void reset() { + fError= false; + updateBarColor(); + setValue(0); + } + + public void start(int total) { + setMaximum(total); + reset(); + } + + public void step(int value, boolean successful) { + setValue(value); + if (!fError && !successful) { + fError= true; + updateBarColor(); + } + } + + protected void updateBarColor() { + setForeground(getStatusColor()); + } +} diff --git a/src/junit/swingui/StatusLine.java b/src/junit/swingui/StatusLine.java new file mode 100644 index 0000000..e18fda2 --- /dev/null +++ b/src/junit/swingui/StatusLine.java @@ -0,0 +1,45 @@ +package junit.swingui; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; + +import javax.swing.BorderFactory; +import javax.swing.JTextField; +import javax.swing.border.BevelBorder; + +/** + * A status line component. + */ +public class StatusLine extends JTextField { + public static final Font PLAIN_FONT= new Font("dialog", Font.PLAIN, 12); + public static final Font BOLD_FONT= new Font("dialog", Font.BOLD, 12); + + public StatusLine(int preferredWidth) { + super(); + setFont(BOLD_FONT); + setEditable(false); + setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); + Dimension d= getPreferredSize(); + d.width= preferredWidth; + setPreferredSize(d); + } + + public void showInfo(String message) { + setFont(PLAIN_FONT); + setForeground(Color.black); + setText(message); + } + + public void showError(String status) { + setFont(BOLD_FONT); + setForeground(Color.red); + setText(status); + setToolTipText(status); + } + + public void clear() { + setText(""); + setToolTipText(null); + } +}
\ No newline at end of file diff --git a/src/junit/swingui/TestHierarchyRunView.java b/src/junit/swingui/TestHierarchyRunView.java new file mode 100644 index 0000000..89bd297 --- /dev/null +++ b/src/junit/swingui/TestHierarchyRunView.java @@ -0,0 +1,77 @@ +package junit.swingui; + +import java.util.Vector; + +import javax.swing.Icon; +import javax.swing.JTabbedPane; +import javax.swing.JTree; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.TreePath; + +import junit.framework.Test; +import junit.framework.TestResult; + +/** + * A hierarchical view of a test run. + * The contents of a test suite is shown + * as a tree. + */ +public class TestHierarchyRunView implements TestRunView { + TestSuitePanel fTreeBrowser; + TestRunContext fTestContext; + + public TestHierarchyRunView(TestRunContext context) { + fTestContext= context; + fTreeBrowser= new TestSuitePanel(); + fTreeBrowser.getTree().addTreeSelectionListener( + new TreeSelectionListener() { + public void valueChanged(TreeSelectionEvent e) { + testSelected(); + } + } + ); + } + + public void addTab(JTabbedPane pane) { + Icon treeIcon= TestRunner.getIconResource(getClass(), "icons/hierarchy.gif"); + pane.addTab("Test Hierarchy", treeIcon, fTreeBrowser, "The test hierarchy"); + } + + public Test getSelectedTest() { + return fTreeBrowser.getSelectedTest(); + } + + public void activate() { + testSelected(); + } + + public void revealFailure(Test failure) { + JTree tree= fTreeBrowser.getTree(); + TestTreeModel model= (TestTreeModel)tree.getModel(); + Vector vpath= new Vector(); + int index= model.findTest(failure, (Test)model.getRoot(), vpath); + if (index >= 0) { + Object[] path= new Object[vpath.size()+1]; + vpath.copyInto(path); + Object last= path[vpath.size()-1]; + path[vpath.size()]= model.getChild(last, index); + TreePath selectionPath= new TreePath(path); + tree.setSelectionPath(selectionPath); + tree.makeVisible(selectionPath); + } + } + + public void aboutToStart(Test suite, TestResult result) { + fTreeBrowser.showTestTree(suite); + result.addListener(fTreeBrowser); + } + + public void runFinished(Test suite, TestResult result) { + result.removeListener(fTreeBrowser); + } + + protected void testSelected() { + fTestContext.handleTestSelected(getSelectedTest()); + } +} diff --git a/src/junit/swingui/TestRunContext.java b/src/junit/swingui/TestRunContext.java new file mode 100644 index 0000000..038e3c4 --- /dev/null +++ b/src/junit/swingui/TestRunContext.java @@ -0,0 +1,21 @@ +package junit.swingui; + +import javax.swing.ListModel; + +import junit.framework.Test; + +/** + * The interface for accessing the Test run context. Test run views + * should use this interface rather than accessing the TestRunner + * directly. + */ +public interface TestRunContext { + /** + * Handles the selection of a Test. + */ + public void handleTestSelected(Test test); + /** + * Returns the failure model + */ + public ListModel getFailures(); +}
\ No newline at end of file diff --git a/src/junit/swingui/TestRunView.java b/src/junit/swingui/TestRunView.java new file mode 100644 index 0000000..1eb5491 --- /dev/null +++ b/src/junit/swingui/TestRunView.java @@ -0,0 +1,39 @@ +package junit.swingui; + +import javax.swing.JTabbedPane; + +import junit.framework.Test; +import junit.framework.TestResult; + +/** + * A TestRunView is shown as a page in a tabbed folder. + * It contributes the page contents and can return + * the currently selected tests. A TestRunView is + * notified about the start and finish of a run. + */ +interface TestRunView { + /** + * Returns the currently selected Test in the View + */ + public Test getSelectedTest(); + /** + * Activates the TestRunView + */ + public void activate(); + /** + * Reveals the given failure + */ + public void revealFailure(Test failure); + /** + * Adds the TestRunView to the test run views tab + */ + public void addTab(JTabbedPane pane); + /** + * Informs that the suite is about to start + */ + public void aboutToStart(Test suite, TestResult result); + /** + * Informs that the run of the test suite has finished + */ + public void runFinished(Test suite, TestResult result); +}
\ No newline at end of file diff --git a/src/junit/swingui/TestRunner.java b/src/junit/swingui/TestRunner.java new file mode 100644 index 0000000..44aa7a7 --- /dev/null +++ b/src/junit/swingui/TestRunner.java @@ -0,0 +1,849 @@ +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.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 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(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(); + } +} diff --git a/src/junit/swingui/TestSelector.java b/src/junit/swingui/TestSelector.java new file mode 100644 index 0000000..f0f1f9e --- /dev/null +++ b/src/junit/swingui/TestSelector.java @@ -0,0 +1,285 @@ +package junit.swingui; + +import java.awt.Component; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.Enumeration; +import java.util.Vector; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JScrollPane; +import javax.swing.ListModel; +import javax.swing.ListSelectionModel; +import javax.swing.UIManager; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import junit.runner.Sorter; +import junit.runner.TestCollector; + +/** + * A test class selector. A simple dialog to pick the name of a test suite. + */ +public class TestSelector extends JDialog { + private JButton fCancel; + private JButton fOk; + private JList fList; + private JScrollPane fScrolledList; + private JLabel fDescription; + private String fSelectedItem; + + /** + * Renders TestFailures in a JList + */ + static class TestCellRenderer extends DefaultListCellRenderer { + Icon fLeafIcon; + Icon fSuiteIcon; + + public TestCellRenderer() { + fLeafIcon= UIManager.getIcon("Tree.leafIcon"); + fSuiteIcon= UIManager.getIcon("Tree.closedIcon"); + } + + public Component getListCellRendererComponent( + JList list, Object value, int modelIndex, + boolean isSelected, boolean cellHasFocus) { + Component c= super.getListCellRendererComponent(list, value, modelIndex, isSelected, cellHasFocus); + String displayString= displayString((String)value); + + if (displayString.startsWith("AllTests")) + setIcon(fSuiteIcon); + else + setIcon(fLeafIcon); + + setText(displayString); + return c; + } + + public static String displayString(String className) { + int typeIndex= className.lastIndexOf('.'); + if (typeIndex < 0) + return className; + return className.substring(typeIndex+1) + " - " + className.substring(0, typeIndex); + } + + public static boolean matchesKey(String s, char ch) { + return ch == Character.toUpperCase(s.charAt(typeIndex(s))); + } + + private static int typeIndex(String s) { + int typeIndex= s.lastIndexOf('.'); + int i= 0; + if (typeIndex > 0) + i= typeIndex+1; + return i; + } + } + + protected class DoubleClickListener extends MouseAdapter { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + okSelected(); + } + } + } + + protected class KeySelectListener extends KeyAdapter { + public void keyTyped(KeyEvent e) { + keySelectTestClass(e.getKeyChar()); + } + } + + public TestSelector(Frame parent, TestCollector testCollector) { + super(parent, true); + setSize(350, 300); + setResizable(false); + // setLocationRelativeTo only exists in 1.4 + try { + setLocationRelativeTo(parent); + } catch (NoSuchMethodError e) { + centerWindow(this); + } + setTitle("Test Selector"); + + Vector list= null; + try { + parent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + list= createTestList(testCollector); + } finally { + parent.setCursor(Cursor.getDefaultCursor()); + } + fList= new JList(list); + fList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + fList.setCellRenderer(new TestCellRenderer()); + fScrolledList= new JScrollPane(fList); + + fCancel= new JButton("Cancel"); + fDescription= new JLabel("Select the Test class:"); + fOk= new JButton("OK"); + fOk.setEnabled(false); + getRootPane().setDefaultButton(fOk); + + defineLayout(); + addListeners(); + } + + public static void centerWindow(Component c) { + Dimension paneSize= c.getSize(); + Dimension screenSize= c.getToolkit().getScreenSize(); + c.setLocation((screenSize.width-paneSize.width)/2, (screenSize.height-paneSize.height)/2); + } + + private void addListeners() { + fCancel.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + dispose(); + } + } + ); + + fOk.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + okSelected(); + } + } + ); + + fList.addMouseListener(new DoubleClickListener()); + fList.addKeyListener(new KeySelectListener()); + fList.addListSelectionListener( + new ListSelectionListener() { + public void valueChanged(ListSelectionEvent e) { + checkEnableOK(e); + } + } + ); + + addWindowListener( + new WindowAdapter() { + public void windowClosing(WindowEvent e) { + dispose(); + } + } + ); + } + + private void defineLayout() { + getContentPane().setLayout(new GridBagLayout()); + GridBagConstraints labelConstraints = new GridBagConstraints(); + labelConstraints.gridx= 0; labelConstraints.gridy= 0; + labelConstraints.gridwidth= 1; labelConstraints.gridheight= 1; + labelConstraints.fill= GridBagConstraints.BOTH; + labelConstraints.anchor= GridBagConstraints.WEST; + labelConstraints.weightx= 1.0; + labelConstraints.weighty= 0.0; + labelConstraints.insets= new Insets(8, 8, 0, 8); + getContentPane().add(fDescription, labelConstraints); + + GridBagConstraints listConstraints = new GridBagConstraints(); + listConstraints.gridx= 0; listConstraints.gridy= 1; + listConstraints.gridwidth= 4; listConstraints.gridheight= 1; + listConstraints.fill= GridBagConstraints.BOTH; + listConstraints.anchor= GridBagConstraints.CENTER; + listConstraints.weightx= 1.0; + listConstraints.weighty= 1.0; + listConstraints.insets= new Insets(8, 8, 8, 8); + getContentPane().add(fScrolledList, listConstraints); + + GridBagConstraints okConstraints= new GridBagConstraints(); + okConstraints.gridx= 2; okConstraints.gridy= 2; + okConstraints.gridwidth= 1; okConstraints.gridheight= 1; + okConstraints.anchor= java.awt.GridBagConstraints.EAST; + okConstraints.insets= new Insets(0, 8, 8, 8); + getContentPane().add(fOk, okConstraints); + + + GridBagConstraints cancelConstraints = new GridBagConstraints(); + cancelConstraints.gridx= 3; cancelConstraints.gridy= 2; + cancelConstraints.gridwidth= 1; cancelConstraints.gridheight= 1; + cancelConstraints.anchor= java.awt.GridBagConstraints.EAST; + cancelConstraints.insets= new Insets(0, 8, 8, 8); + getContentPane().add(fCancel, cancelConstraints); + } + + public void checkEnableOK(ListSelectionEvent e) { + fOk.setEnabled(fList.getSelectedIndex() != -1); + } + + public void okSelected() { + fSelectedItem= (String)fList.getSelectedValue(); + dispose(); + } + + public boolean isEmpty() { + return fList.getModel().getSize() == 0; + } + + public void keySelectTestClass(char ch) { + ListModel model= fList.getModel(); + if (!Character.isJavaIdentifierStart(ch)) + return; + for (int i= 0; i < model.getSize(); i++) { + String s= (String)model.getElementAt(i); + if (TestCellRenderer.matchesKey(s, Character.toUpperCase(ch))) { + fList.setSelectedIndex(i); + fList.ensureIndexIsVisible(i); + return; + } + } + Toolkit.getDefaultToolkit().beep(); + } + + public String getSelectedItem() { + return fSelectedItem; + } + + private Vector createTestList(TestCollector collector) { + Enumeration each= collector.collectTests(); + Vector v= new Vector(200); + Vector displayVector= new Vector(v.size()); + while(each.hasMoreElements()) { + String s= (String)each.nextElement(); + v.addElement(s); + displayVector.addElement(TestCellRenderer.displayString(s)); + } + if (v.size() > 0) + Sorter.sortStrings(displayVector, 0, displayVector.size()-1, new ParallelSwapper(v)); + return v; + } + + private class ParallelSwapper implements Sorter.Swapper { + Vector fOther; + + ParallelSwapper(Vector other) { + fOther= other; + } + public void swap(Vector values, int left, int right) { + Object tmp= values.elementAt(left); + values.setElementAt(values.elementAt(right), left); + values.setElementAt(tmp, right); + Object tmp2= fOther.elementAt(left); + fOther.setElementAt(fOther.elementAt(right), left); + fOther.setElementAt(tmp2, right); + } + } +}
\ No newline at end of file diff --git a/src/junit/swingui/TestSuitePanel.java b/src/junit/swingui/TestSuitePanel.java new file mode 100644 index 0000000..d8902ad --- /dev/null +++ b/src/junit/swingui/TestSuitePanel.java @@ -0,0 +1,172 @@ +package junit.swingui; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.util.Vector; + +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTree; +import javax.swing.SwingUtilities; +import javax.swing.ToolTipManager; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestListener; + +/** + * A Panel showing a test suite as a tree. + */ +class TestSuitePanel extends JPanel implements TestListener { + private JTree fTree; + private JScrollPane fScrollTree; + private TestTreeModel fModel; + + static class TestTreeCellRenderer extends DefaultTreeCellRenderer { + private Icon fErrorIcon; + private Icon fOkIcon; + private Icon fFailureIcon; + + TestTreeCellRenderer() { + super(); + loadIcons(); + } + + void loadIcons() { + fErrorIcon= TestRunner.getIconResource(getClass(), "icons/error.gif"); + fOkIcon= TestRunner.getIconResource(getClass(), "icons/ok.gif"); + fFailureIcon= TestRunner.getIconResource(getClass(), "icons/failure.gif"); + } + + String stripParenthesis(Object o) { + String text= o.toString (); + int pos= text.indexOf('('); + if (pos < 1) + return text; + return text.substring (0, pos); + } + + public Component getTreeCellRendererComponent(JTree tree, Object value, + boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { + + Component c= super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); + TreeModel model= tree.getModel(); + if (model instanceof TestTreeModel) { + TestTreeModel testModel= (TestTreeModel)model; + Test t= (Test)value; + String s= ""; + if (testModel.isFailure(t)) { + if (fFailureIcon != null) + setIcon(fFailureIcon); + s= " - Failed"; + } + else if (testModel.isError(t)) { + if (fErrorIcon != null) + setIcon(fErrorIcon); + s= " - Error"; + } + else if (testModel.wasRun(t)) { + if (fOkIcon != null) + setIcon(fOkIcon); + s= " - Passed"; + } + if (c instanceof JComponent) + ((JComponent)c).setToolTipText(getText()+s); + } + setText(stripParenthesis(value)); + return c; + } + } + + public TestSuitePanel() { + super(new BorderLayout()); + setPreferredSize(new Dimension(300, 100)); + fTree= new JTree(); + fTree.setModel(null); + fTree.setRowHeight(20); + ToolTipManager.sharedInstance().registerComponent(fTree); + fTree.putClientProperty("JTree.lineStyle", "Angled"); + fScrollTree= new JScrollPane(fTree); + add(fScrollTree, BorderLayout.CENTER); + } + + public void addError(final Test test, final Throwable t) { + fModel.addError(test); + fireTestChanged(test, true); + } + + public void addFailure(final Test test, final AssertionFailedError t) { + fModel.addFailure(test); + fireTestChanged(test, true); + } + + /** + * A test ended. + */ + public void endTest(Test test) { + fModel.addRunTest(test); + fireTestChanged(test, false); + } + + /** + * A test started. + */ + public void startTest(Test test) { + } + + /** + * Returns the selected test or null if multiple or none is selected + */ + public Test getSelectedTest() { + TreePath[] paths= fTree.getSelectionPaths(); + if (paths != null && paths.length == 1) + return (Test)paths[0].getLastPathComponent(); + return null; + } + + /** + * Returns the Tree + */ + public JTree getTree() { + return fTree; + } + + /** + * Shows the test hierarchy starting at the given test + */ + public void showTestTree(Test root) { + fModel= new TestTreeModel(root); + fTree.setModel(fModel); + fTree.setCellRenderer(new TestTreeCellRenderer()); + } + + private void fireTestChanged(final Test test, final boolean expand) { + SwingUtilities.invokeLater( + new Runnable() { + public void run() { + Vector vpath= new Vector(); + int index= fModel.findTest(test, (Test)fModel.getRoot(), vpath); + if (index >= 0) { + Object[] path= new Object[vpath.size()]; + vpath.copyInto(path); + TreePath treePath= new TreePath(path); + fModel.fireNodeChanged(treePath, index); + if (expand) { + Object[] fullPath= new Object[vpath.size()+1]; + vpath.copyInto(fullPath); + fullPath[vpath.size()]= fModel.getChild(treePath.getLastPathComponent(), index);; + TreePath fullTreePath= new TreePath(fullPath); + fTree.scrollPathToVisible(fullTreePath); + } + } + } + } + ); + } +}
\ No newline at end of file diff --git a/src/junit/swingui/TestTreeModel.java b/src/junit/swingui/TestTreeModel.java new file mode 100644 index 0000000..9f3b0d3 --- /dev/null +++ b/src/junit/swingui/TestTreeModel.java @@ -0,0 +1,190 @@ +package junit.swingui; + +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; + +import junit.extensions.TestDecorator; +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * A tree model for a Test. + */ +class TestTreeModel implements TreeModel { + private Test fRoot; + private Vector fModelListeners= new Vector(); + private Hashtable fFailures= new Hashtable(); + private Hashtable fErrors= new Hashtable(); + private Hashtable fRunTests= new Hashtable(); + + /** + * Constructs a tree model with the given test as its root. + */ + public TestTreeModel(Test root) { + super(); + fRoot= root; + } + + /** + * adds a TreeModelListener + */ + public void addTreeModelListener(TreeModelListener l) { + if (!fModelListeners.contains(l)) + fModelListeners.addElement(l); + } + /** + * Removes a TestModelListener + */ + public void removeTreeModelListener(TreeModelListener l) { + fModelListeners.removeElement(l); + } + /** + * Finds the path to a test. Returns the index of the test in its + * parent test suite. + */ + public int findTest(Test target, Test node, Vector path) { + if (target.equals(node)) + return 0; + + TestSuite suite= isTestSuite(node); + for (int i= 0; i < getChildCount(node); i++) { + Test t= suite.testAt(i); + int index= findTest(target, t, path); + if (index >= 0) { + path.insertElementAt(node, 0); + if (path.size() == 1) + return i; + return index; + } + } + return -1; + } + /** + * Fires a node changed event + */ + public void fireNodeChanged(TreePath path, int index) { + int[] indices= {index}; + Object[] changedChildren= {getChild(path.getLastPathComponent(), index)}; + TreeModelEvent event= new TreeModelEvent(this, path, indices, changedChildren); + + Enumeration e= fModelListeners.elements(); + while (e.hasMoreElements()) { + TreeModelListener l= (TreeModelListener) e.nextElement(); + l.treeNodesChanged(event); + } + } + /** + * Gets the test at the given index + */ + public Object getChild(Object parent, int index) { + TestSuite suite= isTestSuite(parent); + if (suite != null) + return suite.testAt(index); + return null; + } + /** + * Gets the number of tests. + */ + public int getChildCount(Object parent) { + TestSuite suite= isTestSuite(parent); + if (suite != null) + return suite.testCount(); + return 0; + } + /** + * Gets the index of a test in a test suite + */ + public int getIndexOfChild(Object parent, Object child) { + TestSuite suite= isTestSuite(parent); + if (suite != null) { + int i= 0; + for (Enumeration e= suite.tests(); e.hasMoreElements(); i++) { + if (child.equals(e.nextElement())) + return i; + } + } + return -1; + } + /** + * Returns the root of the tree + */ + public Object getRoot() { + return fRoot; + } + /** + * Tests if the test is a leaf. + */ + public boolean isLeaf(Object node) { + return isTestSuite(node) == null; + } + /** + * Tests if the node is a TestSuite. + */ + TestSuite isTestSuite(Object node) { + if (node instanceof TestSuite) + return (TestSuite)node; + if (node instanceof TestDecorator) { + Test baseTest= ((TestDecorator)node).getTest(); + return isTestSuite(baseTest); + } + return null; + } + + /** + * Called when the value of the model object was changed in the view + */ + public void valueForPathChanged(TreePath path, Object newValue) { + // we don't support direct editing of the model + System.out.println("TreeModel.valueForPathChanged: not implemented"); + } + /** + * Remembers a test failure + */ + void addFailure(Test t) { + fFailures.put(t, t); + } + /** + * Remembers a test error + */ + void addError(Test t) { + fErrors.put(t, t); + } + /** + * Remembers that a test was run + */ + void addRunTest(Test t) { + fRunTests.put(t, t); + } + /** + * Returns whether a test was run + */ + boolean wasRun(Test t) { + return fRunTests.get(t) != null; + } + /** + * Tests whether a test was an error + */ + boolean isError(Test t) { + return (fErrors != null) && fErrors.get(t) != null; + } + /** + * Tests whether a test was a failure + */ + boolean isFailure(Test t) { + return (fFailures != null) && fFailures.get(t) != null; + } + /** + * Resets the test results + */ + void resetResults() { + fFailures= new Hashtable(); + fRunTests= new Hashtable(); + fErrors= new Hashtable(); + } +}
\ No newline at end of file diff --git a/src/junit/swingui/icons/error.gif b/src/junit/swingui/icons/error.gif Binary files differnew file mode 100644 index 0000000..fe13a6a --- /dev/null +++ b/src/junit/swingui/icons/error.gif diff --git a/src/junit/swingui/icons/failure.gif b/src/junit/swingui/icons/failure.gif Binary files differnew file mode 100644 index 0000000..156ecd6 --- /dev/null +++ b/src/junit/swingui/icons/failure.gif diff --git a/src/junit/swingui/icons/hierarchy.gif b/src/junit/swingui/icons/hierarchy.gif Binary files differnew file mode 100644 index 0000000..9f05ed2 --- /dev/null +++ b/src/junit/swingui/icons/hierarchy.gif diff --git a/src/junit/swingui/icons/ok.gif b/src/junit/swingui/icons/ok.gif Binary files differnew file mode 100644 index 0000000..034825b --- /dev/null +++ b/src/junit/swingui/icons/ok.gif diff --git a/src/junit/textui/ResultPrinter.java b/src/junit/textui/ResultPrinter.java new file mode 100644 index 0000000..1ebb7a1 --- /dev/null +++ b/src/junit/textui/ResultPrinter.java @@ -0,0 +1,139 @@ + +package junit.textui; + +import java.io.PrintStream; +import java.text.NumberFormat; +import java.util.Enumeration; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestFailure; +import junit.framework.TestListener; +import junit.framework.TestResult; +import junit.runner.BaseTestRunner; + +public class ResultPrinter implements TestListener { + PrintStream fWriter; + int fColumn= 0; + + public ResultPrinter(PrintStream writer) { + fWriter= writer; + } + + /* API for use by textui.TestRunner + */ + + synchronized void print(TestResult result, long runTime) { + printHeader(runTime); + printErrors(result); + printFailures(result); + printFooter(result); + } + + void printWaitPrompt() { + getWriter().println(); + getWriter().println("<RETURN> to continue"); + } + + /* Internal methods + */ + + protected void printHeader(long runTime) { + getWriter().println(); + getWriter().println("Time: "+elapsedTimeAsString(runTime)); + } + + protected void printErrors(TestResult result) { + printDefects(result.errors(), result.errorCount(), "error"); + } + + protected void printFailures(TestResult result) { + printDefects(result.failures(), result.failureCount(), "failure"); + } + + protected void printDefects(Enumeration booBoos, int count, String type) { + if (count == 0) return; + if (count == 1) + getWriter().println("There was " + count + " " + type + ":"); + else + getWriter().println("There were " + count + " " + type + "s:"); + for (int i= 1; booBoos.hasMoreElements(); i++) { + printDefect((TestFailure) booBoos.nextElement(), i); + } + } + + public void printDefect(TestFailure booBoo, int count) { // only public for testing purposes + printDefectHeader(booBoo, count); + printDefectTrace(booBoo); + } + + protected void printDefectHeader(TestFailure booBoo, int count) { + // I feel like making this a println, then adding a line giving the throwable a chance to print something + // before we get to the stack trace. + getWriter().print(count + ") " + booBoo.failedTest()); + } + + protected void printDefectTrace(TestFailure booBoo) { + getWriter().print(BaseTestRunner.getFilteredTrace(booBoo.trace())); + } + + protected void printFooter(TestResult result) { + if (result.wasSuccessful()) { + getWriter().println(); + getWriter().print("OK"); + getWriter().println (" (" + result.runCount() + " test" + (result.runCount() == 1 ? "": "s") + ")"); + + } else { + getWriter().println(); + getWriter().println("FAILURES!!!"); + getWriter().println("Tests run: "+result.runCount()+ + ", Failures: "+result.failureCount()+ + ", Errors: "+result.errorCount()); + } + getWriter().println(); + } + + + /** + * Returns the formatted string of the elapsed time. + * Duplicated from BaseTestRunner. Fix it. + */ + protected String elapsedTimeAsString(long runTime) { + return NumberFormat.getInstance().format((double)runTime/1000); + } + + public PrintStream getWriter() { + return fWriter; + } + /** + * @see junit.framework.TestListener#addError(Test, Throwable) + */ + public void addError(Test test, Throwable t) { + getWriter().print("E"); + } + + /** + * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError) + */ + public void addFailure(Test test, AssertionFailedError t) { + getWriter().print("F"); + } + + /** + * @see junit.framework.TestListener#endTest(Test) + */ + public void endTest(Test test) { + } + + /** + * @see junit.framework.TestListener#startTest(Test) + */ + public void startTest(Test test) { + getWriter().print("."); + if (fColumn++ >= 40) { + getWriter().println(); + fColumn= 0; + } + } + +} diff --git a/src/junit/textui/TestRunner.java b/src/junit/textui/TestRunner.java new file mode 100644 index 0000000..01b9d6d --- /dev/null +++ b/src/junit/textui/TestRunner.java @@ -0,0 +1,207 @@ +package junit.textui; + + +import java.io.PrintStream; + +import junit.framework.Test; +import junit.framework.TestResult; +import junit.framework.TestSuite; +import junit.runner.BaseTestRunner; +import junit.runner.StandardTestSuiteLoader; +import junit.runner.TestSuiteLoader; +import junit.runner.Version; + +/** + * A command line based tool to run tests. + * <pre> + * java junit.textui.TestRunner [-wait] TestCaseClass + * </pre> + * TestRunner expects the name of a TestCase class as argument. + * If this class defines a static <code>suite</code> method it + * will be invoked and the returned test is run. Otherwise all + * the methods starting with "test" having no arguments are run. + * <p> + * When the wait command line argument is given TestRunner + * waits until the users types RETURN. + * <p> + * TestRunner prints a trace as the tests are executed followed by a + * summary at the end. + */ +public class TestRunner extends BaseTestRunner { + private ResultPrinter fPrinter; + + public static final int SUCCESS_EXIT= 0; + public static final int FAILURE_EXIT= 1; + public static final int EXCEPTION_EXIT= 2; + + /** + * Constructs a TestRunner. + */ + public TestRunner() { + this(System.out); + } + + /** + * Constructs a TestRunner using the given stream for all the output + */ + public TestRunner(PrintStream writer) { + this(new ResultPrinter(writer)); + } + + /** + * Constructs a TestRunner using the given ResultPrinter all the output + */ + public TestRunner(ResultPrinter printer) { + fPrinter= printer; + } + + /** + * Runs a suite extracted from a TestCase subclass. + */ + static public void run(Class testClass) { + run(new TestSuite(testClass)); + } + + /** + * Runs a single test and collects its results. + * This method can be used to start a test run + * from your program. + * <pre> + * public static void main (String[] args) { + * test.textui.TestRunner.run(suite()); + * } + * </pre> + */ + static public TestResult run(Test test) { + TestRunner runner= new TestRunner(); + return runner.doRun(test); + } + + /** + * Runs a single test and waits until the user + * types RETURN. + */ + static public void runAndWait(Test suite) { + TestRunner aTestRunner= new TestRunner(); + aTestRunner.doRun(suite, true); + } + + /** + * Always use the StandardTestSuiteLoader. Overridden from + * BaseTestRunner. + */ + public TestSuiteLoader getLoader() { + return new StandardTestSuiteLoader(); + } + + public void testFailed(int status, Test test, Throwable t) { + } + + public void testStarted(String testName) { + } + + public void testEnded(String testName) { + } + + /** + * Creates the TestResult to be used for the test run. + */ + protected TestResult createTestResult() { + return new TestResult(); + } + + public TestResult doRun(Test test) { + return doRun(test, false); + } + + public TestResult doRun(Test suite, boolean wait) { + TestResult result= createTestResult(); + result.addListener(fPrinter); + long startTime= System.currentTimeMillis(); + suite.run(result); + long endTime= System.currentTimeMillis(); + long runTime= endTime-startTime; + fPrinter.print(result, runTime); + + pause(wait); + return result; + } + + protected void pause(boolean wait) { + if (!wait) return; + fPrinter.printWaitPrompt(); + try { + System.in.read(); + } + catch(Exception e) { + } + } + + public static void main(String args[]) { + TestRunner aTestRunner= new TestRunner(); + try { + TestResult r= aTestRunner.start(args); + if (!r.wasSuccessful()) + System.exit(FAILURE_EXIT); + System.exit(SUCCESS_EXIT); + } catch(Exception e) { + System.err.println(e.getMessage()); + System.exit(EXCEPTION_EXIT); + } + } + + /** + * Starts a test run. Analyzes the command line arguments and runs the given + * test suite. + */ + public TestResult start(String args[]) throws Exception { + String testCase= ""; + String method= ""; + boolean wait= false; + + for (int i= 0; i < args.length; i++) { + if (args[i].equals("-wait")) + wait= true; + else if (args[i].equals("-c")) + testCase= extractClassName(args[++i]); + else if (args[i].equals("-m")) { + String arg= args[++i]; + int lastIndex= arg.lastIndexOf('.'); + testCase= arg.substring(0, lastIndex); + method= arg.substring(lastIndex + 1); + } else if (args[i].equals("-v")) + System.err.println("JUnit " + Version.id() + " by Kent Beck and Erich Gamma"); + else + testCase= args[i]; + } + + if (testCase.equals("")) + throw new Exception("Usage: TestRunner [-wait] testCaseName, where name is the name of the TestCase class"); + + try { + if (!method.equals("")) + return runSingleMethod(testCase, method, wait); + Test suite= getTest(testCase); + return doRun(suite, wait); + } catch (Exception e) { + throw new Exception("Could not create and run test suite: " + e); + } + } + + protected TestResult runSingleMethod(String testCase, String method, boolean wait) throws Exception { + Class testClass= loadSuiteClass(testCase); + Test test= TestSuite.createTest(testClass, method); + return doRun(test, wait); + } + + protected void runFailed(String message) { + System.err.println(message); + System.exit(FAILURE_EXIT); + } + + public void setPrinter(ResultPrinter printer) { + fPrinter= printer; + } + + +}
\ No newline at end of file |