2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com
.intellij
.execution
.testframework
.sm
.runner
.ui
;
18 import com
.intellij
.execution
.configurations
.ConfigurationPerRunnerSettings
;
19 import com
.intellij
.execution
.configurations
.RunConfigurationBase
;
20 import com
.intellij
.execution
.configurations
.RunnerSettings
;
21 import com
.intellij
.execution
.testframework
.*;
22 import com
.intellij
.execution
.testframework
.sm
.SMRunnerUtil
;
23 import com
.intellij
.execution
.testframework
.sm
.runner
.SMTRunnerEventsListener
;
24 import com
.intellij
.execution
.testframework
.sm
.runner
.SMTRunnerTreeBuilder
;
25 import com
.intellij
.execution
.testframework
.sm
.runner
.SMTRunnerTreeStructure
;
26 import com
.intellij
.execution
.testframework
.sm
.runner
.SMTestProxy
;
27 import com
.intellij
.execution
.testframework
.sm
.runner
.ui
.statistics
.StatisticsPanel
;
28 import com
.intellij
.execution
.testframework
.ui
.AbstractTestTreeBuilder
;
29 import com
.intellij
.execution
.testframework
.ui
.PrintableTestProxy
;
30 import com
.intellij
.execution
.testframework
.ui
.TestResultsPanel
;
31 import com
.intellij
.execution
.testframework
.ui
.TestsProgressAnimator
;
32 import com
.intellij
.openapi
.Disposable
;
33 import com
.intellij
.openapi
.actionSystem
.AnAction
;
34 import com
.intellij
.openapi
.application
.ModalityState
;
35 import com
.intellij
.openapi
.progress
.util
.ColorProgressBar
;
36 import com
.intellij
.openapi
.project
.Project
;
37 import com
.intellij
.openapi
.util
.Disposer
;
38 import com
.intellij
.openapi
.wm
.IdeFocusManager
;
39 import org
.jetbrains
.annotations
.NonNls
;
40 import org
.jetbrains
.annotations
.NotNull
;
41 import org
.jetbrains
.annotations
.Nullable
;
44 import javax
.swing
.event
.TreeSelectionEvent
;
45 import javax
.swing
.event
.TreeSelectionListener
;
47 import java
.awt
.event
.InputEvent
;
48 import java
.awt
.event
.KeyEvent
;
49 import java
.text
.DateFormat
;
50 import java
.text
.SimpleDateFormat
;
52 import java
.util
.List
;
55 * @author: Roman Chernyatchik
57 public class SMTestRunnerResultsForm
extends TestResultsPanel
implements TestFrameworkRunningModel
, TestResultsViewer
, SMTRunnerEventsListener
{
58 @NonNls private static final String DEFAULT_SM_RUNNER_SPLITTER_PROPERTY
= "SMTestRunner.Splitter.Proportion";
60 private SMTRunnerTestTreeView myTreeView
;
62 private TestsProgressAnimator myAnimator
;
65 * Fake parent suite for all tests and suites
67 private final SMTestProxy myTestsRootNode
;
68 private SMTRunnerTreeBuilder myTreeBuilder
;
69 private final TestConsoleProperties myConsoleProperties
;
71 private final List
<EventsListener
> myEventListeners
= new ArrayList
<EventsListener
>();
73 private PropagateSelectionHandler myShowStatisticForProxyHandler
;
75 private final Project myProject
;
77 private int myTestsCurrentCount
;
78 private int myTestsTotal
= 0;
79 private int myTestsFailuresCount
;
80 private long myStartTime
;
81 private long myEndTime
;
82 private StatisticsPanel myStatisticsPane
;
85 private String myCurrentCustomProgressCategory
;
86 private Set
<String
> myMentionedCategories
= new LinkedHashSet
<String
>();
88 public SMTestRunnerResultsForm(final RunConfigurationBase runConfiguration
,
89 @NotNull final JComponent console
,
90 final TestConsoleProperties consoleProperties
,
91 final RunnerSettings runnerSettings
,
92 final ConfigurationPerRunnerSettings configurationSettings
) {
93 this(runConfiguration
, console
, AnAction
.EMPTY_ARRAY
, consoleProperties
, runnerSettings
, configurationSettings
, null);
96 public SMTestRunnerResultsForm(final RunConfigurationBase runConfiguration
,
97 @NotNull final JComponent console
,
98 AnAction
[] consoleActions
,
99 final TestConsoleProperties consoleProperties
,
100 final RunnerSettings runnerSettings
,
101 final ConfigurationPerRunnerSettings configurationSettings
,
102 final String splitterPropertyName
) {
103 super(console
, consoleActions
, consoleProperties
, runnerSettings
, configurationSettings
,
104 splitterPropertyName
!= null ? DEFAULT_SM_RUNNER_SPLITTER_PROPERTY
: splitterPropertyName
, 0.5f
);
105 myConsoleProperties
= consoleProperties
;
107 myProject
= runConfiguration
.getProject();
109 //Create tests common suite root
110 //noinspection HardCodedStringLiteral
111 myTestsRootNode
= new SMTestProxy("[root]", true, null);
113 // Fire selection changed and move focus on SHIFT+ENTER
114 //TODO[romeo] improve
116 final ArrayList<Component> components = new ArrayList<Component>();
117 components.add(myTreeView);
118 components.add(myTabs.getComponent());
119 myContentPane.setFocusTraversalPolicy(new MyFocusTraversalPolicy(components));
120 myContentPane.setFocusCycleRoot(true);
125 public void initUI() {
128 final KeyStroke shiftEnterKey
= KeyStroke
.getKeyStroke(KeyEvent
.VK_ENTER
, InputEvent
.SHIFT_MASK
);
129 SMRunnerUtil
.registerAsAction(shiftEnterKey
, "show-statistics-for-test-proxy",
132 showStatisticsForSelectedProxy();
138 protected ToolbarPanel
createToolbarPanel() {
139 return new SMTRunnerToolbarPanel(myConsoleProperties
, myRunnerSettings
, myConfigurationSettings
, this, this);
142 protected JComponent
createTestTreeView() {
143 myTreeView
= new SMTRunnerTestTreeView();
145 myTreeView
.setLargeModel(true);
146 myTreeView
.attachToModel(this);
147 myTreeView
.setTestResultsViewer(this);
149 final SMTRunnerTreeStructure structure
= new SMTRunnerTreeStructure(myProject
, myTestsRootNode
);
150 myTreeBuilder
= new SMTRunnerTreeBuilder(myTreeView
, structure
);
151 Disposer
.register(this, myTreeBuilder
);
152 myAnimator
= new MyAnimator(this, myTreeBuilder
);
157 protected JComponent
createStatisticsPanel() {
159 final StatisticsPanel statisticsPane
= new StatisticsPanel(myProject
, this);
160 // handler to select in results viewer by statistics pane events
161 statisticsPane
.addPropagateSelectionListener(createSelectMeListener());
162 // handler to select test statistics pane by result viewer events
163 setShowStatisticForProxyHandler(statisticsPane
.createSelectMeListener());
165 myStatisticsPane
= statisticsPane
;
166 return myStatisticsPane
.getContentPane();
169 public StatisticsPanel
getStatisticsPane() {
170 return myStatisticsPane
;
173 public void addTestsTreeSelectionListener(final TreeSelectionListener listener
) {
174 myTreeView
.getSelectionModel().addTreeSelectionListener(listener
);
178 * Is used for navigation from tree view to other UI components
181 public void setShowStatisticForProxyHandler(final PropagateSelectionHandler handler
) {
182 myShowStatisticForProxyHandler
= handler
;
186 * Returns root node, fake parent suite for all tests and suites
190 public void onTestingStarted(@NotNull SMTestProxy testsRoot
) {
191 myAnimator
.setCurrentTestCase(myTestsRootNode
);
194 myStatusLine
.setStatusColor(ColorProgressBar
.GREEN
);
197 selectAndNotify(myTestsRootNode
);
199 myStartTime
= System
.currentTimeMillis();
200 final Date today
= new Date(myStartTime
);
201 myTestsRootNode
.addSystemOutput("Testing started at "
202 + DateFormat
.getTimeInstance(SimpleDateFormat
.SHORT
).format(today
)
207 fireOnTestingStarted();
210 public void onTestingFinished(@NotNull SMTestProxy testsRoot
) {
211 myEndTime
= System
.currentTimeMillis();
213 if (myTestsTotal
== 0) {
214 myTestsTotal
= myTestsCurrentCount
;
215 myStatusLine
.setFraction(1);
219 if (myTestsRootNode
.getChildren().size() == 0) {
221 myStatusLine
.setStatusColor(ColorProgressBar
.RED
);
224 myAnimator
.stopMovie();
225 myTreeBuilder
.updateFromRoot();
227 LvcsHelper
.addLabel(this);
229 fireOnTestingFinished();
232 public void onTestsCountInSuite(final int count
) {
233 updateCountersAndProgressOnTestCount(count
, false);
237 * Adds test to tree and updates status line.
238 * Test proxy should be initialized, proxy parent must be some suite (already added to tree)
240 * @param testProxy Proxy
242 public void onTestStarted(@NotNull final SMTestProxy testProxy
) {
243 updateCountersAndProgressOnTestStarted(false);
245 _addTestOrSuite(testProxy
);
247 fireOnTestNodeAdded(testProxy
);
250 public void onTestFailed(@NotNull final SMTestProxy test
) {
251 updateCountersAndProgressOnTestFailed(false);
254 public void onTestIgnored(@NotNull final SMTestProxy test
) {
260 * Suite proxy should be initialized, proxy parent must be some suite (already added to tree)
261 * If parent is null, then suite will be added to tests root.
263 * @param newSuite Tests suite
265 public void onSuiteStarted(@NotNull final SMTestProxy newSuite
) {
266 _addTestOrSuite(newSuite
);
269 public void onCustomProgressTestsCategory(@Nullable String categoryName
, int testCount
) {
270 myCurrentCustomProgressCategory
= categoryName
;
271 updateCountersAndProgressOnTestCount(testCount
, true);
274 public void onCustomProgressTestStarted() {
275 updateCountersAndProgressOnTestStarted(true);
278 public void onCustomProgressTestFailed() {
279 updateCountersAndProgressOnTestFailed(true);
282 public void onTestFinished(@NotNull final SMTestProxy test
) {
286 public void onSuiteFinished(@NotNull final SMTestProxy suite
) {
290 public SMTestProxy
getTestsRootNode() {
291 return myTestsRootNode
;
294 public TestConsoleProperties
getProperties() {
295 return myConsoleProperties
;
298 public void setFilter(final Filter filter
) {
299 // is used by Test Runner actions, e.g. hide passed, etc
300 final SMTRunnerTreeStructure treeStructure
= myTreeBuilder
.getRTestUnitTreeStructure();
301 treeStructure
.setFilter(filter
);
302 myTreeBuilder
.updateFromRoot();
305 public boolean isRunning() {
306 return getRoot().isInProgress();
309 public TestTreeView
getTreeView() {
313 public boolean hasTestSuites() {
314 return getRoot().getChildren().size() > 0;
318 public AbstractTestProxy
getRoot() {
319 return myTestsRootNode
;
323 * Manual test proxy selection in tests tree. E.g. do select root node on
324 * testing started or do select current node if TRACK_RUNNING_TEST is enabled
327 * Will select proxy in Event Dispatch Thread. Invocation of this
328 * method may be not in event dispatch thread
329 * @param testProxy Test or suite
331 public void selectAndNotify(@Nullable final AbstractTestProxy testProxy
) {
332 selectWithoutNotify(testProxy
);
334 // Is used by Statistic tab to differ use selection in tree
335 // from manual selection from API (e.g. test runner events)
336 showStatisticsForSelectedProxy(testProxy
, false);
339 public void addEventsListener(final EventsListener listener
) {
340 myEventListeners
.add(listener
);
341 addTestsTreeSelectionListener(new TreeSelectionListener() {
342 public void valueChanged(final TreeSelectionEvent e
) {
343 //We should fire event only if it was generated by this component,
344 //e.g. it is focused. Otherwise it is side effect of selecting proxy in
345 //try by other component
346 //if (myTreeView.isFocusOwner()) {
347 @Nullable final PrintableTestProxy selectedProxy
= (PrintableTestProxy
)getTreeView().getSelectedTest();
348 listener
.onSelected(selectedProxy
, SMTestRunnerResultsForm
.this, SMTestRunnerResultsForm
.this);
354 public void dispose() {
356 myShowStatisticForProxyHandler
= null;
357 myEventListeners
.clear();
360 public void showStatisticsForSelectedProxy() {
361 TestConsoleProperties
.SHOW_STATISTICS
.set(myProperties
, true);
362 final AbstractTestProxy selectedProxy
= myTreeView
.getSelectedTest();
363 showStatisticsForSelectedProxy(selectedProxy
, true);
366 private void showStatisticsForSelectedProxy(final AbstractTestProxy selectedProxy
,
367 final boolean requestFocus
) {
368 if (selectedProxy
instanceof SMTestProxy
&& myShowStatisticForProxyHandler
!= null) {
369 myShowStatisticForProxyHandler
.handlePropagateSelectionRequest((SMTestProxy
)selectedProxy
, this, requestFocus
);
373 protected int getTestsCurrentCount() {
374 return myTestsCurrentCount
;
377 protected int getTestsFailuresCount() {
378 return myTestsFailuresCount
;
381 protected Color
getTestsStatusColor() {
382 return myStatusLine
.getStatusColor();
385 protected int getTestsTotal() {
389 public Set
<String
> getMentionedCategories() {
390 return myMentionedCategories
;
393 protected long getStartTime() {
397 protected long getEndTime() {
401 private void _addTestOrSuite(@NotNull final SMTestProxy newTestOrSuite
) {
403 final SMTestProxy parentSuite
= newTestOrSuite
.getParent();
404 assert parentSuite
!= null;
407 myTreeBuilder
.updateTestsSubtree(parentSuite
);
408 myTreeBuilder
.repaintWithParents(newTestOrSuite
);
410 myAnimator
.setCurrentTestCase(newTestOrSuite
);
413 private void fireOnTestNodeAdded(final SMTestProxy test
) {
414 for (EventsListener eventListener
: myEventListeners
) {
415 eventListener
.onTestNodeAdded(this, test
);
419 private void fireOnTestingFinished() {
420 for (EventsListener eventListener
: myEventListeners
) {
421 eventListener
.onTestingFinished(this);
425 private void fireOnTestingStarted() {
426 for (EventsListener eventListener
: myEventListeners
) {
427 eventListener
.onTestingStarted(this);
431 private void selectWithoutNotify(final AbstractTestProxy testProxy
) {
432 if (testProxy
== null) {
436 SMRunnerUtil
.runInEventDispatchThread(new Runnable() {
438 myTreeBuilder
.select(testProxy
, null);
440 }, ModalityState
.NON_MODAL
);
443 private void updateStatusLabel() {
444 if (myTestsFailuresCount
> 0) {
445 myStatusLine
.setStatusColor(ColorProgressBar
.RED
);
447 myStatusLine
.setText(TestsPresentationUtil
.getProgressStatus_Text(myStartTime
, myEndTime
,
448 myTestsTotal
, myTestsCurrentCount
,
449 myTestsFailuresCount
, myMentionedCategories
));
453 * for java unit tests
455 public void performUpdate() {
456 myTreeBuilder
.performUpdate();
460 * On event change selection and probably requests focus. Is used when we want
461 * navigate from other component to this
464 public PropagateSelectionHandler
createSelectMeListener() {
465 return new PropagateSelectionHandler() {
466 public void handlePropagateSelectionRequest(@Nullable final SMTestProxy selectedTestProxy
, @NotNull final Object sender
,
467 final boolean requestFocus
) {
468 SMRunnerUtil
.addToInvokeLater(new Runnable() {
470 selectWithoutNotify(selectedTestProxy
);
472 // Request focus if necessary
474 //myTreeView.requestFocusInWindow();
475 IdeFocusManager
.getInstance(myProject
).requestFocus(myTreeView
, true);
484 private static class MyAnimator
extends TestsProgressAnimator
{
485 public MyAnimator(final Disposable parentDisposable
, final AbstractTestTreeBuilder builder
) {
486 super(parentDisposable
);
491 private void updateCountersAndProgressOnTestCount(final int count
, final boolean isCustomMessage
) {
492 if (!isModeConsistent(isCustomMessage
)) return;
494 //This is for beter support groups of TestSuites
495 //Each group notifies about it's size
496 myTestsTotal
+= count
;
500 private void updateCountersAndProgressOnTestStarted(final boolean isCustomMessage
) {
501 if (!isModeConsistent(isCustomMessage
)) return;
503 // for mixed tests results : mention category only if it contained tests
504 myMentionedCategories
.add(myCurrentCustomProgressCategory
!= null ? myCurrentCustomProgressCategory
: TestsPresentationUtil
.DEFAULT_TESTS_CATEGORY
);
507 myTestsCurrentCount
++;
509 // fix total count if it is corrupted
510 // but if test count wasn't set at all let's process such case separately
511 if (myTestsCurrentCount
> myTestsTotal
&& myTestsTotal
!= 0) {
512 myTestsTotal
= myTestsCurrentCount
;
516 if (myTestsTotal
!= 0) {
518 myStatusLine
.setFraction((double)myTestsCurrentCount
/ myTestsTotal
);
520 // just set progress in the middle to show user that tests are running
521 myStatusLine
.setFraction(0.5);
526 private void updateCountersAndProgressOnTestFailed(final boolean isCustomMessage
) {
527 if (!isModeConsistent(isCustomMessage
)) return;
529 myTestsFailuresCount
++;
533 private boolean isModeConsistent(boolean isCustomMessage
) {
534 // check that we are in consistent mode
535 return isCustomMessage
!= (myCurrentCustomProgressCategory
== null);
539 private static class MyFocusTraversalPolicy
extends FocusTraversalPolicy
{
540 final List
<Component
> myComponents
;
542 private MyFocusTraversalPolicy(final List
<Component
> components
) {
543 myComponents
= components
;
546 public Component
getComponentAfter(final Container container
, final Component component
) {
547 return myComponents
.get((myComponents
.indexOf(component
) + 1) % myComponents
.size());
550 public Component
getComponentBefore(final Container container
, final Component component
) {
551 final int prevIndex
= myComponents
.indexOf(component
) - 1;
552 final int normalizedIndex
= prevIndex
< 0 ? myComponents
.size() - 1 : prevIndex
;
554 return myComponents
.get(normalizedIndex
);
557 public Component
getFirstComponent(final Container container
) {
558 return myComponents
.get(0);
561 public Component
getLastComponent(final Container container
) {
562 return myComponents
.get(myComponents
.size() - 1);
565 public Component
getDefaultComponent(final Container container
) {
566 return getFirstComponent(container
);