1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.atlassian.theplugin.idea.bamboo;
18
19 import com.atlassian.theplugin.commons.bamboo.TestDetails;
20 import com.atlassian.theplugin.idea.Constants;
21 import com.atlassian.theplugin.util.ColorToHtml;
22 import com.intellij.execution.*;
23 import com.intellij.execution.configurations.ConfigurationFactory;
24 import com.intellij.execution.filters.TextConsoleBuilder;
25 import com.intellij.execution.filters.TextConsoleBuilderFactory;
26 import com.intellij.execution.impl.RunManagerImpl;
27 import com.intellij.execution.junit.JUnitConfiguration;
28 import com.intellij.execution.runners.JavaProgramRunner;
29 import com.intellij.execution.runners.RunStrategy;
30 import com.intellij.execution.ui.ConsoleView;
31 import com.intellij.execution.ui.ConsoleViewContentType;
32 import com.intellij.openapi.actionSystem.ActionGroup;
33 import com.intellij.openapi.actionSystem.ActionManager;
34 import com.intellij.openapi.actionSystem.ActionToolbar;
35 import com.intellij.openapi.actionSystem.AnActionEvent;
36 import com.intellij.openapi.project.Project;
37 import com.intellij.openapi.ui.Splitter;
38 import com.intellij.openapi.util.IconLoader;
39 import com.intellij.openapi.wm.ToolWindow;
40 import com.intellij.openapi.wm.ToolWindowAnchor;
41 import com.intellij.openapi.wm.ToolWindowManager;
42 import com.intellij.peer.PeerFactory;
43 import com.intellij.psi.PsiClass;
44 import com.intellij.psi.PsiManager;
45 import com.intellij.psi.PsiMethod;
46 import com.intellij.psi.search.GlobalSearchScope;
47 import com.intellij.ui.content.Content;
48 import com.intellij.util.ui.UIUtil;
49
50 import javax.swing.*;
51 import javax.swing.event.TreeSelectionEvent;
52 import javax.swing.event.TreeSelectionListener;
53 import javax.swing.tree.*;
54 import java.awt.*;
55 import java.awt.event.MouseAdapter;
56 import java.awt.event.MouseEvent;
57 import java.util.HashMap;
58 import java.util.LinkedHashMap;
59 import java.util.List;
60 import java.util.Map;
61
62 public final class TestResultsToolWindow {
63 private final Project project;
64
65 public interface TestTree extends Expandable {
66 boolean PASSED_TESTS_VISIBLE_DEFAULT = false;
67
68 void setPassedTestsVisible(boolean visible);
69 boolean isPassedTestsVisible();
70 boolean createTestConfiguration(JUnitConfiguration configuration);
71 }
72
73 private static final String TOOL_WINDOW_TITLE = "Bamboo Failed Tests";
74 private static final Icon TEST_PASSED_ICON = IconLoader.getIcon("/runConfigurations/testPassed.png");
75 private static final Icon TEST_FAILED_ICON = IconLoader.getIcon("/runConfigurations/testFailed.png");
76
77 private HashMap<String, TestDetailsPanel> panelMap = new HashMap<String, TestDetailsPanel>();
78
79 public TestResultsToolWindow(Project project) {
80 this.project = project;
81 }
82
83
84 public TestTree getTestTree(String name) {
85 return panelMap.get(name);
86 }
87
88 public void showTestResults(String buildKey, String buildNumber,
89 List<TestDetails> failedTests, List<TestDetails> succeededTests) {
90 TestDetailsPanel detailsPanel;
91 String contentKey = buildKey + "-" + buildNumber;
92
93 ToolWindowManager twm = ToolWindowManager.getInstance(project);
94 ToolWindow testDetailsToolWindow = twm.getToolWindow(TOOL_WINDOW_TITLE);
95 if (testDetailsToolWindow == null) {
96 testDetailsToolWindow = twm.registerToolWindow(TOOL_WINDOW_TITLE, true, ToolWindowAnchor.BOTTOM);
97 testDetailsToolWindow.setIcon(Constants.BAMBOO_TRACE_ICON);
98 }
99
100 Content content = testDetailsToolWindow.getContentManager().findContent(contentKey);
101
102 if (content == null) {
103 detailsPanel = new TestDetailsPanel(contentKey, failedTests, succeededTests);
104 panelMap.remove(contentKey);
105 panelMap.put(contentKey, detailsPanel);
106
107 PeerFactory peerFactory = PeerFactory.getInstance();
108 content = peerFactory.getContentFactory().createContent(detailsPanel, contentKey, true);
109 content.setIcon(Constants.BAMBOO_TRACE_ICON);
110 content.putUserData(com.intellij.openapi.wm.ToolWindow.SHOW_CONTENT_ICON, Boolean.TRUE);
111 testDetailsToolWindow.getContentManager().addContent(content);
112 }
113
114 testDetailsToolWindow.getContentManager().setSelectedContent(content);
115 testDetailsToolWindow.show(null);
116 }
117
118 public void runFailedTests(AnActionEvent ev, boolean debug) {
119 RunManagerImpl runManager = (RunManagerImpl) RunManager.getInstance(project);
120 ConfigurationFactory factory = runManager.getFactory("JUnit", null);
121 RunnerAndConfigurationSettings settings = runManager.createRunConfiguration("test from bamboo", factory);
122 JUnitConfiguration conf = (JUnitConfiguration) settings.getConfiguration();
123
124 TestTree tree = getTestTree(ev.getPlace());
125 if (tree == null) {
126 return;
127 }
128 if (!tree.createTestConfiguration(conf)) {
129 return;
130 }
131
132 JavaProgramRunner runner;
133 if (debug) {
134 runner = ExecutionRegistry.getInstance().getDebuggerRunner();
135 } else {
136 runner = ExecutionRegistry.getInstance().getDefaultRunner();
137 }
138
139 RunStrategy strategy = RunStrategy.getInstance();
140 try {
141 strategy.execute(settings, runner, ev.getDataContext());
142 } catch (ExecutionException e) {
143 e.printStackTrace();
144 }
145 }
146
147 public boolean canRunFailedTests(AnActionEvent ev) {
148 TestTree tree = getTestTree(ev.getPlace());
149 if (tree == null) {
150 return false;
151 }
152 return tree.createTestConfiguration(null);
153 }
154
155 private abstract class AbstractTreeNode extends DefaultMutableTreeNode {
156 public AbstractTreeNode(String s) {
157 super(s);
158 }
159
160 public abstract void selected();
161 public abstract boolean isFailed();
162 public String getTestStats() {
163 return "";
164 }
165
166 public abstract void navigate();
167
168 public abstract boolean createTestConfiguration(JUnitConfiguration configuration);
169 }
170
171 private class TestDetailsPanel extends JPanel implements TestTree {
172 private static final float SPLIT_RATIO = 0.3f;
173
174 private JTree tree;
175 private JScrollPane scroll;
176
177 private List<TestDetails> succeededTests;
178 private List<TestDetails> failedTests;
179
180 private ConsoleView console;
181 private boolean passedTestsVisible;
182
183 private abstract class NonLeafNode extends AbstractTreeNode {
184 protected int totalTests;
185 protected int failedTests;
186
187 public NonLeafNode(String s, int totalTests, int failedTests) {
188 super(s);
189 this.totalTests = totalTests;
190 this.failedTests = failedTests;
191 }
192
193 @Override
194 public void selected() {
195 print("");
196 }
197
198 @Override
199 public boolean isFailed() {
200 return failedTests > 0;
201 }
202
203 @Override
204 public String getTestStats() {
205 return " (" + failedTests + " out of " + totalTests + " failed)";
206 }
207
208 public void addFailedTest() {
209 ++failedTests;
210 }
211
212 public void addTest() {
213 ++totalTests;
214 }
215
216 public int getFailedTests() {
217 return failedTests;
218 }
219
220 public int getTotalTests() {
221 return totalTests;
222 }
223 }
224
225 private class PackageNode extends NonLeafNode {
226 public PackageNode(String s, int totalTests, int failedTests) {
227 super(s, totalTests, failedTests);
228 }
229
230 @Override
231 public void navigate() {
232
233 }
234
235 public boolean createTestConfiguration(JUnitConfiguration configuration) {
236
237 return false;
238 }
239 }
240
241 private class ClassNode extends NonLeafNode {
242 private String className;
243
244 public ClassNode(String fqcn, int totalTests, int failedTests) {
245 super(fqcn.substring(fqcn.lastIndexOf('.') + 1), totalTests, failedTests);
246 className = fqcn;
247 }
248
249 @Override
250 public void navigate() {
251 PsiClass cls = PsiManager.getInstance(project).findClass(className,
252 GlobalSearchScope.allScope(project));
253 if (cls != null) {
254 cls.navigate(true);
255 }
256 }
257
258 public boolean createTestConfiguration(JUnitConfiguration configuration) {
259 PsiManager mgr = PsiManager.getInstance(project);
260
261 PsiClass cls = mgr.findClass(className, GlobalSearchScope.allScope(project));
262 if (cls == null) {
263 return false;
264 }
265 if (configuration != null) {
266 configuration.beClassConfiguration(cls);
267 }
268 return true;
269 }
270 }
271
272 private abstract class TestNode extends AbstractTreeNode {
273 protected TestDetails details;
274
275 public TestNode(TestDetails details) {
276 super(details.getTestMethodName());
277 this.details = details;
278 }
279
280 private PsiMethod getMethod() {
281 PsiClass cls = PsiManager.getInstance(project).findClass(details.getTestClassName(),
282 GlobalSearchScope.allScope(project));
283 if (cls == null) {
284 return null;
285 }
286 PsiMethod[] methods = cls.findMethodsByName(details.getTestMethodName(), false);
287 if (methods.length == 0 || methods[0] == null) {
288 return null;
289 }
290
291 return methods[0];
292 }
293
294 @Override
295 public void navigate() {
296 PsiMethod m = getMethod();
297 if (m != null) {
298 m.navigate(true);
299 }
300 }
301
302 public boolean createTestConfiguration(JUnitConfiguration configuration) {
303 PsiMethod m = getMethod();
304
305 if (m != null) {
306 if (configuration != null) {
307 configuration.beMethodConfiguration(PsiLocation.fromPsiElement(project, m));
308 }
309 return true;
310 }
311 return false;
312 }
313 }
314
315 private class TestErrorInfoNode extends TestNode {
316 public TestErrorInfoNode(TestDetails details) {
317 super(details);
318 }
319
320 @Override
321 public void selected() {
322 print(details.getErrors());
323 }
324
325 @Override
326 public boolean isFailed() {
327 return true;
328 }
329 }
330
331 private class TestSuccessInfoNode extends TestNode {
332 public TestSuccessInfoNode(TestDetails details) {
333 super(details);
334 }
335
336 @Override
337 public void selected() {
338 print("Test successful");
339 }
340
341 @Override
342 public boolean isFailed() {
343 return false;
344 }
345 }
346
347 private JTree createTestTree() {
348 NonLeafNode root = new PackageNode("All Tests",
349 failedTests.size() + succeededTests.size(), failedTests.size());
350
351 Map<String, NonLeafNode> packages = new LinkedHashMap<String, NonLeafNode>();
352 for (TestDetails d : succeededTests) {
353 String fqcn = d.getTestClassName();
354 String pkg = fqcn.substring(0, fqcn.lastIndexOf('.'));
355 NonLeafNode n = packages.get(pkg);
356 if (n == null) {
357 packages.put(pkg, new PackageNode(pkg, 1, 0));
358 } else {
359 n.addTest();
360 }
361 }
362 for (TestDetails d : failedTests) {
363 String fqcn = d.getTestClassName();
364 String pkg = fqcn.substring(0, fqcn.lastIndexOf('.'));
365 NonLeafNode n = packages.get(pkg);
366 if (n == null) {
367 packages.put(pkg, new PackageNode(pkg, 1, 1));
368 } else {
369 n.addTest();
370 n.addFailedTest();
371 }
372 }
373
374 for (Map.Entry<String, NonLeafNode> p : packages.entrySet()) {
375 NonLeafNode n = p.getValue();
376 if (n.isFailed() || passedTestsVisible) {
377 root.add(n);
378 }
379 }
380
381 Map<String, NonLeafNode> classes = new LinkedHashMap<String, NonLeafNode>();
382 for (TestDetails d : succeededTests) {
383 String fqcn = d.getTestClassName();
384 NonLeafNode n = classes.get(fqcn);
385 if (n == null) {
386 classes.put(fqcn, new ClassNode(fqcn, 1, 0));
387 } else {
388 n.addTest();
389 }
390 }
391 for (TestDetails d : failedTests) {
392 String fqcn = d.getTestClassName();
393 NonLeafNode n = classes.get(fqcn);
394 if (n == null) {
395 classes.put(fqcn, new ClassNode(fqcn, 1, 1));
396 } else {
397 n.addTest();
398 n.addFailedTest();
399 }
400 }
401
402 for (Map.Entry<String, NonLeafNode> c : classes.entrySet()) {
403 String fqcn = c.getKey();
404 String pkg = fqcn.substring(0, fqcn.lastIndexOf('.'));
405 NonLeafNode n = c.getValue(); packages.get(pkg);
406 if (n.isFailed() || passedTestsVisible) {
407 packages.get(pkg).add(n);
408 }
409 }
410
411 if (passedTestsVisible) {
412 for (TestDetails d : succeededTests) {
413 String fqcn = d.getTestClassName();
414 AbstractTreeNode node = classes.get(fqcn);
415 node.add(new TestSuccessInfoNode(d));
416 }
417 }
418 for (TestDetails d : failedTests) {
419 String fqcn = d.getTestClassName();
420 AbstractTreeNode node = classes.get(fqcn);
421 node.add(new TestErrorInfoNode(d));
422 }
423
424 DefaultTreeModel tm = new DefaultTreeModel(root);
425 JTree testTree = new JTree(tm);
426 testTree.setRootVisible(true);
427 testTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
428 testTree.addTreeSelectionListener(new TreeSelectionListener() {
429 public void valueChanged(TreeSelectionEvent e) {
430 AbstractTreeNode errInfoNode = (AbstractTreeNode) e.getPath().getLastPathComponent();
431 errInfoNode.selected();
432 }
433 });
434 testTree.addMouseListener(new MouseAdapter() {
435
436 @Override
437 public void mouseClicked(MouseEvent e) {
438 if (e.getClickCount() >= 2) {
439 TreePath path = tree.getPathForLocation(e.getX(), e.getY());
440 AbstractTreeNode node = (AbstractTreeNode) path.getLastPathComponent();
441 node.navigate();
442 }
443 }
444 });
445
446 DefaultTreeCellRenderer renderer = new MyDefaultTreeCellRenderer();
447 testTree.setCellRenderer(renderer);
448
449 return testTree;
450 }
451
452 public TestDetailsPanel(String name, final List<TestDetails> failedTests,
453 final List<TestDetails> succeededTests) {
454 this.failedTests = failedTests;
455 this.succeededTests = succeededTests;
456
457 if (failedTests.size() > 0) {
458
459 Splitter split = new Splitter(false, SPLIT_RATIO);
460 split.setShowDividerControls(true);
461
462 setLayout(new GridBagLayout());
463 GridBagConstraints gbc = new GridBagConstraints();
464 gbc.gridx = 0;
465 gbc.gridy = 0;
466 gbc.weightx = 1.0;
467 gbc.weighty = 1.0;
468 gbc.fill = GridBagConstraints.BOTH;
469
470 JPanel treePanel = new JPanel();
471 treePanel.setLayout(new GridBagLayout());
472 GridBagConstraints gbc1 = new GridBagConstraints();
473
474 ActionManager manager = ActionManager.getInstance();
475 ActionGroup group = (ActionGroup) manager.getAction("ThePlugin.Bamboo.TestResultsToolBar");
476 ActionToolbar toolbar = manager.createActionToolbar(name, group, true);
477 JComponent comp = toolbar.getComponent();
478
479 gbc1.gridx = 0;
480 gbc1.gridy = 0;
481 gbc1.weightx = 1.0;
482 gbc1.weighty = 0.0;
483 gbc1.fill = GridBagConstraints.HORIZONTAL;
484
485 treePanel.add(comp, gbc1);
486
487 passedTestsVisible = PASSED_TESTS_VISIBLE_DEFAULT;
488
489 tree = createTestTree();
490 expand();
491 scroll = new JScrollPane(tree);
492
493 gbc1.gridy = 1;
494 gbc1.weighty = 1.0;
495 gbc1.fill = GridBagConstraints.BOTH;
496
497 treePanel.add(scroll, gbc1);
498
499 split.setFirstComponent(treePanel);
500
501 JPanel consolePanel = new JPanel();
502 consolePanel.setLayout(new GridBagLayout());
503
504 gbc1.gridy = 0;
505 gbc1.weighty = 0.0;
506 gbc1.fill = GridBagConstraints.NONE;
507 gbc1.anchor = GridBagConstraints.LINE_START;
508
509 JLabel label = new JLabel("Test Stack Trace");
510
511 Dimension d = label.getPreferredSize();
512 d.height = toolbar.getMaxButtonHeight();
513 label.setMinimumSize(d);
514 label.setPreferredSize(d);
515 consolePanel.add(label, gbc1);
516
517 TextConsoleBuilderFactory factory = TextConsoleBuilderFactory.getInstance();
518 TextConsoleBuilder builder = factory.createBuilder(project);
519 console = builder.getConsole();
520
521 gbc1.gridy = 1;
522 gbc1.weighty = 1.0;
523 gbc1.fill = GridBagConstraints.BOTH;
524
525 consolePanel.add(console.getComponent(), gbc1);
526
527 split.setSecondComponent(consolePanel);
528
529 add(split, gbc);
530 } else {
531 add(new JLabel("No failed failedTests in build " + name));
532 }
533 }
534
535 public void print(String txt) {
536 console.clear();
537 console.print(txt, ConsoleViewContentType.ERROR_OUTPUT);
538 }
539
540 public void expand() {
541 for (int row = 1; row < tree.getRowCount(); ++row) {
542 tree.expandRow(row);
543 }
544 }
545
546 public void collapse() {
547 for (int row = tree.getRowCount() - 1; row > 0; --row) {
548 tree.collapseRow(row);
549 }
550 }
551
552 public void setPassedTestsVisible(boolean visible) {
553 passedTestsVisible = visible;
554 tree = createTestTree();
555 expand();
556 scroll.setViewportView(tree);
557 }
558
559 public boolean isPassedTestsVisible() {
560 return passedTestsVisible;
561 }
562
563 public boolean createTestConfiguration(JUnitConfiguration configuration) {
564 TreePath p = tree.getSelectionPath();
565 return p != null && ((AbstractTreeNode) p.getLastPathComponent()).createTestConfiguration(configuration);
566 }
567 }
568
569 private static class MyDefaultTreeCellRenderer extends DefaultTreeCellRenderer {
570 @Override
571 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,
572 boolean expanded, boolean leaf, int row, boolean hasFocus) {
573
574 Component c = super.getTreeCellRendererComponent(
575 tree, value, selected, expanded, leaf, row, hasFocus);
576
577
578
579
580 try {
581 AbstractTreeNode node = (AbstractTreeNode) value;
582 if (node.isFailed()) {
583 setIcon(TEST_FAILED_ICON);
584 } else {
585 setIcon(TEST_PASSED_ICON);
586 }
587 Color statsColor = selected
588 ? UIUtil.getTreeSelectionForeground() : UIUtil.getInactiveTextColor();
589 StringBuilder txt = new StringBuilder();
590 txt.append("<html><body>");
591 txt.append(getText());
592 txt.append(" <font color=");
593 txt.append(ColorToHtml.getHtmlFromColor(statsColor));
594 txt.append("><i>");
595 txt.append(node.getTestStats());
596 txt.append("</i></font>");
597 txt.append("</body></html>");
598 setText(txt.toString());
599 } catch (ClassCastException e) {
600
601 setIcon(null);
602 }
603
604 return c;
605 }
606 }
607
608 }