smTest: better naming
[idea/community.git] / platform / smRunner / src / com / intellij / execution / testframework / sm / runner / GeneralToSMTRunnerEventsConvertor.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.sm.SMTestRunnerConnectionUtil;
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.ObjectUtils;
26 import org.jetbrains.annotations.NotNull;
27 import org.jetbrains.annotations.Nullable;
28 import org.jetbrains.annotations.TestOnly;
29
30 import java.util.*;
31
32 /**
33  * This class fires events to SMTRunnerEventsListener in event dispatch thread.
34  *
35  * @author: Roman Chernyatchik
36  */
37 public class GeneralToSMTRunnerEventsConvertor extends GeneralTestEventsProcessor {
38
39   private final Map<String, SMTestProxy> myRunningTestsFullNameToProxy = new HashMap<String, SMTestProxy>();
40   private final TestSuiteStack mySuitesStack;
41   private final Set<SMTestProxy> myCurrentChildren = new LinkedHashSet<SMTestProxy>();
42   private boolean myGetChildren = true;
43   private final SMTestProxy.SMRootTestProxy myTestsRootNode;
44
45   private boolean myIsTestingFinished;
46   private SMTestLocator myLocator = null;
47   private boolean myTreeBuildBeforeStart = false;
48
49   public GeneralToSMTRunnerEventsConvertor(Project project, @NotNull SMTestProxy.SMRootTestProxy testsRootNode,
50                                            @NotNull String testFrameworkName) {
51     super(project, testFrameworkName);
52     myTestsRootNode = testsRootNode;
53     mySuitesStack = new TestSuiteStack(testFrameworkName);
54   }
55
56   @Override
57   public void setLocator(@NotNull SMTestLocator locator) {
58     myLocator = locator;
59   }
60
61   public void onStartTesting() {
62     addToInvokeLater(new Runnable() {
63       public void run() {
64         mySuitesStack.pushSuite(myTestsRootNode);
65         myTestsRootNode.setStarted();
66
67         //fire
68         fireOnTestingStarted(myTestsRootNode);
69       }
70     });
71   }
72
73   @Override
74   public void onTestsReporterAttached() {
75     addToInvokeLater(new Runnable() {
76       public void run() {
77         fireOnTestsReporterAttached(myTestsRootNode);
78       }
79     });
80   }
81
82   public void onFinishTesting() {
83     addToInvokeLater(new Runnable() {
84       public void run() {
85         if (myIsTestingFinished) {
86           // has been already invoked!
87           return;
88         }
89         myIsTestingFinished = true;
90
91         // We don't know whether process was destroyed by user
92         // or it finished after all tests have been run
93         // Lets assume, if at finish all suites except root suite are passed
94         // then all is ok otherwise process was terminated by user
95         if (!isTreeComplete(myRunningTestsFullNameToProxy.keySet(), myTestsRootNode)) {
96           myTestsRootNode.setTerminated();
97           myRunningTestsFullNameToProxy.clear();
98         }
99         mySuitesStack.clear();
100         myTestsRootNode.setFinished();
101
102
103         //fire events
104         fireOnTestingFinished(myTestsRootNode);
105       }
106     });
107     stopEventProcessing();
108   }
109
110   @Override
111   public void onRootPresentationAdded(final String rootName, final String comment, final String rootLocation) {
112     addToInvokeLater(new Runnable() {
113       @Override
114       public void run() {
115         myTestsRootNode.setPresentation(rootName);
116         myTestsRootNode.setComment(comment);
117         myTestsRootNode.setRootLocationUrl(rootLocation);
118         if (myLocator != null) {
119           myTestsRootNode.setLocator(myLocator);
120         }
121       }
122     });
123   }
124
125   private final List<Runnable> myBuildTreeRunnables = new ArrayList<Runnable>();
126   
127   @Override
128   public void onSuiteTreeNodeAdded(final String testName, final String locationHint) {
129     myTreeBuildBeforeStart = true;
130     myBuildTreeRunnables.add(new Runnable() {
131       @Override
132       public void run() {
133         final SMTestProxy testProxy = new SMTestProxy(testName, false, locationHint);
134         if (myLocator != null) {
135           testProxy.setLocator(myLocator);
136         }
137         getCurrentSuite().addChild(testProxy);
138         myEventPublisher.onSuiteTreeNodeAdded(testProxy);
139         for (SMTRunnerEventsListener adapter : myListenerAdapters) {
140           adapter.onSuiteTreeNodeAdded(testProxy);
141         }
142       }
143     });
144   }
145
146   @Override
147   public void onSuiteTreeStarted(final String suiteName, final String locationHint) {
148     myTreeBuildBeforeStart = true;
149     myBuildTreeRunnables.add(new Runnable() {
150       @Override
151       public void run() {
152         final SMTestProxy parentSuite = getCurrentSuite();
153         final SMTestProxy newSuite = new SMTestProxy(suiteName, true, locationHint);
154         if (myLocator != null) {
155           newSuite.setLocator(myLocator);
156         }
157         parentSuite.addChild(newSuite);
158
159         mySuitesStack.pushSuite(newSuite);
160
161         myEventPublisher.onSuiteTreeStarted(newSuite);
162         for (SMTRunnerEventsListener adapter : myListenerAdapters) {
163           adapter.onSuiteTreeStarted(newSuite);
164         }
165       }
166     });
167   }
168
169   @Override
170   public void onSuiteTreeEnded(final String suiteName) {
171     myBuildTreeRunnables.add(new Runnable() {
172       @Override
173       public void run() {
174         mySuitesStack.popSuite(suiteName);
175       }
176     });
177     
178     if (myBuildTreeRunnables.size() > 100) {
179       final ArrayList<Runnable> runnables = new ArrayList<Runnable>(myBuildTreeRunnables);
180       myBuildTreeRunnables.clear();
181       processTreeBuildEvents(runnables);
182     }
183   }
184
185   @Override
186   public void onBuildTreeEnded() {
187     processTreeBuildEvents(myBuildTreeRunnables);
188   }
189
190   private void processTreeBuildEvents(final List<Runnable> runnables) {
191     addToInvokeLater(new Runnable() {
192       @Override
193       public void run() {
194         for (Runnable runnable : runnables) {
195           runnable.run();
196         }
197         runnables.clear();
198       }
199     });
200   }
201
202   @Override
203   public void setPrinterProvider(@NotNull TestProxyPrinterProvider printerProvider) {
204   }
205
206   public void onTestStarted(@NotNull final TestStartedEvent testStartedEvent) {
207     addToInvokeLater(new Runnable() {
208       public void run() {
209         final String testName = testStartedEvent.getName();
210         final String locationUrl = testStartedEvent.getLocationUrl();
211         final boolean isConfig = testStartedEvent.isConfig();
212         final String fullName = getFullTestName(testName);
213
214         if (myRunningTestsFullNameToProxy.containsKey(fullName)) {
215           //Duplicated event
216           logProblem("Test [" + fullName + "] has been already started");
217           if (SMTestRunnerConnectionUtil.isInDebugMode()) {
218             return;
219           }
220         }
221
222         SMTestProxy parentSuite = getCurrentSuite();
223         SMTestProxy testProxy = findChildByName(parentSuite, fullName);
224         if (testProxy == null) {
225           // creates test
226           testProxy = new SMTestProxy(testName, false, locationUrl);
227           testProxy.setConfig(isConfig);
228
229           if (myLocator != null) {
230             testProxy.setLocator(myLocator);
231           }
232
233           parentSuite.addChild(testProxy);
234
235           if (myTreeBuildBeforeStart && myGetChildren) {
236             for (SMTestProxy proxy : parentSuite.getChildren()) {
237               if (!proxy.isFinal()) {
238                 myCurrentChildren.add(proxy);
239               }
240             }
241             myGetChildren = false;
242           }
243         }
244
245         // adds to running tests map
246         myRunningTestsFullNameToProxy.put(fullName, testProxy);
247
248         //Progress started
249         testProxy.setStarted();
250
251         //fire events
252         fireOnTestStarted(testProxy);
253       }
254     });
255   }
256
257   public void onSuiteStarted(@NotNull final TestSuiteStartedEvent suiteStartedEvent) {
258     addToInvokeLater(new Runnable() {
259       public void run() {
260         final String suiteName = suiteStartedEvent.getName();
261         final String locationUrl = suiteStartedEvent.getLocationUrl();
262
263         SMTestProxy parentSuite = getCurrentSuite();
264         SMTestProxy newSuite = findChildByName(parentSuite, suiteName);
265         if (newSuite == null) {
266           //new suite
267           newSuite = new SMTestProxy(suiteName, true, locationUrl, parentSuite.isPreservePresentableName());
268
269           if (myLocator != null) {
270             newSuite.setLocator(myLocator);
271           }
272
273           parentSuite.addChild(newSuite);
274         }
275
276         myGetChildren = true;
277         mySuitesStack.pushSuite(newSuite);
278
279         //Progress started
280         newSuite.setSuiteStarted();
281
282         //fire event
283         fireOnSuiteStarted(newSuite);
284       }
285     });
286   }
287
288   private SMTestProxy findChildByName(SMTestProxy parentSuite, String fullName) {
289     if (myTreeBuildBeforeStart) {
290       final Collection<? extends SMTestProxy> children = myGetChildren ? parentSuite.getChildren() : myCurrentChildren;
291       for (SMTestProxy proxy : children) {
292         if (fullName.equals(proxy.getName()) && !proxy.isFinal()) {
293           return proxy;
294         }
295       }
296     }
297     return null;
298   }
299
300   public void onTestFinished(@NotNull final TestFinishedEvent testFinishedEvent) {
301     addToInvokeLater(new Runnable() {
302       public void run() {
303         final String testName = testFinishedEvent.getName();
304         final long duration = testFinishedEvent.getDuration();
305         final String fullTestName = getFullTestName(testName);
306         final SMTestProxy testProxy = getProxyByFullTestName(fullTestName);
307
308         if (testProxy == null) {
309           logProblem("Test wasn't started! TestFinished event: name = {" + testName + "}. " +
310                      cannotFindFullTestNameMsg(fullTestName));
311           return;
312         }
313
314         testProxy.setDuration(duration);
315         testProxy.setFrameworkOutputFile(testFinishedEvent.getOutputFile());
316         testProxy.setFinished();
317         myRunningTestsFullNameToProxy.remove(fullTestName);
318         myCurrentChildren.remove(testProxy);
319
320         //fire events
321         fireOnTestFinished(testProxy);
322       }
323     });
324   }
325
326   public void onSuiteFinished(@NotNull final TestSuiteFinishedEvent suiteFinishedEvent) {
327     addToInvokeLater(new Runnable() {
328       public void run() {
329         final String suiteName = suiteFinishedEvent.getName();
330         final SMTestProxy mySuite = mySuitesStack.popSuite(suiteName);
331         if (mySuite != null) {
332           mySuite.setFinished();
333           myCurrentChildren.clear();
334           myGetChildren = true;
335
336           //fire events
337           fireOnSuiteFinished(mySuite);
338         }
339       }
340     });
341   }
342
343   public void onUncapturedOutput(@NotNull final String text, final Key outputType) {
344     addToInvokeLater(new Runnable() {
345       public void run() {
346         final SMTestProxy currentProxy = findCurrentTestOrSuite();
347
348         if (ProcessOutputTypes.STDERR.equals(outputType)) {
349           currentProxy.addStdErr(text);
350         } else if (ProcessOutputTypes.SYSTEM.equals(outputType)) {
351           currentProxy.addSystemOutput(text);
352         } else {
353           currentProxy.addStdOutput(text, outputType);
354         }
355       }
356     });
357   }
358
359   public void onError(@NotNull final String localizedMessage,
360                       @Nullable final String stackTrace,
361                       final boolean isCritical) {
362     addToInvokeLater(new Runnable() {
363       public void run() {
364         final SMTestProxy currentProxy = findCurrentTestOrSuite();
365         currentProxy.addError(localizedMessage, stackTrace, isCritical);
366       }
367     });
368   }
369   
370
371   public void onTestFailure(@NotNull final TestFailedEvent testFailedEvent) {
372     addToInvokeLater(new Runnable() {
373       public void run() {
374         final String testName = testFailedEvent.getName();
375         if (testName == null) {
376           logProblem("No test name specified in " + testFailedEvent);
377           return;
378         }
379         final String localizedMessage = testFailedEvent.getLocalizedFailureMessage();
380         final String stackTrace = testFailedEvent.getStacktrace();
381         final boolean isTestError = testFailedEvent.isTestError();
382         final String comparisionFailureActualText = testFailedEvent.getComparisonFailureActualText();
383         final String comparisionFailureExpectedText = testFailedEvent.getComparisonFailureExpectedText();
384         final boolean inDebugMode = SMTestRunnerConnectionUtil.isInDebugMode();
385
386         final String fullTestName = getFullTestName(testName);
387         SMTestProxy testProxy = getProxyByFullTestName(fullTestName);
388         if (testProxy == null) {
389           logProblem("Test wasn't started! TestFailure event: name = {" + testName + "}" +
390                              ", message = {" + localizedMessage + "}" +
391                              ", stackTrace = {" + stackTrace + "}. " +
392                              cannotFindFullTestNameMsg(fullTestName));
393           if (inDebugMode) {
394             return;
395           }
396           else {
397             // if hasn't been already reported
398             // 1. report
399             onTestStarted(new TestStartedEvent(testName, null));
400             // 2. add failure
401             testProxy = getProxyByFullTestName(fullTestName);
402           }
403         }
404
405         if (testProxy == null) {
406           return;
407         }
408
409         if (comparisionFailureActualText != null && comparisionFailureExpectedText != null) {
410           testProxy.setTestComparisonFailed(localizedMessage, stackTrace,
411                                             comparisionFailureActualText, comparisionFailureExpectedText, 
412                                             testFailedEvent.getExpectedFilePath(), testFailedEvent.getActualFilePath());
413         }
414         else if (comparisionFailureActualText == null && comparisionFailureExpectedText == null) {
415           testProxy.setTestFailed(localizedMessage, stackTrace, isTestError);
416         }
417         else {
418           logProblem("Comparison failure actual and expected texts should be both null or not null.\n"
419                      + "Expected:\n"
420                      + comparisionFailureExpectedText + "\n"
421                      + "Actual:\n"
422                      + comparisionFailureActualText);
423         }
424
425         // fire event
426         fireOnTestFailed(testProxy);
427       }
428     });
429   }
430
431   public void onTestIgnored(@NotNull final TestIgnoredEvent testIgnoredEvent) {
432      addToInvokeLater(new Runnable() {
433       public void run() {
434         final String testName = ObjectUtils.assertNotNull(testIgnoredEvent.getName());
435         String ignoreComment = testIgnoredEvent.getIgnoreComment();
436         final String stackTrace = testIgnoredEvent.getStacktrace();
437         final String fullTestName = getFullTestName(testName);
438         SMTestProxy testProxy = getProxyByFullTestName(fullTestName);
439         if (testProxy == null) {
440           final boolean debugMode = SMTestRunnerConnectionUtil.isInDebugMode();
441           logProblem("Test wasn't started! " +
442                      "TestIgnored event: name = {" + testName + "}, " +
443                      "message = {" + ignoreComment + "}. " +
444                      cannotFindFullTestNameMsg(fullTestName));
445           if (debugMode) {
446             return;
447           } else {
448             // try to fix
449             // 1. report test opened
450             onTestStarted(new TestStartedEvent(testName, null));
451
452             // 2. report failure
453             testProxy = getProxyByFullTestName(fullTestName);
454           }
455
456         }
457         if (testProxy == null) {
458           return;
459         }
460         testProxy.setTestIgnored(ignoreComment, stackTrace);
461
462         // fire event
463         fireOnTestIgnored(testProxy);
464       }
465     });
466   }
467
468   public void onTestOutput(@NotNull final TestOutputEvent testOutputEvent) {
469      addToInvokeLater(new Runnable() {
470       public void run() {
471         final String testName = testOutputEvent.getName();
472         final String text = testOutputEvent.getText();
473         final boolean stdOut = testOutputEvent.isStdOut();
474         final String fullTestName = getFullTestName(testName);
475         final SMTestProxy testProxy = getProxyByFullTestName(fullTestName);
476         if (testProxy == null) {
477           logProblem("Test wasn't started! TestOutput event: name = {" + testName + "}, " +
478                      "isStdOut = " + stdOut + ", " +
479                      "text = {" + text + "}. " +
480                      cannotFindFullTestNameMsg(fullTestName));
481           return;
482         }
483
484         if (stdOut) {
485           testProxy.addStdOutput(text, ProcessOutputTypes.STDOUT);
486         } else {
487           testProxy.addStdErr(text);
488         }
489       }
490     });
491   }
492
493   public void onTestsCountInSuite(final int count) {
494      addToInvokeLater(new Runnable() {
495        public void run() {
496          fireOnTestsCountInSuite(count);
497        }
498      });
499   }
500
501   @NotNull
502   protected final SMTestProxy getCurrentSuite() {
503     final SMTestProxy currentSuite = mySuitesStack.getCurrentSuite();
504
505     if (currentSuite != null) {
506       return currentSuite;
507     }
508
509     // current suite shouldn't be null otherwise test runner isn't correct
510     // or may be we are in debug mode
511     logProblem("Current suite is undefined. Root suite will be used.");
512     myGetChildren = true;
513     return myTestsRootNode;
514
515   }
516  
517   protected String getFullTestName(final String testName) {
518     // Test name should be unique
519     return testName;
520   }
521
522   protected int getRunningTestsQuantity() {
523     return myRunningTestsFullNameToProxy.size();
524   }
525
526   @Nullable
527   protected SMTestProxy getProxyByFullTestName(final String fullTestName) {
528     return myRunningTestsFullNameToProxy.get(fullTestName);
529   }
530
531   @TestOnly
532   protected void clearInternalSuitesStack() {
533     mySuitesStack.clear();
534   }
535
536   private String cannotFindFullTestNameMsg(String fullTestName) {
537     return "Cant find running test for ["
538               + fullTestName
539               + "]. Current running tests: {"
540               + dumpRunningTestsNames() + "}";
541   }
542
543   private StringBuilder dumpRunningTestsNames() {
544     final Set<String> names = myRunningTestsFullNameToProxy.keySet();
545     final StringBuilder namesDump = new StringBuilder();
546     for (String name : names) {
547       namesDump.append('[').append(name).append(']').append(',');
548     }
549     return namesDump;
550   }
551
552
553   /*
554    * Remove listeners,  etc
555    */
556   public void dispose() {
557     super.dispose();
558      addToInvokeLater(new Runnable() {
559       public void run() {
560
561         disconnectListeners();
562         if (!myRunningTestsFullNameToProxy.isEmpty()) {
563           final Application application = ApplicationManager.getApplication();
564           if (!application.isHeadlessEnvironment() && !application.isUnitTestMode()) {
565             logProblem("Not all events were processed! " + dumpRunningTestsNames());
566           }
567         }
568         myRunningTestsFullNameToProxy.clear();
569         mySuitesStack.clear();
570       }
571     });
572   }
573
574
575   private SMTestProxy findCurrentTestOrSuite() {
576     //if we can locate test - we will send output to it, otherwise to current test suite
577     final SMTestProxy currentProxy;
578     if (myRunningTestsFullNameToProxy.size() == 1) {
579       //current test
580       currentProxy = myRunningTestsFullNameToProxy.values().iterator().next();
581     } else {
582       //current suite
583       //
584       // ProcessHandler can fire output available event before processStarted event
585       currentProxy = mySuitesStack.isEmpty() ? myTestsRootNode : getCurrentSuite();
586     }
587     return currentProxy;
588   }
589 }