A way to filter out non-relevant environments
[idea/community.git] / python / testSrc / com / jetbrains / env / PyEnvTaskRunner.java
1 package com.jetbrains.env;
2
3 import com.google.common.collect.Lists;
4 import com.google.common.collect.Sets;
5 import com.intellij.openapi.diagnostic.Logger;
6 import com.intellij.openapi.projectRoots.Sdk;
7 import com.intellij.openapi.vfs.VfsUtil;
8 import com.intellij.openapi.vfs.VirtualFile;
9 import com.jetbrains.python.psi.LanguageLevel;
10 import com.jetbrains.python.sdk.InvalidSdkException;
11 import com.jetbrains.python.sdk.PythonSdkType;
12 import com.jetbrains.python.sdkTools.PyTestSdkTools;
13 import com.jetbrains.python.sdkTools.SdkCreationType;
14 import org.jetbrains.annotations.NotNull;
15 import org.jetbrains.annotations.Nullable;
16
17 import java.io.IOException;
18 import java.net.URL;
19 import java.util.List;
20 import java.util.Set;
21
22 /**
23  * @author traff
24  */
25 public class PyEnvTaskRunner {
26   private static final Logger LOG = Logger.getInstance(PyEnvTaskRunner.class);
27   private final List<String> myRoots;
28
29   public PyEnvTaskRunner(List<String> roots) {
30     myRoots = roots;
31   }
32
33   // todo: doc
34   public void runTask(PyTestTask testTask, String testName, @NotNull final String... tagsRequiedByTest) {
35     boolean wasExecuted = false;
36
37     List<String> passedRoots = Lists.newArrayList();
38
39     final Set<String> requiredTags = Sets.union(testTask.getTags(), Sets.newHashSet(tagsRequiedByTest));
40
41     final Set<String> tagsToCover = testTask.getTagsToCover();
42
43     for (String root : myRoots) {
44
45       List<String> envTags = PyEnvTestCase.loadEnvTags(root);
46       final boolean suitableForTask = isSuitableForTask(envTags, requiredTags);
47       final boolean shouldRun = shouldRun(root, testTask);
48       if (!suitableForTask || !shouldRun) {
49         LOG.warn(String.format("Skipping %s (compatible with tags: %s, should run:%s)", root, suitableForTask, shouldRun));
50         continue;
51       }
52
53       if (tagsToCover != null && envTags.size() > 0 && !isNeededToRun(tagsToCover, envTags)) {
54         LOG.warn(String.format("Skipping %s (test already was executed on a similar environment)", root));
55         continue;
56       }
57
58       if (tagsToCover != null) {
59         tagsToCover.removeAll(envTags);
60       }
61
62       LOG.warn(String.format("Running on root %s", root));
63
64       try {
65         testTask.setUp(testName);
66         wasExecuted = true;
67         if (isJython(root)) {
68           testTask.useLongTimeout();
69         }
70         else {
71           testTask.useNormalTimeout();
72         }
73         final String executable = getExecutable(root, testTask);
74         if (executable == null) {
75           throw new RuntimeException("Cannot find Python interpreter in " + root);
76         }
77         final Sdk sdk = createSdkByExecutable(executable);
78
79         /**
80          * Skipping test if {@link PyTestTask} reports it does not support this language level
81          */
82         final LanguageLevel languageLevel = PythonSdkType.getLanguageLevelForSdk(sdk);
83         if (testTask.isLanguageLevelSupported(languageLevel)) {
84           testTask.runTestOn(executable);
85           passedRoots.add(root);
86         }
87         else {
88           LOG.warn(String.format("Skipping root %s", root));
89         }
90       }
91       catch (final Throwable e) {
92         // Direct output of enteredTheMatrix may break idea or TC since can't distinguish test output from real test result
93         // Exception is thrown anyway, so we escape message before logging
94         if (e.getMessage().contains("enteredTheMatrix")) {
95           // .error( may lead to new exception with out of stacktrace.
96           LOG.warn(PyEnvTestCase.escapeTestMessage(e.getMessage()));
97         }
98         else {
99           LOG.error(e);
100         }
101         throw new RuntimeException(
102           PyEnvTestCase.joinStrings(passedRoots, "Tests passed environments: ") + "Test failed on " + getEnvType() + " environment " + root,
103           e);
104       }
105       finally {
106         try {
107           testTask.tearDown();
108         }
109         catch (Exception e) {
110           throw new RuntimeException("Couldn't tear down task", e);
111         }
112       }
113     }
114
115     if (!wasExecuted) {
116       throw new RuntimeException("test" +
117                                  testName +
118                                  " was not executed.\n" +
119                                  PyEnvTestCase.joinStrings(myRoots, "All roots: ") +
120                                  "\n" +
121                                  PyEnvTestCase.joinStrings(testTask.getTags(), "Required tags in tags.txt in root: "));
122     }
123   }
124
125   private static boolean isNeededToRun(@NotNull Set<String> tagsToCover, @NotNull List<String> envTags) {
126     for (String tag : envTags) {
127       if (tagsToCover.contains(tag)) {
128         return true;
129       }
130     }
131
132     return false;
133   }
134
135   /**
136    * Create SDK by path to python exectuable
137    *
138    * @param executable path executable
139    * @return sdk or null if there is no sdk on this path
140    * @throws InvalidSdkException bad sdk
141    */
142   @Nullable
143   private static Sdk createSdkByExecutable(@NotNull final String executable) throws InvalidSdkException, IOException {
144     final URL rootUrl = new URL(String.format("file:///%s", executable));
145     final VirtualFile url = VfsUtil.findFileByURL(rootUrl);
146     if (url == null) {
147       return null;
148     }
149     return PyTestSdkTools.createTempSdk(url, SdkCreationType.EMPTY_SDK, null);
150   }
151
152   protected boolean shouldRun(String root, PyTestTask task) {
153     return true;
154   }
155
156   protected String getExecutable(String root, PyTestTask testTask) {
157     return PythonSdkType.getPythonExecutable(root);
158   }
159
160   protected String getEnvType() {
161     return "local";
162   }
163
164   private static boolean isSuitableForTask(List<String> availableTags, @NotNull final Set<String> requiredTags) {
165     return isSuitableForTags(availableTags, requiredTags);
166   }
167
168   public static boolean isSuitableForTags(List<String> envTags, Set<String> taskTags) {
169     Set<String> necessaryTags = Sets.newHashSet(taskTags);
170
171     for (String tag : envTags) {
172       necessaryTags.remove(tag.trim());
173     }
174
175     for (String tag : taskTags) {
176       if (tag.startsWith("-")) { //do not run on envs with that tag
177         if (envTags.contains(tag.substring(1))) {
178           return false;
179         }
180         necessaryTags.remove(tag);
181       }
182     }
183
184     return necessaryTags.isEmpty();
185   }
186
187   public static boolean isJython(@NotNull String sdkHome) {
188     return sdkHome.toLowerCase().contains("jython");
189   }
190 }