1 package com
.intellij
.execution
.testframework
.sm
.runner
.ui
;
3 import com
.intellij
.execution
.configurations
.ConfigurationPerRunnerSettings
;
4 import com
.intellij
.execution
.configurations
.RunConfigurationBase
;
5 import com
.intellij
.execution
.configurations
.RunnerSettings
;
6 import com
.intellij
.execution
.testframework
.*;
7 import com
.intellij
.execution
.testframework
.sm
.SMRunnerUtil
;
8 import com
.intellij
.execution
.testframework
.sm
.runner
.SMTRunnerEventsListener
;
9 import com
.intellij
.execution
.testframework
.sm
.runner
.SMTRunnerTreeBuilder
;
10 import com
.intellij
.execution
.testframework
.sm
.runner
.SMTRunnerTreeStructure
;
11 import com
.intellij
.execution
.testframework
.sm
.runner
.SMTestProxy
;
12 import com
.intellij
.execution
.testframework
.sm
.runner
.ui
.statistics
.StatisticsPanel
;
13 import com
.intellij
.execution
.testframework
.ui
.AbstractTestTreeBuilder
;
14 import com
.intellij
.execution
.testframework
.ui
.PrintableTestProxy
;
15 import com
.intellij
.execution
.testframework
.ui
.TestResultsPanel
;
16 import com
.intellij
.execution
.testframework
.ui
.TestsProgressAnimator
;
17 import com
.intellij
.openapi
.Disposable
;
18 import com
.intellij
.openapi
.actionSystem
.AnAction
;
19 import com
.intellij
.openapi
.application
.ModalityState
;
20 import com
.intellij
.openapi
.progress
.util
.ColorProgressBar
;
21 import com
.intellij
.openapi
.project
.Project
;
22 import com
.intellij
.openapi
.util
.Disposer
;
23 import com
.intellij
.openapi
.wm
.IdeFocusManager
;
24 import org
.jetbrains
.annotations
.NonNls
;
25 import org
.jetbrains
.annotations
.NotNull
;
26 import org
.jetbrains
.annotations
.Nullable
;
29 import javax
.swing
.event
.TreeSelectionEvent
;
30 import javax
.swing
.event
.TreeSelectionListener
;
32 import java
.awt
.event
.InputEvent
;
33 import java
.awt
.event
.KeyEvent
;
34 import java
.util
.ArrayList
;
35 import java
.util
.LinkedHashSet
;
36 import java
.util
.List
;
40 * @author: Roman Chernyatchik
42 public class SMTestRunnerResultsForm
extends TestResultsPanel
implements TestFrameworkRunningModel
, TestResultsViewer
, SMTRunnerEventsListener
{
43 @NonNls private static final String DEFAULT_SM_RUNNER_SPLITTER_PROPERTY
= "SMTestRunner.Splitter.Proportion";
45 private SMTRunnerTestTreeView myTreeView
;
47 private TestsProgressAnimator myAnimator
;
50 * Fake parent suite for all tests and suites
52 private final SMTestProxy myTestsRootNode
;
53 private SMTRunnerTreeBuilder myTreeBuilder
;
54 private final TestConsoleProperties myConsoleProperties
;
56 private final List
<EventsListener
> myEventListeners
= new ArrayList
<EventsListener
>();
58 private PropagateSelectionHandler myShowStatisticForProxyHandler
;
60 private final Project myProject
;
62 private int myTestsCurrentCount
;
63 private int myTestsTotal
= 0;
64 private int myTestsFailuresCount
;
65 private long myStartTime
;
66 private long myEndTime
;
67 private StatisticsPanel myStatisticsPane
;
70 private String myCurrentCustomProgressCategory
;
71 private Set
<String
> myMentionedCategories
= new LinkedHashSet
<String
>();
73 public SMTestRunnerResultsForm(final RunConfigurationBase runConfiguration
,
74 @NotNull final JComponent console
,
75 final TestConsoleProperties consoleProperties
,
76 final RunnerSettings runnerSettings
,
77 final ConfigurationPerRunnerSettings configurationSettings
) {
78 this(runConfiguration
, console
, AnAction
.EMPTY_ARRAY
, consoleProperties
, runnerSettings
, configurationSettings
, null);
81 public SMTestRunnerResultsForm(final RunConfigurationBase runConfiguration
,
82 @NotNull final JComponent console
,
83 AnAction
[] consoleActions
,
84 final TestConsoleProperties consoleProperties
,
85 final RunnerSettings runnerSettings
,
86 final ConfigurationPerRunnerSettings configurationSettings
,
87 final String splitterPropertyName
) {
88 super(console
, consoleActions
, consoleProperties
, runnerSettings
, configurationSettings
,
89 splitterPropertyName
!= null ? DEFAULT_SM_RUNNER_SPLITTER_PROPERTY
: splitterPropertyName
, 0.5f
);
90 myConsoleProperties
= consoleProperties
;
92 myProject
= runConfiguration
.getProject();
94 //Create tests common suite root
95 //noinspection HardCodedStringLiteral
96 myTestsRootNode
= new SMTestProxy("[root]", true, null);
98 // Fire selection changed and move focus on SHIFT+ENTER
101 final ArrayList<Component> components = new ArrayList<Component>();
102 components.add(myTreeView);
103 components.add(myTabs.getComponent());
104 myContentPane.setFocusTraversalPolicy(new MyFocusTraversalPolicy(components));
105 myContentPane.setFocusCycleRoot(true);
110 public void initUI() {
113 final KeyStroke shiftEnterKey
= KeyStroke
.getKeyStroke(KeyEvent
.VK_ENTER
, InputEvent
.SHIFT_MASK
);
114 SMRunnerUtil
.registerAsAction(shiftEnterKey
, "show-statistics-for-test-proxy",
117 showStatisticsForSelectedProxy();
123 protected ToolbarPanel
createToolbarPanel() {
124 return new SMTRunnerToolbarPanel(myConsoleProperties
, myRunnerSettings
, myConfigurationSettings
, this, this);
127 protected JComponent
createTestTreeView() {
128 myTreeView
= new SMTRunnerTestTreeView();
130 myTreeView
.setLargeModel(true);
131 myTreeView
.attachToModel(this);
132 myTreeView
.setTestResultsViewer(this);
134 final SMTRunnerTreeStructure structure
= new SMTRunnerTreeStructure(myProject
, myTestsRootNode
);
135 myTreeBuilder
= new SMTRunnerTreeBuilder(myTreeView
, structure
);
136 Disposer
.register(this, myTreeBuilder
);
137 myAnimator
= new MyAnimator(this, myTreeBuilder
);
142 protected JComponent
createStatisticsPanel() {
144 final StatisticsPanel statisticsPane
= new StatisticsPanel(myProject
, this);
145 // handler to select in results viewer by statistics pane events
146 statisticsPane
.addPropagateSelectionListener(createSelectMeListener());
147 // handler to select test statistics pane by result viewer events
148 setShowStatisticForProxyHandler(statisticsPane
.createSelectMeListener());
150 myStatisticsPane
= statisticsPane
;
151 return myStatisticsPane
.getContentPane();
154 public StatisticsPanel
getStatisticsPane() {
155 return myStatisticsPane
;
158 public void addTestsTreeSelectionListener(final TreeSelectionListener listener
) {
159 myTreeView
.getSelectionModel().addTreeSelectionListener(listener
);
163 * Is used for navigation from tree view to other UI components
166 public void setShowStatisticForProxyHandler(final PropagateSelectionHandler handler
) {
167 myShowStatisticForProxyHandler
= handler
;
171 * Returns root node, fake parent suite for all tests and suites
175 public void onTestingStarted(@NotNull SMTestProxy testsRoot
) {
176 myAnimator
.setCurrentTestCase(myTestsRootNode
);
179 myStatusLine
.setStatusColor(ColorProgressBar
.GREEN
);
182 selectAndNotify(myTestsRootNode
);
184 myStartTime
= System
.currentTimeMillis();
187 fireOnTestingStarted();
190 public void onTestingFinished(@NotNull SMTestProxy testsRoot
) {
191 myEndTime
= System
.currentTimeMillis();
193 if (myTestsTotal
== 0) {
194 myTestsTotal
= myTestsCurrentCount
;
195 myStatusLine
.setFraction(1);
200 myAnimator
.stopMovie();
201 myTreeBuilder
.updateFromRoot();
203 LvcsHelper
.addLabel(this);
205 fireOnTestingFinished();
208 public void onTestsCountInSuite(final int count
) {
209 updateCountersAndProgressOnTestCount(count
, false);
213 * Adds test to tree and updates status line.
214 * Test proxy should be initialized, proxy parent must be some suite (already added to tree)
216 * @param testProxy Proxy
218 public void onTestStarted(@NotNull final SMTestProxy testProxy
) {
219 updateCountersAndProgressOnTestStarted(false);
221 _addTestOrSuite(testProxy
);
223 fireOnTestNodeAdded(testProxy
);
226 public void onTestFailed(@NotNull final SMTestProxy test
) {
227 updateCountersAndProgressOnTestFailed(false);
230 public void onTestIgnored(@NotNull final SMTestProxy test
) {
236 * Suite proxy should be initialized, proxy parent must be some suite (already added to tree)
237 * If parent is null, then suite will be added to tests root.
239 * @param newSuite Tests suite
241 public void onSuiteStarted(@NotNull final SMTestProxy newSuite
) {
242 _addTestOrSuite(newSuite
);
245 public void onCustomProgressTestsCategory(@Nullable String categoryName
, int testCount
) {
246 myCurrentCustomProgressCategory
= categoryName
;
247 updateCountersAndProgressOnTestCount(testCount
, true);
250 public void onCustomProgressTestStarted() {
251 updateCountersAndProgressOnTestStarted(true);
254 public void onCustomProgressTestFailed() {
255 updateCountersAndProgressOnTestFailed(true);
258 public void onTestFinished(@NotNull final SMTestProxy test
) {
262 public void onSuiteFinished(@NotNull final SMTestProxy suite
) {
266 public SMTestProxy
getTestsRootNode() {
267 return myTestsRootNode
;
270 public TestConsoleProperties
getProperties() {
271 return myConsoleProperties
;
274 public void setFilter(final Filter filter
) {
275 // is usded by Test Runner actions, e.g. hide passed, etc
276 final SMTRunnerTreeStructure treeStructure
= myTreeBuilder
.getRTestUnitTreeStructure();
277 treeStructure
.setFilter(filter
);
278 myTreeBuilder
.updateFromRoot();
281 public boolean isRunning() {
282 return getRoot().isInProgress();
285 public TestTreeView
getTreeView() {
289 public boolean hasTestSuites() {
290 return getRoot().getChildren().size() > 0;
294 public AbstractTestProxy
getRoot() {
295 return myTestsRootNode
;
299 * Manual test proxy selectio in tests tree. E.g. do select root node on
300 * testing started or do select current node if TRACK_RUNNING_TEST is enabled
303 * Will select proxy in Event Dispatch Thread. Invocation of this
304 * method may be not in event dispatch thread
305 * @param testProxy Test or suite
307 public void selectAndNotify(@Nullable final AbstractTestProxy testProxy
) {
308 selectWithoutNotify(testProxy
);
310 // Is used by Statistic tab to differ use selection in tree
311 // from manual selection from API (e.g. test runner events)
312 showStatisticsForSelectedProxy(testProxy
, false);
315 public void addEventsListener(final EventsListener listener
) {
316 myEventListeners
.add(listener
);
317 addTestsTreeSelectionListener(new TreeSelectionListener() {
318 public void valueChanged(final TreeSelectionEvent e
) {
319 //We should fire event only if it was generated by this component,
320 //e.g. it is focused. Otherwise it is side effect of selecing proxy in
321 //try by other component
322 //if (myTreeView.isFocusOwner()) {
323 @Nullable final PrintableTestProxy selectedProxy
= (PrintableTestProxy
)getTreeView().getSelectedTest();
324 listener
.onSelected(selectedProxy
, SMTestRunnerResultsForm
.this, SMTestRunnerResultsForm
.this);
330 public void dispose() {
332 myShowStatisticForProxyHandler
= null;
333 myEventListeners
.clear();
336 public void showStatisticsForSelectedProxy() {
337 TestConsoleProperties
.SHOW_STATISTICS
.set(myProperties
, true);
338 final AbstractTestProxy selectedProxy
= myTreeView
.getSelectedTest();
339 showStatisticsForSelectedProxy(selectedProxy
, true);
342 private void showStatisticsForSelectedProxy(final AbstractTestProxy selectedProxy
,
343 final boolean requestFocus
) {
344 if (selectedProxy
instanceof SMTestProxy
&& myShowStatisticForProxyHandler
!= null) {
345 myShowStatisticForProxyHandler
.handlePropagateSelectionRequest((SMTestProxy
)selectedProxy
, this, requestFocus
);
349 protected int getTestsCurrentCount() {
350 return myTestsCurrentCount
;
353 protected int getTestsFailuresCount() {
354 return myTestsFailuresCount
;
357 protected int getTestsTotal() {
361 public Set
<String
> getMentionedCategories() {
362 return myMentionedCategories
;
365 protected long getStartTime() {
369 protected long getEndTime() {
373 private void _addTestOrSuite(@NotNull final SMTestProxy newTestOrSuite
) {
375 final SMTestProxy parentSuite
= newTestOrSuite
.getParent();
376 assert parentSuite
!= null;
379 myTreeBuilder
.updateTestsSubtree(parentSuite
);
380 myTreeBuilder
.repaintWithParents(newTestOrSuite
);
382 myAnimator
.setCurrentTestCase(newTestOrSuite
);
385 private void fireOnTestNodeAdded(final SMTestProxy test
) {
386 for (EventsListener eventListener
: myEventListeners
) {
387 eventListener
.onTestNodeAdded(this, test
);
391 private void fireOnTestingFinished() {
392 for (EventsListener eventListener
: myEventListeners
) {
393 eventListener
.onTestingFinished(this);
397 private void fireOnTestingStarted() {
398 for (EventsListener eventListener
: myEventListeners
) {
399 eventListener
.onTestingStarted(this);
403 private void selectWithoutNotify(final AbstractTestProxy testProxy
) {
404 if (testProxy
== null) {
408 SMRunnerUtil
.runInEventDispatchThread(new Runnable() {
410 //TODO remove manual update!
411 myTreeBuilder
.performUpdate();
413 myTreeBuilder
.select(testProxy
, null);
415 }, ModalityState
.NON_MODAL
);
418 private void updateStatusLabel() {
419 if (myTestsFailuresCount
> 0) {
420 myStatusLine
.setStatusColor(ColorProgressBar
.RED
);
422 myStatusLine
.setText(TestsPresentationUtil
.getProgressStatus_Text(myStartTime
, myEndTime
,
423 myTestsTotal
, myTestsCurrentCount
,
424 myTestsFailuresCount
, myMentionedCategories
));
428 * for java unit tests
430 public void performUpdate() {
431 myTreeBuilder
.performUpdate();
435 * On event change selection and probably requests focus. Is used when we want
436 * navigate from other component to this
439 public PropagateSelectionHandler
createSelectMeListener() {
440 return new PropagateSelectionHandler() {
441 public void handlePropagateSelectionRequest(@Nullable final SMTestProxy selectedTestProxy
, @NotNull final Object sender
,
442 final boolean requestFocus
) {
443 SMRunnerUtil
.addToInvokeLater(new Runnable() {
445 selectWithoutNotify(selectedTestProxy
);
447 // Request focus if necessary
449 //myTreeView.requestFocusInWindow();
450 IdeFocusManager
.getInstance(myProject
).requestFocus(myTreeView
, true);
459 private static class MyAnimator
extends TestsProgressAnimator
{
460 public MyAnimator(final Disposable parentDisposable
, final AbstractTestTreeBuilder builder
) {
461 super(parentDisposable
);
466 private void updateCountersAndProgressOnTestCount(final int count
, final boolean isCustomMessage
) {
467 if (!isModeConsistent(isCustomMessage
)) return;
469 //This is for beter support groups of TestSuites
470 //Each group notifies about it's size
471 myTestsTotal
+= count
;
475 private void updateCountersAndProgressOnTestStarted(final boolean isCustomMessage
) {
476 if (!isModeConsistent(isCustomMessage
)) return;
478 // for mixed tests results : mention category only if it contained tests
479 myMentionedCategories
.add(myCurrentCustomProgressCategory
!= null ? myCurrentCustomProgressCategory
: TestsPresentationUtil
.DEFAULT_TESTS_CATEGORY
);
482 myTestsCurrentCount
++;
484 // fix total count if it is corrupted
485 // but if test count wasn't set at all let's process such case separately
486 if (myTestsCurrentCount
> myTestsTotal
&& myTestsTotal
!= 0) {
487 myTestsTotal
= myTestsCurrentCount
;
491 if (myTestsTotal
!= 0) {
493 myStatusLine
.setFraction((double)myTestsCurrentCount
/ myTestsTotal
);
495 // just set progress in the middle to show user that tests are running
496 myStatusLine
.setFraction(0.5);
501 private void updateCountersAndProgressOnTestFailed(final boolean isCustomMessage
) {
502 if (!isModeConsistent(isCustomMessage
)) return;
504 myTestsFailuresCount
++;
508 private boolean isModeConsistent(boolean isCustomMessage
) {
509 // check that we are in consistent mode
510 return isCustomMessage
!= (myCurrentCustomProgressCategory
== null);
514 private static class MyFocusTraversalPolicy
extends FocusTraversalPolicy
{
515 final List
<Component
> myComponents
;
517 private MyFocusTraversalPolicy(final List
<Component
> components
) {
518 myComponents
= components
;
521 public Component
getComponentAfter(final Container container
, final Component component
) {
522 return myComponents
.get((myComponents
.indexOf(component
) + 1) % myComponents
.size());
525 public Component
getComponentBefore(final Container container
, final Component component
) {
526 final int prevIndex
= myComponents
.indexOf(component
) - 1;
527 final int normalizedIndex
= prevIndex
< 0 ? myComponents
.size() - 1 : prevIndex
;
529 return myComponents
.get(normalizedIndex
);
532 public Component
getFirstComponent(final Container container
) {
533 return myComponents
.get(0);
536 public Component
getLastComponent(final Container container
) {
537 return myComponents
.get(myComponents
.size() - 1);
540 public Component
getDefaultComponent(final Container container
) {
541 return getFirstComponent(container
);