diff --git a/python/helpers/pycharm/pytest_teamcity.py b/python/helpers/pycharm/pytest_teamcity.py index 50d2262f971f..8e305236b88b 100644 --- a/python/helpers/pycharm/pytest_teamcity.py +++ b/python/helpers/pycharm/pytest_teamcity.py @@ -9,6 +9,11 @@ from pycharm_run_utils import adjust_sys_path adjust_sys_path(False) +# Directory where test script exist +CURRENT_DIR_NAME = "" +if sys.argv: + CURRENT_DIR_NAME = os.path.dirname(sys.argv[-1]) + messages = TeamcityServiceMessages(prepend_linebreak=True) messages.testMatrixEntered() try: @@ -31,7 +36,7 @@ if PYVERSION > [1, 4, 0]: current_file_suite = None def pytest_runtest_logstart(nodeid, location): - path = "file://" + os.path.realpath(location[0]) + path = "file://" + os.path.realpath(os.path.join(CURRENT_DIR_NAME, location[0])) if location[1]: path += ":" +str(location[1] + 1) global current_suite, current_file, current_file_suite @@ -45,7 +50,7 @@ if PYVERSION > [1, 4, 0]: messages.testSuiteFinished(current_file_suite) current_file_suite = file_suite if current_file_suite: - messages.testSuiteStarted(current_file_suite, location="file://" + os.path.realpath(location[0])) + messages.testSuiteStarted(current_file_suite, location=path) if location[2].find(".") != -1: suite = location[2].split(".")[0] @@ -69,7 +74,7 @@ if PYVERSION > [1, 4, 0]: messages.testSuiteFinished(current_suite) current_suite = suite if current_suite: - messages.testSuiteStarted(current_suite, location="file://" + os.path.realpath(location[0])) + messages.testSuiteStarted(current_suite, location=path) messages.testStarted(name, location=path) items[nodeid] = name diff --git a/python/src/com/jetbrains/python/run/AbstractPythonRunConfiguration.java b/python/src/com/jetbrains/python/run/AbstractPythonRunConfiguration.java index 17c844604642..f52d6c8fad50 100644 --- a/python/src/com/jetbrains/python/run/AbstractPythonRunConfiguration.java +++ b/python/src/com/jetbrains/python/run/AbstractPythonRunConfiguration.java @@ -22,6 +22,7 @@ import com.intellij.execution.Location; import com.intellij.execution.configuration.AbstractRunConfiguration; import com.intellij.execution.configuration.EnvironmentVariablesComponent; import com.intellij.execution.configurations.*; +import com.intellij.execution.testframework.AbstractTestProxy; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.ModuleType; @@ -59,6 +60,11 @@ import java.util.Map; */ public abstract class AbstractPythonRunConfiguration extends AbstractRunConfiguration implements LocatableConfiguration, AbstractPythonRunConfigurationParams, CommandLinePatcher { + /** + * When passing path to test to runners, you should join parts with this char. + * I.e.: file.py::PyClassTest::test_method + */ + public static final String TEST_NAME_PARTS_SPLITTER = "::"; private String myInterpreterOptions = ""; private String myWorkingDirectory = ""; private String mySdkHome = ""; @@ -406,7 +412,14 @@ public abstract class AbstractPythonRunConfiguration location, @NotNull final AbstractTestProxy failedTest) { PsiElement element = location.getPsiElement(); PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class, false); PyFunction pyFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class, false); @@ -414,10 +427,10 @@ public abstract class AbstractPythonRunConfiguration failedTests = getFailedTests(myProject); for (AbstractTestProxy failedTest : failedTests) { if (failedTest.isLeaf()) { - final Location location = failedTest.getLocation(myProject, myConsoleProperties.getScope()); + final Location location = failedTest.getLocation(myProject, myConsoleProperties.getScope()); if (location != null) { - String spec = getConfiguration().getTestSpec(location); + final String spec = getConfiguration().getTestSpec(location, failedTest); if (spec != null && !specs.contains(spec)) { specs.add(spec); } diff --git a/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java b/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java index 0661d1be819c..b702b94b77d6 100644 --- a/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java +++ b/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java @@ -54,7 +54,7 @@ import java.util.Map; public abstract class PythonTestCommandLineStateBase extends PythonCommandLineState { protected final AbstractPythonRunConfiguration myConfiguration; - public AbstractPythonRunConfiguration getConfiguration() { + public AbstractPythonRunConfiguration getConfiguration() { return myConfiguration; } diff --git a/python/src/com/jetbrains/python/testing/pytest/PyTestRunConfiguration.java b/python/src/com/jetbrains/python/testing/pytest/PyTestRunConfiguration.java index beb9743f296a..d89926a5495f 100644 --- a/python/src/com/jetbrains/python/testing/pytest/PyTestRunConfiguration.java +++ b/python/src/com/jetbrains/python/testing/pytest/PyTestRunConfiguration.java @@ -15,10 +15,13 @@ */ package com.jetbrains.python.testing.pytest; +import com.google.common.collect.Lists; import com.intellij.execution.ExecutionException; import com.intellij.execution.Executor; +import com.intellij.execution.Location; import com.intellij.execution.configurations.*; import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.testframework.AbstractTestProxy; import com.intellij.openapi.components.PathMacroManager; import com.intellij.openapi.options.SettingsEditor; import com.intellij.openapi.project.Project; @@ -27,12 +30,17 @@ import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.JDOMExternalizerUtil; import com.intellij.openapi.util.WriteExternalException; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; import com.jetbrains.python.PyBundle; import com.jetbrains.python.sdk.PythonSdkType; import com.jetbrains.python.testing.AbstractPythonTestRunConfiguration; import com.jetbrains.python.testing.VFSTestFrameworkListener; import org.jdom.Element; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; /** * @author yole @@ -73,8 +81,9 @@ public class PyTestRunConfiguration extends AbstractPythonTestRunConfiguration i } public String getKeywords() { - if (useKeyword) + if (useKeyword) { return myKeywords; + } return ""; } @@ -87,8 +96,9 @@ public class PyTestRunConfiguration extends AbstractPythonTestRunConfiguration i } public String getParams() { - if (useParam) + if (useParam) { return myParams; + } return ""; } @@ -136,8 +146,9 @@ public class PyTestRunConfiguration extends AbstractPythonTestRunConfiguration i throw new RuntimeConfigurationError("Please specify target folder or script"); } Sdk sdkPath = PythonSdkType.findSdkByPath(getInterpreterPath()); - if (sdkPath != null && !VFSTestFrameworkListener.getInstance().isPyTestInstalled(sdkPath)) + if (sdkPath != null && !VFSTestFrameworkListener.getInstance().isPyTestInstalled(sdkPath)) { throw new RuntimeConfigurationWarning(PyBundle.message("runcfg.testing.no.test.framework", "py.test")); + } } @Override @@ -154,4 +165,31 @@ public class PyTestRunConfiguration extends AbstractPythonTestRunConfiguration i protected String getPluralTitle() { return myPluralTitle; } + + @Nullable + @Override + public final String getTestSpec(@NotNull final Location location, @NotNull final AbstractTestProxy failedTest) { + /** + * PyTest supports subtests (with yielding). Such tests are reported as _test_name[index] and location does not point to actual test. + * We need to get rid of braces and calculate name manually, since location is incorrect. + * Test path starts from file. + */ + final int indexOfBrace = failedTest.getName().indexOf('['); + if (indexOfBrace == -1) { + return super.getTestSpec(location, failedTest); + } + final List testNameParts = new ArrayList(); + final VirtualFile file = location.getVirtualFile(); + if (file == null) { + return null; + } + final String fileName = file.getName(); + + testNameParts.add(failedTest.getName().substring(0, indexOfBrace)); + for (AbstractTestProxy test = failedTest.getParent(); test != null && !test.getName().equals(fileName); test = test.getParent()) { + testNameParts.add(test.getName()); + } + testNameParts.add(file.getCanonicalPath()); + return StringUtil.join(Lists.reverse(testNameParts), TEST_NAME_PARTS_SPLITTER); + } } diff --git a/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java b/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java index 4005f3d5232d..52f8f394c011 100644 --- a/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java +++ b/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java @@ -49,7 +49,7 @@ public class PythonPyTestingTest extends PyEnvTestCase { @NotNull @Override protected PyTestTestProcessRunner createProcessRunner() throws Exception { - return new PyTestTestProcessRunner(getTestDataPath() + "/testRunner/env/pytest", "test2.py", 0); + return new PyTestTestProcessRunner(getTestDataPath() + "/testRunner/env/pytest", "test2.py", 1); } @Override @@ -57,6 +57,15 @@ public class PythonPyTestingTest extends PyEnvTestCase { @NotNull final String stdout, @NotNull final String stderr, @NotNull final String all) { + if (runner.getCurrentRerunStep() > 0) { + /** + * We can't rerun one subtest (yield), so we rerun whole "test_even" + */ + assertEquals(7, runner.getAllTestsCount()); + assertEquals(3, runner.getPassedTestsCount()); + assertEquals(4, runner.getFailedTestsCount()); + return; + } assertEquals(9, runner.getAllTestsCount()); assertEquals(5, runner.getPassedTestsCount()); assertEquals(4, runner.getFailedTestsCount());