void (@NotNull final PyTestTask testTask, @NotNull final String testName, final Class<? extends PythonSdkFlavor> @Nullable [] skipOnFlavors, final String @NotNull ... tagsRequiredByTest) { boolean wasExecuted = false; List<String> passedRoots = new ArrayList<>(); final Set<String> requiredTags = Sets.union(testTask.getTags(), Sets.newHashSet(tagsRequiredByTest)); for (String root : getMinimalSetOfTestEnvs(requiredTags)) { final boolean shouldRun = shouldRun(root, testTask); if (!shouldRun) { LOG.warn(String.format("Skipping %s (should run: %s)", root, shouldRun)); continue; } LOG.warn(String.format("Running on root %s", root)); try { testTask.setUp(testName); wasExecuted = true; if (isJython(root)) { testTask.useLongTimeout(); } else { testTask.useNormalTimeout(); } final String executable = PythonSdkUtil.getPythonExecutable(root); assert executable != null : "No executable in " + root; final Sdk sdk = getSdk(executable, testTask); if (skipOnFlavors != null) { final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(sdk); if (ContainerUtil.exists(skipOnFlavors, o -> o.isInstance(flavor))) { LOG.warn("Skipping flavor " + flavor.toString()); continue; } } /* Skipping test if {@link PyTestTask} reports it does not support this language level */ final LanguageLevel languageLevel = PythonSdkType.getLanguageLevelForSdk(sdk); if (testTask.isLanguageLevelSupported(languageLevel)) { if (myLoggingRule != null) { final PyExecutionFixtureTestTask execTask = ObjectUtils.tryCast(testTask, PyExecutionFixtureTestTask.class); if (execTask != null) { // Fill be disabled automatically on project dispose myLoggingRule.startLogging(execTask.getProject(), execTask.getClassesToEnableDebug()); } } testTask.runTestOn(executable, sdk); passedRoots.add(root); } else { LOG.warn(String.format("Skipping root %s", root)); } } catch (final RuntimeException | Error ex) { // Runtime and error are logged including environment info LOG.warn(joinStrings(passedRoots, "Tests passed environments: ") + "Test failed on " + getEnvType() + " environment " + root); throw ex; } catch (final Exception e) { // Exception can't be thrown with out of throw new PyEnvWrappingException(e); } finally { try { // Teardown should be called on main thread because fixture teardown checks for // thread leaks, and blocked main thread is considered as leaked testTask.tearDown(); } catch (final Exception e) { throw new RuntimeException("Couldn't tear down task", e); } } } if (!wasExecuted) { throw new RuntimeException("test " + testName + " was not executed.\n" + joinStrings(myRoots, "All roots: ") + "\n" + joinStrings(requiredTags, "Required tags in tags.txt in root: ")); } }