001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.drjava.ui;
038:
039: import edu.rice.cs.drjava.model.DJDocument;
040: import edu.rice.cs.drjava.model.OpenDefinitionsDocument;
041: import edu.rice.cs.drjava.model.SingleDisplayModel;
042: import edu.rice.cs.drjava.model.compiler.CompilerError;
043: import edu.rice.cs.drjava.model.junit.JUnitError;
044: import edu.rice.cs.drjava.model.junit.JUnitErrorModel;
045: import edu.rice.cs.util.UnexpectedException;
046: import edu.rice.cs.util.swing.BorderlessScrollPane;
047: import edu.rice.cs.util.text.SwingDocument;
048: import edu.rice.cs.util.swing.RightClickMouseAdapter;
049:
050: import javax.swing.*;
051: import javax.swing.border.EmptyBorder;
052: import javax.swing.text.*;
053: import java.awt.*;
054: import java.awt.event.ActionEvent;
055: import java.awt.event.ActionListener;
056: import java.awt.event.MouseEvent;
057: import java.io.File;
058: import java.util.List;
059: import java.util.ArrayList;
060: import java.util.HashMap;
061:
062: /** The panel that displays all the testing errors.
063: * @version $Id: JUnitPanel.java 4255 2007-08-28 19:17:37Z mgricken $
064: */
065: public class JUnitPanel extends ErrorPanel {
066: private static final String START_JUNIT_MSG = "Testing in progress. Please wait ...\n";
067: private static final String JUNIT_FINISHED_MSG = "All tests completed successfully.\n";
068: private static final String NO_TESTS_MSG = "";
069:
070: private static final SimpleAttributeSet OUT_OF_SYNC_ATTRIBUTES = _getOutOfSyncAttributes();
071:
072: private static final SimpleAttributeSet _getOutOfSyncAttributes() {
073: SimpleAttributeSet s = new SimpleAttributeSet();
074: s.addAttribute(StyleConstants.Foreground, Color.red.darker());
075: s.addAttribute(StyleConstants.Bold, Boolean.TRUE);
076: return s;
077: }
078:
079: private static final SimpleAttributeSet TEST_PASS_ATTRIBUTES = _getTestPassAttributes();
080:
081: private static final SimpleAttributeSet _getTestPassAttributes() {
082: SimpleAttributeSet s = new SimpleAttributeSet();
083: s.addAttribute(StyleConstants.Foreground, Color.green.darker());
084: return s;
085: }
086:
087: private static final SimpleAttributeSet TEST_FAIL_ATTRIBUTES = _getTestFailAttributes();
088:
089: private static final SimpleAttributeSet _getTestFailAttributes() {
090: SimpleAttributeSet s = new SimpleAttributeSet();
091: s.addAttribute(StyleConstants.Foreground, Color.red);
092: return s;
093: }
094:
095: private static final String TEST_OUT_OF_SYNC = "The documents being tested have been modified and should be recompiled!\n";
096:
097: protected JUnitErrorListPane _errorListPane;
098: private final MainFrame _mainFrame; // only used in assert statements
099: private int _testCount;
100: private boolean _testsSuccessful;
101:
102: private JUnitProgressBar _progressBar;
103: private List<OpenDefinitionsDocument> _odds = new ArrayList<OpenDefinitionsDocument>();
104:
105: private Action _showStackTraceAction = new AbstractAction(
106: "Show Stack Trace") {
107: public void actionPerformed(ActionEvent ae) {
108: if (_error != null) {
109: _displayStackTrace(_error);
110: }
111: }
112: };
113:
114: private JButton _showStackTraceButton;
115:
116: /** The currently selected error. */
117: private JUnitError _error = null;
118: private Window _stackFrame = null;
119: private JTextArea _stackTextArea;
120: private final JLabel _errorLabel = new JLabel();
121: private final JLabel _testLabel = new JLabel();
122: private final JLabel _fileLabel = new JLabel();
123:
124: /** Constructor.
125: * @param model SingleDisplayModel in which we are running
126: * @param frame MainFrame in which we are displayed
127: */
128: public JUnitPanel(SingleDisplayModel model, MainFrame frame) {
129: super (model, frame, "Test Output", "Test Progress");
130: _mainFrame = frame;
131: _testCount = 0;
132: _testsSuccessful = true;
133:
134: _progressBar = new JUnitProgressBar();
135: _progressBar
136: .setUI(new javax.swing.plaf.basic.BasicProgressBarUI());
137: _showStackTraceButton = new JButton(_showStackTraceAction);
138: customPanel.add(_progressBar, BorderLayout.NORTH);
139: customPanel.add(_showStackTraceButton, BorderLayout.SOUTH);
140:
141: _errorListPane = new JUnitErrorListPane();
142: setErrorListPane(_errorListPane);
143: }
144:
145: /** Returns the JUnitErrorListPane that this panel manages. */
146: public JUnitErrorListPane getErrorListPane() {
147: return _errorListPane;
148: }
149:
150: protected JUnitErrorModel getErrorModel() {
151: return getModel().getJUnitModel().getJUnitErrorModel();
152: }
153:
154: /** Updates all document styles with the attributes contained in newSet.
155: * @param newSet Style containing new attributes to use.
156: */
157: protected void _updateStyles(AttributeSet newSet) {
158: super ._updateStyles(newSet);
159: OUT_OF_SYNC_ATTRIBUTES.addAttributes(newSet);
160: StyleConstants.setBold(OUT_OF_SYNC_ATTRIBUTES, true); // should always be bold
161: TEST_PASS_ATTRIBUTES.addAttributes(newSet);
162: TEST_FAIL_ATTRIBUTES.addAttributes(newSet);
163: }
164:
165: /** called when work begins */
166: public void setJUnitInProgress() {
167: _errorListPane.setJUnitInProgress();
168: }
169:
170: /** Clean up when the tab is closed. */
171: protected void _close() {
172: super ._close();
173: getModel().getJUnitModel().resetJUnitErrors();
174: reset();
175: }
176:
177: /** Reset the errors to the current error information. */
178: public void reset() {
179: JUnitErrorModel juem = getModel().getJUnitModel()
180: .getJUnitErrorModel();
181: boolean testsHaveRun = false;
182: if (juem != null) {
183: _numErrors = juem.getNumErrors();
184: testsHaveRun = juem.haveTestsRun();
185: } else
186: _numErrors = 0;
187: _errorListPane.updateListPane(testsHaveRun); //changed!!
188: }
189:
190: /** Resets the progress bar to start counting the given number of tests. */
191: public void progressReset(int numTests) {
192: _progressBar.reset();
193: _progressBar.start(numTests);
194: _testsSuccessful = true;
195: _testCount = 0;
196: }
197:
198: /** Steps the progress bar forward by one test.
199: * @param successful Whether the last test was successful or not.
200: */
201: public void progressStep(boolean successful) {
202: _testCount++;
203: _testsSuccessful &= successful;
204: _progressBar.step(_testCount, _testsSuccessful);
205: }
206:
207: public void testStarted(String className, String testName) {
208: }
209:
210: private void _displayStackTrace(JUnitError e) {
211: _errorLabel.setText((e.isWarning() ? "Error: " : "Failure: ")
212: + e.message());
213: _fileLabel.setText("File: "
214: + (new File(e.fileName())).getName());
215: if (!e.testName().equals("")) {
216: _testLabel.setText("Test: " + e.testName());
217: } else {
218: _testLabel.setText("");
219: }
220: _stackTextArea.setText(e.stackTrace());
221: _stackTextArea.setCaretPosition(0);
222: _frame.setPopupLoc(_stackFrame);
223: _stackFrame.setVisible(true);
224: }
225:
226: /** A pane to show JUnit errors. It acts like a listbox (clicking selects an item) but items can each wrap, etc. */
227: public class JUnitErrorListPane extends ErrorPanel.ErrorListPane {
228: private JPopupMenu _popMenu;
229: private String _runningTestName;
230: private boolean _warnedOutOfSync;
231: private static final String JUNIT_WARNING = "junit.framework.TestSuite$1.warning";
232:
233: /** Maps any test names in the currently running suite to the position that they appear in the list pane. */
234: private final HashMap<String, Position> _runningTestNamePositions;
235:
236: /** Constructs the JUnitErrorListPane. */
237: public JUnitErrorListPane() {
238: removeMouseListener(defaultMouseListener);
239: _popMenu = new JPopupMenu();
240: _popMenu.add(_showStackTraceAction);
241: _error = null;
242: _setupStackTraceFrame();
243: addMouseListener(new PopupAdapter());
244: _runningTestName = null;
245: _runningTestNamePositions = new HashMap<String, Position>();
246: _showStackTraceButton.setEnabled(false);
247: }
248:
249: private String _getTestFromName(String name) {
250: int paren = name.indexOf('(');
251:
252: if ((paren > -1) && (paren < name.length()))
253: return name.substring(0, paren);
254:
255: else
256: throw new IllegalArgumentException(
257: "Name does not contain any parens: " + name);
258: }
259:
260: private String _getClassFromName(String name) {
261: int paren = name.indexOf('(');
262:
263: if ((paren > -1) && (paren < name.length()))
264: return name.substring(paren + 1, name.length() - 1);
265: else
266: throw new IllegalArgumentException(
267: "Name does not contain any parens: " + name);
268: }
269:
270: /** Provides the ability to display the name of the test being run. */
271: public void testStarted(String name) {
272: String testName = _getTestFromName(name);
273: String className = _getClassFromName(name);
274: String fullName = className + "." + testName;
275: if (fullName.equals(JUNIT_WARNING))
276: return;
277: SwingDocument doc = getSwingDocument();
278: doc.acquireWriteLock();
279: try {
280: int len = doc.getLength();
281: // Insert the classname if it has changed
282: if (!className.equals(_runningTestName)) {
283: _runningTestName = className;
284: doc.insertString(len, " " + className + "\n",
285: NORMAL_ATTRIBUTES);
286: len = doc.getLength();
287: }
288:
289: // Insert the test name, remembering its position
290: doc.insertString(len, " ", NORMAL_ATTRIBUTES);
291: len = doc.getLength();
292: doc.insertString(len, testName + "\n",
293: NORMAL_ATTRIBUTES);
294: Position pos = doc.createPosition(len);
295: _runningTestNamePositions.put(fullName, pos);
296: setCaretPosition(len);
297: } catch (BadLocationException ble) {
298: // Inserting at end, shouldn't happen
299: throw new UnexpectedException(ble);
300: } finally {
301: doc.releaseWriteLock();
302: }
303: }
304:
305: /** Displays the results of a test that has finished. */
306: public void testEnded(String name, boolean wasSuccessful,
307: boolean causedError) {
308: String testName = _getTestFromName(name);
309: String fullName = _getClassFromName(name) + "." + testName;
310: if (fullName.equals(JUNIT_WARNING))
311: return;
312:
313: SwingDocument doc = getSwingDocument();
314: Position namePos = _runningTestNamePositions.get(fullName);
315: AttributeSet set;
316: if (!wasSuccessful || causedError)
317: set = TEST_FAIL_ATTRIBUTES;
318: else
319: set = TEST_PASS_ATTRIBUTES;
320: if (namePos != null) {
321: int index = namePos.getOffset();
322: int length = testName.length();
323: doc.setCharacterAttributes(index, length, set, false);
324: }
325: }
326:
327: /** Puts the error pane into "junit in progress" state. Only runs in event thread. */
328: public void setJUnitInProgress() {
329: assert EventQueue.isDispatchThread();
330: _errorListPositions = new Position[0];
331: progressReset(0);
332: _runningTestNamePositions.clear();
333: _runningTestName = null;
334: _warnedOutOfSync = false;
335:
336: SwingDocument doc = new SwingDocument();
337: // _checkSync(doc);
338:
339: doc.append(START_JUNIT_MSG, BOLD_ATTRIBUTES);
340: setDocument(doc);
341: selectNothing();
342: }
343:
344: /** Used to show that testing was unsuccessful. */
345: protected void _updateWithErrors() throws BadLocationException {
346: //DefaultStyledDocument doc = new DefaultStyledDocument();
347: SwingDocument doc = getSwingDocument();
348: // _checkSync(doc);
349: _updateWithErrors("test", "failed", doc);
350: }
351:
352: /** Gets the message indicating the number of errors and warnings.*/
353: protected String _getNumErrorsMessage(String failureName,
354: String failureMeaning) {
355: StringBuilder numErrMsg;
356:
357: /** Used for display purposes only */
358: int numCompErrs = getErrorModel().getNumCompErrors();
359: int numWarnings = getErrorModel().getNumWarnings();
360:
361: if (!getErrorModel().hasOnlyWarnings()) {
362: numErrMsg = new StringBuilder(numCompErrs + " "
363: + failureName); //failureName = error or test (for compilation and JUnit testing respectively)
364: if (numCompErrs > 1)
365: numErrMsg.append("s");
366: numErrMsg.append(" " + failureMeaning);
367: if (numWarnings > 0)
368: numErrMsg
369: .append(" and " + numWarnings + " warning");
370: } else
371: numErrMsg = new StringBuilder(numWarnings + " warning");
372:
373: if (numWarnings > 1)
374: numErrMsg.append("s");
375: if (numWarnings > 0)
376: numErrMsg.append(" found");
377:
378: numErrMsg.append(":\n");
379:
380: return numErrMsg.toString();
381: }
382:
383: protected void _updateWithErrors(String failureName,
384: String failureMeaning, SwingDocument doc)
385: throws BadLocationException {
386: // Print how many errors
387: _replaceInProgressText(_getNumErrorsMessage(failureName,
388: failureMeaning));
389:
390: _insertErrors(doc);
391:
392: // Select the first error
393: switchToError(0);
394: }
395:
396: /** Replaces the "Testing in progress..." text with the given message. Only runs in event thread.
397: * @param msg the text to insert
398: */
399: public void _replaceInProgressText(String msg)
400: throws BadLocationException {
401: assert !_mainFrame.isVisible()
402: || EventQueue.isDispatchThread();
403: int start = 0;
404: if (_warnedOutOfSync) {
405: start = TEST_OUT_OF_SYNC.length();
406: }
407: int len = START_JUNIT_MSG.length();
408: SwingDocument doc = getSwingDocument();
409: if (doc.getLength() >= len + start) {
410: doc.remove(start, len);
411: doc.insertString(start, msg, BOLD_ATTRIBUTES);
412: }
413: }
414:
415: /** Returns the string to identify a warning. In JUnit, warnings (the odd case) indicate errors/exceptions.
416: */
417: protected String _getWarningText() {
418: return "Error: ";
419: }
420:
421: /** Returns the string to identify an error. In JUnit, errors (the normal case) indicate TestFailures. */
422: protected String _getErrorText() {
423: return "Failure: ";
424: }
425:
426: /** Updates the list pane with no errors. */
427: protected void _updateNoErrors(boolean haveTestsRun)
428: throws BadLocationException {
429: //DefaultStyledDocument doc = new DefaultStyledDocument();
430: // _checkSync(getDocument());
431: _replaceInProgressText(haveTestsRun ? JUNIT_FINISHED_MSG
432: : NO_TESTS_MSG);
433:
434: selectNothing();
435: setCaretPosition(0);
436: }
437:
438: // /** Checks the document being tested to see if it's in sync. If not,
439: // * displays a message in the document in the test output pane.
440: // */
441: // private void _checkSync(Document doc) {
442: // if (_warnedOutOfSync) return;
443: // List<OpenDefinitionsDocument> odds = _odds; // grab current _odds
444: // for (OpenDefinitionsDocument odoc: odds) {
445: // if (! odoc.checkIfClassFileInSync()) {
446: // try {
447: // doc.insertString(0, TEST_OUT_OF_SYNC, OUT_OF_SYNC_ATTRIBUTES);
448: // _warnedOutOfSync = true;
449: // return;
450: // }
451: // catch (BadLocationException ble) {
452: // throw new UnexpectedException(ble);
453: // }
454: // }
455: // }
456: // }
457:
458: private void _setupStackTraceFrame() {
459: //DrJava.consoleOut().println("Stack Trace for Error: \n"+ e.stackTrace());
460: JDialog _dialog = new JDialog(_frame,
461: "JUnit Error Stack Trace", false);
462: _stackFrame = _dialog;
463: _stackTextArea = new JTextArea();
464: _stackTextArea.setEditable(false);
465: _stackTextArea.setLineWrap(false);
466: JScrollPane scroll = new BorderlessScrollPane(
467: _stackTextArea,
468: JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
469: JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
470:
471: ActionListener closeListener = new ActionListener() {
472: public void actionPerformed(ActionEvent e) {
473: _stackFrame.setVisible(false);
474: }
475: };
476: JButton closeButton = new JButton("Close");
477: closeButton.addActionListener(closeListener);
478: JPanel closePanel = new JPanel(new BorderLayout());
479: closePanel.setBorder(new EmptyBorder(5, 5, 0, 0));
480: closePanel.add(closeButton, BorderLayout.EAST);
481: JPanel cp = new JPanel(new BorderLayout());
482: _dialog.setContentPane(cp);
483: cp.setBorder(new EmptyBorder(5, 5, 5, 5));
484: cp.add(scroll, BorderLayout.CENTER);
485: cp.add(closePanel, BorderLayout.SOUTH);
486: JPanel topPanel = new JPanel(new GridLayout(0, 1, 0, 5));
487: topPanel.setBorder(new EmptyBorder(0, 0, 5, 0));
488: topPanel.add(_fileLabel);
489: topPanel.add(_testLabel);
490: topPanel.add(_errorLabel);
491: cp.add(topPanel, BorderLayout.NORTH);
492: _dialog.setSize(600, 500);
493: // initial location is relative to parent (MainFrame)
494: }
495:
496: /** Overrides selectItem in ErrorListPane to update the current _error selected
497: * and enabling the _showStackTraceButton.
498: */
499: public void selectItem(CompilerError error) {
500: super .selectItem(error);
501: _error = (JUnitError) error;
502: _showStackTraceButton.setEnabled(true);
503: }
504:
505: /**
506: * Overrides _removeListHighlight in ErrorListPane to disable the _showStackTraceButton.
507: */
508: protected void _removeListHighlight() {
509: super ._removeListHighlight();
510: _showStackTraceButton.setEnabled(false);
511: }
512:
513: /**
514: * Updates the UI to a new look and feel.
515: * Need to update the contained popup menu as well.
516: *
517: * Currently, we don't support changing the look and feel
518: * on the fly, so this is disabled.
519: *
520: public void updateUI() {
521: super.updateUI();
522: if (_popMenu != null) {
523: SwingUtilities.updateComponentTreeUI(_popMenu);
524: }
525: }*/
526:
527: private class PopupAdapter extends RightClickMouseAdapter {
528: /**
529: * Show popup if the click is on an error.
530: * @param e the MouseEvent correponding to this click
531: */
532: public void mousePressed(MouseEvent e) {
533: if (_selectError(e)) {
534: super .mousePressed(e);
535: }
536: }
537:
538: /**
539: * Show popup if the click is on an error.
540: * @param e the MouseEvent correponding to this click
541: */
542: public void mouseReleased(MouseEvent e) {
543: if (_selectError(e)) {
544: super .mouseReleased(e);
545: }
546: }
547:
548: /**
549: * Select the error at the given mouse event.
550: * @param e the MouseEvent correponding to this click
551: * @return true iff the mouse click is over an error
552: */
553: private boolean _selectError(MouseEvent e) {
554: //TODO: get rid of cast in the next line, if possible
555: _error = (JUnitError) _errorAtPoint(e.getPoint());
556:
557: if (_isEmptySelection() && _error != null) {
558: _errorListPane.switchToError(_error);
559: return true;
560: } else {
561: selectNothing();
562: return false;
563: }
564: }
565:
566: /**
567: * Shows the popup menu for this mouse adapter.
568: * @param e the MouseEvent correponding to this click
569: */
570: protected void _popupAction(MouseEvent e) {
571: _popMenu.show(e.getComponent(), e.getX(), e.getY());
572: }
573:
574: // public JUnitError getError() {
575: // return _error;
576: // }
577: }
578: }
579:
580: /**
581: * A progress bar showing the status of JUnit tests.
582: * Green until a test fails, then red.
583: * Adapted from JUnit code.
584: */
585: static class JUnitProgressBar extends JProgressBar {
586: private boolean _hasError = false;
587:
588: public JUnitProgressBar() {
589: super ();
590: setForeground(getStatusColor());
591: }
592:
593: private Color getStatusColor() {
594: if (_hasError) {
595: return Color.red;
596: } else {
597: return Color.green;
598: }
599: }
600:
601: public void reset() {
602: _hasError = false;
603: setForeground(getStatusColor());
604: setValue(0);
605: }
606:
607: public void start(int total) {
608: setMaximum(total);
609: reset();
610: }
611:
612: public void step(int value, boolean successful) {
613: setValue(value);
614: if (!_hasError && !successful) {
615: _hasError = true;
616: setForeground(getStatusColor());
617: }
618: }
619: }
620: }
|