mocha: show actual file in Diff dialog (WEB-15907)
[idea/community.git] / platform / smRunner / src / com / intellij / execution / testframework / sm / runner / GeneralIdBasedToSMTRunnerEventsConvertor.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.execution.testframework.sm.runner;
17
18 import com.intellij.execution.process.ProcessOutputTypes;
19 import com.intellij.execution.testframework.Printer;
20 import com.intellij.execution.testframework.sm.runner.events.*;
21 import com.intellij.openapi.application.Application;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.util.Key;
25 import com.intellij.util.containers.ContainerUtil;
26 import com.intellij.util.containers.hash.HashMap;
27 import org.jetbrains.annotations.NotNull;
28 import org.jetbrains.annotations.Nullable;
29
30 import java.util.Set;
31
32 public class GeneralIdBasedToSMTRunnerEventsConvertor extends GeneralTestEventsProcessor {
33
34   private final HashMap<String, Node> myNodeByIdMap = new HashMap<>();
35   private final Set<Node> myRunningTestNodes = ContainerUtil.newHashSet();
36   private final SMTestProxy.SMRootTestProxy myTestsRootProxy;
37   private final Node myTestsRootNode;
38
39   private boolean myIsTestingFinished = false;
40   private SMTestLocator myLocator = null;
41   private TestProxyPrinterProvider myTestProxyPrinterProvider = null;
42
43   public GeneralIdBasedToSMTRunnerEventsConvertor(Project project,
44                                                   @NotNull SMTestProxy.SMRootTestProxy testsRootProxy,
45                                                   @NotNull String testFrameworkName) {
46     super(project, testFrameworkName);
47     myTestsRootProxy = testsRootProxy;
48     myTestsRootNode = new Node(TreeNodeEvent.ROOT_NODE_ID, null, testsRootProxy);
49     myNodeByIdMap.put(myTestsRootNode.getId(), myTestsRootNode);
50   }
51
52   @Override
53   public void setLocator(@NotNull SMTestLocator locator) {
54     myLocator = locator;
55   }
56
57   public void onStartTesting() {
58     addToInvokeLater(new Runnable() {
59       public void run() {
60         myTestsRootNode.setState(State.RUNNING, GeneralIdBasedToSMTRunnerEventsConvertor.this);
61         myTestsRootProxy.setStarted();
62         fireOnTestingStarted(myTestsRootProxy);
63       }
64     });
65   }
66
67   @Override
68   public void onTestsReporterAttached() {
69     addToInvokeLater(new Runnable() {
70       public void run() {
71         fireOnTestsReporterAttached(myTestsRootProxy);
72       }
73     });
74   }
75
76   public void onFinishTesting() {
77     addToInvokeLater(new Runnable() {
78       public void run() {
79         if (myIsTestingFinished) {
80           // has been already invoked!
81           return;
82         }
83         myIsTestingFinished = true;
84
85         // We don't know whether process was destroyed by user
86         // or it finished after all tests have been run
87         // Lets assume, if at finish all nodes except root suite have final state (passed, failed or ignored),
88         // then all is ok otherwise process was terminated by user
89         boolean completeTree = isTreeComplete(myRunningTestNodes, myTestsRootProxy);
90         if (completeTree) {
91           myTestsRootProxy.setFinished();
92         } else {
93           myTestsRootProxy.setTerminated();
94         }
95         if (!myRunningTestNodes.isEmpty()) {
96           logProblem("Unexpected running nodes: " + myRunningTestNodes);
97         }
98         myNodeByIdMap.clear();
99         myRunningTestNodes.clear();
100
101         fireOnTestingFinished(myTestsRootProxy);
102       }
103     });
104     stopEventProcessing();
105   }
106
107   @Override
108   public void setPrinterProvider(@NotNull TestProxyPrinterProvider printerProvider) {
109     myTestProxyPrinterProvider = printerProvider;
110   }
111
112   public void onTestStarted(@NotNull final TestStartedEvent testStartedEvent) {
113     addToInvokeLater(new Runnable() {
114       public void run() {
115         doStartNode(testStartedEvent, false);
116       }
117     });
118   }
119
120   public void onSuiteStarted(@NotNull final TestSuiteStartedEvent suiteStartedEvent) {
121     addToInvokeLater(new Runnable() {
122       public void run() {
123         doStartNode(suiteStartedEvent, true);
124       }
125     });
126   }
127
128   private void doStartNode(@NotNull BaseStartedNodeEvent startedNodeEvent, boolean suite) {
129     Node node = findNode(startedNodeEvent);
130     if (node != null) {
131       if (node.getState() == State.NOT_RUNNING && startedNodeEvent.isRunning()) {
132         setNodeAndAncestorsRunning(node);
133       }
134       else {
135         logProblem(startedNodeEvent + " has been already started: " + node + "!");
136       }
137       return;
138     }
139
140     Node parentNode = findValidParentNode(startedNodeEvent);
141     if (parentNode == null) {
142       return;
143     }
144
145     if (!validateNodeId(startedNodeEvent)) {
146       return;
147     }
148
149     String nodeName = startedNodeEvent.getName();
150     SMTestProxy childProxy = new SMTestProxy(nodeName, suite, startedNodeEvent.getLocationUrl(), true);
151     TestProxyPrinterProvider printerProvider = myTestProxyPrinterProvider;
152     String nodeType = startedNodeEvent.getNodeType();
153     if (printerProvider != null && nodeType != null && nodeName != null) {
154       Printer printer = printerProvider.getPrinterByType(nodeType, nodeName, startedNodeEvent.getNodeArgs());
155       if (printer != null) {
156         childProxy.setPreferredPrinter(printer);
157       }
158     }
159     node = new Node(startedNodeEvent.getId(), parentNode, childProxy);
160     myNodeByIdMap.put(startedNodeEvent.getId(), node);
161     if (myLocator != null) {
162       childProxy.setLocator(myLocator);
163     }
164     parentNode.getProxy().addChild(childProxy);
165     if (startedNodeEvent.isRunning()) {
166       setNodeAndAncestorsRunning(node);
167     }
168   }
169
170   @Nullable
171   private Node findValidParentNode(@NotNull BaseStartedNodeEvent startedNodeEvent) {
172     String parentId = startedNodeEvent.getParentId();
173     if (parentId == null) {
174       logProblem("Parent node id should be defined: " + startedNodeEvent + ".", true);
175       return null;
176     }
177     Node parentNode = myNodeByIdMap.get(parentId);
178     if (parentNode == null) {
179       logProblem("Parent node is undefined for " + startedNodeEvent + ".", true);
180       return null;
181     }
182     if (parentNode.getState() != State.NOT_RUNNING && parentNode.getState() != State.RUNNING) {
183       logProblem("Parent node should be registered or running: " + parentNode + ", " + startedNodeEvent);
184       return null;
185     }
186     return parentNode;
187   }
188
189   public void onTestFinished(@NotNull final TestFinishedEvent testFinishedEvent) {
190     addToInvokeLater(new Runnable() {
191       public void run() {
192         Node node = findNodeToTerminate(testFinishedEvent);
193         if (node != null) {
194           SMTestProxy testProxy = node.getProxy();
195           testProxy.setDuration(testFinishedEvent.getDuration());
196           testProxy.setFrameworkOutputFile(testFinishedEvent.getOutputFile());
197           testProxy.setFinished();
198           fireOnTestFinished(testProxy);
199           terminateNode(node, State.FINISHED);
200         }
201       }
202     });
203   }
204
205   public void onSuiteFinished(@NotNull final TestSuiteFinishedEvent suiteFinishedEvent) {
206     addToInvokeLater(new Runnable() {
207       public void run() {
208         Node node = findNodeToTerminate(suiteFinishedEvent);
209         if (node != null) {
210           SMTestProxy suiteProxy = node.getProxy();
211           suiteProxy.setFinished();
212           fireOnSuiteFinished(suiteProxy);
213           terminateNode(node, State.FINISHED);
214         }
215       }
216     });
217   }
218
219   @Nullable
220   private Node findNodeToTerminate(@NotNull TreeNodeEvent treeNodeEvent) {
221     Node node = findNode(treeNodeEvent);
222     if (node == null) {
223       logProblem("Trying to finish not existent node: " + treeNodeEvent);
224       return null;
225     }
226     return node;
227   }
228
229   public void onUncapturedOutput(@NotNull final String text, final Key outputType) {
230     addToInvokeLater(new Runnable() {
231       public void run() {
232         Node activeNode = findActiveNode();
233         SMTestProxy activeProxy = activeNode.getProxy();
234         if (ProcessOutputTypes.STDERR.equals(outputType)) {
235           activeProxy.addStdErr(text);
236         } else if (ProcessOutputTypes.SYSTEM.equals(outputType)) {
237           activeProxy.addSystemOutput(text);
238         } else {
239           activeProxy.addStdOutput(text, outputType);
240         }
241       }
242     });
243   }
244
245   public void onError(@NotNull final String localizedMessage,
246                       @Nullable final String stackTrace,
247                       final boolean isCritical) {
248     addToInvokeLater(new Runnable() {
249       public void run() {
250         Node activeNode = findActiveNode();
251         SMTestProxy activeProxy = activeNode.getProxy();
252         activeProxy.addError(localizedMessage, stackTrace, isCritical);
253       }
254     });
255   }
256
257   public void onTestFailure(@NotNull final TestFailedEvent testFailedEvent) {
258     addToInvokeLater(new Runnable() {
259       public void run() {
260         Node node = findNodeToTerminate(testFailedEvent);
261         if (node == null) {
262           return;
263         }
264
265         SMTestProxy testProxy = node.getProxy();
266
267         String comparisonFailureActualText = testFailedEvent.getComparisonFailureActualText();
268         String comparisonFailureExpectedText = testFailedEvent.getComparisonFailureExpectedText();
269         String failureMessage = testFailedEvent.getLocalizedFailureMessage();
270         String stackTrace = testFailedEvent.getStacktrace();
271         if (comparisonFailureActualText != null && comparisonFailureExpectedText != null) {
272           testProxy.setTestComparisonFailed(failureMessage, stackTrace,
273                                             comparisonFailureActualText, comparisonFailureExpectedText,
274                                             testFailedEvent.getExpectedFilePath(),
275                                             testFailedEvent.getActualFilePath());
276         } else if (comparisonFailureActualText == null && comparisonFailureExpectedText == null) {
277           testProxy.setTestFailed(failureMessage, stackTrace, testFailedEvent.isTestError());
278         } else {
279           logProblem("Comparison failure actual and expected texts should be both null or not null.\n"
280                      + "Expected:\n"
281                      + comparisonFailureExpectedText + "\n"
282                      + "Actual:\n"
283                      + comparisonFailureActualText);
284         }
285         long duration = testFailedEvent.getDurationMillis();
286         if (duration >= 0) {
287           testProxy.setDuration(duration);
288         }
289
290         // fire event
291         fireOnTestFailed(testProxy);
292
293         terminateNode(node, State.FAILED);
294       }
295     });
296   }
297
298   public void onTestIgnored(@NotNull final TestIgnoredEvent testIgnoredEvent) {
299     addToInvokeLater(new Runnable() {
300       public void run() {
301         Node node = findNodeToTerminate(testIgnoredEvent);
302         if (node != null) {
303           SMTestProxy testProxy = node.getProxy();
304           testProxy.setTestIgnored(testIgnoredEvent.getIgnoreComment(), testIgnoredEvent.getStacktrace());
305           // fire event
306           fireOnTestIgnored(testProxy);
307           terminateNode(node, State.IGNORED);
308         }
309       }
310     });
311   }
312
313   public void onTestOutput(@NotNull final TestOutputEvent testOutputEvent) {
314     addToInvokeLater(new Runnable() {
315       public void run() {
316         Node node = findNode(testOutputEvent);
317         if (node == null) {
318           logProblem("Test wasn't started! But " + testOutputEvent + "!");
319           return;
320         }
321         SMTestProxy testProxy = node.getProxy();
322
323         if (testOutputEvent.isStdOut()) {
324           testProxy.addStdOutput(testOutputEvent.getText(), ProcessOutputTypes.STDOUT);
325         } else {
326           testProxy.addStdErr(testOutputEvent.getText());
327         }
328       }
329     });
330   }
331
332   public void onTestsCountInSuite(final int count) {
333     addToInvokeLater(new Runnable() {
334       public void run() {
335         fireOnTestsCountInSuite(count);
336       }
337     });
338   }
339
340   private boolean validateNodeId(@NotNull TreeNodeEvent treeNodeEvent) {
341     String nodeId = treeNodeEvent.getId();
342     if (nodeId == null || nodeId.equals(TreeNodeEvent.ROOT_NODE_ID)) {
343       logProblem("Node id should be initialized: " + treeNodeEvent + ".", true);
344       return false;
345     }
346     return true;
347   }
348
349   @Nullable
350   private Node findNode(@NotNull TreeNodeEvent treeNodeEvent) {
351     if (!validateNodeId(treeNodeEvent)) {
352       return null;
353     }
354     return myNodeByIdMap.get(treeNodeEvent.getId());
355   }
356
357   @Nullable
358   public SMTestProxy findProxyById(@NotNull String id) {
359     Node node = myNodeByIdMap.get(id);
360     return node != null ? node.getProxy() : null;
361   }
362   
363   /*
364    * Remove listeners,  etc
365    */
366   public void dispose() {
367     super.dispose();
368     addToInvokeLater(new Runnable() {
369       public void run() {
370         disconnectListeners();
371
372         if (!myRunningTestNodes.isEmpty()) {
373           Application application = ApplicationManager.getApplication();
374           if (!application.isHeadlessEnvironment() && !application.isUnitTestMode()) {
375             logProblem("Not all events were processed!");
376           }
377         }
378         myRunningTestNodes.clear();
379         myNodeByIdMap.clear();
380       }
381     });
382   }
383
384   private void setNodeAndAncestorsRunning(@NotNull Node lowestNode) {
385     Node node = lowestNode;
386     while (node != null && node != myTestsRootNode && node.getState() == State.NOT_RUNNING) {
387       node.setState(State.RUNNING, this);
388       SMTestProxy proxy = node.getProxy();
389       proxy.setStarted();
390       if (proxy.isSuite()) {
391         fireOnSuiteStarted(proxy);
392       } else {
393         myRunningTestNodes.add(lowestNode);
394         fireOnTestStarted(proxy);
395       }
396       node = node.getParentNode();
397     }
398   }
399
400   private void terminateNode(@NotNull Node node, @NotNull State terminateState) {
401     node.setState(terminateState, this);
402     myRunningTestNodes.remove(node);
403   }
404
405   @NotNull
406   private Node findActiveNode() {
407     if (myRunningTestNodes.isEmpty()) {
408       return myTestsRootNode;
409     }
410     return myRunningTestNodes.iterator().next();
411   }
412
413   private enum State {
414     NOT_RUNNING, RUNNING, FINISHED, FAILED, IGNORED
415   }
416
417   private static class Node {
418     private final String myId;
419     private final Node myParentNode;
420     private final SMTestProxy myProxy;
421     private State myState;
422
423     Node(@Nullable String id, @Nullable Node parentNode, @NotNull SMTestProxy proxy) {
424       myId = id;
425       myParentNode = parentNode;
426       myProxy = proxy;
427       myState = State.NOT_RUNNING;
428     }
429
430     @Nullable
431     public String getId() {
432       return myId;
433     }
434
435     @Nullable
436     public Node getParentNode() {
437       return myParentNode;
438     }
439
440     @NotNull
441     public SMTestProxy getProxy() {
442       return myProxy;
443     }
444
445     @NotNull
446     public State getState() {
447       return myState;
448     }
449
450     public void setState(@NotNull State newState, @NotNull GeneralIdBasedToSMTRunnerEventsConvertor convertor) {
451       // allowed sequences: NOT_RUNNING -> RUNNING or IGNORED; RUNNING -> FINISHED, FAILED or IGNORED; FINISHED <-> FAILED; IGNORED -> FINISHED
452       if (myState == State.NOT_RUNNING && newState != State.RUNNING && newState != State.IGNORED ||
453           myState == State.RUNNING && newState != State.FINISHED && newState != State.FAILED && newState != State.IGNORED ||
454           myState == State.FINISHED && newState != State.FAILED ||
455           myState == State.FAILED && newState != State.FINISHED ||
456           myState == State.IGNORED && newState != State.FINISHED) {
457         convertor.logProblem("Illegal state change [" + myState + " -> " + newState + "]: " + toString(), false);
458       }
459
460       if (myState.ordinal() < newState.ordinal()) {
461         // for example State.FINISHED comes later than State.FAILED, do not update state in this case
462         myState = newState;
463       }
464     }
465
466     @Override
467     public boolean equals(Object o) {
468       if (this == o) return true;
469       if (o == null || getClass() != o.getClass()) return false;
470
471       Node node = (Node)o;
472
473       return myId == node.myId;
474     }
475
476     @Override
477     public int hashCode() {
478       return myId != null ? myId.hashCode() : -1;
479     }
480
481     @Override
482     public String toString() {
483       return "{" +
484              "id=" + myId +
485              ", parentId=" + (myParentNode != null ? String.valueOf(myParentNode.getId()) : "<undefined>") +
486              ", name='" + myProxy.getName() +
487              "', isSuite=" + myProxy.isSuite() +
488              ", state=" + myState +
489              '}';
490     }
491   }
492
493 }