blob: 8713b48d39effe074d5cc04635268566bf418bd7 [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.execution.testframework.sm.runner.ui;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.testframework.*;
import com.intellij.execution.testframework.sm.SMRunnerUtil;
import com.intellij.execution.testframework.sm.runner.*;
import com.intellij.execution.testframework.sm.runner.ui.statistics.StatisticsPanel;
import com.intellij.execution.testframework.ui.AbstractTestTreeBuilder;
import com.intellij.execution.testframework.ui.TestResultsPanel;
import com.intellij.execution.testframework.ui.TestsProgressAnimator;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.progress.util.ColorProgressBar;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.ui.JBColor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.DateFormatUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* @author: Roman Chernyatchik
*/
public class SMTestRunnerResultsForm extends TestResultsPanel
implements TestFrameworkRunningModel, TestResultsViewer, SMTRunnerEventsListener {
@NonNls private static final String DEFAULT_SM_RUNNER_SPLITTER_PROPERTY = "SMTestRunner.Splitter.Proportion";
public static final Color DARK_YELLOW = JBColor.YELLOW.darker();
private SMTRunnerTestTreeView myTreeView;
private TestsProgressAnimator myAnimator;
/**
* Fake parent suite for all tests and suites
*/
private final SMTestProxy.SMRootTestProxy myTestsRootNode;
private SMTRunnerTreeBuilder myTreeBuilder;
private final TestConsoleProperties myConsoleProperties;
private final List<EventsListener> myEventListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private PropagateSelectionHandler myShowStatisticForProxyHandler;
private final Project myProject;
private int myTotalTestCount = 0;
private int myStartedTestCount = 0;
private int myFinishedTestCount = 0;
private int myFailedTestCount = 0;
private int myIgnoredTestCount = 0;
private long myStartTime;
private long myEndTime;
private StatisticsPanel myStatisticsPane;
// custom progress
private String myCurrentCustomProgressCategory;
private final Set<String> myMentionedCategories = new LinkedHashSet<String>();
private boolean myTestsRunning = true;
public SMTestRunnerResultsForm(final RunConfiguration runConfiguration,
@NotNull final JComponent console,
final TestConsoleProperties consoleProperties,
final ExecutionEnvironment environment) {
this(runConfiguration, console, AnAction.EMPTY_ARRAY, consoleProperties, environment, null);
}
public SMTestRunnerResultsForm(final RunConfiguration runConfiguration,
@NotNull final JComponent console,
AnAction[] consoleActions,
final TestConsoleProperties consoleProperties,
final ExecutionEnvironment environment,
final String splitterPropertyName) {
super(console, consoleActions, consoleProperties, environment,
splitterPropertyName != null ? splitterPropertyName : DEFAULT_SM_RUNNER_SPLITTER_PROPERTY, 0.5f);
myConsoleProperties = consoleProperties;
myProject = runConfiguration.getProject();
//Create tests common suite root
//noinspection HardCodedStringLiteral
myTestsRootNode = new SMTestProxy.SMRootTestProxy();
//todo myTestsRootNode.setOutputFilePath(runConfiguration.getOutputFilePath());
// Fire selection changed and move focus on SHIFT+ENTER
//TODO[romeo] improve
/*
final ArrayList<Component> components = new ArrayList<Component>();
components.add(myTreeView);
components.add(myTabs.getComponent());
myContentPane.setFocusTraversalPolicy(new MyFocusTraversalPolicy(components));
myContentPane.setFocusCycleRoot(true);
*/
}
@Override
public void initUI() {
super.initUI();
final KeyStroke shiftEnterKey = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_MASK);
SMRunnerUtil.registerAsAction(shiftEnterKey, "show-statistics-for-test-proxy",
new Runnable() {
public void run() {
showStatisticsForSelectedProxy();
}
},
myTreeView);
}
protected ToolbarPanel createToolbarPanel() {
return new SMTRunnerToolbarPanel(myConsoleProperties, myEnvironment, this, this);
}
protected JComponent createTestTreeView() {
myTreeView = new SMTRunnerTestTreeView();
myTreeView.setLargeModel(true);
myTreeView.attachToModel(this);
myTreeView.setTestResultsViewer(this);
addTestsTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
AbstractTestProxy selectedTest = getTreeView().getSelectedTest();
if (selectedTest instanceof SMTestProxy) {
myStatisticsPane.selectProxy(((SMTestProxy)selectedTest), this, false);
}
}
});
final SMTRunnerTreeStructure structure = new SMTRunnerTreeStructure(myProject, myTestsRootNode);
myTreeBuilder = new SMTRunnerTreeBuilder(myTreeView, structure);
myTreeBuilder.setTestsComparator(TestConsoleProperties.SORT_ALPHABETICALLY.value(myProperties));
Disposer.register(this, myTreeBuilder);
myAnimator = new MyAnimator(this, myTreeBuilder);
//TODO always hide root node
//myTreeView.setRootVisible(false);
return myTreeView;
}
protected JComponent createStatisticsPanel() {
// Statistics tab
final StatisticsPanel statisticsPane = new StatisticsPanel(myProject, this);
// handler to select in results viewer by statistics pane events
statisticsPane.addPropagateSelectionListener(createSelectMeListener());
// handler to select test statistics pane by result viewer events
setShowStatisticForProxyHandler(statisticsPane.createSelectMeListener());
myStatisticsPane = statisticsPane;
return myStatisticsPane.getContentPane();
}
public StatisticsPanel getStatisticsPane() {
return myStatisticsPane;
}
public void addTestsTreeSelectionListener(final TreeSelectionListener listener) {
myTreeView.getSelectionModel().addTreeSelectionListener(listener);
}
/**
* Is used for navigation from tree view to other UI components
*
* @param handler
*/
public void setShowStatisticForProxyHandler(final PropagateSelectionHandler handler) {
myShowStatisticForProxyHandler = handler;
}
/**
* Returns root node, fake parent suite for all tests and suites
*
* @param testsRoot
* @return
*/
public void onTestingStarted(@NotNull SMTestProxy.SMRootTestProxy testsRoot) {
myAnimator.setCurrentTestCase(myTestsRootNode);
// Status line
myStatusLine.setStatusColor(ColorProgressBar.GREEN);
// Tests tree
selectAndNotify(myTestsRootNode);
myStartTime = System.currentTimeMillis();
boolean printTestingStartedTime = true;
if (myConsoleProperties instanceof SMTRunnerConsoleProperties) {
printTestingStartedTime = ((SMTRunnerConsoleProperties) myConsoleProperties).isPrintTestingStartedTime();
}
if (printTestingStartedTime) {
myTestsRootNode.addSystemOutput("Testing started at "
+ DateFormatUtil.formatTime(myStartTime)
+ " ...\n");
}
updateStatusLabel(false);
// TODO : show info - "Loading..." msg
fireOnTestingStarted();
}
public void onTestingFinished(@NotNull SMTestProxy.SMRootTestProxy testsRoot) {
myEndTime = System.currentTimeMillis();
if (myTotalTestCount == 0) {
myTotalTestCount = myStartedTestCount;
myStatusLine.setFraction(1);
}
updateStatusLabel(true);
updateIconProgress();
myAnimator.stopMovie();
myTreeBuilder.updateFromRoot();
LvcsHelper.addLabel(this);
selectAndNotify(myTestsRootNode, new Runnable() {
@Override
public void run() {
myTestsRunning = false;
}
});
fireOnTestingFinished();
}
public void onTestsCountInSuite(final int count) {
updateCountersAndProgressOnTestCount(count, false);
}
/**
* Adds test to tree and updates status line.
* Test proxy should be initialized, proxy parent must be some suite (already added to tree)
*
* @param testProxy Proxy
*/
public void onTestStarted(@NotNull final SMTestProxy testProxy) {
updateOnTestStarted(false);
_addTestOrSuite(testProxy);
fireOnTestNodeAdded(testProxy);
}
public void onTestFailed(@NotNull final SMTestProxy test) {
updateOnTestFailed(false);
updateIconProgress();
}
public void onTestIgnored(@NotNull final SMTestProxy test) {
updateOnTestIgnored();
}
/**
* Adds suite to tree
* Suite proxy should be initialized, proxy parent must be some suite (already added to tree)
* If parent is null, then suite will be added to tests root.
*
* @param newSuite Tests suite
*/
public void onSuiteStarted(@NotNull final SMTestProxy newSuite) {
_addTestOrSuite(newSuite);
}
public void onCustomProgressTestsCategory(@Nullable String categoryName, int testCount) {
myCurrentCustomProgressCategory = categoryName;
updateCountersAndProgressOnTestCount(testCount, true);
}
public void onCustomProgressTestStarted() {
updateOnTestStarted(true);
}
public void onCustomProgressTestFailed() {
updateOnTestFailed(true);
}
public void onTestFinished(@NotNull final SMTestProxy test) {
updateOnTestFinished(false);
updateIconProgress();
}
public void onSuiteFinished(@NotNull final SMTestProxy suite) {
//Do nothing
}
public SMTestProxy.SMRootTestProxy getTestsRootNode() {
return myTestsRootNode;
}
public TestConsoleProperties getProperties() {
return myConsoleProperties;
}
public void setFilter(final Filter filter) {
// is used by Test Runner actions, e.g. hide passed, etc
final SMTRunnerTreeStructure treeStructure = myTreeBuilder.getSMRunnerTreeStructure();
treeStructure.setFilter(filter);
// TODO - show correct info if no children are available
// (e.g no tests found or all tests passed, etc.)
// treeStructure.getChildElements(treeStructure.getRootElement()).length == 0
myTreeBuilder.queueUpdate();
}
public boolean isRunning() {
return myTestsRunning;
}
public TestTreeView getTreeView() {
return myTreeView;
}
@Override
public SMTRunnerTreeBuilder getTreeBuilder() {
return myTreeBuilder;
}
public boolean hasTestSuites() {
return getRoot().getChildren().size() > 0;
}
@NotNull
public AbstractTestProxy getRoot() {
return myTestsRootNode;
}
/**
* Manual test proxy selection in tests tree. E.g. do select root node on
* testing started or do select current node if TRACK_RUNNING_TEST is enabled
* <p/>
* <p/>
* Will select proxy in Event Dispatch Thread. Invocation of this
* method may be not in event dispatch thread
*
* @param testProxy Test or suite
*/
@Override
public void selectAndNotify(AbstractTestProxy testProxy) {
selectAndNotify(testProxy, null);
}
private void selectAndNotify(@Nullable final AbstractTestProxy testProxy, @Nullable Runnable onDone) {
selectWithoutNotify(testProxy, onDone);
// Is used by Statistic tab to differ use selection in tree
// from manual selection from API (e.g. test runner events)
showStatisticsForSelectedProxy(testProxy, false);
}
public void addEventsListener(final EventsListener listener) {
myEventListeners.add(listener);
addTestsTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(final TreeSelectionEvent e) {
//We should fire event only if it was generated by this component,
//e.g. it is focused. Otherwise it is side effect of selecting proxy in
//try by other component
//if (myTreeView.isFocusOwner()) {
@Nullable final SMTestProxy selectedProxy = (SMTestProxy)getTreeView().getSelectedTest();
listener.onSelected(selectedProxy, SMTestRunnerResultsForm.this, SMTestRunnerResultsForm.this);
//}
}
});
}
public void dispose() {
super.dispose();
myShowStatisticForProxyHandler = null;
myEventListeners.clear();
myStatisticsPane.doDispose();
}
public void showStatisticsForSelectedProxy() {
TestConsoleProperties.SHOW_STATISTICS.set(myProperties, true);
final AbstractTestProxy selectedProxy = myTreeView.getSelectedTest();
showStatisticsForSelectedProxy(selectedProxy, true);
}
private void showStatisticsForSelectedProxy(final AbstractTestProxy selectedProxy,
final boolean requestFocus) {
if (selectedProxy instanceof SMTestProxy && myShowStatisticForProxyHandler != null) {
myShowStatisticForProxyHandler.handlePropagateSelectionRequest((SMTestProxy)selectedProxy, this, requestFocus);
}
}
protected int getTotalTestCount() {
return myTotalTestCount;
}
protected int getStartedTestCount() {
return myStartedTestCount;
}
protected int getFinishedTestCount() {
return myFinishedTestCount;
}
protected int getFailedTestCount() {
return myFailedTestCount;
}
protected int getIgnoredTestCount() {
return myIgnoredTestCount;
}
protected Color getTestsStatusColor() {
return myStatusLine.getStatusColor();
}
public Set<String> getMentionedCategories() {
return myMentionedCategories;
}
protected long getStartTime() {
return myStartTime;
}
protected long getEndTime() {
return myEndTime;
}
private void _addTestOrSuite(@NotNull final SMTestProxy newTestOrSuite) {
final SMTestProxy parentSuite = newTestOrSuite.getParent();
assert parentSuite != null;
// Tree
myTreeBuilder.updateTestsSubtree(parentSuite);
myTreeBuilder.repaintWithParents(newTestOrSuite);
myAnimator.setCurrentTestCase(newTestOrSuite);
}
private void fireOnTestNodeAdded(final SMTestProxy test) {
for (EventsListener eventListener : myEventListeners) {
eventListener.onTestNodeAdded(this, test);
}
}
private void fireOnTestingFinished() {
for (EventsListener eventListener : myEventListeners) {
eventListener.onTestingFinished(this);
}
}
private void fireOnTestingStarted() {
for (EventsListener eventListener : myEventListeners) {
eventListener.onTestingStarted(this);
}
}
private void selectWithoutNotify(final AbstractTestProxy testProxy, @Nullable final Runnable onDone) {
if (testProxy == null) {
return;
}
SMRunnerUtil.runInEventDispatchThread(new Runnable() {
public void run() {
if (myTreeBuilder.isDisposed()) {
return;
}
myTreeBuilder.select(testProxy, onDone);
}
}, ModalityState.NON_MODAL);
}
private void updateStatusLabel(final boolean testingFinished) {
if (myFailedTestCount > 0) {
myStatusLine.setStatusColor(ColorProgressBar.RED);
}
else if (myIgnoredTestCount > 0) {
myStatusLine.setStatusColor(DARK_YELLOW);
}
if (testingFinished) {
if (myTotalTestCount == 0) {
myStatusLine.setStatusColor(myTestsRootNode.wasLaunched() || !myTestsRootNode.isTestsReporterAttached()
? JBColor.LIGHT_GRAY
: ColorProgressBar.RED);
}
// else color will be according failed/passed tests
}
// launchedAndFinished - is launched and not in progress. If we remove "launched' that onTestingStarted() before
// initializing will be "launchedAndFinished"
final boolean launchedAndFinished = myTestsRootNode.wasLaunched() && !myTestsRootNode.isInProgress();
myStatusLine.setText(TestsPresentationUtil.getProgressStatus_Text(myStartTime, myEndTime,
myTotalTestCount, myFinishedTestCount,
myFailedTestCount, myMentionedCategories,
launchedAndFinished));
}
/**
* for java unit tests
*/
public void performUpdate() {
myTreeBuilder.performUpdate();
}
private void updateIconProgress() {
final int totalTestCount, doneTestCount;
if (myTotalTestCount == 0) {
totalTestCount = 2;
doneTestCount = 1;
}
else {
totalTestCount = myTotalTestCount;
doneTestCount = myFinishedTestCount + myFailedTestCount + myIgnoredTestCount;
}
TestsUIUtil.showIconProgress(myProject, doneTestCount, totalTestCount, myFailedTestCount);
}
/**
* On event change selection and probably requests focus. Is used when we want
* navigate from other component to this
*
* @return Listener
*/
public PropagateSelectionHandler createSelectMeListener() {
return new PropagateSelectionHandler() {
public void handlePropagateSelectionRequest(@Nullable final SMTestProxy selectedTestProxy, @NotNull final Object sender,
final boolean requestFocus) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
selectWithoutNotify(selectedTestProxy, null);
// Request focus if necessary
if (requestFocus) {
//myTreeView.requestFocusInWindow();
IdeFocusManager.getInstance(myProject).requestFocus(myTreeView, true);
}
}
});
}
};
}
private static class MyAnimator extends TestsProgressAnimator {
public MyAnimator(final Disposable parentDisposable, final AbstractTestTreeBuilder builder) {
super(parentDisposable);
init(builder);
}
}
private void updateCountersAndProgressOnTestCount(final int count, final boolean isCustomMessage) {
if (!isModeConsistent(isCustomMessage)) return;
//This is for better support groups of TestSuites
//Each group notifies about it's size
myTotalTestCount += count;
updateStatusLabel(false);
}
private void updateOnTestStarted(final boolean isCustomMessage) {
if (!isModeConsistent(isCustomMessage)) return;
// for mixed tests results : mention category only if it contained tests
myMentionedCategories
.add(myCurrentCustomProgressCategory != null ? myCurrentCustomProgressCategory : TestsPresentationUtil.DEFAULT_TESTS_CATEGORY);
myStartedTestCount++;
// fix total count if it is corrupted
// but if test count wasn't set at all let's process such case separately
if (myStartedTestCount > myTotalTestCount && myTotalTestCount != 0) {
myTotalTestCount = myStartedTestCount;
}
updateStatusLabel(false);
}
private void updateProgressOnTestDone() {
int doneTestCount = myFinishedTestCount + myFailedTestCount + myIgnoredTestCount;
// update progress
if (myTotalTestCount != 0) {
// if total is set
myStatusLine.setFraction((double) doneTestCount / myTotalTestCount);
}
else {
// if at least one test was launcher than just set progress in the middle to show user that tests are running
myStatusLine.setFraction(doneTestCount > 0 ? 0.5 : 0);
}
}
private void updateOnTestFailed(final boolean isCustomMessage) {
if (!isModeConsistent(isCustomMessage)) return;
myFailedTestCount++;
updateProgressOnTestDone();
updateStatusLabel(false);
}
private void updateOnTestFinished(final boolean isCustomMessage) {
if (!isModeConsistent(isCustomMessage)) return;
myFinishedTestCount++;
updateProgressOnTestDone();
}
private void updateOnTestIgnored() {
myIgnoredTestCount++;
updateProgressOnTestDone();
updateStatusLabel(false);
}
private boolean isModeConsistent(boolean isCustomMessage) {
// check that we are in consistent mode
return isCustomMessage != (myCurrentCustomProgressCategory == null);
}
}