1 package eu.simuline.testhelpers;
2
3 import eu.simuline.util.images.GifResource;
4
5 import eu.simuline.junit.Logo;
6 import eu.simuline.junit.Hierarchy;
7 import eu.simuline.junit.SmallLogo;
8
9 import eu.simuline.util.JavaPath;
10
11 import org.junit.runner.Description;
12
13 import java.awt.Container;
14 import java.awt.Component;
15 import java.awt.Color;
16 import java.awt.Rectangle;
17
18 import java.util.List;
19
20 import java.io.IOException;
21 import java.io.File;
22
23 import javax.swing.JFrame;
24 import javax.swing.JLabel;
25 import javax.swing.JMenuBar;
26 import javax.swing.JFileChooser;
27 import javax.swing.JMenuItem;
28 import javax.swing.DefaultBoundedRangeModel;
29 import javax.swing.Icon;
30 import javax.swing.JProgressBar;
31 import javax.swing.JSplitPane;
32 import javax.swing.JComponent;
33 import javax.swing.JTabbedPane;
34 import javax.swing.JTree;
35 import javax.swing.JSeparator;
36 import javax.swing.JScrollPane;
37 import javax.swing.JButton;
38 import javax.swing.BoxLayout;
39 import javax.swing.Box;
40 import javax.swing.ImageIcon;
41 import javax.swing.DefaultListModel;
42 import javax.swing.JList;
43 import javax.swing.ListSelectionModel;
44 import javax.swing.DefaultListSelectionModel;
45 import javax.swing.ListCellRenderer;
46 import javax.swing.SwingConstants;
47 import javax.swing.SwingUtilities;
48 import javax.swing.UIManager;
49
50 import javax.swing.filechooser.FileFilter;
51
52 import javax.swing.tree.TreeModel;
53 import javax.swing.tree.DefaultTreeModel;
54 import javax.swing.tree.TreeNode;
55 import javax.swing.tree.MutableTreeNode;
56 import javax.swing.tree.DefaultMutableTreeNode;
57 import javax.swing.tree.TreePath;
58 import javax.swing.tree.TreeSelectionModel;
59 import javax.swing.tree.DefaultTreeSelectionModel;
60 import javax.swing.tree.DefaultTreeCellRenderer;
61
62 import javax.swing.event.TreeSelectionEvent;
63 import javax.swing.event.TreeSelectionListener;
64 import javax.swing.event.ListSelectionEvent;
65 import javax.swing.event.ListSelectionListener;
66 import javax.swing.event.ChangeListener;
67 import javax.swing.event.ChangeEvent;
68
69 import javax.swing.border.Border;
70 import javax.swing.border.EmptyBorder;
71
72
73 import java.io.Serializable;
74
75 import java.util.Map;
76 import java.util.EnumMap;
77 import java.util.Arrays;
78 import java.util.Collection;
79
80 /**
81 * The GUI of a JUnit test-runner.
82 * Comprises
83 * <ul>
84 * <li>
85 * a menubar to select running, stopping or breaking a testcase
86 * or even exiting the application
87 * <li>
88 * A label for the name of the testclass.
89 * <li>
90 * A progress bar represented by the class {@link GUIRunner.TestProgressBar}.
91 * <li>
92 * A component displaying the number of tests to run,
93 * already runned, ignored and failed.
94 * This is an instance of the class {@link GUIRunner.StatisticsTestState}.
95 * <li>
96 * a treeview on the testsuite
97 * represented by the class {@link GUIRunner.HierarchyWrapper}.
98 * This needs support from the classes and
99 * {@link GUIRunner.TreePathIterator},
100 * {@link GUIRunner.TestTreeCellRenderer}.
101 * <li>
102 * a list of the testcases that failed in a sense
103 * given as a {@link GUIRunner.TestCaseLister}.
104 * <li>
105 * Closely tied to the list of testcases failed so far,
106 * is the list-view on the stacktraces
107 * given by a {@link GUIRunner.StackTraceLister}.
108 * <li>
109 * a statusbar.
110 * <li>
111 * Note that the selection on the treeview of all testcases
112 * and on the listview of the testcases already failed
113 * must be synchronized.
114 * This is performed by the class {@link GUIRunner.TabChangeListener}
115 * and the interface {@link GUIRunner.Selector}.
116 * </ul>
117 *
118 * Partially the methods serve to make up the gui;
119 * the others are invoked by the {@link GUIRunListener}s
120 * to report the current state of the testsuitey.
121 *
122 * Created: Sat Jun 3 18:29:52 2006
123 *
124 * @author <a href="mailto:ernst.reissner@simuline.eu">Ernst Reissner</a>
125 * @version 1.0
126 */
127 @SuppressWarnings("PMD.ExcessiveClassLength")
128 class GUIRunner {
129
130 /* -------------------------------------------------------------------- *
131 * constants. *
132 * -------------------------------------------------------------------- */
133
134
135 /**
136 * The (big) JUnit-logo.
137 */
138 private static final ImageIcon LOGO_ICON =
139 GifResource.getIcon(Logo.class);
140
141 /**
142 * The icon representing the hierarchy of tests:
143 * used for the tabbed pane.
144 */
145 private static final ImageIcon HIERARCHY_ICON =
146 GifResource.getIcon(Hierarchy.class);
147
148 /**
149 * The small JUnit-logo on top left of this frame.
150 * **** still this is not displayed properly ****.
151 */
152 private static final ImageIcon SMALL_LOGO_ICON =
153 GifResource.getIcon(SmallLogo.class);
154
155 /**
156 * Represents the horizontal space used for the boundary.
157 */
158 private static final Component HORIZ_BOUNDARY =
159 Box.createHorizontalStrut(10);
160
161 /**
162 * Represents double of the horizontal space used for the boundary.
163 */
164 private static final Component HORIZ_BOUNDARY2 =
165 Box.createHorizontalStrut(20);
166
167 /**
168 * Represents the vertical space used for the boundary.
169 */
170 private static final Component VERTI_BOUNDARY =
171 Box.createVerticalStrut(10);
172
173 /**
174 * The horizontal size of the frame.
175 */
176 private static final int HORIZ_FRAME = 800;
177
178 /**
179 * The vertical size of the frame.
180 */
181 private static final int VERTI_FRAME = 800;
182
183 /**
184 * The name of the system property
185 * the value of which points to the directory
186 * which is opened by the file chooser.
187 * This is the directory containing the test classes.
188 */
189 private static final String CHOOSE_CLASSPATH = "chooseClasspath";
190
191 /**
192 * The name of the system property
193 * the value of which is the source path.
194 * This comprises the product sources (in maven called main sources)
195 * and the test sources.
196 * This is used to edit sources at places throwing an exception.
197 */
198 private static final String SOURCEPATH = "sourcepath";
199
200
201
202 /* -------------------------------------------------------------------- *
203 * inner classes. *
204 * -------------------------------------------------------------------- */
205
206 /**
207 * The progress bar indicating how much of the testcases already passed.
208 * After the first testcase ending irregular,
209 * the bar takes {@link Quality#COLOR_FAIL}.
210 * Else after the first ignored testscase,
211 * the bar takes {@link Quality#COLOR_IGNORED}.
212 * Else, the bar takes {@link Quality#COLOR_OK}.
213 */
214 static class TestProgressBar extends JProgressBar {
215
216 /* ---------------------------------------------------------------- *
217 * constants. *
218 * ---------------------------------------------------------------- */
219
220 private static final long serialVersionUID = -2479143000061671589L;
221
222 /* ---------------------------------------------------------------- *
223 * attributes. *
224 * ---------------------------------------------------------------- */
225
226 /**
227 * The maximal quality found in testcases so far.
228 * This determines the foreground color of this progress bar.
229 * This is <code>null</code> initially
230 * and initialized in {@link #startTestRun(TestCase)}.
231 */
232 private Quality qual;
233
234
235 /* ---------------------------------------------------------------- *
236 * constructors. *
237 * ---------------------------------------------------------------- */
238
239 /**
240 * Creates a new <code>TestProgressBar</code> instance.
241 */
242 TestProgressBar() {
243 super(new DefaultBoundedRangeModel());
244 this.model.setValueIsAdjusting(true);
245 this.qual = null;
246 //.setString("progress");//*** even better; fail or ok
247 //.setStringPainted(true);
248 }
249
250 /* ---------------------------------------------------------------- *
251 * methods. *
252 * ---------------------------------------------------------------- */
253
254 /**
255 * Notifies that the structure of the test class may have been updated.
256 * <p>
257 * Sets maximum, minimum an current value of the progress bar.
258 * Sets {@link #qual} to {@link Quality#Scheduled}
259 * indicating that no failure occurred yet
260 * and the color is the ok-color.
261 *
262 * @param desc
263 * a description of the test structure defined in the test class
264 * which is a hierarchy of suites and singular tests.
265 */
266 void initClassStructure(Description desc) {
267 setMinimum(0);
268 setMaximum(desc.testCount());
269 setValue(0);
270 this.qual = Quality.Scheduled; // color OK
271 //this.model.setExtent(0);/// **** how much is visible:
272 // minimum <= value <= value+extent <= maximum
273 }
274
275 // for TestProgressBar
276 /**
277 * Notifies that a test run given by <code>testCase</code>
278 * is going to be run next.
279 * <p>
280 * Only the tests which are {@link Quality#Scheduled} are really run.
281 * The others just go into the length and color of the progress bar
282 * when starting the run:
283 * The initial length depends on the number of non-scheduled testcases
284 * and the color is the worst-case color of all these testcases.
285 *
286 * @param testCase
287 * the hierarchy of all tests defined by the test class
288 * and in particular of those to be run:
289 * those which are {@link Quality#Scheduled}.
290 */
291 void startTestRun(TestCase testCase) {
292 this.qual = Quality.Scheduled; // color OK
293 setValue(0);
294 detValQualRec(testCase);
295 setForeground(this.qual.getColor());
296 }
297
298 /**
299 * Determines recursively the length of the progress bar
300 * and {@link #qual} based on the non-scheduled test cases.
301 *
302 * @param testCase
303 * the hierarchy of all tests defined by the test class
304 * and in particular of those to be run:
305 * those which are {@link Quality#Scheduled}.
306 */
307 private void detValQualRec(TestCase testCase) {
308 if (testCase.isTest()) {
309 Quality qual = testCase.getQuality();
310 switch (qual) {
311 case Started:
312 assert false;
313 break;
314 case Scheduled:
315 break;
316 default:
317 // all but Scheduled and Started:
318 setValue(getValue() + 1);
319 this.qual = this.qual.max(qual);
320 break;
321 }
322
323 return;
324 }
325 for (TestCase child : testCase.getChildren()) {
326 detValQualRec(child);
327 }
328 }
329
330 /**
331 * Notifies that the singular test <code>testCase</code> is finished.
332 * <p>
333 * Pushes the progress bar one further
334 * and upates the color of the progress bar
335 * as described in {@link GUIRunner.TestProgressBar}.
336 *
337 * @param testCase
338 * The testcase comprising the result of the singular test finished.
339 */
340 void noteReportResult(TestCase testCase) {
341 setValue(getValue() + 1);
342 this.qual = this.qual.max(testCase.getQuality());
343 setForeground(this.qual.getColor());
344 }
345
346 } // class TestProgressBar
347
348
349 /**
350 * To render a cell of the hierarchy tree.
351 * The icon represents the state of the {@link TestCase}.
352 */
353 static class TestTreeCellRenderer extends DefaultTreeCellRenderer {
354
355 private static final long serialVersionUID = -2479143000061671589L;
356
357 /* ---------------------------------------------------------------- *
358 * constructors. *
359 * ---------------------------------------------------------------- */
360
361 /**
362 * Creates a new <code>TestTreeCellRenderer</code> instance.
363 * The user objects of the tree nodes are always testcases.
364 * We display essentially the icon
365 * attached to {@link TestCase#getQuality}.
366 */
367 TestTreeCellRenderer() {
368 // is empty.
369 }
370
371 /* ---------------------------------------------------------------- *
372 * methods. *
373 * ---------------------------------------------------------------- */
374
375 /**
376 * Renders the <code>value</code> interpreting it as Node
377 * the user object of which is a {@link TestCase}.
378 * Rendering is by setting the icon
379 * associated with the state of the @link TestCase}.
380 */
381 public Component getTreeCellRendererComponent(JTree tree,
382 Object value,
383 boolean sel,
384 boolean expanded,
385 boolean leaf,
386 int row,
387 boolean hasFocus) {
388
389 DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
390 TestCase userObj = (TestCase) node.getUserObject();
391 assert userObj != null;
392 if (userObj.getQuality() != null) {
393 // node is a leaf
394 setLeafIcon(userObj.getQuality().getIcon());
395 }
396
397 return super.getTreeCellRendererComponent(tree,
398 value,
399 sel,
400 expanded,
401 leaf,
402 row,
403 hasFocus);
404 }
405 } // class TestTreeCellRenderer
406
407 /**
408 * Minimal interface for notifying about singular selection events.
409 * Implemented by {@link GUIRunner.HierarchyWrapper},
410 * by {@link GUIRunner.TestCaseLister} and
411 * by {@link GUIRunner.TabChangeListener#EMPTY_SELECTOR}.
412 *
413 * @see GUIRunner.TabChangeListener
414 */
415 interface Selector {
416
417 /**
418 * Sets selection of <code>index</code>th item
419 * and clears other selections.
420 *
421 * @param index
422 * a non-negative <code>int</code> value
423 * representing the index of a testcase.
424 * **** CAUTION: This presupposes that in a tree
425 * only the leaves can be selected.
426 */
427 void setSelection(int index);
428
429 /**
430 * Clears the selection.
431 */
432 void clearSelection();
433
434 /**
435 * Acquaints this selector with another one
436 * which is notified of the selection events
437 * of this <code>Selector</code>.
438 * The one in the foreground is notified directly by a mouse event,
439 * whereas the one in the background is selected via registration.
440 * The one in the background in turn
441 * notifies the empty selector
442 * {@link GUIRunner.TabChangeListener#EMPTY_SELECTOR}
443 * which takes no actions.
444 *
445 * @param sel
446 * another <code>Selector</code>.
447 * @throws IllegalStateException
448 * only for {@link GUIRunner.TabChangeListener#EMPTY_SELECTOR}.
449 * @see GUIRunner.TabChangeListener#setSelUnSel(int)
450 */
451 void registerSelector(Selector sel);
452
453 } // interface Selector
454
455 /**
456 * Represents a path {@link #currPath}
457 * in the tree of testsuites represented by {@link #treeModel}.
458 * This is initialized by {@link #setFirstPath()}
459 * to the uppermost complete path,
460 * can be incremented via {@link #incPath()}
461 * and be returned by {@link #getPath()}.
462 */
463 static class TreePathIterator {
464
465
466 /* ---------------------------------------------------------------- *
467 * inner class. *
468 * ---------------------------------------------------------------- */
469
470 /**
471 * Expands the tree along the current path,
472 * for {@link #Generic} after incrementing the current path.
473 */
474 enum TreePathUpdater {
475
476 First() {
477 void updatePath(TreePathIterator treePathIter) {
478 treePathIter.treePathUpdater = Generic;
479 treePathIter.setFirstPath();
480 }
481 },
482 Generic() {
483 void updatePath(TreePathIterator treePathIter) {
484 treePathIter.incPath();
485 }
486 };
487 abstract void updatePath(TreePathIterator treePathIter);
488 } // enum TreePathUpdater
489
490 /* ---------------------------------------------------------------- *
491 * attributes. *
492 * ---------------------------------------------------------------- */
493
494 /**
495 * A model of the tree of testsuites and tests.
496 */
497 private final TreeModel treeModel;
498
499 // is null after this is created.
500 private TreePath currPath;
501
502 /**
503 * Decides how to update the current path:
504 * For the first path, just inkoke ***** .
505 */
506 private TreePathUpdater treePathUpdater;
507
508
509
510 /* ---------------------------------------------------------------- *
511 * constructors. *
512 * ---------------------------------------------------------------- */
513
514 // for index==0, treePathUpdater is First,
515 // setFirstPath not invoked and so like null-pointer
516 // and so after next update iterator points to 0th entry
517 // for index>0, treePathUpdater is Generic,
518 // iterator points to one before correct position
519 // and so aftter next update iterator points to index-th entry
520 TreePathIterator(JTree tree, int index) {
521 this.treeModel = tree.getModel();
522 this.currPath = null; // formally only
523 this.treePathUpdater = TreePathUpdater.First;
524 for (int i = 0; i < index; i++) {
525 updatePathI();
526 }
527 }
528
529
530 /* ---------------------------------------------------------------- *
531 * methods. *
532 * ---------------------------------------------------------------- */
533
534 void updatePathI() {
535 this.treePathUpdater.updatePath(this);
536 }
537
538 /**
539 * Initializes {@link #currPath}
540 * with the first path in {@link #treeModel}.
541 * **** Shall be invoked by TreePathUpdater only ****
542 */
543 void setFirstPath() {
544 TreeNode lastNode = (TreeNode) this.treeModel.getRoot();
545 this.currPath = prolonguePath(new TreePath(lastNode));
546 }
547
548 /**
549 * Prolongues path as long as possible in each step with 0th child.
550 */
551 static TreePath prolonguePath(TreePath path) {
552 TreeNode lastNode = (TreeNode) path.getLastPathComponent();
553 while (!lastNode.isLeaf()) {
554 // one has to add the 0th child of lastNode.
555 lastNode = lastNode.getChildAt(0);
556 path = path.pathByAddingChild(lastNode);
557 }
558 assert lastNode.isLeaf();
559 return path;
560 }
561
562 /**
563 * Replaces {@link #currPath} removing the last node
564 * as long as the last node in the path
565 * is the last child of the last but one node
566 * and after having done this
567 * returns the index of the last node
568 * as a child of the last but one node.
569 * <p>
570 * CAUTION: This method shall be invoked only
571 * if {@link #currPath} can be incremented.
572 *
573 * @throws NullPointerException
574 * if {@link #currPath} cannot be incremented.
575 */
576 private int shortenPath() {
577 TreeNode lastNode = (TreeNode)
578 this.currPath.getLastPathComponent();
579 TreePath prefix = this.currPath.getParentPath();
580 TreeNode lastButOneNode = (TreeNode) prefix.getLastPathComponent();
581 int index = lastButOneNode.getIndex(lastNode);
582
583 while (index == lastButOneNode.getChildCount() - 1) {
584 this.currPath = prefix;
585 lastNode = lastButOneNode;
586 // if currPath is the root, prefix is null
587 prefix = this.currPath.getParentPath();
588 assert prefix != null : "tried to ";
589 lastButOneNode = (TreeNode) prefix.getLastPathComponent();
590 index = lastButOneNode.getIndex(lastNode);
591 }
592 return index;
593 }
594
595 // **** Shall be invoked by TreePathUpdater only ****
596 /**
597 * Increments {@link #currPath} and returns the result.
598 */
599 TreePath incPath() {
600 int index = shortenPath();
601 TreePath prefix = this.currPath.getParentPath();
602 TreeNode lastButOneNode = (TreeNode) prefix.getLastPathComponent();
603 TreeNode lastNode = lastButOneNode.getChildAt(index + 1);
604 this.currPath = prefix.pathByAddingChild(lastNode);
605 this.currPath = prolonguePath(this.currPath);
606 return this.currPath;
607 }
608
609 TreePath getPath() {
610 return this.currPath;
611 }
612 } // class TreePathIterator
613
614 /**
615 * Represents the hierarchy of testsuites and testcases
616 * as a tree {@link #hierarchyTree} possibly with a single selected node
617 * given by {@link #singleSelectedNode}.
618 */
619 static class HierarchyWrapper
620 implements Selector, TreeSelectionListener {
621
622
623 /* ---------------------------------------------------------------- *
624 * fields. *
625 * ---------------------------------------------------------------- */
626
627
628 /**
629 * The hierarchy of testsuites and testcases as a tree.
630 * After creation this is a default tree,
631 * after invocation of {@link #initClassStructure(TestCase)}
632 * the hierarchy reflects the given testcase.
633 * The selection model is given by {@link #treeSelection}
634 * and this is the {@link TreeSelectionListener}.
635 */
636 private final JTree hierarchyTree;
637
638 /**
639 * The selection model for {@link #hierarchyTree}.
640 * At most one entry is selected.
641 * If the entry represents a failure, the according element in
642 * {@link GUIRunner.TestCaseLister#failureSelection} is selected also.
643 *
644 * @see #valueChanged(TreeSelectionEvent)
645 */
646 private final TreeSelectionModel treeSelection;
647
648 /**
649 * This is used only by {@link #valueChanged(TreeSelectionEvent)}
650 * to set the filter via
651 * {@link Actions#setFilter(Description)}.
652 */
653 private final Actions actions;
654
655 /**
656 * The the {@link TestCaseLister}
657 * listing the failed test cases.
658 * This is used in {@link #noteReportResult(TestCase)} only
659 * and is to notify that a certain tescase failed
660 * and is selected.
661 */
662 private final TestCaseLister testCaseLister;
663
664 /**
665 * Represents the selected node in {@link #hierarchyTree}.
666 * This is <code>null</code> if nothing selected
667 * which is also the initial value.
668 * This is set by {@link #valueChanged(TreeSelectionEvent)}
669 * and updated also by {@link #initClassStructure(TestCase)}.
670 */
671 private DefaultMutableTreeNode singleSelectedNode;
672
673 /**
674 * Represents the path to the testcase currently run.
675 * This is <code>null</code> after this is created
676 * and is initialized by {@link #startTestRun(Description)}.
677 */
678 private TreePathIterator currPathIter;
679
680 /**
681 * Selector to be influenced:
682 * If this is in the selected tab, {@link #selector}
683 * is the tab with the {@link GUIRunner.TestCaseLister TestCaseLister};
684 * otherwise it is {@link GUIRunner.TabChangeListener#EMPTY_SELECTOR}.
685 * Set by {@link #registerSelector(GUIRunner.Selector)}.
686 */
687 private Selector selector;
688
689 /* ---------------------------------------------------------------- *
690 * constructors. *
691 * ---------------------------------------------------------------- */
692
693 /**
694 * Creates a new HierarchyWrapper
695 * with the given <code>actions</code> and <code>testCaseLister</code>
696 * which are used
697 * to initialize {@link #actions} and {@link #testCaseLister}.
698 *
699 * @param actions
700 * the Actions to be written into {@link #actions}.
701 * @param testCaseLister
702 * TestCaseLister to be written into {@link #testCaseLister}.
703 */
704 HierarchyWrapper(Actions actions,
705 TestCaseLister testCaseLister) {
706 assert SwingUtilities.isEventDispatchThread();
707
708 this.hierarchyTree = new JTree();
709 // generate selection model
710 this.treeSelection = new DefaultTreeSelectionModel();
711 this.treeSelection.setSelectionMode
712 (TreeSelectionModel.SINGLE_TREE_SELECTION);
713 this.hierarchyTree.setSelectionModel(this.treeSelection);
714 this.hierarchyTree.addTreeSelectionListener(this);
715 this.hierarchyTree.setRootVisible(true);
716
717 TreeNode root = (TreeNode) this.hierarchyTree.getModel().getRoot();
718 this.hierarchyTree.setModel(new DefaultTreeModel(root));
719
720 assert actions != null;
721 this.actions = actions;
722 assert testCaseLister != null;
723 this.testCaseLister = testCaseLister;
724
725 // purely formally
726 this.singleSelectedNode = null;
727 this.currPathIter = null;
728 this.selector = null;
729 }
730
731 /* ---------------------------------------------------------------- *
732 * methods. *
733 * ---------------------------------------------------------------- */
734
735 /**
736 * Converts the (tree of) testcases given by <code>testCase</code>.
737 * ****
738 */
739 private static
740 DefaultMutableTreeNode testCase2treeNode(TestCase testCase) {
741 if (testCase.isTest()) {
742 return new DefaultMutableTreeNode(testCase);
743 }
744 DefaultMutableTreeNode ret = new DefaultMutableTreeNode(testCase);
745 List<TestCase> childrenIn = testCase.getChildren();
746 DefaultMutableTreeNode childOut;
747 for (TestCase childIn : childrenIn) {
748 childOut = testCase2treeNode(childIn);
749 ret.add(childOut);
750 }
751 return ret;
752 }
753
754 TestCase getRoot() {
755 DefaultMutableTreeNode root = (DefaultMutableTreeNode)
756 this.hierarchyTree.getModel().getRoot();
757 return (TestCase) root.getUserObject();
758 }
759
760 /**
761 * Returns the action. Invoked by the enclosing GUIRunner.
762 *
763 * @see GUIRunner#testRunStarted(Description)
764 * @see GUIRunner#testRunFinished(long)
765 * @see GUIRunner#testRunAborted()
766 */
767 Actions getActions() {
768 return this.actions;
769 }
770
771 /* ---------------------------------------------------------------- *
772 * further methods. *
773 * ---------------------------------------------------------------- */
774
775
776 /**
777 * Notifies that the structure of the test class may have been updated.
778 * <p>
779 * Converts the overall test case hierarchy <code>testCase</code>
780 * into a a tree node hierarchy
781 * invoking {@link #testCase2treeNode(TestCase)}.
782 * This defines the tree model of the hierarchy {@link #hierarchyTree}.
783 * The selected node {@link #singleSelectedNode} is set to the root.
784 * <p>
785 * Since {@link #currPathIter} points to the first testcase run,
786 * it is set to <code>null</code>.
787 * Finally, {@link #actions} is notified about the test to be run next
788 * which is specified by the testcase given by the selected node.
789 * by invoking {@link #setFilter()}.
790 *
791 * @param testCase
792 * a description of the test structure defined in the test class
793 * which is a hierarchy of suites and singular tests.
794 */
795 void initClassStructure(TestCase testCase) {
796 this.singleSelectedNode = testCase2treeNode(testCase);
797 TreeModel newModel = new DefaultTreeModel(this.singleSelectedNode);
798 this.hierarchyTree.setModel(newModel);
799 // cell renderer after model.
800 this.hierarchyTree.setCellRenderer(new TestTreeCellRenderer());
801
802 this.currPathIter = null;
803 setFilter();
804 }
805
806 // invoked by {@link #initClassStructure(TestCase)}
807 // and by {@link #valueChanged(TreeSelectionEvent)}
808 /**
809 * Sets the filter (given by a description)
810 * invoking {@link Actions#setFilter(Description)}:
811 * The description is taken from {@link #singleSelectedNode}.
812 */
813 private void setFilter() {
814 TestCase testCase = (TestCase) this.singleSelectedNode
815 .getUserObject();
816 this.actions.setFilter(testCase.getDesc());
817 }
818
819 // for HierarchyWrapper
820 /**
821 * Notifies that a test run with structure given by <code>desc</code>
822 * is going to be run next.
823 * <p>
824 * Sets the quality of the testcases
825 * given by {@link #singleSelectedNode}
826 * recursively to {@link Quality#Scheduled}
827 * and sets {@link #currPathIter}.
828 *
829 * @param desc
830 * describes the (hierarchy of) tests to be run.
831 * This is a sub-hierarchy of the one given by the test class.
832 * This parameter is purely formally
833 * because it is given by {@link #singleSelectedNode}.
834 */
835 void startTestRun(Description desc) {
836 TestCase testCase = (TestCase)
837 this.singleSelectedNode.getUserObject();
838 // no longer true: ****
839 //assert testCase.getDesc().equals(desc);
840 //assert desc == testCase.getDesc();
841 testCase.setScheduledRec();
842
843 testCase = (TestCase) this.singleSelectedNode.getFirstLeaf()
844 .getUserObject();
845 this.currPathIter = new TreePathIterator
846 (this.hierarchyTree, testCase.getIdx());
847 }
848
849 /**
850 * Expands the path to the leaf {@link #currPathIter} points to.
851 * This is invoked by {@link #noteTestStartedI(Quality)}
852 * when a testcase is started or ignored.
853 *
854 * @see #collapseAlongPath()
855 */
856 void expandAlongPath() {
857 this.hierarchyTree.expandPath(this.currPathIter.getPath()
858 // that the last node is no leaf
859 .getParentPath());
860 }
861
862 /**
863 * Collapses the path to the leaf {@link #currPathIter} points to
864 * as much as possible in order not to hide {@link #singleSelectedNode}
865 * and the leafs corresponding with singular tests
866 * which failed already (assumption failure, failure and error).
867 * This is invoked by {@link #noteReportResult(TestCase)}
868 * when a testcase is finished or after being ignored.
869 *
870 * @see #expandAlongPath()
871 */
872 void collapseAlongPath() {
873 Collection<?> selPath =
874 Arrays.asList(this.singleSelectedNode.getPath());
875
876 TreePath treePath = this.currPathIter.getPath().getParentPath();
877 Object[] pathArr = treePath.getPath();
878 TestCase testCase;
879 DefaultMutableTreeNode node;
880 for (int idx = pathArr.length - 1; idx >= 0; idx--) {
881 node = (DefaultMutableTreeNode) pathArr[idx];
882 if (selPath.contains(node)) {
883 break;
884 }
885
886 testCase = (TestCase) node.getUserObject();
887
888 if (testCase.fullSuccess()) {
889 Object[] pathArrNew = new Object[idx + 1]; // 0,...,idx
890 System.arraycopy(pathArr, 0,
891 pathArrNew, 0,
892 pathArrNew.length);
893 treePath = new TreePath(pathArrNew);
894 assert treePath.getLastPathComponent() == pathArr[idx];
895 this.hierarchyTree.collapsePath(treePath);
896 }
897 } // for
898 }
899
900 JTree getTree() {
901 return this.hierarchyTree;
902 }
903
904 // **** for tests only
905 TreePath getPath() {
906 return this.currPathIter.getPath();
907 }
908
909 // invoked by noteTestStartedI(Quality)
910 /**
911 * Notifies that an atomic test is started or ignored.
912 * <p>
913 * To Updates {@link #currPathIter} pointing to the current testcase
914 * and expands along the path given by the current testcase.
915 * Then sets the quality of the according testcase
916 * to <code>qual</code> and updates the according tree node.
917 *
918 * @param qual
919 * the quality of the testcase:
920 * This is {@link Quality#Started} or {@link Quality#Ignored}.
921 * @return
922 * the current testcase.
923 */
924 TestCase noteTestStartedI(Quality qual) {
925 this.currPathIter.updatePathI();
926 expandAlongPath();
927
928 DefaultMutableTreeNode lastNode = (DefaultMutableTreeNode)
929 this.currPathIter.getPath().getLastPathComponent();
930 TestCase result = (TestCase) lastNode.getUserObject();
931 result.setQualStartedIgnored(qual);
932 ((DefaultTreeModel) this.hierarchyTree.getModel())
933 .nodeChanged(lastNode);
934 return result;
935 }
936
937 // invoked by noteReportResult(TestCase)
938 /**
939 * Notifies that the singular test <code>testCase</code> is finished.
940 * <p>
941 * Collapses the current path invoking {@link #collapseAlongPath()},
942 * updates the tree node {@link #currPathIter} points to
943 * and notifies {@link #testCaseLister}
944 * if <code>testCase</code> failed and is selected invoking
945 * {@link TestCaseLister#addSelectedTestCaseByNeed(TestCase)}.
946 *
947 * @param testCase
948 * The testcase comprising the result of the singular test finished.
949 */
950 void noteReportResult(TestCase testCase) {
951 collapseAlongPath();
952
953 MutableTreeNode lastNode = (MutableTreeNode)
954 this.currPathIter.getPath().getLastPathComponent();
955 ((DefaultTreeModel) this.hierarchyTree.getModel())
956 .nodeChanged(lastNode);
957
958 // If testCase is selected and testCase.hasFailed(),
959 // it shall be added to testCaseLister if not yet listed.
960 if (testCase.hasFailed() && isSelected(testCase)) {
961 this.testCaseLister.addSelectedTestCaseByNeed(testCase);
962 }
963 }
964
965 /**
966 * Returns whether the given testcase <code>testCase</code>
967 * is selected in this HierarchyTree.
968 *
969 * @return
970 * whether the given testcase <code>testCase</code>
971 * is selected in this HierarchyTree.
972 */
973 private boolean isSelected(TestCase testCase) {
974 int numSel = this.treeSelection.getSelectionCount();
975 if (numSel == 0) {
976 return false;
977 }
978 assert numSel == 1;
979 DefaultMutableTreeNode selNode = (DefaultMutableTreeNode)
980 this.treeSelection.getSelectionPath().getLastPathComponent();
981 TestCase selTestCase = (TestCase) selNode.getUserObject();
982
983 return selTestCase == testCase; // NOPMD
984 }
985
986 /* ---------------------------------------------------------------- *
987 * methods implementing Selector. *
988 * ---------------------------------------------------------------- */
989
990 // api-docs inherited from Selector
991 public void setSelection(int index) {
992 // **** note: for index==0 one update is required and so index+1
993 // abuse of TreePathIterator to obtain the right selection path
994 TreePathIterator currPathIter =
995 new TreePathIterator(this.hierarchyTree, index + 1);
996 this.treeSelection.addSelectionPath(currPathIter.getPath());
997 //**** still a problem with update
998 // what follows does not solve the problem.
999 //this.frame.update(this.frame.getGraphics());
1000 }
1001
1002 // api-docs inherited from Selector
1003 public void clearSelection() {
1004 this.treeSelection.clearSelection();
1005 }
1006
1007 // api-docs inherited from Selector
1008 public void registerSelector(Selector selector) {
1009 assert selector instanceof TestCaseLister
1010 || selector == TabChangeListener.EMPTY_SELECTOR;
1011 this.selector = selector;
1012 }
1013
1014 /* ---------------------------------------------------------------- *
1015 * methods implementing TreeSelectionListener. *
1016 * ---------------------------------------------------------------- */
1017
1018 /**
1019 * Called whenever the value of the selection changes.
1020 * Note that deselection is treated as selection of the root,
1021 * so w.l.o.g, we have to consider selections only.
1022 * <p>
1023 * If an entry is selected,
1024 * {@link #singleSelectedNode} holds the selected node.
1025 * Next {@link #selector} is notified of the selection:
1026 * <ul>
1027 * <li>
1028 * if a failure is selected (which is a leaf)
1029 * triggers according selection in the failure list,
1030 * otherwise triggers a deselection.
1031 * </ul>
1032 * Both have further effects invoking
1033 * {@link GUIRunner.TestCaseLister#valueChanged(ListSelectionEvent)}.
1034 * <p>
1035 * Finally, invokes {@link #setFilter()}
1036 * setting the filter for the next test run.
1037 */
1038 public void valueChanged(TreeSelectionEvent selEvent) {
1039 // paths is a list of paths with changed selection
1040 TreePath[] paths = selEvent.getPaths();
1041 // two paths if one selected and the other deselected.
1042 // one path: either selected or deselected
1043 assert paths.length == 1 || paths.length == 2;
1044
1045 for (int i = 0; i < paths.length; i++) {
1046
1047 // set singleSelectedNode
1048 if (selEvent.isAddedPath(paths[i])) {
1049 // Here, paths[i] has been selected
1050 this.singleSelectedNode = (DefaultMutableTreeNode)
1051 paths[i].getLastPathComponent();
1052 } else {
1053 // Here, paths[i] has been deselected
1054 if (paths.length == 2) {
1055 // Here, the other path is selected
1056 // which handles all we need
1057 continue;
1058 }
1059
1060 // Here, the deselection is the only change
1061 assert paths.length == 1;
1062 // treat as if root was selected
1063 // **** this is merely a copy!
1064 this.singleSelectedNode = (DefaultMutableTreeNode)
1065 paths[i].getPathComponent(0);
1066 }
1067
1068 setFilter();
1069
1070 if (this.singleSelectedNode.isLeaf()) {
1071 TestCase testCase = (TestCase)
1072 this.singleSelectedNode.getUserObject();
1073 this.selector.setSelection(testCase.getIdx());
1074 } else {
1075 this.selector.clearSelection();
1076 }
1077 } // for
1078 }
1079
1080 } // class HierarchyWrapper
1081
1082 /**
1083 * Represents the table displaying the number of runs,
1084 * both, passed and to be performed altogether,
1085 * the tests already ignored and those a failure or an error was found.
1086 * <p>
1087 * The numbers {@link #numRunsDone}, {@link #numRuns} and {@link #qual2num}
1088 * represent the number of runs done,
1089 * the overall number of runs, done or not, and
1090 * the number of finished tests of the according quality.
1091 */
1092 static class StatisticsTestState extends JComponent {
1093
1094 private static final long serialVersionUID = -2479143000061671589L;
1095
1096 /* ---------------------------------------------------------------- *
1097 * attributes. *
1098 * ---------------------------------------------------------------- */
1099
1100 /**
1101 * Maps the quality with level <code>> 0</code>
1102 * to the according labels.
1103 */
1104 private final Map<Quality, JLabel> qual2label;
1105
1106 /**
1107 * Maps the quality with any level
1108 * to the number of testcases already finished or ignored
1109 * with according state.
1110 */
1111 private final Map<Quality, Integer> qual2num;
1112
1113 /**
1114 * Label for the number of runs done and to be executed.
1115 *
1116 * @see #numRunsDone
1117 * @see #numRuns
1118 */
1119 private final JLabel runs;
1120
1121 /**
1122 * The number of runs already finished.
1123 */
1124 private int numRunsDone;
1125
1126 /**
1127 * The overall number of runs, to be done,
1128 * in execution or not yet started.
1129 * The constructor initializes this with <code>0</code>.
1130 */
1131 private int numRuns;
1132
1133
1134 /* ---------------------------------------------------------------- *
1135 * constructors. *
1136 * ---------------------------------------------------------------- */
1137
1138 StatisticsTestState() {
1139 super();
1140
1141 this.runs = new JLabel();
1142 //this.numRuns = 0;// formally. This is set by #start()
1143
1144 this.qual2label = new EnumMap<Quality, JLabel>(Quality.class);
1145 for (Quality qual : Quality.values()) {
1146 if (qual.isNeutral()) {
1147 continue;
1148 }
1149 this.qual2label.put(qual, new JLabel());
1150 }
1151 this.qual2num = new EnumMap<Quality, Integer>(Quality.class);
1152 }
1153
1154 /* ---------------------------------------------------------------- *
1155 * methods. *
1156 * ---------------------------------------------------------------- */
1157
1158 /**
1159 * Returns a horizontal box with the labels in {@link #qual2label}
1160 * intermangled with according icons.
1161 */
1162 Box getBox() {
1163 Box res = Box.createHorizontalBox();
1164 res.add(this.runs);
1165 for (Quality qual : Quality.values()) {
1166 if (qual.isNeutral()) {
1167 continue;
1168 }
1169 res.add(new JLabel(qual.getIcon()));
1170 res.add(this.qual2label.get(qual));
1171 }
1172
1173 res.add(Box.createGlue());
1174 return res;
1175 }
1176
1177 /**
1178 * Notifies that the structure of the test class may have been updated.
1179 * <p>
1180 * Initiates
1181 * <ul>
1182 * <li>
1183 * {@link #numRuns} with the testcount given by <code>desc</code>,
1184 * <li>
1185 * {@link #numRunsDone} and all numbers in {@link #qual2num}
1186 * with <code>0</code>,
1187 * </ul>
1188 * and updates the labels {@link #runs}
1189 * and those in {@link #qual2label}.
1190 *
1191 * @param desc
1192 * a description of the test structure defined in the test class
1193 * which is a hierarchy of suites and singular tests.
1194 */
1195 void initClassStructure(Description desc) {
1196 this.numRuns = desc.testCount();
1197 this.numRunsDone = 0;
1198
1199 for (Quality qual : Quality.values()) {
1200 this.qual2num.put(qual, 0);
1201 }
1202
1203 updateLabels();
1204 }
1205
1206 // for StatisticsTestState
1207 /**
1208 * Notifies that a test with structure given by <code>desc</code>
1209 * is going to be run next.
1210 * <p>
1211 *
1212 * Initiates
1213 * <ul>
1214 * <li>
1215 * {@link #numRunsDone} to <code>0</code>,
1216 * <li>
1217 * {@link #qual2num} with the testcount given by <code>testCase</code>
1218 * invoking {@link #detQual2NumRec(TestCase)},
1219 * </ul>
1220 * and updates the labels {@link #runs}
1221 * and those in {@link #qual2label}.
1222 *
1223 * @param testCase
1224 * describes the (hierarchy of) tests defined by the test class.
1225 * Those which are in phase {@link Quality#Scheduled}
1226 * are to be run.
1227 */
1228 void startTestRun(TestCase testCase) {
1229 this.numRunsDone = 0;
1230 detQual2NumRec(testCase);
1231 updateLabels();
1232 }
1233
1234 /**
1235 * Initializes teh statistics in {@link #qual2num}
1236 * according to <code>testCase</code>.
1237 *
1238 * @param testCase
1239 * describes the (hierarchy of) tests defined by the test class.
1240 * Those which are in phase {@link Quality#Scheduled}
1241 * are to be run.
1242 */
1243 private void detQual2NumRec(TestCase testCase) {
1244 if (testCase.isTest()) {
1245 Quality qual = testCase.getQuality();
1246 switch (qual) {
1247 case Started:
1248 assert false;
1249 break;
1250 case Scheduled:
1251 break;
1252 default:
1253 this.numRunsDone++;
1254 break;
1255 }
1256
1257 int num = this.qual2num.get(qual);
1258 this.qual2num.put(qual, ++num);
1259 return;
1260 }
1261 assert !testCase.isTest();
1262 for (TestCase child : testCase.getChildren()) {
1263 detQual2NumRec(child);
1264 }
1265 }
1266
1267 /**
1268 * Notifies that the singular test <code>testCase</code> is finished.
1269 * <p>
1270 * Updates all counters and labels
1271 * according to <code>testCase</code>'s quality:
1272 * It may succeed, be ignored, had a failure or
1273 * could not be executed due to an exception or an error.
1274 *
1275 * @param testCase
1276 * The testcase comprising the result of the singular test finished.
1277 * @see TestCase#getQuality()
1278 */
1279 void noteReportResult(TestCase testCase) {
1280 this.numRunsDone++;
1281
1282 int num = this.qual2num.get(testCase.getQuality());
1283 /* */this.qual2num.put(testCase.getQuality(), ++num);
1284
1285 updateLabels();
1286 }
1287
1288 /**
1289 * Updates all labels if a counter has changed.
1290 * This is invoked by {@link #initClassStructure(Description)}
1291 * and by {@link #startTestRun(TestCase)}.
1292 */
1293 private void updateLabels() {
1294 this.runs.setText("Runs: " + this.numRunsDone +
1295 "/" + this.numRuns + " ");
1296
1297 for (Quality qual : Quality.values()) {
1298 if (qual.isNeutral()) {
1299 continue;
1300 }
1301 /* */this.qual2label.get(qual)
1302 .setText(qual.toString() + "s: " +
1303 this.qual2num .get(qual) + " ");
1304 }
1305 }
1306 } // class StatisticsTestState
1307
1308
1309 /**
1310 * A special renderer object
1311 * consisting of a label and a location within java code
1312 * for an item in a failure list.
1313 */
1314 static class TestListCellRenderer
1315 implements ListCellRenderer<TestCase>, Serializable {
1316
1317 /**
1318 * A special kind of label which fires only change in the text
1319 * and which allows to set details: selection and focus.
1320 */
1321 static class XLabel extends JLabel {
1322
1323 private static final long serialVersionUID = -2479143000061671589L;
1324
1325 XLabel(Icon icon) {
1326 super(icon);
1327 }
1328
1329 XLabel(String text) {
1330 super(text);
1331 }
1332
1333 void setDetails(JList<?> list,
1334 boolean isSelected,
1335 boolean cellHasFocus) {
1336 if (isSelected) {
1337 setBackground(list.getSelectionBackground());
1338 setForeground(list.getSelectionForeground());
1339 } else {
1340 setBackground(list.getBackground());
1341 setForeground(list.getForeground());
1342 }
1343 setEnabled(list.isEnabled());
1344 setFont(list.getFont());
1345 setBorder(cellHasFocus
1346 ? UIManager.getBorder(BORDER_DESC)
1347 : NO_FOCUS_BORDER);
1348 setOpaque(true);
1349 }
1350
1351 /**
1352 * Overridden for performance reasons.
1353 * See the <a href="#override">Implementation Note</a>
1354 * for more information.
1355 *
1356 * @since 1.5
1357 * @return <code>true</code> if the background is completely opaque
1358 * and differs from the JList's background;
1359 * <code>false</code> otherwise
1360 */
1361 public boolean isOpaque() {
1362 Color back = getBackground();
1363 Component comp = getParent();
1364 if (comp != null) {
1365 comp = comp.getParent();
1366 }
1367 // comp should now be the JList.
1368 boolean colorMatch =
1369 back != null &&
1370 comp != null &&
1371 back.equals(comp.getBackground()) &&
1372 comp.isOpaque();
1373 return !colorMatch && super.isOpaque();
1374 }
1375
1376 /**
1377 * Overridden for performance reasons.
1378 * See the <a href="#override">Implementation Note</a>
1379 * for more information.
1380 */
1381 public void validate() {
1382 // is empty.
1383 }
1384
1385 /**
1386 * Overridden for performance reasons.
1387 * See the <a href="#override">Implementation Note</a>
1388 * for more information.
1389 *
1390 * @since 1.5
1391 */
1392 public void invalidate() {
1393 // is empty.
1394 }
1395
1396 /**
1397 * Overridden for performance reasons.
1398 * See the <a href="#override">Implementation Note</a>
1399 * for more information.
1400 *
1401 * @since 1.5
1402 */
1403 public void repaint() {
1404 // is empty.
1405 }
1406
1407 /**
1408 * Overridden for performance reasons.
1409 * See the <a href="#override">Implementation Note</a>
1410 * for more information.
1411 */
1412 public void revalidate() {
1413 // is empty.
1414 }
1415
1416 /**
1417 * Overridden for performance reasons.
1418 * See the <a href="#override">Implementation Note</a>
1419 * for more information.
1420 */
1421 public void repaint(long timm,
1422 int xCoord, int yCoord,
1423 int width, int height) {
1424 // is empty.
1425 }
1426
1427 /**
1428 * Overridden for performance reasons.
1429 * See the <a href="#override">Implementation Note</a>
1430 * for more information.
1431 */
1432 public void repaint(Rectangle rect) {
1433 // is empty.
1434 }
1435
1436 /**
1437 * Overridden for performance reasons.
1438 * See the <a href="#override">Implementation Note</a>
1439 * for more information.
1440 */
1441 protected void firePropertyChange(String propertyName,
1442 Object oldValue,
1443 Object newValue) {
1444 // Strings get interned...
1445 // if (propertyName == "text") { // NOPMD
1446 // super.firePropertyChange(propertyName, oldValue, newValue);
1447 // }
1448 }
1449
1450 /**
1451 * Overridden for performance reasons.
1452 * See the <a href="#override">Implementation Note</a>
1453 * for more information.
1454 */
1455 public void firePropertyChange(String propertyName,
1456 byte oldValue,
1457 byte newValue) {
1458 // is empty.
1459 }
1460
1461 /**
1462 * Overridden for performance reasons.
1463 * See the <a href="#override">Implementation Note</a>
1464 * for more information.
1465 */
1466 public void firePropertyChange(String propertyName,
1467 char oldValue,
1468 char newValue) {
1469 // is empty.
1470 }
1471
1472 /**
1473 * Overridden for performance reasons.
1474 * See the <a href="#override">Implementation Note</a>
1475 * for more information.
1476 */
1477 public void firePropertyChange(String propertyName,
1478 short oldValue,
1479 short newValue) {
1480 // is empty.
1481 }
1482
1483 /**
1484 * Overridden for performance reasons.
1485 * See the <a href="#override">Implementation Note</a>
1486 * for more information.
1487 */
1488 public void firePropertyChange(String propertyName,
1489 int oldValue,
1490 int newValue) {
1491 // is empty.
1492 }
1493
1494 /**
1495 * Overridden for performance reasons.
1496 * See the <a href="#override">Implementation Note</a>
1497 * for more information.
1498 */
1499 public void firePropertyChange(String propertyName,
1500 long oldValue,
1501 long newValue) {
1502 // is empty.
1503 }
1504
1505 /**
1506 * Overridden for performance reasons.
1507 * See the <a href="#override">Implementation Note</a>
1508 * for more information.
1509 */
1510 public void firePropertyChange(String propertyName,
1511 float oldValue,
1512 float newValue) {
1513 // is empty.
1514 }
1515
1516 /**
1517 * Overridden for performance reasons.
1518 * See the <a href="#override">Implementation Note</a>
1519 * for more information.
1520 */
1521 public void firePropertyChange(String propertyName,
1522 double oldValue,
1523 double newValue) {
1524 // is empty.
1525 }
1526
1527 /**
1528 * Overridden for performance reasons.
1529 * See the <a href="#override">Implementation Note</a>
1530 * for more information.
1531 */
1532 public void firePropertyChange(String propertyName,
1533 boolean oldValue,
1534 boolean newValue) {
1535 // is empty.
1536 }
1537 } // class XLabel
1538
1539 private static final long serialVersionUID = -2479143000061671589L;
1540
1541 private static final String BORDER_DESC =
1542 "List.focusCellHighlightBorder";
1543
1544 protected static final Border NO_FOCUS_BORDER =
1545 new EmptyBorder(1, 1, 1, 1);
1546
1547 /* ------------------------------------------------------------------ *
1548 * constructor. *
1549 * ------------------------------------------------------------------ */
1550
1551 /**
1552 * Constructs a special renderer object
1553 * consisting of a label and a location within java code
1554 * for an item in a failure list.
1555 */
1556 TestListCellRenderer() {
1557 super();
1558 }
1559
1560 /* ------------------------------------------------------------------ *
1561 * methods. *
1562 * ------------------------------------------------------------------ */
1563
1564 // **** hack ****
1565 private String thrwToString(Throwable thrw) {
1566 // if ("null".equals(thrw.getMessage())) {
1567 // return thrw.getClass().toString();
1568 // } else {
1569 // return thrw .toString();
1570 // }
1571
1572
1573 return "null".equals(thrw.getMessage())
1574 ? thrw.getClass().toString()
1575 : thrw .toString();
1576 }
1577
1578 public Component getListCellRendererComponent
1579 (JList<? extends TestCase> list,
1580 TestCase testCase,
1581 int index,
1582 boolean isSelected,
1583 boolean cellHasFocus) {
1584
1585 Box failureEntry = Box.createHorizontalBox();
1586
1587 XLabel iconLabel = new XLabel(testCase.getQuality().getIcon());
1588
1589 // for rerun testcases which eventually do no longer fail.
1590 XLabel textLabel = new XLabel(testCase.hasFailed()
1591 ? thrwToString(testCase.getThrown())
1592 : testCase.getQuality().getMessage());
1593 iconLabel.setDetails(list, isSelected, cellHasFocus);
1594 iconLabel.setDetails(list, isSelected, cellHasFocus);
1595 failureEntry.add(iconLabel);
1596 failureEntry.add(textLabel);
1597
1598
1599 return failureEntry;
1600 }
1601
1602 /**
1603 * A subclass of DefaultListCellRenderer that implements UIResource.
1604 * DefaultListCellRenderer doesn't implement UIResource
1605 * directly so that applications can safely override the
1606 * cellRenderer property with DefaultListCellRenderer subclasses.
1607 * <p>
1608 * <strong>Warning:</strong>
1609 * Serialized objects of this class will not be compatible with
1610 * future Swing releases. The current serialization support is
1611 * appropriate for short term storage or RMI
1612 * between applications running the same version of Swing.
1613 * As of 1.4, support for long term storage
1614 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1615 * has been added to the <code>java.beans</code> package.
1616 * Please see <code>java.beans.XMLEncoder</code>.
1617 */
1618 /*
1619 public static class UIResource extends MyListCellRenderer
1620 implements javax.swing.plaf.UIResource {
1621 private static final long serialVersionUID = -2479143000061671589L;
1622 }
1623 */
1624
1625 } // class TestListCellRenderer
1626
1627
1628
1629 /**
1630 * Represents the list of testcases already failed
1631 * shown in one of the tabs
1632 * and a {@link #stackTraceLister}
1633 * which represents the stack trace box below the tabbed pane.
1634 */
1635 static class TestCaseLister implements ListSelectionListener, Selector {
1636
1637 /* ---------------------------------------------------------------- *
1638 * fields. *
1639 * ---------------------------------------------------------------- */
1640
1641 /**
1642 * The current selection of {@link #failureListMod}.
1643 * At most one entry is selected.
1644 * If so, the according element in
1645 * {@link GUIRunner.HierarchyWrapper#treeSelection} is selected also.
1646 *
1647 * @see #valueChanged(ListSelectionEvent)
1648 */
1649 private final ListSelectionModel failureSelection;
1650
1651 /**
1652 * The list of testcases which are either ignored,
1653 * failed in some sense or with hurt assumption.
1654 */
1655 private final DefaultListModel<TestCase> failureListMod;
1656
1657 /**
1658 * Contains the stack trace
1659 * if a failure in {@link #failureListMod} is selected
1660 * which caused an exception.
1661 * This is true if an assertion was wrong, an assumption was wron
1662 * or if the execution of a testcase could not be completed
1663 * due to an exception.
1664 */
1665 private final StackTraceLister stackTraceLister;
1666
1667 // selector to be influenced.
1668 /**
1669 * Selector to be influenced:
1670 * If this is in the selected tab, {@link #selector}
1671 * is the tab with the {@link GUIRunner.HierarchyWrapper};
1672 * otherwise it is {@link GUIRunner.TabChangeListener#EMPTY_SELECTOR}.
1673 * Set by {@link #registerSelector(GUIRunner.Selector)}.
1674 */
1675 private Selector selector;
1676
1677 /* ---------------------------------------------------------------- *
1678 * constructors. *
1679 * ---------------------------------------------------------------- */
1680
1681 TestCaseLister() {
1682 // init failureSelection
1683 this.failureSelection = new DefaultListSelectionModel();
1684 this.failureSelection
1685 .setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
1686 this.failureSelection.setValueIsAdjusting(true);
1687 this.failureSelection.addListSelectionListener(this);
1688 // init failureListMod
1689 this.failureListMod = new DefaultListModel<TestCase>();
1690 // init stacktrace
1691 this.stackTraceLister = new StackTraceLister();
1692 }
1693
1694
1695 /* ---------------------------------------------------------------- *
1696 * methods. *
1697 * ---------------------------------------------------------------- */
1698
1699 // invoked by {@link GUIRunner#setCenter(Actions)}
1700 JList<TestCase> getFailList() {
1701 JList<TestCase> jFailureList =
1702 new JList<TestCase>(this.failureListMod);
1703 jFailureList.setCellRenderer(new TestListCellRenderer());
1704 jFailureList.setSelectionModel(this.failureSelection);
1705 return jFailureList;
1706 }
1707
1708 // invoked by {@link GUIRunner#setCenter(Actions)}
1709 Component getStackTraceBox() {
1710 return this.stackTraceLister.getStackTraceBox();
1711 }
1712
1713 /* ---------------------------------------------------------------- *
1714 * further methods. *
1715 * ---------------------------------------------------------------- */
1716
1717 /**
1718 * Clears the failure list, its selection and the failure stack.
1719 */
1720 void initClassStructure() {
1721 this.failureSelection.clearSelection();
1722 this.failureListMod .clear();
1723 this.stackTraceLister.clearStack();
1724 }
1725
1726 // for TestCaseLister
1727
1728 // maybe remove from failureListMod only
1729 // what is in desc
1730 void startTestRun() {
1731 }
1732
1733 /**
1734 * Notifies that the singular test <code>testCase</code> is finished.
1735 * <p>
1736 * If the test failed,
1737 * it should be in the failure list {@link #failureListMod}.
1738 * If so the element is added if not yet present.
1739 * <p>
1740 * If <code>testCase</code> is selected
1741 * then if <code>testCase</code> failed,
1742 * the stack trace {@link #stackTraceLister}
1743 * is set to the stacktrace of the failure;
1744 * otherwise the stacktrace is cleared.
1745 *
1746 * @param testCase
1747 * The testcase comprising the result of the singular test finished.
1748 */
1749 void noteReportResult(TestCase testCase) {
1750 // add testCase to failureListMod by need
1751 if (testCase.hasFailed()
1752 && !this.failureListMod.contains (testCase)) {
1753 /**/this.failureListMod.addElement(testCase);
1754 // Here, the new element is not selected
1755 // and so stackTraceLister needs no update.
1756 return;
1757 }
1758 assert !testCase.hasFailed()
1759 || this.failureListMod.contains(testCase);
1760
1761 // update stackTraceLister by need
1762 int selIndex = this.failureSelection.getMinSelectionIndex();
1763 if (selIndex == -1
1764 || testCase != this.failureListMod.getElementAt(selIndex)) {
1765 // Here, no testcase is selected
1766 // or some testcase is selected but not testCase.
1767 // In any case, stackTraceLister is empty and needs no update.
1768 return;
1769 }
1770
1771 // Here, testCase is selected
1772 if (testCase.hasFailed()) {
1773 this.stackTraceLister.setStack(testCase.getThrown());
1774 } else {
1775 this.stackTraceLister.clearStack();
1776 }
1777 }
1778
1779 /**
1780 * Adds <code>testCase</code> to the failure list if not yet listed.
1781 * It is assumed that <code>testCase</code> failed
1782 * and that it is selected in the {@link HierarchyWrapper}.
1783 *
1784 * @param testCase
1785 * a testcase which failed.
1786 */
1787 void addSelectedTestCaseByNeed(TestCase testCase) {
1788 assert testCase.hasFailed();
1789 int selIndex;
1790 if (this.failureListMod.contains(testCase)) {
1791 // nothing to be added; check only
1792 selIndex = this.failureSelection.getMinSelectionIndex();
1793 assert this.failureListMod.get(selIndex) == testCase;
1794 } else {
1795 // add testCase and select it
1796 this.failureListMod.addElement(testCase);
1797
1798 selIndex = this.failureSelection.getMinSelectionIndex();
1799 // because testCase is selected in the HierarchyTree,
1800 // and selections are synchronized,
1801 // nothing is selected in the list
1802 assert selIndex == -1;
1803
1804 selIndex = this.failureListMod.size() - 1;
1805 assert this.failureListMod.get(selIndex) == testCase;
1806 this.failureSelection.setSelectionInterval(selIndex, selIndex);
1807 // Here, testCase is in the list and is selected.
1808 }
1809 }
1810
1811 /* ---------------------------------------------------------------- *
1812 * methods implementing Selector. *
1813 * ---------------------------------------------------------------- */
1814
1815 // api-docs inherited from Selector
1816 // index starts with 0
1817 public void setSelection(int index) {
1818 for (int idx = 0; idx < this.failureListMod.getSize(); idx++) {
1819 // **** this.failureListMod.get(idx) == testCase should do
1820 if (this.failureListMod.get(idx).getIdx() == index) {
1821 this.failureSelection.setSelectionInterval(idx, idx);
1822 return;
1823 }
1824 }
1825 clearSelection();
1826 }
1827
1828 // api-docs inherited from Selector
1829 public void clearSelection() {
1830 this.failureSelection.clearSelection();
1831 this.stackTraceLister.clearStack();
1832 }
1833
1834 // api-docs inherited from Selector
1835 public void registerSelector(Selector selector) {
1836 assert selector instanceof HierarchyWrapper
1837 || selector == TabChangeListener.EMPTY_SELECTOR;
1838 this.selector = selector;
1839 }
1840
1841 /* ---------------------------------------------------------------- *
1842 * methods implementing ListSelectionListener. *
1843 * ---------------------------------------------------------------- */
1844
1845 /**
1846 * Called whenever the value of the selection changes.
1847 * <p>
1848 * CAUTION: From the documentation of {@link ListSelectionEvent}:
1849 * queries from {@link ListSelectionModel},
1850 * rather from {@link ListSelectionEvent}.
1851 * <p>
1852 * If an entry is selected,
1853 * {@link #stackTraceLister} is notified of the stacktrace
1854 * of the throwable of the according testcase
1855 * and {@link #selector} is notified of the selection
1856 * to trigger the according selection
1857 * which in turn has further effects invoking
1858 * {@link GUIRunner.HierarchyWrapper#valueChanged(TreeSelectionEvent)}.
1859 * Deselection causes {@link #stackTraceLister} to clear the stacktrace
1860 * and also affects {@link #selector} like selection of the root.
1861 */
1862 public void valueChanged(ListSelectionEvent lse) {
1863 // the index of the (single) selected entry or -1 if none selected
1864 int selIndex = this.failureSelection.getMinSelectionIndex();
1865 if (selIndex == -1) {
1866 // Here, the selection is empty.
1867 this.stackTraceLister.clearStack();
1868 this.selector.clearSelection();
1869 return;
1870 }
1871 // Here, the selection consists of a single entry.
1872 TestCase testCase = this.failureListMod.getElementAt(selIndex);
1873 this.stackTraceLister.setStack(testCase.getThrown());
1874 this.selector.setSelection(testCase.getIdx());
1875
1876 //GUIRunner.this.splitPane.resetToPreferredSizes();
1877 }
1878
1879 } // class TestCaseLister
1880
1881 /**
1882 * Represents the stack trace of the throwable, {@link #thrw}
1883 * currently selected in the error list.
1884 * The representation consists in
1885 * the string representation {@link #thrwMessager}
1886 * and in the stack trace given by {@link #stacktrace}.
1887 * <p>
1888 * This class is also a {@link ListSelectionListener}
1889 * which opens <code>emacsclient</code>
1890 * with the source file and the line number
1891 * given by the selected stack trace element.
1892 */
1893 static class StackTraceLister implements ListSelectionListener {
1894
1895 /* ---------------------------------------------------------------- *
1896 * attributes. *
1897 * ---------------------------------------------------------------- */
1898
1899 /**
1900 * Represents a throwable or is <code>null</code>
1901 * if no throwable is represented.
1902 * This is also the initial value.
1903 */
1904 private Throwable thrw;
1905
1906 /**
1907 * Is empty iff {@link #thrw} is <code>null</code>
1908 * and contains the string representation
1909 * of the represented throwable {@link #thrw}.
1910 */
1911 private final JLabel thrwMessager;
1912
1913 /**
1914 * Is either empty or contains the stacktrace
1915 * of the represented throwable {@link #thrw}.
1916 */
1917 private final DefaultListModel<String> stacktrace;
1918
1919 /**
1920 * The selection of this stack trace:
1921 * This is either empty
1922 * (which is mandatory for empty {@link #thrwMessager})
1923 * or selects a single stack element.
1924 * If so, by selection <code>emacsclient</code> is started
1925 * at the place the stack element points to.
1926 */
1927 private final ListSelectionModel stackElemSelection;
1928
1929
1930 /* ---------------------------------------------------------------- *
1931 * constructors. *
1932 * ---------------------------------------------------------------- */
1933
1934 /**
1935 * Creates a new StackTraceLister with empty throwable.
1936 */
1937 StackTraceLister() {
1938 // init thrw and its string representation.
1939 this.thrw = null;
1940 this.thrwMessager = new JLabel("", SwingConstants.LEADING);
1941
1942 // init stacktrace
1943 this.stacktrace = new DefaultListModel<String>();
1944 // init stackElemSelection
1945 this.stackElemSelection = new DefaultListSelectionModel();
1946 this.stackElemSelection
1947 .setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
1948 this.stackElemSelection.setValueIsAdjusting(true);
1949 this.stackElemSelection
1950 .addListSelectionListener(this);
1951 }
1952
1953 /* ---------------------------------------------------------------- *
1954 * methods. *
1955 * ---------------------------------------------------------------- */
1956
1957 /**
1958 * Returns a graphical representation of this StackTraceLister.
1959 */
1960 Component getStackTraceBox() {
1961 JList<String> stacktraceList = new JList<String>(this.stacktrace);
1962 stacktraceList.setSelectionModel(this.stackElemSelection);
1963
1964 Box stackTraceBox = Box.createVerticalBox();
1965 stackTraceBox.add(this.thrwMessager);
1966 stackTraceBox.add(new JScrollPane(stacktraceList));
1967 return stackTraceBox;
1968 }
1969
1970 /**
1971 * Clears the represented stack including {@link #thrw} and text.
1972 */
1973 void clearStack() {
1974 this.thrw = null;
1975 this.thrwMessager.setText("");
1976 //this.thrwMessager.setHorizontalAlignment(SwingConstants.LEADING);
1977 this.stacktrace.clear();
1978 this.stackElemSelection.clearSelection();
1979 }
1980
1981 /**
1982 * Represents the throwable <code>thrw</code>
1983 * if this is not <code>null</code>;
1984 * otherwise just clears this stack trace lister
1985 * as is done by {@link #clearStack()}.
1986 * The selection is cleared anyway.
1987 */
1988 void setStack(Throwable thrw) {
1989 clearStack();
1990 if (thrw == null) {
1991 // **** can only occur for rerun testcases
1992 // which eventually succeed.
1993 // and from testcase selected without exception,
1994 // as ignored testcases.
1995 // This is like clearing the stack trace
1996 return;
1997 }
1998
1999 this.thrw = thrw;
2000 this.thrwMessager.setText(this.thrw.toString());
2001 StackTraceElement[] stack = this.thrw.getStackTrace();
2002 for (int i = 0; i < stack.length; i++) {
2003 this.stacktrace.addElement(stack[i].toString());
2004 }
2005 }
2006
2007 /**
2008 * If an entry is selected, move with emacs to the according place.
2009 */
2010 // api-docs inherited from ListSelectionListener
2011 public void valueChanged(ListSelectionEvent lse) {
2012 // ***Here, the stacktrace cannot be empty. *** not right.
2013 int selIndex = this.stackElemSelection.getMinSelectionIndex();
2014 if (selIndex == -1) {
2015 // this can come only from invoking clear.
2016 // *** not ok because event is fired although nothing happened
2017 //assert this.stacktrace.getSize() == 0;
2018 return;
2019 }
2020
2021 StackTraceElement location = this.thrw.getStackTrace()[selIndex];
2022 System.out.println("location: " + location);
2023 JavaPath jPath = new JavaPath(System.getProperty(SOURCEPATH));
2024 File toBeLoaded = jPath.getFile(location.getClassName(),
2025 JavaPath.ClsSrc.Source);
2026 if (toBeLoaded == null) {
2027 return;
2028 }
2029
2030 // move with emacs to the selected position
2031 try {
2032 Runtime.getRuntime().exec(new String[] {
2033 "emacsclient",
2034 "--no-wait",
2035 "+" + location.getLineNumber(),
2036 toBeLoaded.getPath()
2037 },
2038 // environment variables and working directory
2039 // inherited from the current process
2040 null, null);
2041 } catch (IOException ioe) {
2042 System.err.println("Failed to invoke emacs. ");
2043 ioe.printStackTrace();
2044 }
2045 }
2046 } // class StackTraceLister
2047
2048 /**
2049 * A listener to the switching of a tab in the foreground;
2050 * the other in the background.
2051 */
2052 static class TabChangeListener implements ChangeListener {
2053
2054 /* ---------------------------------------------------------------- *
2055 * constants. *
2056 * ---------------------------------------------------------------- */
2057
2058 /**
2059 * The index of the tab selected initially.
2060 * This may be 0 or 1.
2061 * Note that the failure list is the 0th tab for some reason
2062 * as seen from {@link GUIRunner#setCenter(Actions)}.
2063 * It must be the one initially in the foreground.
2064 */
2065 private static final int SEL_IND1 = 0;
2066
2067 /**
2068 * Registered with the unselected Selector.
2069 */
2070 private static final Selector EMPTY_SELECTOR =
2071 new Selector() {
2072 public void setSelection(int index) {
2073 // is empty.
2074 }
2075 public void clearSelection() {
2076 // is empty.
2077 }
2078 public void registerSelector(Selector sel) {
2079 throw new IllegalStateException();
2080 }
2081 }; // EMPTY_SELECTOR
2082
2083 /* ---------------------------------------------------------------- *
2084 * attributes. *
2085 * ---------------------------------------------------------------- */
2086
2087 private final JTabbedPane tabbedPane;
2088 private final Selector[] selectors;
2089
2090 /* ---------------------------------------------------------------- *
2091 * constructors. *
2092 * ---------------------------------------------------------------- */
2093
2094 TabChangeListener(JTabbedPane tabbedPane,
2095 TestCaseLister testCaseLister,
2096 HierarchyWrapper testHierarchy) {
2097 this.tabbedPane = tabbedPane;
2098 this.selectors = new Selector[] {
2099 testHierarchy,
2100 testCaseLister,
2101 };
2102 setSelUnSel(SEL_IND1);
2103 }
2104
2105 /**
2106 * Makes the tab with the given index in the foreground
2107 * and the other one in the background.
2108 * This must be invoked
2109 * with the index of the tab initially in the foreground
2110 * and if the tab is changed, this methd must be invoked accordingly.
2111 * <p>
2112 * As described
2113 * for {@link GUIRunner.Selector#registerSelector(GUIRunner.Selector)},
2114 * the tab in the foreground receives its selection events directly,
2115 * whereas the one in the background
2116 * must be registered at the one in the foreground
2117 * to receive the according selections.
2118 * For sake of unification, the one in the background also sends
2119 * selection events to the one which is registered,
2120 * but this is just {@link #EMPTY_SELECTOR}.
2121 *
2122 * @param index
2123 * the index of the tab/Selector in the foreground.
2124 * This may be either 0 or 1.
2125 */
2126 private void setSelUnSel(int index) {
2127 assert index == 0 || index == 1;
2128 Selector sel = this.selectors[ index];
2129 Selector notSel = this.selectors[1 - index];
2130 sel .registerSelector(notSel);
2131 notSel.registerSelector(EMPTY_SELECTOR);
2132 }
2133
2134 /* ---------------------------------------------------------------- *
2135 * methods implementing ChangeListener. *
2136 * ---------------------------------------------------------------- */
2137
2138 // Invoked when the target of the listener has changed its state.
2139 public void stateChanged(ChangeEvent che) {
2140 assert this.tabbedPane == che.getSource();
2141 setSelUnSel(this.tabbedPane.getSelectedIndex());
2142 }
2143 } // class TabChangeListener
2144
2145 /**
2146 * Provides a method to choose a test class.
2147 * This is triggered by clicking on the 'open' icon
2148 * in the enclosing GUIRunner.
2149 * Class ClassChooser is based
2150 * on the {@link JFileChooser} {@link #clsFileChooser}
2151 * It is not static because of its access to {@link GUIRunner#frame}.
2152 */
2153 class ClassChooser {
2154
2155 /* ---------------------------------------------------------------- *
2156 * attributes. *
2157 * ---------------------------------------------------------------- */
2158
2159 /**
2160 * File chooser for class files representing test classes.
2161 */
2162 private final JFileChooser clsFileChooser;
2163
2164 /**
2165 * The class path for test classes.
2166 */
2167 private final JavaPath clsPath;
2168
2169 /* ---------------------------------------------------------------- *
2170 * constructors. *
2171 * ---------------------------------------------------------------- */
2172
2173 ClassChooser() {
2174 String classpath = System.getProperty(CHOOSE_CLASSPATH);
2175 this.clsPath = new JavaPath(classpath);
2176 this.clsFileChooser = new JFileChooser(classpath);
2177 this.clsFileChooser.setMultiSelectionEnabled(false);
2178 this.clsFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
2179 this.clsFileChooser.setFileHidingEnabled(true);
2180 // File filter accepting directories
2181 // and files ending with class but without inner classes
2182 this.clsFileChooser.setFileFilter(new FileFilter() {
2183 public boolean accept(File file) {
2184 if (!file.isFile()) {
2185 // directories accepted
2186 return true;
2187 }
2188 String name = file.getName();
2189 // file accepted if class file but no inner class
2190 return name.endsWith(".class") && !name.contains("$");
2191 }
2192 public String getDescription() {
2193 return "Java class files ending with .class ";
2194 }
2195 });
2196 this.clsFileChooser.setDialogType(JFileChooser.OPEN_DIALOG);
2197 this.clsFileChooser.setDialogTitle("Testclasses");
2198 }
2199
2200 /* ---------------------------------------------------------------- *
2201 * methods. *
2202 * ---------------------------------------------------------------- */
2203
2204
2205 /**
2206 * Opens the class chooser dialog
2207 * and returns the choosen class or <code>null</code>
2208 * if either no file was selected,
2209 * a file is selected which does not exist
2210 * or does not represent a java class file.
2211 */
2212 String getChosenClass() {
2213 if (this.clsFileChooser.showOpenDialog(GUIRunner.this.frame) !=
2214 JFileChooser.APPROVE_OPTION) {
2215 //
2216 System.out.println("selection not approved.");
2217 return null;
2218 }
2219 File clsFile = this.clsFileChooser.getSelectedFile();
2220 if (!clsFile.exists()) {
2221 System.out.println("Approved file '" + clsFile
2222 + "' does not exist.");
2223 return null;
2224 }
2225 return this.clsPath.absFile2cls(clsFile,
2226 JavaPath.ClsSrc.Class);
2227 }
2228 } // class ClassChooser
2229
2230
2231
2232 /* -------------------------------------------------------------------- *
2233 * fields. *
2234 * -------------------------------------------------------------------- */
2235
2236 /**
2237 * The frame in which the Testrunne GUI is displayed.
2238 * Since this is the outermost component,
2239 * this is used in {@link GUIRunner.ClassChooser}
2240 * and in {@link #updateG()}.
2241 */
2242 private final JFrame frame;
2243
2244 /**
2245 * A chooser for testclasses.
2246 */
2247 private final ClassChooser classChooser;
2248
2249 /**
2250 * Contains the fully qualified name of the testclasse
2251 * currently under consideration.
2252 */
2253 private final JLabel className;
2254
2255 /**
2256 * The progress bar indicating how much of the testcases already passed.
2257 */
2258 private final TestProgressBar progress;
2259
2260 /**
2261 * Represents the table displaying the number of runs,
2262 * both passed and to be performed altogether,
2263 * the tests already ignored and those in which an error was found.
2264 */
2265 private final StatisticsTestState statisticsTestState;
2266
2267 /**
2268 * Represents the list of testcases already failed.
2269 * This is shown in one of the tabs.
2270 */
2271 private TestCaseLister testCaseLister;
2272
2273 /**
2274 * Represents the hierarchy of testcases.
2275 * This is shown in one of the tabs.
2276 */
2277 private HierarchyWrapper testHierarchy;
2278
2279 /**
2280 * Represents the split pane with a tabbed pane as top component
2281 * and the stack trace box as bottom component.
2282 * The tabbed pane contains a tab for the {@link #testCaseLister}
2283 * and another one for the {@link #testHierarchy}.
2284 * The stack trace box is given by
2285 * {@link GUIRunner.TestCaseLister#getStackTraceBox()}.
2286 * <p>
2287 * This field is used to reset to preferred size.
2288 */
2289 private JSplitPane splitPane;
2290
2291 /**
2292 * Contains a status message.
2293 */
2294 private final JLabel statusBar;
2295
2296
2297 /* -------------------------------------------------------------------- *
2298 * constructor with its methods. *
2299 * -------------------------------------------------------------------- */
2300
2301 // TBC: not used.
2302 /**
2303 * Opens the class chooser dialog
2304 * and returns the choosen class or <code>null</code>
2305 * if either no file was selected,
2306 * a file is selected which does not exist
2307 * or does not represent a java class file.
2308 * <p>
2309 * Essentially delegates
2310 * to {@link GUIRunner.ClassChooser#getChosenClass()}.
2311 */
2312 String openClassChooser() {
2313 return this.classChooser.getChosenClass();
2314 }
2315
2316 /**
2317 * Creates a new <code>GUIRunner</code> instance
2318 * which defines components with unloaded test class.
2319 * Loading is done
2320 * by invoking {@link #testClassStructureLoaded(Description)}.
2321 */
2322 GUIRunner(Actions actions) {
2323 assert SwingUtilities.isEventDispatchThread();
2324 this.frame = new JFrame("JUnit GUI");
2325 this.frame.setIconImage(SMALL_LOGO_ICON.getImage());
2326 this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
2327
2328 this.classChooser = new ClassChooser();
2329
2330 setMenuBar(actions);
2331
2332 this.className = new JLabel("class name");
2333 this.statusBar = new JLabel("status bar");
2334
2335 Container content = this.frame.getContentPane();
2336 content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
2337 //content.setLayout(new BorderLayout());
2338
2339
2340 this.statisticsTestState = new StatisticsTestState();
2341
2342 setCenter(actions);
2343
2344 Box testClassName = Box.createHorizontalBox();
2345 // add classname
2346 testClassName.add(new JLabel("Test class name: "));
2347 testClassName.add(this.className);
2348 testClassName.add(Box.createHorizontalGlue());
2349 this.frame.add(testClassName);
2350 //this.frame.add(new JLabel("Test class name: "));
2351 //this.frame.add(this.className);
2352
2353 // add progress bar
2354 Box progLogo = Box.createHorizontalBox();
2355 this.progress = new TestProgressBar();
2356 progLogo.add(this.progress);
2357 progLogo.add(HORIZ_BOUNDARY2);
2358 progLogo.add(new JLabel(LOGO_ICON));
2359 progLogo.add(HORIZ_BOUNDARY);
2360 this.frame.add(progLogo);
2361
2362 // add separator and line with runs statistics
2363 this.frame.add(this.statisticsTestState.getBox());
2364 this.frame.add(new JSeparator());
2365 this.frame.add(VERTI_BOUNDARY);
2366
2367 this.frame.add(Box.createVerticalGlue());
2368
2369 // add results-line
2370 Box results = Box.createHorizontalBox();
2371 results.add(HORIZ_BOUNDARY);
2372 results.add(new JLabel("Results:"));
2373 results.add(Box.createGlue());
2374 this.frame.add(results);
2375 //this.frame.add(new JLabel("Results:"));
2376
2377 //this.frame.add(Box.createGlue());
2378
2379 // add split pane and Run2-button
2380 Box resRun2 = Box.createHorizontalBox();
2381 resRun2.add(HORIZ_BOUNDARY);
2382 resRun2.add(this.splitPane);
2383 resRun2.add(Box.createGlue());
2384 resRun2.add(new JButton("Run2"));
2385 resRun2.add(HORIZ_BOUNDARY);
2386 this.frame.add(resRun2);
2387 //this.frame.add(this.splitPane);
2388
2389 // add status bar and Exit-button
2390 Box statExit = Box.createHorizontalBox();
2391 statExit.add(HORIZ_BOUNDARY);
2392 statExit.add(this.statusBar);
2393 statExit.add(Box.createGlue());
2394 statExit.add(new JButton("Exit"));
2395 statExit.add(HORIZ_BOUNDARY);
2396
2397 this.frame.add(statExit);
2398
2399 this.frame.setSize(HORIZ_FRAME, VERTI_FRAME);
2400 //this.frame.pack();
2401 this.frame.setVisible(true);
2402 }
2403
2404 final void setMenuBar(Actions actions) {
2405 JMenuBar menubar = new JMenuBar();
2406 //JMenu file = new JMenu("File");
2407 //menubar.add(file);
2408
2409 menubar.add(new JMenuItem(actions.getOpenAction()));
2410 menubar.add(new JMenuItem(actions.getStartAction()));
2411 menubar.add(new JMenuItem(actions.getStopAction()));
2412 menubar.add(new JMenuItem(actions.getBreakAction()));
2413 menubar.add(new JMenuItem(actions.getExitAction()));
2414 // **** hack to have buttons aligned left
2415 menubar.add(new JLabel(" "));
2416
2417 // menubar.add(new JMenuItem(exit));
2418 // menubar.add(new JMenu("help"));
2419
2420
2421 //JMenu help = new JMenu("Help");
2422 //menubar.setHelpMenu(help);
2423
2424 this.frame.setJMenuBar(menubar);
2425 }
2426
2427 final void setCenter(Actions actions) {
2428 final JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.BOTTOM);
2429 // add FailureList
2430 this.testCaseLister = new TestCaseLister();
2431 // add Tree as second because otherwise a problem becomes visible
2432 this.testHierarchy = new HierarchyWrapper(actions,
2433 this.testCaseLister);
2434
2435
2436 tabbedPane.addTab("Test Hierarchy",
2437 HIERARCHY_ICON,
2438 new JScrollPane(this.testHierarchy.getTree()));
2439 tabbedPane.addTab("Failures",
2440 Quality.Error.getIcon(),
2441 new JScrollPane(this.testCaseLister.getFailList()));
2442 tabbedPane
2443 .addChangeListener(new TabChangeListener(tabbedPane,
2444 this.testCaseLister,
2445 this.testHierarchy));
2446
2447 // make up SplitPane
2448 this.splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
2449 this.splitPane.setTopComponent(tabbedPane);
2450 this.splitPane.setBottomComponent
2451 (this.testCaseLister.getStackTraceBox());
2452 }
2453
2454 /* -------------------------------------------------------------------- *
2455 * methods not tied to constructor. *
2456 * -------------------------------------------------------------------- */
2457
2458 private void updateG() {
2459 this.frame.update(this.frame.getGraphics());
2460 }
2461
2462 /**
2463 * Sets the message <code>msg</code> to the status bar.
2464 */
2465 void setStatus(String msg) {
2466 // assert SwingUtilities.isEventDispatchThread();
2467 this.statusBar.setText(msg);
2468 updateG();
2469 }
2470
2471 /**
2472 * Sets a status message describing <code>testCase</code>
2473 * to the status bar using {@link #setStatus(String)}.
2474 */
2475 private void setStatus(TestCase testCase) {
2476 setStatus("Test " +
2477 testCase.getQuality().getMessage() + ": " +
2478 testCase.getDesc());
2479 }
2480
2481 /**
2482 * Notifies that an atomic test is started or ignored.
2483 *
2484 * @param qual
2485 * the quality of the testcase:
2486 * This is either {@link Quality#Started} or {@link Quality#Ignored}.
2487 */
2488 TestCase noteTestStartedI(Quality qual) {
2489
2490 // assert SwingUtilities.isEventDispatchThread();
2491 assert qual == Quality.Started
2492 || qual == Quality.Ignored;
2493 TestCase testCase = this.testHierarchy.noteTestStartedI(qual);
2494 setStatus(testCase);
2495 return testCase;
2496 }
2497
2498 /**
2499 * Notifies that the singular test <code>testCase</code> is finished,
2500 * whether successful or not or that a test is ignored
2501 * after invoking {@link #noteTestStartedI(Quality)}
2502 * with parameter {@link Quality#Ignored}.
2503 *
2504 * @param testCase
2505 * The testcase comprising the result of the singular test finished.
2506 */
2507 void noteReportResult(TestCase testCase) {
2508 // assert SwingUtilities.isEventDispatchThread();
2509
2510 assert testCase.getQuality() == Quality.Ignored
2511 || testCase.getQuality() == Quality.Invalidated
2512 || testCase.getQuality() == Quality.Success
2513 || testCase.getQuality() == Quality.Failure
2514 || testCase.getQuality() == Quality.Error;
2515
2516 this.testHierarchy .noteReportResult(testCase);
2517 this.progress .noteReportResult(testCase);
2518 this.statisticsTestState.noteReportResult(testCase);
2519 this.testCaseLister .noteReportResult(testCase);
2520 setStatus (testCase);
2521
2522 //this.splitPane .resetToPreferredSizes();
2523 }
2524
2525 /**
2526 * Notifies that the structure of the test class may have been updated.
2527 *
2528 * @param desc
2529 * a description of the test structure defined in the test class
2530 * which is a hierarchy of suites and singular tests.
2531 * @see #testRunStarted(Description)
2532 */
2533 void testClassStructureLoaded(Description desc) {
2534 // assert SwingUtilities.isEventDispatchThread();
2535 setStatus("testClassLoaded(");
2536
2537 // **** strange way to obtain the classname *****
2538 this.className.setText(desc.getChildren().get(0).toString());
2539 TestCase testCaseAll = new TestCase(desc);
2540
2541 this.testHierarchy .initClassStructure(testCaseAll);
2542 this.testCaseLister .initClassStructure();
2543 this.progress .initClassStructure(desc);
2544 this.statisticsTestState.initClassStructure(desc);
2545 }
2546
2547 /**
2548 * Notifies that a test with structure given by <code>desc</code>
2549 * is going to be run next.
2550 * Called before any tests have been run.
2551 * At least once before this one,
2552 * {@link #testClassStructureLoaded(Description)} has been invoked.
2553 *
2554 * @param desc
2555 * describes the (hierarchy of) tests to be run.
2556 * This is a sub-hierarchy of the one given by the test class.
2557 */
2558 void testRunStarted(Description desc) {
2559 // assert SwingUtilities.isEventDispatchThread();
2560 // **** strange way to obtain the classname *****
2561 setStatus("testRunStarted(");
2562 this.testHierarchy.getActions().setEnableForRun(true); //running
2563
2564 TestCase root = this.testHierarchy.getRoot();
2565
2566 this.testHierarchy .startTestRun(desc);
2567 this.testCaseLister .startTestRun(); // purely formally
2568 this.progress .startTestRun(root);
2569 this.statisticsTestState.startTestRun(root);
2570 }
2571
2572 /**
2573 * Notifies that a test has been finished sufficiently or not.
2574 *
2575 * @param runTime
2576 * the time execution of the test took in milliseconds.
2577 */
2578 void testRunFinished(long runTime) {
2579 setStatus("testRunFinished(required: " +
2580 runTime + "ms. ");
2581 this.testHierarchy.getActions().setEnableForRun(false); //!running
2582 }
2583
2584 /**
2585 * Notifies that a test has been aborted by the user.
2586 */
2587 void testRunAborted() {
2588 setStatus("testRunAborted(");
2589 this.testHierarchy.getActions().setEnableForRun(false); //!running
2590 }
2591
2592 } // NOPMD coupling is not that high!
2593