Fixed python UT testing.
[idea/community.git] / platform / smRunner / src / com / intellij / execution / testframework / sm / runner / SMTestProxy.java
1 /*
2  * Copyright 2000-2009 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.google.common.collect.Lists;
19 import com.intellij.execution.Location;
20 import com.intellij.execution.testframework.AbstractTestProxy;
21 import com.intellij.execution.testframework.Filter;
22 import com.intellij.execution.testframework.Printable;
23 import com.intellij.execution.testframework.Printer;
24 import com.intellij.execution.testframework.sm.TestsLocationProviderUtil;
25 import com.intellij.execution.testframework.sm.runner.states.*;
26 import com.intellij.execution.testframework.sm.runner.ui.TestsPresentationUtil;
27 import com.intellij.execution.ui.ConsoleViewContentType;
28 import com.intellij.ide.util.EditSourceUtil;
29 import com.intellij.openapi.diagnostic.Logger;
30 import com.intellij.openapi.extensions.Extensions;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.util.Key;
33 import com.intellij.pom.Navigatable;
34 import com.intellij.testIntegration.TestLocationProvider;
35 import org.jetbrains.annotations.NotNull;
36 import org.jetbrains.annotations.Nullable;
37
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.List;
41
42 /**
43  * @author: Roman Chernyatchik
44  */
45 public class SMTestProxy extends AbstractTestProxy {
46   private static final Logger LOG = Logger.getInstance(SMTestProxy.class.getName());
47
48   private List<SMTestProxy> myChildren;
49   private SMTestProxy myParent;
50
51   private AbstractState myState = NotRunState.getInstance();
52   private final String myName;
53   private Integer myDuration = null; // duration is unknown
54   @Nullable private final String myLocationUrl;
55   private boolean myDurationIsCached = false; // is used for separating unknown and unset duration
56   private boolean myHasErrors = false;
57   private boolean myHasErrorsCached = false;
58
59   private final boolean myIsSuite;
60   private boolean myIsEmptyIsCached = false; // is used for separating unknown and unset values
61   private boolean myIsEmpty = true;
62   TestLocationProvider myLocator = null;
63
64   public SMTestProxy(final String testName, final boolean isSuite,
65                      @Nullable final String locationUrl) {
66     myName = testName;
67     myIsSuite = isSuite;
68     myLocationUrl = locationUrl;
69   }
70
71   public void setLocator(@NotNull TestLocationProvider locator) {
72     myLocator = locator;
73   }
74
75   public boolean isInProgress() {
76     //final SMTestProxy parent = getParent();
77
78     return myState.isInProgress();
79   }
80
81   public boolean isDefect() {
82     return myState.isDefect();
83   }
84
85   public boolean shouldRun() {
86     return true;
87   }
88
89   public int getMagnitude() {
90     // Is used by some of Tests Filters
91
92     //WARN: It is Hack, see PoolOfTestStates, API is necessary
93     return getMagnitudeInfo().getValue();
94   }
95
96   public TestStateInfo.Magnitude getMagnitudeInfo() {
97     return myState.getMagnitude();
98   }
99
100   public boolean hasErrors() {
101     // if already cached
102     if (myHasErrorsCached) {
103       return myHasErrors;
104     }
105
106     final boolean canCacheErrors = !myState.isInProgress();
107     // calculate
108     final boolean hasErrors = calcHasErrors();
109     if (canCacheErrors) {
110       myHasErrors = hasErrors;
111       myHasErrorsCached = true;
112     }
113     return hasErrors;
114   }
115
116   private boolean calcHasErrors() {
117     if (myHasErrors) {
118       return true;
119     }
120
121     for (SMTestProxy child : getChildren()) {
122       if (child.hasErrors()) {
123         return true;
124       }
125     }
126     return false;
127   }
128
129   public boolean isLeaf() {
130     return myChildren == null || myChildren.isEmpty();
131   }
132
133   @Override
134   public boolean isInterrupted() {
135     return myState.wasTerminated();
136   }
137
138   public boolean isPassed() {
139     return myState.getMagnitude() == TestStateInfo.Magnitude.SKIPPED_INDEX ||
140            myState.getMagnitude() == TestStateInfo.Magnitude.COMPLETE_INDEX ||
141            myState.getMagnitude() == TestStateInfo.Magnitude.PASSED_INDEX; 
142   }
143
144   public void addChild(final SMTestProxy child) {
145     if (myChildren == null) {
146       myChildren = new ArrayList<SMTestProxy>();
147     }
148     myChildren.add(child);
149
150     // add printable
151     //
152     // add link to child's future output in correct place
153     // actually if after this suite will obtain output
154     // it will place it after this child and before future child
155     addLast(child);
156
157     // add child
158     //
159     //TODO reset children cache
160     child.setParent(this);
161     // if parent is being printed then all childs output
162     // should be also send to the same printer
163     child.setPrinter(myPrinter);
164   }
165
166   public String getName() {
167     return myName;
168   }
169
170   @Nullable
171   public Location getLocation(final Project project) {
172     //determines location of test proxy
173
174     //TODO multiresolve support
175
176     if (myLocationUrl == null) {
177       return null;
178     }
179
180     final String protocolId = TestsLocationProviderUtil.extractProtocol(myLocationUrl);
181     final String path = TestsLocationProviderUtil.extractPath(myLocationUrl);
182
183     if (protocolId != null && path != null) {
184       if (myLocator != null) {
185         List<Location> locations = myLocator.getLocation(protocolId, path, project);
186         if (!locations.isEmpty()) {
187           return locations.iterator().next();
188         }
189       }
190       for (TestLocationProvider provider : Extensions.getExtensions(TestLocationProvider.EP_NAME)) {
191         final List<Location> locations = provider.getLocation(protocolId, path, project);
192         if (!locations.isEmpty()) {
193           return locations.iterator().next();
194         }
195       }
196     }
197
198     return null;
199   }
200
201   @Nullable
202   public Navigatable getDescriptor(final Location location) {
203     // by location gets navigatable element.
204     // It can be file or place in file (e.g. when OPEN_FAILURE_LINE is enabled)
205
206     if (location != null) {
207       return EditSourceUtil.getDescriptor(location.getPsiElement());
208     }
209     return null;
210   }
211
212   public boolean isSuite() {
213     return myIsSuite;
214   }
215
216   public SMTestProxy getParent() {
217     return myParent;
218   }
219
220   public List<? extends SMTestProxy> getChildren() {
221     return myChildren != null ? myChildren : Collections.<SMTestProxy>emptyList();
222   }
223
224   public List<SMTestProxy> getAllTests() {
225     final List<SMTestProxy> allTests = new ArrayList<SMTestProxy>();
226
227     allTests.add(this);
228
229     for (SMTestProxy child : getChildren()) {
230       allTests.addAll(child.getAllTests());
231     }
232
233     return allTests;
234   }
235
236
237   public void setStarted() {
238     myState = !myIsSuite ? TestInProgressState.TEST : new SuiteInProgressState(this);
239   }
240
241   /**
242    * Calculates and caches duration of test or suite
243    * @return null if duration is unknown, otherwise duration value in milliseconds;
244    */
245   @Nullable
246   @Override
247   public Integer getDuration() {
248     // Returns duration value for tests
249     // or cached duration for suites
250     if (myDurationIsCached || !isSuite()) {
251       return myDuration;
252     }
253
254     //For suites counts and caches durations of its children. Also it evaluates partial duration,
255     //i.e. if duration is unknown it will be ignored in summary value.
256     //If duration for all children is unknown summary duration will be also unknown
257     //if one of children is ignored - it's duration will be 0 and if child wasn't run,
258     //then it's duration will be unknown
259     myDuration = calcSuiteDuration();
260     myDurationIsCached = true;
261
262     return myDuration;
263   }
264
265   @Override
266   public boolean shouldSkipRootNodeForExport() {
267     return true;
268   }
269
270   /**
271    * Sets duration of test
272    * @param duration In milliseconds
273    */
274   public void setDuration(final int duration) {
275     invalidateCachedDurationForContainerSuites();
276
277     if (!isSuite()) {
278       myDurationIsCached = true;
279       myDuration = (duration >= 0) ? duration : null;
280       return;
281     }
282
283     // Not allow to directly set duration for suites.
284     // It should be the sum of children. This requirement is only
285     // for safety of current model and may be changed
286     LOG.warn("Unsupported operation");
287   }
288
289   public void setFinished() {
290     if (myState.isFinal()) {
291       // we shouldn't fire new printable because final state
292       // has been already fired
293       return;
294     }
295
296     if (!isSuite()) {
297       // if isn't in other finished state (ignored, failed or passed)
298       myState = TestPassedState.INSTANCE;
299     } else {
300       //Test Suite
301       myState = determineSuiteStateOnFinished();
302     }
303     // prints final state additional info
304     fireOnNewPrintable(myState);
305   }
306
307   public void setTestFailed(@NotNull final String localizedMessage,
308                             @Nullable final String stackTrace,
309                             final boolean testError) {
310     myState = testError
311               ? new TestErrorState(localizedMessage, stackTrace)
312               : new TestFailedState(localizedMessage, stackTrace);
313     fireOnNewPrintable(myState);
314   }
315
316   public void setTestComparisonFailed(@NotNull final String localizedMessage,
317                                       @Nullable final String stackTrace,
318                                       @NotNull final String actualText,
319                                       @NotNull final String expectedText) {
320     myState = new TestComparisionFailedState(localizedMessage, stackTrace,
321                                              actualText, expectedText);
322     fireOnNewPrintable(myState);
323   }
324
325   public void setTestIgnored(@NotNull final String ignoreComment,
326                              @Nullable final String stackTrace) {
327     myState = new TestIgnoredState(ignoreComment, stackTrace);
328     fireOnNewPrintable(myState);
329   }
330
331   public void setParent(@Nullable final SMTestProxy parent) {
332     myParent = parent;
333   }
334
335   public List<? extends SMTestProxy> collectChildren(@Nullable final Filter filter) {
336     return filterChildren(filter, collectChildren());
337   }
338
339   public List<? extends SMTestProxy> collectChildren() {
340     final List<? extends SMTestProxy> allChildren = getChildren();
341
342     final List<SMTestProxy> result = Lists.newArrayList();
343
344     result.addAll(allChildren);
345
346     for (SMTestProxy p: allChildren) {
347       result.addAll(p.collectChildren());
348     }
349
350     return result;
351   }
352
353   public List<? extends SMTestProxy> getChildren(@Nullable final Filter filter) {
354     final List<? extends SMTestProxy> allChildren = getChildren();
355
356     return filterChildren(filter, allChildren);
357   }
358
359   private static List<? extends SMTestProxy> filterChildren(@Nullable Filter filter, List<? extends SMTestProxy> allChildren) {
360     if (filter == Filter.NO_FILTER || filter == null) {
361       return allChildren;
362     }
363
364     final List<SMTestProxy> selectedChildren = new ArrayList<SMTestProxy>();
365     for (SMTestProxy child : allChildren) {
366       if (filter.shouldAccept(child)) {
367         selectedChildren.add(child);
368       }
369     }
370
371     if ((selectedChildren.isEmpty())) {
372       return Collections.<SMTestProxy>emptyList();
373     }
374     return selectedChildren;
375   }
376
377   public boolean wasLaunched() {
378     return myState.wasLaunched();
379   }
380
381
382   /**
383    * Prints this proxy and all its children on given printer
384    * @param printer Printer
385    */
386   public void printOn(final Printer printer) {
387     super.printOn(printer);
388
389     invokeInAlarm(new Runnable() {
390       @Override
391       public void run() {
392         //Tests State, that provide and formats additional output
393         myState.printOn(printer);
394       }
395     });
396   }
397
398   public void addStdOutput(final String output, final Key outputType) {
399     addLast(new Printable() {
400       public void printOn(final Printer printer) {
401         printer.print(output, ConsoleViewContentType.getConsoleViewType(outputType));
402       }
403     });
404   }
405
406   public void addStdErr(final String output) {
407     addLast(new Printable() {
408       public void printOn(final Printer printer) {
409         printer.print(output, ConsoleViewContentType.ERROR_OUTPUT);
410       }
411     });
412   }
413
414   public void addError(final String output,
415                        @Nullable final String stackTrace) {
416     myHasErrors = true;
417     addLast(new Printable() {
418       public void printOn(final Printer printer) {
419         final String errorText = TestFailedState.buildErrorPresentationText(output, stackTrace);
420         LOG.assertTrue(errorText != null);
421
422         TestFailedState.printError(printer, errorText);
423       }
424     });
425   }
426
427   public void addSystemOutput(final String output) {
428     addLast(new Printable() {
429       public void printOn(final Printer printer) {
430         printer.print(output, ConsoleViewContentType.SYSTEM_OUTPUT);
431       }
432     });
433   }
434
435   @NotNull
436   public String getPresentableName() {
437     return TestsPresentationUtil.getPresentableName(this);
438   }
439
440   @Override
441   @Nullable
442   public AssertEqualsDiffViewerProvider getDiffViewerProvider() {
443     if (myState instanceof AssertEqualsDiffViewerProvider) {
444       return (AssertEqualsDiffViewerProvider)myState;
445     }
446     return null;
447   }
448
449   @Override
450   public String toString() {
451     return getPresentableName();
452   }
453
454   /**
455    * Process was terminated
456    */
457   public void setTerminated() {
458     if (myState.isFinal()) {
459       return;
460     }
461     myState = TerminatedState.INSTANCE;
462     final List<? extends SMTestProxy> children = getChildren();
463     for (SMTestProxy child : children) {
464       child.setTerminated();
465     }
466     fireOnNewPrintable(myState);
467   }
468
469   public boolean wasTerminated() {
470     return myState.wasTerminated();
471   }
472
473   @Nullable
474   protected String getLocationUrl() {
475     return myLocationUrl;
476   }
477
478   /**
479    * Check if suite contains error tests or suites
480    * @return True if contains
481    */
482   private boolean containsErrorTests() {
483     final List<? extends SMTestProxy> children = getChildren();
484     for (SMTestProxy child : children) {
485       if (child.getMagnitudeInfo() == TestStateInfo.Magnitude.ERROR_INDEX) {
486         return true;
487       }
488     }
489     return false;
490   }
491
492   private boolean containsFailedTests() {
493     final List<? extends SMTestProxy> children = getChildren();
494     for (SMTestProxy child : children) {
495       if (child.getMagnitudeInfo() == TestStateInfo.Magnitude.FAILED_INDEX) {
496         return true;
497       }
498     }
499     return false;
500   }
501
502   /**
503    * Determines site state after it has been finished
504    * @return New state
505    */
506   protected AbstractState determineSuiteStateOnFinished() {
507     final AbstractState state;
508     if (isLeaf() || isEmptySuite()) {
509       state = SuiteFinishedState.EMPTY_SUITE;
510     } else {
511       if (isDefect()) {
512         // Test suit contains errors if at least one of its tests contains error
513         if (containsErrorTests()) {
514           state = SuiteFinishedState.ERROR_SUITE;
515         } else {
516           // if suite contains failed tests - all suite should be
517           // consider as failed
518           state = containsFailedTests()
519                    ? SuiteFinishedState.FAILED_SUITE
520                    : SuiteFinishedState.WITH_IGNORED_TESTS_SUITE;
521         }
522       } else {
523         state = SuiteFinishedState.PASSED_SUITE;
524       }
525     }
526     return state;
527   }
528
529   public boolean isEmptySuite() {
530     if (myIsEmptyIsCached) {
531       return myIsEmpty;
532     }
533
534     if (!isSuite()) {
535       // test - no matter what we will return
536       myIsEmpty = true;
537       myIsEmptyIsCached = true;
538
539       return myIsEmpty;
540     }
541
542     myIsEmpty = true;
543     final List<? extends SMTestProxy> allTestCases = getChildren();
544     for (SMTestProxy testOrSuite : allTestCases) {
545       if (testOrSuite.isSuite()) {
546         // suite
547         if (!testOrSuite.isEmptySuite()) {
548           // => parent suite isn't empty
549           myIsEmpty = false;
550           myIsEmptyIsCached = true;
551           break;
552         }
553         // all suites are empty
554         myIsEmpty = true;
555         // we can cache only final state, otherwise test may be added
556         myIsEmptyIsCached = myState.isFinal();
557       } else {
558         // test => parent suite isn't empty
559         myIsEmpty = false;
560         myIsEmptyIsCached = true;
561         break;
562       }
563     }
564     return myIsEmpty;
565   }
566
567
568
569   @Nullable
570   private Integer calcSuiteDuration() {
571     int partialDuration = 0;
572     boolean durationOfChildrenIsUnknown = true;
573
574     for (SMTestProxy child : getChildren()) {
575       final Integer duration = child.getDuration();
576       if (duration != null) {
577         durationOfChildrenIsUnknown = false;
578         partialDuration += duration.intValue();
579       }
580     }
581     // Lets convert partial duration in integer object. Negative partial duration
582     // means that duration of all children is unknown
583     return durationOfChildrenIsUnknown ? null : partialDuration;
584   }
585
586   /**
587    * Recursively invalidates cached duration for container(parent) suites
588    */
589   private void invalidateCachedDurationForContainerSuites() {
590     // Invalidates duration of this suite
591     myDuration = null;
592     myDurationIsCached = false;
593
594     // Invalidates duration of container suite
595     final SMTestProxy containerSuite = getParent();
596     if (containerSuite != null) {
597       containerSuite.invalidateCachedDurationForContainerSuites();
598     }
599   }
600
601   public static class SMRootTestProxy extends SMTestProxy {
602     private boolean myTestsReporterAttached; // false by default
603
604     public SMRootTestProxy() {
605       super("[root]", true, null);
606     }
607
608     public void setTestsReporterAttached() {
609       myTestsReporterAttached = true;
610     }
611
612     public boolean isTestsReporterAttached() {
613       return myTestsReporterAttached;
614     }
615
616     @Override
617     protected AbstractState determineSuiteStateOnFinished() {
618       if (isLeaf() && !isTestsReporterAttached()) {
619         return SuiteFinishedState.TESTS_REPORTER_NOT_ATTACHED;
620       }
621       return super.determineSuiteStateOnFinished();
622     }
623   }
624 }