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