From e55e8652639aea114e9efca477a9bd3cc20e4fbe Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Wed, 12 Apr 2017 20:48:28 +0300 Subject: [PATCH] PY-23504, PY-23303, PY-23733, PY-23647, PY-23613: Root closest to file should be used as working directory at least for pytest If you have several roots (src, content, what ever) they all passed as sys.path. But pytest does not resolve its argument against sys.path: it uses working directory. This change does 2 things: * If workdir not set --use closest root * For newly created config set closest root as dir --- .../universalTests/PyTestRunnerUtils.kt | 12 +-- .../universalTests/PyUniversalPyTest.kt | 14 +++ .../universalTests/PyUniversalTests.kt | 6 +- .../test_with_src/foo/src/test_test.py | 2 + .../python/testing/PythonPyTestingTest.java | 86 ++++++++++++++++++- 5 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 python/testData/testRunner/env/createConfigurationTest/test_with_src/foo/src/test_test.py diff --git a/python/src/com/jetbrains/python/testing/universalTests/PyTestRunnerUtils.kt b/python/src/com/jetbrains/python/testing/universalTests/PyTestRunnerUtils.kt index f4db3bb9bf71..09ec1d9755cb 100644 --- a/python/src/com/jetbrains/python/testing/universalTests/PyTestRunnerUtils.kt +++ b/python/src/com/jetbrains/python/testing/universalTests/PyTestRunnerUtils.kt @@ -95,7 +95,7 @@ internal fun findPathWithPackagesByFsItem(elementPath: PsiFileSystemItem): Virtu /** * Finds root closest to some file */ -private fun findVFSItemRoot(virtualFile: VirtualFile, project: Project): VirtualFile? { +internal fun findVFSItemRoot(virtualFile: VirtualFile, project: Project): VirtualFile? { val module = ModuleUtil.findModuleForFile(virtualFile, project) if (module == null) { Logger.getInstance(PyUniversalTestConfiguration::class.java).warn("No module for " + virtualFile) @@ -103,16 +103,18 @@ private fun findVFSItemRoot(virtualFile: VirtualFile, project: Project): Virtual } return PyUtil.getSourceRoots(module) .map { - val path = VfsUtil.getRelativePath(virtualFile, it) ?: return null - com.intellij.openapi.util.Pair(path, it) + val path = VfsUtil.getRelativePath(virtualFile, it) + if (path != null) com.intellij.openapi.util.Pair(path, it) + else { + null + } } .filterNotNull() .sortedBy { it.first.length } .map(com.intellij.openapi.util.Pair::second) - .firstOrNull() ?: return null - + .firstOrNull() } diff --git a/python/src/com/jetbrains/python/testing/universalTests/PyUniversalPyTest.kt b/python/src/com/jetbrains/python/testing/universalTests/PyUniversalPyTest.kt index f0c6e03cdef6..432484fd469d 100644 --- a/python/src/com/jetbrains/python/testing/universalTests/PyUniversalPyTest.kt +++ b/python/src/com/jetbrains/python/testing/universalTests/PyUniversalPyTest.kt @@ -21,6 +21,7 @@ import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.openapi.options.SettingsEditor import com.intellij.openapi.project.Project import com.jetbrains.python.PythonHelper +import com.jetbrains.python.psi.types.TypeEvalContext import com.jetbrains.python.testing.PythonTestConfigurationsModel import com.jetbrains.python.testing.VFSTestFrameworkListener @@ -56,6 +57,19 @@ class PyUniversalPyTestConfiguration(project: Project, factory: PyUniversalPyTes } override fun isFrameworkInstalled() = VFSTestFrameworkListener.getInstance().isPyTestInstalled(sdk) + + override fun getWorkingDirectorySafe(): String { + val dirProvidedByUser = super.getWorkingDirectory() + if (! dirProvidedByUser.isNullOrEmpty()) { + return dirProvidedByUser + } + // If dir is not set then find closest src because pytest resolves files against workdir and no sys.path + val module = module ?: return super.getWorkingDirectorySafe() + val context = TypeEvalContext.userInitiated(project, null) + val targetElement = target.asPsiElement(module, context, null) ?: return super.getWorkingDirectorySafe() + val root = findVFSItemRoot(targetElement.containingFile.virtualFile, project)?: return super.getWorkingDirectorySafe() + return root.path + } } object PyUniversalPyTestFactory : PyUniversalTestFactory() { diff --git a/python/src/com/jetbrains/python/testing/universalTests/PyUniversalTests.kt b/python/src/com/jetbrains/python/testing/universalTests/PyUniversalTests.kt index 72e4ce7f828e..d3d2102be401 100644 --- a/python/src/com/jetbrains/python/testing/universalTests/PyUniversalTests.kt +++ b/python/src/com/jetbrains/python/testing/universalTests/PyUniversalTests.kt @@ -562,7 +562,6 @@ object PyUniversalTestsConfigurationProducer : AbstractPythonTestConfigurationPr return null } if (fixPackages) { - val pathResult = findPathWithPackagesByName(element) if (pathResult == null) { Logger.getInstance(PyUniversalTestConfiguration::class.java).warn("Can't resolve") @@ -570,6 +569,11 @@ object PyUniversalTestsConfigurationProducer : AbstractPythonTestConfigurationPr } configuration.workingDirectory = pathResult.first.path qualifiedName = pathResult.second.toString() + } else { + val virtualFile = element.containingFile.virtualFile + if (virtualFile != null) { + configuration.workingDirectory = findVFSItemRoot(virtualFile, element.project)?.path + } } return ConfigurationTarget(qualifiedName, TestTargetType.PYTHON) } diff --git a/python/testData/testRunner/env/createConfigurationTest/test_with_src/foo/src/test_test.py b/python/testData/testRunner/env/createConfigurationTest/test_with_src/foo/src/test_test.py new file mode 100644 index 000000000000..3f404a730936 --- /dev/null +++ b/python/testData/testRunner/env/createConfigurationTest/test_with_src/foo/src/test_test.py @@ -0,0 +1,2 @@ +def test_test(): + pass diff --git a/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java b/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java index a7cb9f2715cd..b347ce6ad20d 100644 --- a/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java +++ b/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java @@ -3,13 +3,17 @@ package com.jetbrains.env.python.testing; import com.intellij.execution.configurations.RuntimeConfigurationWarning; import com.intellij.execution.testframework.AbstractTestProxy; import com.intellij.execution.testframework.sm.runner.ui.MockPrinter; +import com.intellij.openapi.roots.ModifiableRootModel; +import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; +import com.intellij.testFramework.fixtures.CodeInsightTestFixture; import com.intellij.util.PathUtil; import com.jetbrains.env.EnvTestTagsRequired; import com.jetbrains.env.PyEnvTestCase; import com.jetbrains.env.PyProcessWithConsoleTestTask; import com.jetbrains.env.ut.PyTestTestProcessRunner; +import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.sdkTools.SdkCreationType; import com.jetbrains.python.testing.PythonTestConfigurationsModel; import com.jetbrains.python.testing.universalTests.PyUniversalPyTestConfiguration; @@ -21,8 +25,10 @@ import org.junit.Assert; import org.junit.Test; import java.io.IOException; +import java.util.Collections; import java.util.List; +import static com.jetbrains.env.ut.PyScriptTestProcessRunner.TEST_TARGET_PREFIX; import static org.junit.Assert.assertEquals; /** @@ -56,14 +62,88 @@ public final class PythonPyTestingTest extends PyEnvTestCase { @NotNull String stderr, @NotNull String all) { Assert.assertEquals("Marker support broken", "Test tree:\n" + - "[root]\n" + - ".test_with_markers\n" + - "..test_fast(+)\n", + "[root]\n" + + ".test_with_markers\n" + + "..test_fast(+)\n", runner.getFormattedTestTree()); } }); } + + /** + * New configuration should have closest src set as its working dir + */ + @Test + public void testClosestSrcIsWorkDirOnNewConfig() throws Exception { + runPythonTest( + new CreateConfigurationTestTask(PythonTestConfigurationsModel.PY_TEST_NAME, + PyUniversalPyTestConfiguration.class) { + @NotNull + @Override + protected List getPsiElementsToRightClickOn() { + configureSrcFolder(myFixture); + + myFixture.configureByFile("test_with_src/foo/src/test_test.py"); + final PyFunction test = myFixture.findElementByText("test_test", PyFunction.class); + assert test != null; + return Collections.singletonList(test); + } + + @Override + protected void checkConfiguration(@NotNull PyUniversalPyTestConfiguration configuration, + @NotNull PsiElement elementToRightClickOn) { + super.checkConfiguration(configuration, elementToRightClickOn); + Assert + .assertThat("Wrong configuration directory set on new config", configuration.getWorkingDirectory(), Matchers.endsWith("src")); + } + }); + } + + /** + * In case when workdir is not set we should use closest src + */ + @Test + public void testClosestSrcIsWorkDirDynamically() throws Exception { + runPythonTest( + new PyProcessWithConsoleTestTask("/testRunner/env/createConfigurationTest/", SdkCreationType.EMPTY_SDK) { + @NotNull + @Override + protected PyTestTestProcessRunner createProcessRunner() throws Exception { + return new PyTestTestProcessRunner(TEST_TARGET_PREFIX + "test_test.test_test", 0) { + @Override + protected void configurationCreatedAndWillLaunch(@NotNull PyUniversalPyTestConfiguration configuration) throws IOException { + super.configurationCreatedAndWillLaunch(configuration); + // Reset dir to check it is calculated correctly + configuration.setWorkingDirectory(null); + configureSrcFolder(myFixture); + Assert + .assertThat("Wrong configuration directory calculated", configuration.getWorkingDirectorySafe(), Matchers.endsWith("src")); + } + }; + } + + + @Override + protected void checkTestResults(@NotNull final PyTestTestProcessRunner runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + Assert.assertEquals("Failed to run test" + stderr, 1, runner.getPassedTestsCount()); + } + }); + } + + //TODO: DOC + private static void configureSrcFolder(@NotNull final CodeInsightTestFixture fixture) { + final ModuleRootManager manager = ModuleRootManager.getInstance(fixture.getModule()); + final ModifiableRootModel model = manager.getModifiableModel(); + final VirtualFile srcToMark = fixture.getTempDirFixture().getFile("test_with_src/foo/src"); + assert srcToMark != null; + model.addContentEntry(srcToMark); + model.commit(); + } + @Test public void testConfigurationProducer() throws Exception { runPythonTest(