/*
* Copyright 2009 Google Inc.
*
* 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.google.jstestdriver.idea.execution.tree;
import com.google.common.collect.Maps;
import com.google.jstestdriver.idea.config.JstdConfigStructure;
import com.google.jstestdriver.idea.execution.TestListenerContext;
import com.intellij.execution.testframework.sm.runner.SMTestProxy;
import com.intellij.execution.testframework.sm.runner.ui.SMTestRunnerResultsForm;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Iterator;
import java.util.Map;
import static com.google.jstestdriver.TestResult.Result;
/**
* Updates the Test Result UI panel in the IDE with Test Results. The results are streaming from the JSTD runner, so
* we update the UI as each event arrives, to give better user feedback.
*
* @author alexeagle@google.com (Alex Eagle)
*/
public class RemoteTestListener {
private final TestListenerContext myContext;
private final Map<String, BrowserNode> browserMap = Maps.newHashMap();
private final VirtualFile myDirectory;
private final StringBuilder myRootNodeLog = new StringBuilder();
private final MultiMap<String, TestNode> myRunningTestByFullNameMap = new MultiMap<String, TestNode>();
private Node myLastTestCaseParentNode;
private File myLastConfigFile;
public RemoteTestListener(@NotNull TestListenerContext ctx, @Nullable VirtualFile directory) {
myContext = ctx;
myDirectory = directory;
}
// This method must only be called on the AWT event thread, as it updates the UI.
public void onTestStarted(@NotNull TestResultProtocolMessage message) {
TestNode testNode = createTestNode(message);
String fullTestName = message.getFullTestName();
if (fullTestName != null) {
myRunningTestByFullNameMap.putValue(message.getFullTestName(), testNode);
}
}
private SMTestRunnerResultsForm getSMTestRunnerResultsForm() {
return myContext.resultsForm();
}
private SMTestProxy.SMRootTestProxy getTestsRootNode() {
return myContext.resultsForm().getTestsRootNode();
}
@NotNull
private TestNode createTestNode(@NotNull TestResultProtocolMessage message) {
BrowserNode browserNode = browserMap.get(message.browser);
if (browserNode == null) {
browserNode = new BrowserNode(message.browser);
browserMap.put(message.browser, browserNode);
onSuiteStarted(getTestsRootNode(), browserNode.getTestProxy());
}
JstdConfigFileNode jstdConfigFileNode = browserNode.getJstdConfigFileNodeByPath(message.jstdConfigFilePath);
boolean fakeJstdConfigFileNode = myDirectory == null;
boolean jstdConfigFileNodeAlreadyExists = jstdConfigFileNode != null;
if (!jstdConfigFileNodeAlreadyExists) {
jstdConfigFileNode = new JstdConfigFileNode(browserNode, myDirectory, message.jstdConfigFilePath, fakeJstdConfigFileNode);
if (!fakeJstdConfigFileNode) {
onSuiteStarted(browserNode.getTestProxy(), jstdConfigFileNode.getTestProxy());
}
}
Node testCaseParentNode = fakeJstdConfigFileNode ? browserNode : jstdConfigFileNode;
if (!jstdConfigFileNodeAlreadyExists) {
JstdConfigStructure configStructure = JstdConfigStructure.newConfigStructure(jstdConfigFileNode.getConfigFile());
StacktracePrinter stacktracePrinter = new StacktracePrinter(myContext.consoleView(), configStructure, message.browser);
testCaseParentNode.wirePrinter(stacktracePrinter);
}
myLastTestCaseParentNode = testCaseParentNode;
TestCaseNode testCaseNode = jstdConfigFileNode.getTestCaseNode(message.testCase);
myLastConfigFile = jstdConfigFileNode.getConfigFile();
if (testCaseNode == null) {
testCaseNode = new TestCaseNode(jstdConfigFileNode, message.testCase);
onSuiteStarted(testCaseParentNode.getTestProxy(), testCaseNode.getTestProxy());
}
TestNode testNode = testCaseNode.getTestByName(message.testName);
if (testNode == null) {
testNode = new TestNode(testCaseNode, message.testName);
onTestStarted(testCaseNode.getTestProxy(), testNode.getTestProxy());
}
return testNode;
}
@Nullable
private TestNode findTestNode(TestResultProtocolMessage message) {
BrowserNode browserNode = browserMap.get(message.browser);
if (browserNode == null) {
return null;
}
JstdConfigFileNode jstdConfigFileNode = browserNode.getJstdConfigFileNodeByPath(message.jstdConfigFilePath);
if (jstdConfigFileNode == null) {
return null;
}
TestCaseNode testCaseNode = jstdConfigFileNode.getTestCaseNode(message.testCase);
if (testCaseNode == null) {
return null;
}
return testCaseNode.getTestByName(message.testName);
}
// This method must only be called on the AWT event thread, as it updates the UI.
public void onTestFinished(final TestResultProtocolMessage message) {
TestNode testNode = findTestNode(message);
if (testNode == null) {
String fullName = message.getFullTestName();
final Iterator<TestNode> startedTestNodesIt = myRunningTestByFullNameMap.get(fullName).iterator();
if (startedTestNodesIt.hasNext()) {
TestNode startedTestNode = startedTestNodesIt.next();
startedTestNodesIt.remove();
// fixes problem when testCase or test name contains '.'
startedTestNode.getTestProxy().setFixedName(message.testName);
TestCaseNode startedTestCaseNode = startedTestNode.getTestCaseNode();
startedTestCaseNode.getTestProxy().setFixedName(message.testCase);
testNode = startedTestNode;
} else {
// jasmine adapter hack
testNode = createTestNode(message);
}
}
testNode.done();
TestCaseNode testCaseNode = testNode.getTestCaseNode();
JstdConfigFileNode jstdConfigFileNode = testCaseNode.getJstdConfigFileNode();
BrowserNode browserNode = jstdConfigFileNode.getBrowserNode();
SMTestProxy testProxy = testNode.getTestProxy();
testProxy.addStdOutput(message.log, Key.create("result"));
testProxy.setDuration(Math.round(message.duration));
Result result = Result.valueOf(message.result);
if (result == Result.passed) {
onTestFinished(testProxy);
} else {
final String stackStr;
if (message.stack.startsWith(message.message)) {
String s = message.stack.substring(message.message.length());
stackStr = s.replaceFirst("^[\n\r]*", "");
} else {
stackStr = message.stack;
}
testProxy.setTestFailed(message.message, stackStr, result == Result.error);
getSMTestRunnerResultsForm().onTestFailed(testProxy);
testCaseNode.setTestFailed(result);
jstdConfigFileNode.setTestFailed(result);
browserNode.setTestFailed(result);
}
if (testCaseNode.isComplete()) {
onSuiteFinished(testCaseNode.getTestProxy());
}
if (jstdConfigFileNode.isComplete()) {
onSuiteFinished(jstdConfigFileNode.getTestProxy());
}
if (browserNode.isComplete()) {
onSuiteFinished(browserNode.getTestProxy());
}
}
private static void onSuiteStarted(SMTestProxy parent, SMTestProxy child) {
parent.addChild(child);
}
public void onTestStarted(SMTestProxy parent, SMTestProxy child) {
parent.addChild(child);
getSMTestRunnerResultsForm().onTestStarted(child);
}
public static void onSuiteFinished(SMTestProxy node) {
node.setFinished();
}
public void onTestFinished(SMTestProxy node) {
node.setFinished();
getSMTestRunnerResultsForm().onTestFinished(node);
}
public void onTestRunnerFailed(JstdTestRunnerFailure testRunnerFailure) {
if (testRunnerFailure.getFailureType() == JstdTestRunnerFailure.FailureType.SINGLE_JSTD_CONFIG) {
if (myLastConfigFile != null && myLastConfigFile.equals(new File(testRunnerFailure.getJstdConfigPath()))) {
myLastTestCaseParentNode.getTestProxy().setTestFailed(testRunnerFailure.getMessage(), null, true);
} else {
SMTestProxy configNode = JstdConfigFileNode.createTestProxy(myDirectory, testRunnerFailure.getJstdConfigPath());
getTestsRootNode().addChild(configNode);
configNode.setTestFailed(testRunnerFailure.getMessage(), null, true);
}
} else {
myRootNodeLog.append(testRunnerFailure.getMessage()).append("\n");
getTestsRootNode().setTestFailed(myRootNodeLog.toString(), null, true);
}
}
}