| /* |
| * 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.process.ProcessOutputTypes; |
| import com.intellij.execution.testframework.AbstractTestProxy; |
| import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil; |
| import com.intellij.execution.testframework.sm.SMTestsRunnerBundle; |
| import com.intellij.execution.testframework.sm.runner.events.*; |
| import com.intellij.openapi.application.Application; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.testIntegration.TestLocationProvider; |
| import com.intellij.util.ObjectUtils; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.annotations.TestOnly; |
| |
| import java.util.*; |
| |
| /** |
| * @author: Roman Chernyatchik |
| * |
| * This class fires events to RTestUnitEventsListener in EventDispatch thread |
| */ |
| public class GeneralToSMTRunnerEventsConvertor extends GeneralTestEventsProcessor { |
| private static final Logger LOG = Logger.getInstance(GeneralToSMTRunnerEventsConvertor.class.getName()); |
| |
| private final Map<String, SMTestProxy> myRunningTestsFullNameToProxy = new HashMap<String, SMTestProxy>(); |
| |
| private final Set<AbstractTestProxy> myFailedTestsSet = new HashSet<AbstractTestProxy>(); |
| |
| private final TestSuiteStack mySuitesStack = new TestSuiteStack(); |
| private final List<SMTRunnerEventsListener> myEventsListeners = ContainerUtil.createLockFreeCopyOnWriteList(); |
| private final SMTestProxy.SMRootTestProxy myTestsRootNode; |
| private final String myTestFrameworkName; |
| private boolean myIsTestingFinished; |
| private TestLocationProvider myLocator = null; |
| |
| public GeneralToSMTRunnerEventsConvertor(@NotNull final SMTestProxy.SMRootTestProxy testsRootNode, |
| @NotNull final String testFrameworkName) { |
| myTestsRootNode = testsRootNode; |
| myTestFrameworkName = testFrameworkName; |
| } |
| |
| public void setLocator(@NotNull TestLocationProvider customLocator) { |
| myLocator = customLocator; |
| } |
| |
| public void addEventsListener(@NotNull final SMTRunnerEventsListener listener) { |
| myEventsListeners.add(listener); |
| } |
| |
| public void onStartTesting() { |
| addToInvokeLater(new Runnable() { |
| public void run() { |
| mySuitesStack.pushSuite(myTestsRootNode); |
| myTestsRootNode.setStarted(); |
| |
| //fire |
| fireOnTestingStarted(); |
| } |
| }); |
| } |
| |
| @Override |
| public void onTestsReporterAttached() { |
| addToInvokeLater(new Runnable() { |
| public void run() { |
| myTestsRootNode.setTestsReporterAttached(); |
| } |
| }); |
| } |
| |
| public void onFinishTesting() { |
| addToInvokeLater(new Runnable() { |
| public void run() { |
| if (myIsTestingFinished) { |
| // has been already invoked! |
| return; |
| } |
| myIsTestingFinished = true; |
| |
| // We don't know whether process was destroyed by user |
| // or it finished after all tests have been run |
| // Lets assume, if at finish all suites except root suite are passed |
| // then all is ok otherwise process was terminated by user |
| if (!myTestsRootNode.equals(mySuitesStack.getCurrentSuite())) { |
| myTestsRootNode.setTerminated(); |
| myRunningTestsFullNameToProxy.clear(); |
| } |
| mySuitesStack.clear(); |
| myTestsRootNode.setFinished(); |
| |
| |
| //fire events |
| fireOnTestingFinished(); |
| } |
| }); |
| } |
| |
| @Override |
| public void setPrinterProvider(@NotNull TestProxyPrinterProvider printerProvider) { |
| } |
| |
| public void onTestStarted(@NotNull final TestStartedEvent testStartedEvent) { |
| addToInvokeLater(new Runnable() { |
| public void run() { |
| final String testName = testStartedEvent.getName(); |
| final String locationUrl = testStartedEvent.getLocationUrl(); |
| final String fullName = getFullTestName(testName); |
| |
| if (myRunningTestsFullNameToProxy.containsKey(fullName)) { |
| //Duplicated event |
| logProblem("Test [" + fullName + "] has been already started"); |
| |
| if (SMTestRunnerConnectionUtil.isInDebugMode()) { |
| return; |
| } |
| } |
| |
| final SMTestProxy parentSuite = getCurrentSuite(); |
| |
| // creates test |
| final SMTestProxy testProxy = new SMTestProxy(testName, false, locationUrl); |
| if (myLocator != null) { |
| testProxy.setLocator(myLocator); |
| } |
| parentSuite.addChild(testProxy); |
| // adds to running tests map |
| myRunningTestsFullNameToProxy.put(fullName, testProxy); |
| |
| //Progress started |
| testProxy.setStarted(); |
| |
| //fire events |
| fireOnTestStarted(testProxy); |
| } |
| }); |
| } |
| |
| public void onSuiteStarted(@NotNull final TestSuiteStartedEvent suiteStartedEvent) { |
| addToInvokeLater(new Runnable() { |
| public void run() { |
| final String suiteName = suiteStartedEvent.getName(); |
| final String locationUrl = suiteStartedEvent.getLocationUrl(); |
| final SMTestProxy parentSuite = getCurrentSuite(); |
| //new suite |
| final SMTestProxy newSuite = new SMTestProxy(suiteName, true, locationUrl); |
| if (myLocator != null) { |
| newSuite.setLocator(myLocator); |
| } |
| parentSuite.addChild(newSuite); |
| |
| mySuitesStack.pushSuite(newSuite); |
| |
| //Progress started |
| newSuite.setStarted(); |
| |
| //fire event |
| fireOnSuiteStarted(newSuite); |
| } |
| }); |
| } |
| |
| public void onTestFinished(@NotNull final TestFinishedEvent testFinishedEvent) { |
| addToInvokeLater(new Runnable() { |
| public void run() { |
| final String testName = testFinishedEvent.getName(); |
| final long duration = testFinishedEvent.getDuration(); |
| final String fullTestName = getFullTestName(testName); |
| final SMTestProxy testProxy = getProxyByFullTestName(fullTestName); |
| |
| if (testProxy == null) { |
| logProblem("Test wasn't started! TestFinished event: name = {" + testName + "}. " + |
| cannotFindFullTestNameMsg(fullTestName)); |
| return; |
| } |
| |
| testProxy.setDuration(duration); |
| testProxy.setFinished(); |
| myRunningTestsFullNameToProxy.remove(fullTestName); |
| |
| //fire events |
| fireOnTestFinished(testProxy); |
| } |
| }); |
| } |
| |
| public void onSuiteFinished(@NotNull final TestSuiteFinishedEvent suiteFinishedEvent) { |
| addToInvokeLater(new Runnable() { |
| public void run() { |
| final String suiteName = suiteFinishedEvent.getName(); |
| final SMTestProxy mySuite = mySuitesStack.popSuite(suiteName); |
| if (mySuite != null) { |
| mySuite.setFinished(); |
| |
| //fire events |
| fireOnSuiteFinished(mySuite); |
| } |
| } |
| }); |
| } |
| |
| public void onUncapturedOutput(@NotNull final String text, final Key outputType) { |
| addToInvokeLater(new Runnable() { |
| public void run() { |
| final SMTestProxy currentProxy = findCurrentTestOrSuite(); |
| |
| if (ProcessOutputTypes.STDERR.equals(outputType)) { |
| currentProxy.addStdErr(text); |
| } else if (ProcessOutputTypes.SYSTEM.equals(outputType)) { |
| currentProxy.addSystemOutput(text); |
| } else { |
| currentProxy.addStdOutput(text, outputType); |
| } |
| } |
| }); |
| } |
| |
| public void onError(@NotNull final String localizedMessage, |
| @Nullable final String stackTrace, |
| final boolean isCritical) { |
| addToInvokeLater(new Runnable() { |
| public void run() { |
| final SMTestProxy currentProxy = findCurrentTestOrSuite(); |
| currentProxy.addError(localizedMessage, stackTrace, isCritical); |
| } |
| }); |
| } |
| |
| public void onCustomProgressTestsCategory(@Nullable final String categoryName, |
| final int testCount) { |
| addToInvokeLater(new Runnable() { |
| public void run() { |
| fireOnCustomProgressTestsCategory(categoryName, testCount); |
| } |
| }); |
| } |
| |
| public void onCustomProgressTestStarted() { |
| addToInvokeLater(new Runnable() { |
| public void run() { |
| fireOnCustomProgressTestStarted(); |
| } |
| }); |
| } |
| |
| public void onCustomProgressTestFailed() { |
| addToInvokeLater(new Runnable() { |
| public void run() { |
| fireOnCustomProgressTestFailed(); |
| } |
| }); |
| } |
| |
| public void onTestFailure(@NotNull final TestFailedEvent testFailedEvent) { |
| addToInvokeLater(new Runnable() { |
| public void run() { |
| final String testName = testFailedEvent.getName(); |
| if (testName == null) { |
| logProblem("No test name specified in " + testFailedEvent); |
| return; |
| } |
| final String localizedMessage = testFailedEvent.getLocalizedFailureMessage(); |
| final String stackTrace = testFailedEvent.getStacktrace(); |
| final boolean isTestError = testFailedEvent.isTestError(); |
| final String comparisionFailureActualText = testFailedEvent.getComparisonFailureActualText(); |
| final String comparisionFailureExpectedText = testFailedEvent.getComparisonFailureExpectedText(); |
| final boolean inDebugMode = SMTestRunnerConnectionUtil.isInDebugMode(); |
| |
| final String fullTestName = getFullTestName(testName); |
| SMTestProxy testProxy = getProxyByFullTestName(fullTestName); |
| if (testProxy == null) { |
| logProblem("Test wasn't started! TestFailure event: name = {" + testName + "}" + |
| ", message = {" + localizedMessage + "}" + |
| ", stackTrace = {" + stackTrace + "}. " + |
| cannotFindFullTestNameMsg(fullTestName)); |
| if (inDebugMode) { |
| return; |
| } else { |
| // try to fix the problem: |
| if (!myFailedTestsSet.contains(testProxy)) { |
| // if hasn't been already reported |
| // 1. report |
| onTestStarted(new TestStartedEvent(testName, null)); |
| // 2. add failure |
| testProxy = getProxyByFullTestName(fullTestName); |
| } |
| } |
| } |
| |
| if (testProxy == null) { |
| return; |
| } |
| |
| if (comparisionFailureActualText != null && comparisionFailureExpectedText != null) { |
| if (myFailedTestsSet.contains(testProxy)) { |
| // duplicate message |
| logProblem("Duplicate failure for test [" + fullTestName + "]: msg = " + localizedMessage + ", stacktrace = " + stackTrace); |
| |
| if (inDebugMode) { |
| return; |
| } |
| } |
| |
| testProxy.setTestComparisonFailed(localizedMessage, stackTrace, |
| comparisionFailureActualText, comparisionFailureExpectedText); |
| } else if (comparisionFailureActualText == null && comparisionFailureExpectedText == null) { |
| testProxy.setTestFailed(localizedMessage, stackTrace, isTestError); |
| } else { |
| logProblem("Comparison failure actual and expected texts should be both null or not null.\n" |
| + "Expected:\n" |
| + comparisionFailureExpectedText + "\n" |
| + "Actual:\n" |
| + comparisionFailureActualText); |
| } |
| |
| myFailedTestsSet.add(testProxy); |
| |
| // fire event |
| fireOnTestFailed(testProxy); |
| } |
| }); |
| } |
| |
| public void onTestIgnored(@NotNull final TestIgnoredEvent testIgnoredEvent) { |
| addToInvokeLater(new Runnable() { |
| public void run() { |
| final String testName = ObjectUtils.assertNotNull(testIgnoredEvent.getName()); |
| String ignoreComment = testIgnoredEvent.getIgnoreComment(); |
| if (StringUtil.isEmpty(ignoreComment)) { |
| ignoreComment = SMTestsRunnerBundle.message("sm.test.runner.states.test.is.ignored"); |
| } |
| final String stackTrace = testIgnoredEvent.getStacktrace(); |
| final String fullTestName = getFullTestName(testName); |
| SMTestProxy testProxy = getProxyByFullTestName(fullTestName); |
| if (testProxy == null) { |
| final boolean debugMode = SMTestRunnerConnectionUtil.isInDebugMode(); |
| logProblem("Test wasn't started! " + |
| "TestIgnored event: name = {" + testName + "}, " + |
| "message = {" + ignoreComment + "}. " + |
| cannotFindFullTestNameMsg(fullTestName)); |
| if (debugMode) { |
| return; |
| } else { |
| // try to fix |
| // 1. report test opened |
| onTestStarted(new TestStartedEvent(testName, null)); |
| |
| // 2. report failure |
| testProxy = getProxyByFullTestName(fullTestName); |
| } |
| |
| } |
| if (testProxy == null) { |
| return; |
| } |
| testProxy.setTestIgnored(ignoreComment, stackTrace); |
| |
| // fire event |
| fireOnTestIgnored(testProxy); |
| } |
| }); |
| } |
| |
| public void onTestOutput(@NotNull final TestOutputEvent testOutputEvent) { |
| addToInvokeLater(new Runnable() { |
| public void run() { |
| final String testName = testOutputEvent.getName(); |
| final String text = testOutputEvent.getText(); |
| final boolean stdOut = testOutputEvent.isStdOut(); |
| final String fullTestName = getFullTestName(testName); |
| final SMTestProxy testProxy = getProxyByFullTestName(fullTestName); |
| if (testProxy == null) { |
| logProblem("Test wasn't started! TestOutput event: name = {" + testName + "}, " + |
| "isStdOut = " + stdOut + ", " + |
| "text = {" + text + "}. " + |
| cannotFindFullTestNameMsg(fullTestName)); |
| return; |
| } |
| |
| if (stdOut) { |
| testProxy.addStdOutput(text, ProcessOutputTypes.STDOUT); |
| } else { |
| testProxy.addStdErr(text); |
| } |
| } |
| }); |
| } |
| |
| public void onTestsCountInSuite(final int count) { |
| addToInvokeLater(new Runnable() { |
| public void run() { |
| fireOnTestsCountInSuite(count); |
| } |
| }); |
| } |
| |
| @NotNull |
| protected final SMTestProxy getCurrentSuite() { |
| final SMTestProxy currentSuite = mySuitesStack.getCurrentSuite(); |
| |
| if (currentSuite != null) { |
| return currentSuite; |
| } |
| |
| // current suite shouldn't be null otherwise test runner isn't correct |
| // or may be we are in debug mode |
| logProblem("Current suite is undefined. Root suite will be used."); |
| return myTestsRootNode; |
| |
| } |
| |
| protected String getFullTestName(final String testName) { |
| // Test name should be unique |
| return testName; |
| } |
| |
| protected int getRunningTestsQuantity() { |
| return myRunningTestsFullNameToProxy.size(); |
| } |
| |
| protected Set<AbstractTestProxy> getFailedTestsSet() { |
| return Collections.unmodifiableSet(myFailedTestsSet); |
| } |
| |
| @Nullable |
| protected SMTestProxy getProxyByFullTestName(final String fullTestName) { |
| return myRunningTestsFullNameToProxy.get(fullTestName); |
| } |
| |
| @TestOnly |
| protected void clearInternalSuitesStack() { |
| mySuitesStack.clear(); |
| } |
| |
| private String cannotFindFullTestNameMsg(String fullTestName) { |
| return "Cant find running test for [" |
| + fullTestName |
| + "]. Current running tests: {" |
| + dumpRunningTestsNames() + "}"; |
| } |
| |
| private StringBuilder dumpRunningTestsNames() { |
| final Set<String> names = myRunningTestsFullNameToProxy.keySet(); |
| final StringBuilder namesDump = new StringBuilder(); |
| for (String name : names) { |
| namesDump.append('[').append(name).append(']').append(','); |
| } |
| return namesDump; |
| } |
| |
| private void fireOnTestingStarted() { |
| for (SMTRunnerEventsListener listener : myEventsListeners) { |
| listener.onTestingStarted(myTestsRootNode); |
| } |
| } |
| |
| private void fireOnTestingFinished() { |
| for (SMTRunnerEventsListener listener : myEventsListeners) { |
| listener.onTestingFinished(myTestsRootNode); |
| } |
| } |
| |
| private void fireOnTestsCountInSuite(final int count) { |
| for (SMTRunnerEventsListener listener : myEventsListeners) { |
| listener.onTestsCountInSuite(count); |
| } |
| } |
| |
| |
| private void fireOnTestStarted(final SMTestProxy test) { |
| for (SMTRunnerEventsListener listener : myEventsListeners) { |
| listener.onTestStarted(test); |
| } |
| } |
| |
| private void fireOnTestFinished(final SMTestProxy test) { |
| for (SMTRunnerEventsListener listener : myEventsListeners) { |
| listener.onTestFinished(test); |
| } |
| } |
| |
| private void fireOnTestFailed(final SMTestProxy test) { |
| for (SMTRunnerEventsListener listener : myEventsListeners) { |
| listener.onTestFailed(test); |
| } |
| } |
| |
| private void fireOnTestIgnored(final SMTestProxy test) { |
| for (SMTRunnerEventsListener listener : myEventsListeners) { |
| listener.onTestIgnored(test); |
| } |
| } |
| |
| private void fireOnSuiteStarted(final SMTestProxy suite) { |
| for (SMTRunnerEventsListener listener : myEventsListeners) { |
| listener.onSuiteStarted(suite); |
| } |
| } |
| |
| private void fireOnSuiteFinished(final SMTestProxy suite) { |
| for (SMTRunnerEventsListener listener : myEventsListeners) { |
| listener.onSuiteFinished(suite); |
| } |
| } |
| |
| |
| private void fireOnCustomProgressTestsCategory(@Nullable final String categoryName, int testCount) { |
| for (SMTRunnerEventsListener listener : myEventsListeners) { |
| listener.onCustomProgressTestsCategory(categoryName, testCount); |
| } |
| } |
| |
| private void fireOnCustomProgressTestStarted() { |
| for (SMTRunnerEventsListener listener : myEventsListeners) { |
| listener.onCustomProgressTestStarted(); |
| } |
| } |
| |
| private void fireOnCustomProgressTestFailed() { |
| for (SMTRunnerEventsListener listener : myEventsListeners) { |
| listener.onCustomProgressTestFailed(); |
| } |
| } |
| |
| /* |
| * Remove listeners, etc |
| */ |
| public void dispose() { |
| super.dispose(); |
| addToInvokeLater(new Runnable() { |
| public void run() { |
| myEventsListeners.clear(); |
| |
| if (!myRunningTestsFullNameToProxy.isEmpty()) { |
| final Application application = ApplicationManager.getApplication(); |
| if (!application.isHeadlessEnvironment() && !application.isUnitTestMode()) { |
| logProblem("Not all events were processed! " + dumpRunningTestsNames()); |
| } |
| } |
| myRunningTestsFullNameToProxy.clear(); |
| mySuitesStack.clear(); |
| } |
| }); |
| } |
| |
| |
| private SMTestProxy findCurrentTestOrSuite() { |
| //if we can locate test - we will send output to it, otherwise to current test suite |
| final SMTestProxy currentProxy; |
| if (myRunningTestsFullNameToProxy.size() == 1) { |
| //current test |
| currentProxy = myRunningTestsFullNameToProxy.values().iterator().next(); |
| } else { |
| //current suite |
| // |
| // ProcessHandler can fire output available event before processStarted event |
| currentProxy = mySuitesStack.isEmpty() ? myTestsRootNode : getCurrentSuite(); |
| } |
| return currentProxy; |
| } |
| |
| public static String getTFrameworkPrefix(final String testFrameworkName) { |
| return "[" + testFrameworkName + "]: "; |
| } |
| |
| private void logProblem(final String msg) { |
| logProblem(LOG, msg, myTestFrameworkName); |
| } |
| |
| public static void logProblem(final Logger log, final String msg, final String testFrameworkName) { |
| logProblem(log, msg, SMTestRunnerConnectionUtil.isInDebugMode(), testFrameworkName); |
| } |
| |
| public static void logProblem(final Logger log, final String msg, boolean throwError, final String testFrameworkName) { |
| final String text = getTFrameworkPrefix(testFrameworkName) + msg; |
| if (throwError) { |
| log.error(text); |
| } |
| else { |
| log.warn(text); |
| } |
| } |
| } |