blob: d0850c9eef853c2a252a25e88cb9431b659bd34e [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;
import com.intellij.execution.Location;
import com.intellij.execution.testframework.*;
import com.intellij.execution.testframework.sm.SMStacktraceParser;
import com.intellij.execution.testframework.sm.TestsLocationProviderUtil;
import com.intellij.execution.testframework.sm.runner.states.*;
import com.intellij.execution.testframework.sm.runner.ui.TestsPresentationUtil;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.ide.util.EditSourceUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.pom.Navigatable;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.testIntegration.TestLocationProvider;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.ContainerUtilRt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* @author: Roman Chernyatchik
*/
public class SMTestProxy extends AbstractTestProxy {
private static final Logger LOG = Logger.getInstance(SMTestProxy.class.getName());
private List<SMTestProxy> myChildren;
private SMTestProxy myParent;
private AbstractState myState = NotRunState.getInstance();
private final String myName;
private Long myDuration = null; // duration is unknown
@Nullable private final String myLocationUrl;
private boolean myDurationIsCached = false; // is used for separating unknown and unset duration
private boolean myHasCriticalErrors = false;
private boolean myHasErrorsCached = false;
private boolean myHasPassedTests = false;
private boolean myHasPassedTestsCached = false;
@Nullable private String myStacktrace;
private final boolean myIsSuite;
private boolean myIsEmptyIsCached = false; // is used for separating unknown and unset values
private boolean myIsEmpty = true;
TestLocationProvider myLocator = null;
private final boolean myPreservePresentableName;
private Printer myPreferredPrinter = null;
public SMTestProxy(final String testName, final boolean isSuite,
@Nullable final String locationUrl) {
this(testName, isSuite, locationUrl, false);
}
public SMTestProxy(final String testName, final boolean isSuite,
@Nullable final String locationUrl,
boolean preservePresentableName) {
myName = testName;
myIsSuite = isSuite;
myLocationUrl = locationUrl;
myPreservePresentableName = preservePresentableName;
}
public void setLocator(@NotNull TestLocationProvider locator) {
myLocator = locator;
}
public void setPreferredPrinter(@NotNull Printer preferredPrinter) {
myPreferredPrinter = preferredPrinter;
}
public boolean isInProgress() {
//final SMTestProxy parent = getParent();
return myState.isInProgress();
}
public boolean isDefect() {
return myState.isDefect();
}
public boolean shouldRun() {
return true;
}
public int getMagnitude() {
// Is used by some of Tests Filters
//WARN: It is Hack, see PoolOfTestStates, API is necessary
return getMagnitudeInfo().getValue();
}
public TestStateInfo.Magnitude getMagnitudeInfo() {
return myState.getMagnitude();
}
public boolean hasErrors() {
// if already cached
if (myHasErrorsCached) {
return myHasCriticalErrors;
}
final boolean canCacheErrors = !myState.isInProgress();
// calculate
final boolean hasErrors = calcHasErrors();
if (canCacheErrors) {
myHasCriticalErrors = hasErrors;
myHasErrorsCached = true;
}
return hasErrors;
}
private boolean calcHasErrors() {
if (myHasCriticalErrors) {
return true;
}
for (SMTestProxy child : getChildren()) {
if (child.hasErrors()) {
return true;
}
}
return false;
}
/**
* @return true if the state is final (PASSED, FAILED, IGNORED, TERMINATED)
*/
public boolean isFinal() {
return myState.isFinal();
}
private void setStacktraceIfNotSet(@Nullable String stacktrace) {
if (myStacktrace == null) myStacktrace = stacktrace;
}
public boolean isLeaf() {
return myChildren == null || myChildren.isEmpty();
}
@Override
public boolean isInterrupted() {
return myState.wasTerminated();
}
boolean hasPassedTests() {
if (myHasPassedTestsCached) {
return myHasPassedTests;
}
boolean hasPassedTests = calcPassedTests();
boolean canCache = !myState.isInProgress();
if (canCache) {
myHasPassedTests = hasPassedTests;
myHasPassedTestsCached = true;
}
return hasPassedTests;
}
private boolean calcPassedTests() {
if (isPassed()) {
return true;
}
for (SMTestProxy child : getChildren()) {
if (child.hasPassedTests()) {
return true;
}
}
return false;
}
@Override
public boolean isIgnored() {
if (hasPassedTests()) {
return false;
}
return myState.getMagnitude() == TestStateInfo.Magnitude.IGNORED_INDEX;
}
public boolean isPassed() {
return myState.getMagnitude() == TestStateInfo.Magnitude.SKIPPED_INDEX ||
myState.getMagnitude() == TestStateInfo.Magnitude.COMPLETE_INDEX ||
myState.getMagnitude() == TestStateInfo.Magnitude.PASSED_INDEX;
}
public void addChild(@NotNull SMTestProxy child) {
ApplicationManager.getApplication().assertIsDispatchThread();
if (myChildren == null) {
myChildren = ContainerUtil.newArrayListWithCapacity(4);
}
myChildren.add(child);
// add printable
//
// add link to child's future output in correct place
// actually if after this suite will obtain output
// it will place it after this child and before future child
addLast(child);
// add child
//
//TODO reset children cache
child.setParent(this);
// if parent is being printed then all childs output
// should be also send to the same printer
child.setPrinter(myPrinter);
if (myPreferredPrinter != null && child.myPreferredPrinter == null) {
child.setPreferredPrinter(myPreferredPrinter);
}
}
@Nullable
private Printer getRightPrinter(@Nullable Printer printer) {
if (myPreferredPrinter != null && printer != null) {
return myPreferredPrinter;
}
return printer;
}
public void setPrinter(Printer printer) {
super.setPrinter(getRightPrinter(printer));
}
public String getName() {
return myName;
}
@Nullable
public Location getLocation(final Project project, GlobalSearchScope searchScope) {
//determines location of test proxy
//TODO multiresolve support
if (myLocationUrl == null || myLocator == null) {
return null;
}
final String protocolId = VirtualFileManager.extractProtocol(myLocationUrl);
final String path = TestsLocationProviderUtil.extractPath(myLocationUrl);
if (protocolId != null && path != null) {
List<Location> locations = myLocator.getLocation(protocolId, path, project);
if (!locations.isEmpty()) {
return locations.iterator().next();
}
}
return null;
}
@Nullable
public Navigatable getDescriptor(final Location location, final TestConsoleProperties testConsoleProperties) {
// by location gets navigatable element.
// It can be file or place in file (e.g. when OPEN_FAILURE_LINE is enabled)
if (location == null) return null;
final String stacktrace = myStacktrace;
if (stacktrace != null && (testConsoleProperties instanceof SMStacktraceParser) && isLeaf()) {
final Navigatable result = ((SMStacktraceParser)testConsoleProperties).getErrorNavigatable(location.getProject(), stacktrace);
if (result != null) {
return result;
}
}
return EditSourceUtil.getDescriptor(location.getPsiElement());
}
public boolean isSuite() {
return myIsSuite;
}
public SMTestProxy getParent() {
return myParent;
}
public List<? extends SMTestProxy> getChildren() {
return myChildren != null ? myChildren : Collections.<SMTestProxy>emptyList();
}
public List<SMTestProxy> getAllTests() {
final List<SMTestProxy> allTests = new ArrayList<SMTestProxy>();
allTests.add(this);
for (SMTestProxy child : getChildren()) {
allTests.addAll(child.getAllTests());
}
return allTests;
}
public void setStarted() {
myState = !myIsSuite ? TestInProgressState.TEST : new SuiteInProgressState(this);
}
/**
* Calculates and caches duration of test or suite
* @return null if duration is unknown, otherwise duration value in milliseconds;
*/
@Nullable
@Override
public Long getDuration() {
// Returns duration value for tests
// or cached duration for suites
if (myDurationIsCached || !isSuite()) {
return myDuration;
}
//For suites counts and caches durations of its children. Also it evaluates partial duration,
//i.e. if duration is unknown it will be ignored in summary value.
//If duration for all children is unknown summary duration will be also unknown
//if one of children is ignored - it's duration will be 0 and if child wasn't run,
//then it's duration will be unknown
myDuration = calcSuiteDuration();
myDurationIsCached = true;
return myDuration;
}
@Override
public boolean shouldSkipRootNodeForExport() {
return true;
}
/**
* Sets duration of test
* @param duration In milliseconds
*/
public void setDuration(final long duration) {
invalidateCachedDurationForContainerSuites();
if (!isSuite()) {
myDurationIsCached = true;
myDuration = (duration >= 0) ? duration : null;
return;
}
// Not allow to directly set duration for suites.
// It should be the sum of children. This requirement is only
// for safety of current model and may be changed
LOG.warn("Unsupported operation");
}
public void setFinished() {
if (myState.isFinal()) {
// we shouldn't fire new printable because final state
// has been already fired
return;
}
if (!isSuite()) {
// if isn't in other finished state (ignored, failed or passed)
myState = TestPassedState.INSTANCE;
} else {
//Test Suite
myState = determineSuiteStateOnFinished();
}
// prints final state additional info
fireOnNewPrintable(myState);
}
public void setTestFailed(@NotNull final String localizedMessage,
@Nullable final String stackTrace,
final boolean testError) {
setStacktraceIfNotSet(stackTrace);
if (myState instanceof TestFailedState) {
((TestFailedState) myState).addError(localizedMessage, stackTrace, myPrinter);
}
else {
myState = testError
? new TestErrorState(localizedMessage, stackTrace)
: new TestFailedState(localizedMessage, stackTrace);
fireOnNewPrintable(myState);
}
}
public void setTestComparisonFailed(@NotNull final String localizedMessage,
@Nullable final String stackTrace,
@NotNull final String actualText,
@NotNull final String expectedText) {
setStacktraceIfNotSet(stackTrace);
myState = new TestComparisionFailedState(localizedMessage, stackTrace,
actualText, expectedText);
fireOnNewPrintable(myState);
}
public void setTestIgnored(@Nullable String ignoreComment, @Nullable String stackTrace) {
setStacktraceIfNotSet(stackTrace);
myState = new TestIgnoredState(ignoreComment, stackTrace);
fireOnNewPrintable(myState);
}
public void setParent(@Nullable final SMTestProxy parent) {
myParent = parent;
}
public List<? extends SMTestProxy> collectChildren(@Nullable final Filter<SMTestProxy> filter) {
return filterChildren(filter, collectChildren());
}
public List<? extends SMTestProxy> collectChildren() {
final List<? extends SMTestProxy> allChildren = getChildren();
final List<SMTestProxy> result = ContainerUtilRt.newArrayList();
result.addAll(allChildren);
for (SMTestProxy p: allChildren) {
result.addAll(p.collectChildren());
}
return result;
}
public List<? extends SMTestProxy> getChildren(@Nullable final Filter<? super SMTestProxy> filter) {
final List<? extends SMTestProxy> allChildren = getChildren();
return filterChildren(filter, allChildren);
}
private static List<? extends SMTestProxy> filterChildren(@Nullable Filter<? super SMTestProxy> filter,
List<? extends SMTestProxy> allChildren) {
if (filter == Filter.NO_FILTER || filter == null) {
return allChildren;
}
final List<SMTestProxy> selectedChildren = new ArrayList<SMTestProxy>();
for (SMTestProxy child : allChildren) {
if (filter.shouldAccept(child)) {
selectedChildren.add(child);
}
}
if ((selectedChildren.isEmpty())) {
return Collections.<SMTestProxy>emptyList();
}
return selectedChildren;
}
public boolean wasLaunched() {
return myState.wasLaunched();
}
/**
* Prints this proxy and all its children on given printer
* @param printer Printer
*/
public void printOn(final Printer printer) {
final Printer rightPrinter = getRightPrinter(printer);
super.printOn(rightPrinter);
final AbstractState oldState = myState;
invokeInAlarm(new Runnable() {
@Override
public void run() {
//Tests State, that provide and formats additional output
oldState.printOn(rightPrinter);
}
});
}
public void addStdOutput(final String output, final Key outputType) {
addLast(new Printable() {
public void printOn(final Printer printer) {
printer.print(output, ConsoleViewContentType.getConsoleViewType(outputType));
}
});
}
public void addStdErr(final String output) {
addLast(new Printable() {
public void printOn(final Printer printer) {
printer.print(output, ConsoleViewContentType.ERROR_OUTPUT);
}
});
}
/**
* This method was left for backward compatibility.
*
* @param output
* @param stackTrace
* @deprecated use SMTestProxy.addError(String output, String stackTrace, boolean isCritical)
*/
@Deprecated
public void addError(final String output,
@Nullable final String stackTrace) {
addError(output, stackTrace, true);
}
public void addError(final String output,
@Nullable final String stackTrace,
final boolean isCritical) {
myHasCriticalErrors = isCritical;
setStacktraceIfNotSet(stackTrace);
addLast(new Printable() {
public void printOn(final Printer printer) {
final String errorText = TestFailedState.buildErrorPresentationText(output, stackTrace);
LOG.assertTrue(errorText != null);
TestFailedState.printError(printer, Arrays.asList(errorText));
}
});
}
public void addSystemOutput(final String output) {
addLast(new Printable() {
public void printOn(final Printer printer) {
printer.print(output, ConsoleViewContentType.SYSTEM_OUTPUT);
}
});
}
@NotNull
public String getPresentableName() {
if (myPreservePresentableName) {
return TestsPresentationUtil.getPresentableNameTrimmedOnly(this);
}
return TestsPresentationUtil.getPresentableName(this);
}
@Override
@Nullable
public AssertEqualsDiffViewerProvider getDiffViewerProvider() {
if (myState instanceof AssertEqualsDiffViewerProvider) {
return (AssertEqualsDiffViewerProvider)myState;
}
return null;
}
@Override
public String toString() {
return getPresentableName();
}
/**
* Process was terminated
*/
public void setTerminated() {
if (myState.isFinal()) {
return;
}
myState = TerminatedState.INSTANCE;
final List<? extends SMTestProxy> children = getChildren();
for (SMTestProxy child : children) {
child.setTerminated();
}
fireOnNewPrintable(myState);
}
public boolean wasTerminated() {
return myState.wasTerminated();
}
@Nullable
protected String getLocationUrl() {
return myLocationUrl;
}
/**
* Check if suite contains error tests or suites
* @return True if contains
*/
private boolean containsErrorTests() {
final List<? extends SMTestProxy> children = getChildren();
for (SMTestProxy child : children) {
if (child.getMagnitudeInfo() == TestStateInfo.Magnitude.ERROR_INDEX) {
return true;
}
}
return false;
}
private boolean containsFailedTests() {
final List<? extends SMTestProxy> children = getChildren();
for (SMTestProxy child : children) {
if (child.getMagnitudeInfo() == TestStateInfo.Magnitude.FAILED_INDEX) {
return true;
}
}
return false;
}
/**
* Determines site state after it has been finished
* @return New state
*/
protected AbstractState determineSuiteStateOnFinished() {
final AbstractState state;
if (isLeaf()) {
state = SuiteFinishedState.EMPTY_LEAF_SUITE;
} else if (isEmptySuite()) {
state = SuiteFinishedState.EMPTY_SUITE;
} else {
if (isDefect()) {
// Test suit contains errors if at least one of its tests contains error
if (containsErrorTests()) {
state = SuiteFinishedState.ERROR_SUITE;
} else {
// if suite contains failed tests - all suite should be
// consider as failed
state = containsFailedTests()
? SuiteFinishedState.FAILED_SUITE
: SuiteFinishedState.WITH_IGNORED_TESTS_SUITE;
}
} else {
state = SuiteFinishedState.PASSED_SUITE;
}
}
return state;
}
public boolean isEmptySuite() {
if (myIsEmptyIsCached) {
return myIsEmpty;
}
if (!isSuite()) {
// test - no matter what we will return
myIsEmpty = true;
myIsEmptyIsCached = true;
return true;
}
myIsEmpty = true;
final List<? extends SMTestProxy> allTestCases = getChildren();
for (SMTestProxy testOrSuite : allTestCases) {
if (testOrSuite.isSuite()) {
// suite
if (!testOrSuite.isEmptySuite()) {
// => parent suite isn't empty
myIsEmpty = false;
myIsEmptyIsCached = true;
break;
}
// all suites are empty
myIsEmpty = true;
// we can cache only final state, otherwise test may be added
myIsEmptyIsCached = myState.isFinal();
} else {
// test => parent suite isn't empty
myIsEmpty = false;
myIsEmptyIsCached = true;
break;
}
}
return myIsEmpty;
}
@Nullable
private Long calcSuiteDuration() {
long partialDuration = 0;
boolean durationOfChildrenIsUnknown = true;
for (SMTestProxy child : getChildren()) {
final Long duration = child.getDuration();
if (duration != null) {
durationOfChildrenIsUnknown = false;
partialDuration += duration.longValue();
}
}
// Lets convert partial duration in integer object. Negative partial duration
// means that duration of all children is unknown
return durationOfChildrenIsUnknown ? null : partialDuration;
}
/**
* Recursively invalidates cached duration for container(parent) suites
*/
private void invalidateCachedDurationForContainerSuites() {
// Invalidates duration of this suite
myDuration = null;
myDurationIsCached = false;
// Invalidates duration of container suite
final SMTestProxy containerSuite = getParent();
if (containerSuite != null) {
containerSuite.invalidateCachedDurationForContainerSuites();
}
}
public static class SMRootTestProxy extends SMTestProxy {
private boolean myTestsReporterAttached; // false by default
public SMRootTestProxy() {
super("[root]", true, null);
}
public void setTestsReporterAttached() {
myTestsReporterAttached = true;
}
public boolean isTestsReporterAttached() {
return myTestsReporterAttached;
}
@Override
protected AbstractState determineSuiteStateOnFinished() {
if (isLeaf() && !isTestsReporterAttached()) {
return SuiteFinishedState.TESTS_REPORTER_NOT_ATTACHED;
}
return super.determineSuiteStateOnFinished();
}
}
}