a84cefa94b4c758a6c3cc3e8f0b2a06c62b1cc31
[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         } else if (comparisonFailureActualText == null && comparisonFailureExpectedText == null) {
276           testProxy.setTestFailed(failureMessage, stackTrace, testFailedEvent.isTestError());
277         } else {
278           logProblem("Comparison failure actual and expected texts should be both null or not null.\n"
279                      + "Expected:\n"
280                      + comparisonFailureExpectedText + "\n"
281                      + "Actual:\n"
282                      + comparisonFailureActualText);
283         }
284         long duration = testFailedEvent.getDurationMillis();
285         if (duration >= 0) {
286           testProxy.setDuration(duration);
287         }
288
289         // fire event
290         fireOnTestFailed(testProxy);
291
292         terminateNode(node, State.FAILED);
293       }
294     });
295   }
296
297   public void onTestIgnored(@NotNull final TestIgnoredEvent testIgnoredEvent) {
298     addToInvokeLater(new Runnable() {
299       public void run() {
300         Node node = findNodeToTerminate(testIgnoredEvent);
301         if (node != null) {
302           SMTestProxy testProxy = node.getProxy();
303           testProxy.setTestIgnored(testIgnoredEvent.getIgnoreComment(), testIgnoredEvent.getStacktrace());
304           // fire event
305           fireOnTestIgnored(testProxy);
306           terminateNode(node, State.IGNORED);
307         }
308       }
309     });
310   }
311
312   public void onTestOutput(@NotNull final TestOutputEvent testOutputEvent) {
313     addToInvokeLater(new Runnable() {
314       public void run() {
315         Node node = findNode(testOutputEvent);
316         if (node == null) {
317           logProblem("Test wasn't started! But " + testOutputEvent + "!");
318           return;
319         }
320         SMTestProxy testProxy = node.getProxy();
321
322         if (testOutputEvent.isStdOut()) {
323           testProxy.addStdOutput(testOutputEvent.getText(), ProcessOutputTypes.STDOUT);
324         } else {
325           testProxy.addStdErr(testOutputEvent.getText());
326         }
327       }
328     });
329   }
330
331   public void onTestsCountInSuite(final int count) {
332     addToInvokeLater(new Runnable() {
333       public void run() {
334         fireOnTestsCountInSuite(count);
335       }
336     });
337   }
338
339   private boolean validateNodeId(@NotNull TreeNodeEvent treeNodeEvent) {
340     String nodeId = treeNodeEvent.getId();
341     if (nodeId == null || nodeId.equals(TreeNodeEvent.ROOT_NODE_ID)) {
342       logProblem("Node id should be initialized: " + treeNodeEvent + ".", true);
343       return false;
344     }
345     return true;
346   }
347
348   @Nullable
349   private Node findNode(@NotNull TreeNodeEvent treeNodeEvent) {
350     if (!validateNodeId(treeNodeEvent)) {
351       return null;
352     }
353     return myNodeByIdMap.get(treeNodeEvent.getId());
354   }
355
356   @Nullable
357   public SMTestProxy findProxyById(@NotNull String id) {
358     Node node = myNodeByIdMap.get(id);
359     return node != null ? node.getProxy() : null;
360   }
361   
362   /*
363    * Remove listeners,  etc
364    */
365   public void dispose() {
366     super.dispose();
367     addToInvokeLater(new Runnable() {
368       public void run() {
369         disconnectListeners();
370
371         if (!myRunningTestNodes.isEmpty()) {
372           Application application = ApplicationManager.getApplication();
373           if (!application.isHeadlessEnvironment() && !application.isUnitTestMode()) {
374             logProblem("Not all events were processed!");
375           }
376         }
377         myRunningTestNodes.clear();
378         myNodeByIdMap.clear();
379       }
380     });
381   }
382
383   private void setNodeAndAncestorsRunning(@NotNull Node lowestNode) {
384     Node node = lowestNode;
385     while (node != null && node != myTestsRootNode && node.getState() == State.NOT_RUNNING) {
386       node.setState(State.RUNNING, this);
387       SMTestProxy proxy = node.getProxy();
388       proxy.setStarted();
389       if (proxy.isSuite()) {
390         fireOnSuiteStarted(proxy);
391       } else {
392         myRunningTestNodes.add(lowestNode);
393         fireOnTestStarted(proxy);
394       }
395       node = node.getParentNode();
396     }
397   }
398
399   private void terminateNode(@NotNull Node node, @NotNull State terminateState) {
400     node.setState(terminateState, this);
401     myRunningTestNodes.remove(node);
402   }
403
404   @NotNull
405   private Node findActiveNode() {
406     if (myRunningTestNodes.isEmpty()) {
407       return myTestsRootNode;
408     }
409     return myRunningTestNodes.iterator().next();
410   }
411
412   private enum State {
413     NOT_RUNNING, RUNNING, FINISHED, FAILED, IGNORED
414   }
415
416   private static class Node {
417     private final String myId;
418     private final Node myParentNode;
419     private final SMTestProxy myProxy;
420     private State myState;
421
422     Node(@Nullable String id, @Nullable Node parentNode, @NotNull SMTestProxy proxy) {
423       myId = id;
424       myParentNode = parentNode;
425       myProxy = proxy;
426       myState = State.NOT_RUNNING;
427     }
428
429     @Nullable
430     public String getId() {
431       return myId;
432     }
433
434     @Nullable
435     public Node getParentNode() {
436       return myParentNode;
437     }
438
439     @NotNull
440     public SMTestProxy getProxy() {
441       return myProxy;
442     }
443
444     @NotNull
445     public State getState() {
446       return myState;
447     }
448
449     public void setState(@NotNull State newState, @NotNull GeneralIdBasedToSMTRunnerEventsConvertor convertor) {
450       // allowed sequences: NOT_RUNNING -> RUNNING or IGNORED; RUNNING -> FINISHED, FAILED or IGNORED; FINISHED <-> FAILED; IGNORED -> FINISHED
451       if (myState == State.NOT_RUNNING && newState != State.RUNNING && newState != State.IGNORED ||
452           myState == State.RUNNING && newState != State.FINISHED && newState != State.FAILED && newState != State.IGNORED ||
453           myState == State.FINISHED && newState != State.FAILED ||
454           myState == State.FAILED && newState != State.FINISHED ||
455           myState == State.IGNORED && newState != State.FINISHED) {
456         convertor.logProblem("Illegal state change [" + myState + " -> " + newState + "]: " + toString(), false);
457       }
458
459       if (myState.ordinal() < newState.ordinal()) {
460         // for example State.FINISHED comes later than State.FAILED, do not update state in this case
461         myState = newState;
462       }
463     }
464
465     @Override
466     public boolean equals(Object o) {
467       if (this == o) return true;
468       if (o == null || getClass() != o.getClass()) return false;
469
470       Node node = (Node)o;
471
472       return myId == node.myId;
473     }
474
475     @Override
476     public int hashCode() {
477       return myId != null ? myId.hashCode() : -1;
478     }
479
480     @Override
481     public String toString() {
482       return "{" +
483              "id=" + myId +
484              ", parentId=" + (myParentNode != null ? String.valueOf(myParentNode.getId()) : "<undefined>") +
485              ", name='" + myProxy.getName() +
486              "', isSuite=" + myProxy.isSuite() +
487              ", state=" + myState +
488              '}';
489     }
490   }
491
492 }