c81797ec8fda042d8ce2d07fbc21dcf386f132e4
[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.SMTestRunnerConnectionUtil;
21 import com.intellij.execution.testframework.sm.runner.events.*;
22 import com.intellij.openapi.application.Application;
23 import com.intellij.openapi.application.ApplicationManager;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.util.Key;
26 import com.intellij.util.containers.ContainerUtil;
27 import gnu.trove.TIntObjectHashMap;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
30
31 import java.util.List;
32 import java.util.Set;
33
34 /**
35  * @author Sergey Simonchik
36  */
37 public class GeneralIdBasedToSMTRunnerEventsConvertor extends GeneralTestEventsProcessor {
38   private static final Logger LOG = Logger.getInstance(GeneralIdBasedToSMTRunnerEventsConvertor.class.getName());
39
40   private final TIntObjectHashMap<Node> myNodeByIdMap = new TIntObjectHashMap<Node>();
41   private final Set<Node> myRunningTestNodes = ContainerUtil.newHashSet();
42   private final List<SMTRunnerEventsListener> myEventsListeners = ContainerUtil.createLockFreeCopyOnWriteList();
43   private final SMTestProxy.SMRootTestProxy myTestsRootProxy;
44   private final Node myTestsRootNode;
45   private final String myTestFrameworkName;
46
47   private boolean myIsTestingFinished = false;
48   private SMTestLocator myLocator = null;
49   private TestProxyPrinterProvider myTestProxyPrinterProvider = null;
50
51   public GeneralIdBasedToSMTRunnerEventsConvertor(@NotNull SMTestProxy.SMRootTestProxy testsRootProxy, @NotNull String testFrameworkName) {
52     myTestsRootProxy = testsRootProxy;
53     myTestsRootNode = new Node(0, null, testsRootProxy);
54     myTestFrameworkName = testFrameworkName;
55     myNodeByIdMap.put(myTestsRootNode.getId(), myTestsRootNode);
56   }
57
58   @Override
59   public void setLocator(@NotNull SMTestLocator locator) {
60     myLocator = locator;
61   }
62
63   public void addEventsListener(@NotNull SMTRunnerEventsListener listener) {
64     myEventsListeners.add(listener);
65   }
66
67   public void onStartTesting() {
68     addToInvokeLater(new Runnable() {
69       public void run() {
70         myTestsRootNode.setState(State.RUNNING, GeneralIdBasedToSMTRunnerEventsConvertor.this);
71         myTestsRootProxy.setStarted();
72         fireOnTestingStarted();
73       }
74     });
75   }
76
77   @Override
78   public void onTestsReporterAttached() {
79     addToInvokeLater(new Runnable() {
80       public void run() {
81         myTestsRootProxy.setTestsReporterAttached();
82       }
83     });
84   }
85
86   public void onFinishTesting() {
87     addToInvokeLater(new Runnable() {
88       public void run() {
89         if (myIsTestingFinished) {
90           // has been already invoked!
91           return;
92         }
93         myIsTestingFinished = true;
94
95         // We don't know whether process was destroyed by user
96         // or it finished after all tests have been run
97         // Lets assume, if at finish all nodes except root suite have final state (passed, failed or ignored),
98         // then all is ok otherwise process was terminated by user
99         boolean completeTree = isTreeComplete(myRunningTestNodes, myTestsRootProxy);
100         if (completeTree) {
101           myTestsRootProxy.setFinished();
102         } else {
103           myTestsRootProxy.setTerminated();
104         }
105         if (!myRunningTestNodes.isEmpty()) {
106           logProblem("Unexpected running nodes: " + myRunningTestNodes);
107         }
108         myNodeByIdMap.clear();
109         myRunningTestNodes.clear();
110
111         fireOnTestingFinished();
112       }
113     });
114     stopEventProcessing();
115   }
116
117   @Override
118   public void setPrinterProvider(@NotNull TestProxyPrinterProvider printerProvider) {
119     myTestProxyPrinterProvider = printerProvider;
120   }
121
122   public void onTestStarted(@NotNull final TestStartedEvent testStartedEvent) {
123     addToInvokeLater(new Runnable() {
124       public void run() {
125         doStartNode(testStartedEvent, false);
126       }
127     });
128   }
129
130   public void onSuiteStarted(@NotNull final TestSuiteStartedEvent suiteStartedEvent) {
131     addToInvokeLater(new Runnable() {
132       public void run() {
133         doStartNode(suiteStartedEvent, true);
134       }
135     });
136   }
137
138   private void doStartNode(@NotNull BaseStartedNodeEvent startedNodeEvent, boolean suite) {
139     Node node = findNode(startedNodeEvent);
140     if (node != null) {
141       if (node.getState() == State.NOT_RUNNING && startedNodeEvent.isRunning()) {
142         setNodeAndAncestorsRunning(node);
143       }
144       else {
145         logProblem(startedNodeEvent + " has been already started: " + node + "!");
146       }
147       return;
148     }
149
150     Node parentNode = findValidParentNode(startedNodeEvent);
151     if (parentNode == null) {
152       return;
153     }
154
155     if (!validateNodeId(startedNodeEvent)) {
156       return;
157     }
158
159     String nodeName = startedNodeEvent.getName();
160     SMTestProxy childProxy = new SMTestProxy(nodeName, suite, startedNodeEvent.getLocationUrl(), true);
161     TestProxyPrinterProvider printerProvider = myTestProxyPrinterProvider;
162     String nodeType = startedNodeEvent.getNodeType();
163     if (printerProvider != null && nodeType != null && nodeName != null) {
164       Printer printer = printerProvider.getPrinterByType(nodeType, nodeName, startedNodeEvent.getNodeArgs());
165       if (printer != null) {
166         childProxy.setPreferredPrinter(printer);
167       }
168     }
169     node = new Node(startedNodeEvent.getId(), parentNode, childProxy);
170     myNodeByIdMap.put(startedNodeEvent.getId(), node);
171     if (myLocator != null) {
172       childProxy.setLocator(myLocator);
173     }
174     parentNode.getProxy().addChild(childProxy);
175     if (startedNodeEvent.isRunning()) {
176       setNodeAndAncestorsRunning(node);
177     }
178   }
179
180   @Nullable
181   private Node findValidParentNode(@NotNull BaseStartedNodeEvent startedNodeEvent) {
182     int parentId = startedNodeEvent.getParentId();
183     if (parentId < 0) {
184       logProblem("Parent node id should be non-negative: " + startedNodeEvent + ".", true);
185       return null;
186     }
187     Node parentNode = myNodeByIdMap.get(startedNodeEvent.getParentId());
188     if (parentNode == null) {
189       logProblem("Parent node is undefined for " + startedNodeEvent + ".", true);
190       return null;
191     }
192     if (parentNode.getState() != State.NOT_RUNNING && parentNode.getState() != State.RUNNING) {
193       logProblem("Parent node should be registered or running: " + parentNode + ", " + startedNodeEvent);
194       return null;
195     }
196     return parentNode;
197   }
198
199   public void onTestFinished(@NotNull final TestFinishedEvent testFinishedEvent) {
200     addToInvokeLater(new Runnable() {
201       public void run() {
202         Node node = findNodeToTerminate(testFinishedEvent);
203         if (node != null) {
204           SMTestProxy testProxy = node.getProxy();
205           testProxy.setDuration(testFinishedEvent.getDuration());
206           testProxy.setFinished();
207           fireOnTestFinished(testProxy);
208           terminateNode(node, State.FINISHED);
209         }
210       }
211     });
212   }
213
214   public void onSuiteFinished(@NotNull final TestSuiteFinishedEvent suiteFinishedEvent) {
215     addToInvokeLater(new Runnable() {
216       public void run() {
217         Node node = findNodeToTerminate(suiteFinishedEvent);
218         if (node != null) {
219           SMTestProxy suiteProxy = node.getProxy();
220           suiteProxy.setFinished();
221           fireOnSuiteFinished(suiteProxy);
222           terminateNode(node, State.FINISHED);
223         }
224       }
225     });
226   }
227
228   @Nullable
229   private Node findNodeToTerminate(@NotNull TreeNodeEvent treeNodeEvent) {
230     Node node = findNode(treeNodeEvent);
231     if (node == null) {
232       logProblem("Trying to finish not existent node: " + treeNodeEvent);
233       return null;
234     }
235     return node;
236   }
237
238   public void onUncapturedOutput(@NotNull final String text, final Key outputType) {
239     addToInvokeLater(new Runnable() {
240       public void run() {
241         Node activeNode = findActiveNode();
242         SMTestProxy activeProxy = activeNode.getProxy();
243         if (ProcessOutputTypes.STDERR.equals(outputType)) {
244           activeProxy.addStdErr(text);
245         } else if (ProcessOutputTypes.SYSTEM.equals(outputType)) {
246           activeProxy.addSystemOutput(text);
247         } else {
248           activeProxy.addStdOutput(text, outputType);
249         }
250       }
251     });
252   }
253
254   public void onError(@NotNull final String localizedMessage,
255                       @Nullable final String stackTrace,
256                       final boolean isCritical) {
257     addToInvokeLater(new Runnable() {
258       public void run() {
259         Node activeNode = findActiveNode();
260         SMTestProxy activeProxy = activeNode.getProxy();
261         activeProxy.addError(localizedMessage, stackTrace, isCritical);
262       }
263     });
264   }
265
266   public void onCustomProgressTestsCategory(@Nullable final String categoryName,
267                                             final int testCount) {
268     addToInvokeLater(new Runnable() {
269       public void run() {
270         fireOnCustomProgressTestsCategory(categoryName, testCount);
271       }
272     });
273   }
274
275   public void onCustomProgressTestStarted() {
276     addToInvokeLater(new Runnable() {
277       public void run() {
278         fireOnCustomProgressTestStarted();
279       }
280     });
281   }
282
283   @Override
284   public void onCustomProgressTestFinished() {
285     addToInvokeLater(new Runnable() {
286       public void run() {
287         fireOnCustomProgressTestFinished();
288       }
289     });
290   }
291
292   public void onCustomProgressTestFailed() {
293     addToInvokeLater(new Runnable() {
294       public void run() {
295         fireOnCustomProgressTestFailed();
296       }
297     });
298   }
299
300   public void onTestFailure(@NotNull final TestFailedEvent testFailedEvent) {
301     addToInvokeLater(new Runnable() {
302       public void run() {
303         Node node = findNodeToTerminate(testFailedEvent);
304         if (node == null) {
305           return;
306         }
307
308         SMTestProxy testProxy = node.getProxy();
309
310         String comparisonFailureActualText = testFailedEvent.getComparisonFailureActualText();
311         String comparisonFailureExpectedText = testFailedEvent.getComparisonFailureExpectedText();
312         String failureMessage = testFailedEvent.getLocalizedFailureMessage();
313         String stackTrace = testFailedEvent.getStacktrace();
314         if (comparisonFailureActualText != null && comparisonFailureExpectedText != null) {
315           testProxy.setTestComparisonFailed(failureMessage, stackTrace,
316                                             comparisonFailureActualText, comparisonFailureExpectedText);
317         } else if (comparisonFailureActualText == null && comparisonFailureExpectedText == null) {
318           testProxy.setTestFailed(failureMessage, stackTrace, testFailedEvent.isTestError());
319         } else {
320           logProblem("Comparison failure actual and expected texts should be both null or not null.\n"
321                      + "Expected:\n"
322                      + comparisonFailureExpectedText + "\n"
323                      + "Actual:\n"
324                      + comparisonFailureActualText);
325         }
326
327         // fire event
328         fireOnTestFailed(testProxy);
329
330         terminateNode(node, State.FAILED);
331       }
332     });
333   }
334
335   public void onTestIgnored(@NotNull final TestIgnoredEvent testIgnoredEvent) {
336     addToInvokeLater(new Runnable() {
337       public void run() {
338         Node node = findNodeToTerminate(testIgnoredEvent);
339         if (node != null) {
340           SMTestProxy testProxy = node.getProxy();
341           testProxy.setTestIgnored(testIgnoredEvent.getIgnoreComment(), testIgnoredEvent.getStacktrace());
342           // fire event
343           fireOnTestIgnored(testProxy);
344           terminateNode(node, State.IGNORED);
345         }
346       }
347     });
348   }
349
350   public void onTestOutput(@NotNull final TestOutputEvent testOutputEvent) {
351     addToInvokeLater(new Runnable() {
352       public void run() {
353         Node node = findNode(testOutputEvent);
354         if (node == null) {
355           logProblem("Test wasn't started! But " + testOutputEvent + "!");
356           return;
357         }
358         SMTestProxy testProxy = node.getProxy();
359
360         if (testOutputEvent.isStdOut()) {
361           testProxy.addStdOutput(testOutputEvent.getText(), ProcessOutputTypes.STDOUT);
362         } else {
363           testProxy.addStdErr(testOutputEvent.getText());
364         }
365       }
366     });
367   }
368
369   public void onTestsCountInSuite(final int count) {
370     addToInvokeLater(new Runnable() {
371       public void run() {
372         fireOnTestsCountInSuite(count);
373       }
374     });
375   }
376
377   private boolean validateNodeId(@NotNull TreeNodeEvent treeNodeEvent) {
378     int nodeId = treeNodeEvent.getId();
379     if (nodeId <= 0) {
380       logProblem("Node id should be positive: " + treeNodeEvent + ".", true);
381       return false;
382     }
383     return true;
384   }
385
386   @Nullable
387   private Node findNode(@NotNull TreeNodeEvent treeNodeEvent) {
388     if (!validateNodeId(treeNodeEvent)) {
389       return null;
390     }
391     return myNodeByIdMap.get(treeNodeEvent.getId());
392   }
393
394   private void fireOnTestingStarted() {
395     for (SMTRunnerEventsListener listener : myEventsListeners) {
396       listener.onTestingStarted(myTestsRootProxy);
397     }
398   }
399
400   private void fireOnTestingFinished() {
401     for (SMTRunnerEventsListener listener : myEventsListeners) {
402       listener.onTestingFinished(myTestsRootProxy);
403     }
404   }
405
406   private void fireOnTestsCountInSuite(final int count) {
407     for (SMTRunnerEventsListener listener : myEventsListeners) {
408       listener.onTestsCountInSuite(count);
409     }
410   }
411
412
413   private void fireOnTestStarted(final SMTestProxy test) {
414     for (SMTRunnerEventsListener listener : myEventsListeners) {
415       listener.onTestStarted(test);
416     }
417   }
418
419   private void fireOnTestFinished(final SMTestProxy test) {
420     for (SMTRunnerEventsListener listener : myEventsListeners) {
421       listener.onTestFinished(test);
422     }
423   }
424
425   private void fireOnTestFailed(final SMTestProxy test) {
426     for (SMTRunnerEventsListener listener : myEventsListeners) {
427       listener.onTestFailed(test);
428     }
429   }
430
431   private void fireOnTestIgnored(final SMTestProxy test) {
432     for (SMTRunnerEventsListener listener : myEventsListeners) {
433       listener.onTestIgnored(test);
434     }
435   }
436
437   private void fireOnSuiteStarted(final SMTestProxy suite) {
438     for (SMTRunnerEventsListener listener : myEventsListeners) {
439       listener.onSuiteStarted(suite);
440     }
441   }
442
443   private void fireOnSuiteFinished(final SMTestProxy suite) {
444     for (SMTRunnerEventsListener listener : myEventsListeners) {
445       listener.onSuiteFinished(suite);
446     }
447   }
448
449
450   private void fireOnCustomProgressTestsCategory(@Nullable final String categoryName, int testCount) {
451     for (SMTRunnerEventsListener listener : myEventsListeners) {
452       listener.onCustomProgressTestsCategory(categoryName, testCount);
453     }
454   }
455
456   private void fireOnCustomProgressTestStarted() {
457     for (SMTRunnerEventsListener listener : myEventsListeners) {
458       listener.onCustomProgressTestStarted();
459     }
460   }
461
462   private void fireOnCustomProgressTestFinished() {
463     for (SMTRunnerEventsListener listener : myEventsListeners) {
464       listener.onCustomProgressTestFinished();
465     }
466   }
467
468   private void fireOnCustomProgressTestFailed() {
469     for (SMTRunnerEventsListener listener : myEventsListeners) {
470       listener.onCustomProgressTestFailed();
471     }
472   }
473
474   /*
475    * Remove listeners,  etc
476    */
477   public void dispose() {
478     super.dispose();
479     addToInvokeLater(new Runnable() {
480       public void run() {
481         myEventsListeners.clear();
482
483         if (!myRunningTestNodes.isEmpty()) {
484           Application application = ApplicationManager.getApplication();
485           if (!application.isHeadlessEnvironment() && !application.isUnitTestMode()) {
486             logProblem("Not all events were processed!");
487           }
488         }
489         myRunningTestNodes.clear();
490         myNodeByIdMap.clear();
491       }
492     });
493   }
494
495   private void setNodeAndAncestorsRunning(@NotNull Node lowestNode) {
496     Node node = lowestNode;
497     while (node != null && node != myTestsRootNode && node.getState() == State.NOT_RUNNING) {
498       node.setState(State.RUNNING, this);
499       SMTestProxy proxy = node.getProxy();
500       proxy.setStarted();
501       if (proxy.isSuite()) {
502         fireOnSuiteStarted(proxy);
503       } else {
504         myRunningTestNodes.add(lowestNode);
505         fireOnTestStarted(proxy);
506       }
507       node = node.getParentNode();
508     }
509   }
510
511   private void terminateNode(@NotNull Node node, @NotNull State terminateState) {
512     node.setState(terminateState, this);
513     myRunningTestNodes.remove(node);
514   }
515
516   @NotNull
517   private Node findActiveNode() {
518     if (myRunningTestNodes.isEmpty()) {
519       return myTestsRootNode;
520     }
521     return myRunningTestNodes.iterator().next();
522   }
523
524   private void logProblem(@NotNull String msg) {
525     logProblem(msg, SMTestRunnerConnectionUtil.isInDebugMode());
526   }
527
528   private void logProblem(@NotNull String msg, boolean throwError) {
529     final String text = "[" + myTestFrameworkName + "] " + msg;
530     if (throwError) {
531       LOG.error(text);
532     }
533     else {
534       LOG.warn(text);
535     }
536   }
537
538   private enum State {
539     NOT_RUNNING, RUNNING, FINISHED, FAILED, IGNORED
540   }
541
542   private static class Node {
543     private final int myId;
544     private final Node myParentNode;
545     private final SMTestProxy myProxy;
546     private State myState;
547
548     Node(int id, @Nullable Node parentNode, @NotNull SMTestProxy proxy) {
549       myId = id;
550       myParentNode = parentNode;
551       myProxy = proxy;
552       myState = State.NOT_RUNNING;
553     }
554
555     public int getId() {
556       return myId;
557     }
558
559     @Nullable
560     public Node getParentNode() {
561       return myParentNode;
562     }
563
564     @NotNull
565     public SMTestProxy getProxy() {
566       return myProxy;
567     }
568
569     @NotNull
570     public State getState() {
571       return myState;
572     }
573
574     public void setState(@NotNull State newState, @NotNull GeneralIdBasedToSMTRunnerEventsConvertor convertor) {
575       boolean accepted = false;
576       if (myState == State.NOT_RUNNING || myState == State.RUNNING) {
577         accepted = myState.ordinal() < newState.ordinal();
578       }
579       if (accepted) {
580         myState = newState;
581       }
582       else {
583         convertor.logProblem("Illegal state change [" + myState + " -> " + newState + "]: " + toString(), false);
584       }
585     }
586
587     @Override
588     public boolean equals(Object o) {
589       if (this == o) return true;
590       if (o == null || getClass() != o.getClass()) return false;
591
592       Node node = (Node)o;
593
594       return myId == node.myId;
595     }
596
597     @Override
598     public int hashCode() {
599       return myId;
600     }
601
602     @Override
603     public String toString() {
604       return "{" +
605              "id=" + myId +
606              ", parentId=" + (myParentNode != null ? String.valueOf(myParentNode.getId()) : "<undefined>") +
607              ", name='" + myProxy.getName() +
608              "', isSuite=" + myProxy.isSuite() +
609              ", state=" + myState +
610              '}';
611     }
612   }
613
614 }