PY-17378: Rerun fix for PyTests:

* Full path is calculated on helper side
 *  Calculating test path for subtests (yielding) on java side
This commit is contained in:
Ilya.Kazakevich
2015-10-30 16:11:18 +03:00
parent fb98bfe891
commit 9dc206fe98
6 changed files with 79 additions and 14 deletions
+8 -3
View File
@@ -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
@@ -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<T extends AbstractRunConfiguration> 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<T extends AbstractRunConfig
return true;
}
public String getTestSpec(Location location) {
/**
* Create test spec (string to be passed to runner, probably glued with {@link #TEST_NAME_PARTS_SPLITTER})
* @param location test location as reported by runner
* @param failedTest failed test
* @return string spec or null if spec calculation is impossible
*/
@Nullable
public String getTestSpec(@NotNull final Location<?> 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<T extends AbstractRunConfig
if (virtualFile != null) {
String path = virtualFile.getCanonicalPath();
if (pyClass != null) {
path += "::" + pyClass.getName();
path += TEST_NAME_PARTS_SPLITTER + pyClass.getName();
}
if (pyFunction != null) {
path += "::" + pyFunction.getName();
path += TEST_NAME_PARTS_SPLITTER + pyFunction.getName();
}
return path;
}
@@ -440,7 +453,7 @@ public abstract class AbstractPythonRunConfiguration<T extends AbstractRunConfig
@Override
public String getModuleName() {
Module module = getModule();
return module != null? module.getName() : null;
return module != null ? module.getName() : null;
}
@Override
@@ -110,9 +110,9 @@ public class PyRerunFailedTestsAction extends AbstractRerunFailedTestsAction {
List<AbstractTestProxy> 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);
}
@@ -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;
}
@@ -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<String> testNameParts = new ArrayList<String>();
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);
}
}
@@ -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());