/* * Copyright 2000-2015 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.sm.SMTestRunnerConnectionUtil; import com.intellij.execution.testframework.sm.runner.events.*; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.util.ObjectUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import java.util.*; /** * This class fires events to SMTRunnerEventsListener in event dispatch thread. * * @author: Roman Chernyatchik */ public class GeneralToSMTRunnerEventsConvertor extends GeneralTestEventsProcessor { private final Map<String, SMTestProxy> myRunningTestsFullNameToProxy = new HashMap<String, SMTestProxy>(); private final TestSuiteStack mySuitesStack; private final Set<SMTestProxy> myCurrentChildren = new LinkedHashSet<SMTestProxy>(); private boolean myGetChildren = true; private final SMTestProxy.SMRootTestProxy myTestsRootNode; private boolean myIsTestingFinished; private SMTestLocator myLocator = null; private boolean myTreeBuildBeforeStart = false; public GeneralToSMTRunnerEventsConvertor(Project project, @NotNull SMTestProxy.SMRootTestProxy testsRootNode, @NotNull String testFrameworkName) { super(project, testFrameworkName); myTestsRootNode = testsRootNode; mySuitesStack = new TestSuiteStack(testFrameworkName); } @Override public void setLocator(@NotNull SMTestLocator locator) { myLocator = locator; } @Override public void onStartTesting() { addToInvokeLater(new Runnable() { @Override public void run() { mySuitesStack.pushSuite(myTestsRootNode); myTestsRootNode.setStarted(); //fire fireOnTestingStarted(myTestsRootNode); } }); } @Override public void onTestsReporterAttached() { addToInvokeLater(new Runnable() { @Override public void run() { fireOnTestsReporterAttached(myTestsRootNode); } }); } @Override public void onFinishTesting() { addToInvokeLater(new Runnable() { @Override 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 (!isTreeComplete(myRunningTestsFullNameToProxy.keySet(), myTestsRootNode)) { myTestsRootNode.setTerminated(); myRunningTestsFullNameToProxy.clear(); } mySuitesStack.clear(); myTestsRootNode.setFinished(); //fire events fireOnTestingFinished(myTestsRootNode); } }); stopEventProcessing(); } @Override public void onRootPresentationAdded(final String rootName, final String comment, final String rootLocation) { addToInvokeLater(new Runnable() { @Override public void run() { myTestsRootNode.setPresentation(rootName); myTestsRootNode.setComment(comment); myTestsRootNode.setRootLocationUrl(rootLocation); if (myLocator != null) { myTestsRootNode.setLocator(myLocator); } } }); } private final List<Runnable> myBuildTreeRunnables = new ArrayList<Runnable>(); @Override public void onSuiteTreeNodeAdded(final String testName, final String locationHint) { myTreeBuildBeforeStart = true; myBuildTreeRunnables.add(new Runnable() { @Override public void run() { final SMTestProxy testProxy = new SMTestProxy(testName, false, locationHint); if (myLocator != null) { testProxy.setLocator(myLocator); } getCurrentSuite().addChild(testProxy); myEventPublisher.onSuiteTreeNodeAdded(testProxy); for (SMTRunnerEventsListener adapter : myListenerAdapters) { adapter.onSuiteTreeNodeAdded(testProxy); } } }); } @Override public void onSuiteTreeStarted(final String suiteName, final String locationHint) { myTreeBuildBeforeStart = true; myBuildTreeRunnables.add(new Runnable() { @Override public void run() { final SMTestProxy parentSuite = getCurrentSuite(); final SMTestProxy newSuite = new SMTestProxy(suiteName, true, locationHint); if (myLocator != null) { newSuite.setLocator(myLocator); } parentSuite.addChild(newSuite); mySuitesStack.pushSuite(newSuite); myEventPublisher.onSuiteTreeStarted(newSuite); for (SMTRunnerEventsListener adapter : myListenerAdapters) { adapter.onSuiteTreeStarted(newSuite); } } }); } @Override public void onSuiteTreeEnded(final String suiteName) { myBuildTreeRunnables.add(new Runnable() { @Override public void run() { mySuitesStack.popSuite(suiteName); } }); if (myBuildTreeRunnables.size() > 100) { final ArrayList<Runnable> runnables = new ArrayList<Runnable>(myBuildTreeRunnables); myBuildTreeRunnables.clear(); processTreeBuildEvents(runnables); } } @Override public void onBuildTreeEnded() { processTreeBuildEvents(myBuildTreeRunnables); } private void processTreeBuildEvents(final List<Runnable> runnables) { addToInvokeLater(new Runnable() { @Override public void run() { for (Runnable runnable : runnables) { runnable.run(); } runnables.clear(); } }); } @Override public void setPrinterProvider(@NotNull TestProxyPrinterProvider printerProvider) { } @Override public void onTestStarted(@NotNull final TestStartedEvent testStartedEvent) { addToInvokeLater(new Runnable() { @Override public void run() { final String testName = testStartedEvent.getName(); final String locationUrl = testStartedEvent.getLocationUrl(); final boolean isConfig = testStartedEvent.isConfig(); final String fullName = getFullTestName(testName); if (myRunningTestsFullNameToProxy.containsKey(fullName)) { //Duplicated event logProblem("Test [" + fullName + "] has been already started"); if (SMTestRunnerConnectionUtil.isInDebugMode()) { return; } } SMTestProxy parentSuite = getCurrentSuite(); SMTestProxy testProxy = findChildByName(parentSuite, fullName); if (testProxy == null) { // creates test testProxy = new SMTestProxy(testName, false, locationUrl); testProxy.setConfig(isConfig); if (myLocator != null) { testProxy.setLocator(myLocator); } parentSuite.addChild(testProxy); if (myTreeBuildBeforeStart && myGetChildren) { for (SMTestProxy proxy : parentSuite.getChildren()) { if (!proxy.isFinal()) { myCurrentChildren.add(proxy); } } myGetChildren = false; } } // adds to running tests map myRunningTestsFullNameToProxy.put(fullName, testProxy); //Progress started testProxy.setStarted(); //fire events fireOnTestStarted(testProxy); } }); } @Override public void onSuiteStarted(@NotNull final TestSuiteStartedEvent suiteStartedEvent) { addToInvokeLater(new Runnable() { @Override public void run() { final String suiteName = suiteStartedEvent.getName(); final String locationUrl = suiteStartedEvent.getLocationUrl(); SMTestProxy parentSuite = getCurrentSuite(); SMTestProxy newSuite = findChildByName(parentSuite, suiteName); if (newSuite == null) { //new suite newSuite = new SMTestProxy(suiteName, true, locationUrl); if (myLocator != null) { newSuite.setLocator(myLocator); } parentSuite.addChild(newSuite); } myGetChildren = true; mySuitesStack.pushSuite(newSuite); //Progress started newSuite.setSuiteStarted(); //fire event fireOnSuiteStarted(newSuite); } }); } private SMTestProxy findChildByName(SMTestProxy parentSuite, String fullName) { if (myTreeBuildBeforeStart) { final Collection<? extends SMTestProxy> children = myGetChildren ? parentSuite.getChildren() : myCurrentChildren; for (SMTestProxy proxy : children) { if (fullName.equals(proxy.getName()) && !proxy.isFinal()) { return proxy; } } } return null; } @Override public void onTestFinished(@NotNull final TestFinishedEvent testFinishedEvent) { addToInvokeLater(new Runnable() { @Override 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.setFrameworkOutputFile(testFinishedEvent.getOutputFile()); testProxy.setFinished(); myRunningTestsFullNameToProxy.remove(fullTestName); myCurrentChildren.remove(testProxy); //fire events fireOnTestFinished(testProxy); } }); } @Override public void onSuiteFinished(@NotNull final TestSuiteFinishedEvent suiteFinishedEvent) { addToInvokeLater(new Runnable() { @Override public void run() { final String suiteName = suiteFinishedEvent.getName(); final SMTestProxy mySuite = mySuitesStack.popSuite(suiteName); if (mySuite != null) { mySuite.setFinished(); myCurrentChildren.clear(); myGetChildren = true; //fire events fireOnSuiteFinished(mySuite); } } }); } @Override public void onUncapturedOutput(@NotNull final String text, final Key outputType) { addToInvokeLater(new Runnable() { @Override 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); } } }); } @Override public void onError(@NotNull final String localizedMessage, @Nullable final String stackTrace, final boolean isCritical) { addToInvokeLater(new Runnable() { @Override public void run() { final SMTestProxy currentProxy = findCurrentTestOrSuite(); currentProxy.addError(localizedMessage, stackTrace, isCritical); } }); } @Override public void onTestFailure(@NotNull final TestFailedEvent testFailedEvent) { addToInvokeLater(new Runnable() { @Override 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 { // 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) { testProxy.setTestComparisonFailed(localizedMessage, stackTrace, comparisionFailureActualText, comparisionFailureExpectedText, testFailedEvent.getFilePath(), testFailedEvent.getActualFilePath()); } 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); } // fire event fireOnTestFailed(testProxy); } }); } @Override public void onTestIgnored(@NotNull final TestIgnoredEvent testIgnoredEvent) { addToInvokeLater(new Runnable() { @Override public void run() { final String testName = ObjectUtils.assertNotNull(testIgnoredEvent.getName()); String ignoreComment = testIgnoredEvent.getIgnoreComment(); 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); } }); } @Override public void onTestOutput(@NotNull final TestOutputEvent testOutputEvent) { addToInvokeLater(new Runnable() { @Override 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); } } }); } @Override public void onTestsCountInSuite(final int count) { addToInvokeLater(new Runnable() { @Override 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."); myGetChildren = true; return myTestsRootNode; } protected String getFullTestName(final String testName) { // Test name should be unique return testName; } protected int getRunningTestsQuantity() { return myRunningTestsFullNameToProxy.size(); } @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; } /* * Remove listeners, etc */ @Override public void dispose() { super.dispose(); addToInvokeLater(new Runnable() { @Override public void run() { disconnectListeners(); 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; } }