// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.jetbrains.env; import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.impl.ConsoleViewImpl; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.testframework.AbstractTestProxy; import com.intellij.execution.testframework.Filter; import com.intellij.execution.testframework.actions.RerunFailedActionsTestTools; import com.intellij.execution.testframework.sm.runner.BaseSMTRunnerTestCase; import com.intellij.execution.testframework.sm.runner.SMTestProxy; import com.intellij.execution.testframework.sm.runner.SMTestProxy.SMRootTestProxy; import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView; import com.intellij.execution.ui.RunContentDescriptor; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.testFramework.EdtTestUtil; import com.jetbrains.python.run.AbstractPythonRunConfiguration; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.Assert; import javax.swing.*; import java.util.List; /** * Runner for tests. Provides access to test console and test results. *

* It supports failed test rerun. You only need to provide number of times to rerun as ctor param * and then use {@link #getCurrentRerunStep()} to find number of rerun * * @author Ilya.Kazakevich */ public class PyAbstractTestProcessRunner> extends ConfigurationBasedProcessRunner { private final int myTimesToRerunFailedTests; private boolean mySkipExitCodeAssertion; private int myCurrentRerunStep; private SMTRunnerConsoleView myExecutionConsole; private SMRootTestsCounter myProxyManager; /** * @param timesToRerunFailedTests how many times rerun failed tests (0 not to rerun at all) * @see ConfigurationBasedProcessRunner#ConfigurationBasedProcessRunner(ConfigurationFactory, Class, String) */ public PyAbstractTestProcessRunner(@NotNull final ConfigurationFactory configurationFactory, @NotNull final Class expectedConfigurationType, final int timesToRerunFailedTests) { this(new PyTemplateConfigurationProducerForRunner<>(configurationFactory), expectedConfigurationType, timesToRerunFailedTests); } public PyAbstractTestProcessRunner(@NotNull PyConfigurationProducerForRunner configurationProducer, @NotNull final Class expectedConfigurationType, final int timesToRerunFailedTests) { super(expectedConfigurationType, configurationProducer); myTimesToRerunFailedTests = timesToRerunFailedTests; } public final void setSkipExitCodeAssertion(boolean skipExitCodeAssertion) { mySkipExitCodeAssertion = skipExitCodeAssertion; } protected void assertExitCodeForSkippedTests(int code) { Assert.assertEquals("Exit code must be 0 if all tests are ignored", 0, code); } @Override public final void assertExitCodeIsCorrect(int code) { if (mySkipExitCodeAssertion) { return; } // If test framework doesn't support exit codes, overwrite this method SMRootTestProxy proxy = getTestProxy(); if (proxy.isPassed()) { Assert.assertEquals("Exit code must be 0 if all tests are passed", 0, code); } else if (proxy.isIgnored()) { assertExitCodeForSkippedTests(code); } else { Assert.assertNotEquals("Exit code must NOT be 0 if some tests failed", 0, code); } } @Override protected void fetchConsoleAndSetToField(@NotNull final RunContentDescriptor descriptor) { // Fetch test results from console myExecutionConsole = (SMTRunnerConsoleView)descriptor.getExecutionConsole(); final JComponent component = myExecutionConsole.getComponent(); assert component != null; myConsole = (ConsoleViewImpl)myExecutionConsole.getConsole(); myProxyManager = new SMRootTestsCounter(myExecutionConsole.getResultsViewer().getTestsRootNode()); } @Override protected void prepareConsoleAfterProcessEnd() { super.prepareConsoleAfterProcessEnd(); ApplicationManager.getApplication().invokeAndWait(() -> { // Print output of tests to console (because console may be scrolled) myProxyManager.getProxy().getAllTests().get(0).printOn(myExecutionConsole.getPrinter()); }, ModalityState.nonModal()); } /** * Ensures all test passed */ public void assertAllTestsPassed() { final String consoleText = getAllConsoleText(); Assert.assertEquals(getFormattedTestTree() + consoleText, 0, myProxyManager.getProxy().getChildren(Filter.NOT_PASSED).size()); Assert.assertEquals(getFormattedTestTree() + consoleText, 0, getFailedTestsCount()); } /** * Ensures all tests passed or skipped */ public void assertNoFailures() { final String consoleText = getAllConsoleText(); int notPassed = myProxyManager.getProxy().getChildren(Filter.NOT_PASSED).size(); int ignored = myProxyManager.getProxy().getChildren(Filter.IGNORED).size(); Assert.assertEquals(getFormattedTestTree() + consoleText, 0, notPassed - ignored); } /** * Searches for test by its name recursevly in {@link #myTestProxy} * * @param testName test name to find * @return test * @throws AssertionError if no test found */ @NotNull public AbstractTestProxy findTestByName(@NotNull final String testName) { final AbstractTestProxy test = BaseSMTRunnerTestCase.findTestByName(testName, myProxyManager.getProxy()); assert test != null : "No test found with name" + testName; return test; } /** * @return test results proxy */ @NotNull public SMRootTestProxy getTestProxy() { return myProxyManager.getProxy(); } @NotNull public final String getFormattedTestTree() { return BaseSMTRunnerTestCase.getFormattedTestTree(getTestProxy()); } /** * @return number of failed tests */ public int getFailedTestsCount() { return myProxyManager.getFailedTestsCount(); } /** * @return number of passed tests */ public int getPassedTestsCount() { return myProxyManager.getPassedTestsCount(); } public int getIgnoredTestsCount() { return myProxyManager.getIgnoredTestsCount(); } /** * @return number of all tests */ public int getAllTestsCount() { return myProxyManager.getAllTestsCount(); } @Nullable @Override protected ExecutionEnvironment getEnvironmentToRerun(@NotNull final RunContentDescriptor lastRunDescriptor) { if (myTimesToRerunFailedTests == myCurrentRerunStep) { return null; } assert getFailedTestsCount() > 0 : String.format("No failed tests on iteration %d, not sure what to rerun", myCurrentRerunStep); final Logger logger = Logger.getInstance(PyAbstractTestProcessRunner.class); logger.info(String.format("Starting iteration %s", myCurrentRerunStep)); myCurrentRerunStep++; return RerunFailedActionsTestTools.findRestartAction(lastRunDescriptor); } /** * @return number of rerun launch or 0 if first run */ public int getCurrentRerunStep() { return myCurrentRerunStep; } /** * Ensures all test locations are resolved (i.e. user may click on test and navigate to it) * All tests are checked but [root] (it never resolves). */ public final void assertAllTestsAreResolved(@NotNull final Project project) { final List allTests = getTestProxy().getAllTests(); assert !allTests.isEmpty() : "No tests at all."; final GlobalSearchScope scope = GlobalSearchScope.allScope(project); EdtTestUtil.runInEdtAndWait(() -> allTests.subList(1, allTests.size()) .forEach(t -> Assert.assertNotNull("No location " + t, t.getLocation(project, scope)))); } }