From da2d1d3cb6a866343b2eb91391b6373febeefc1f Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Thu, 23 Mar 2017 01:54:38 +0300 Subject: [PATCH] PY-22556, PY-23233, PY-23217: Set working directory for tests that are not in packages When you want to launch test in folder "tests" which is not a package, you have several troubles: * Python frameworks report tests relative to this folder (i.e. test_module.foo instead of tests.test_module.foo) * You can't provide test name as tests.foo.MyTest since "tests" is not package To solve it, we set "tests" as package. But you can't simply set deepest folder as package: it may break relative imports. So, newly created configuration should use topmost folder which is not a package. Consider following layout: tests ( not a package), logic (package), test_module.py Here is: "logic.test_module" and "tests" as working directory. On Java side, we get working dir reported by python and resolve names against it. Several hacks like index lookup of function are not needed now. --- python/helpers/pycharm/_jb_runner_tools.py | 35 ++--- python/helpers/pycharm/_jb_unittest_runner.py | 1 + .../PyQualifiedNameResolveContext.java | 12 ++ .../jetbrains/extenstions/QualifiedNameExt.kt | 24 ++- .../PyQualifiedNameResolveContextImpl.kt | 5 + .../python/psi/resolve/PyResolveImportUtil.kt | 5 +- .../universalTests/PyUniversalNoseTest.kt | 4 + .../universalTests/PyUniversalTests.kt | 141 ++++++++++++------ .../env/testsInFolder/tests/test_spam.py | 14 ++ .../env/unit/dependentTests/test_my_class.py | 2 +- .../env/PyExecutionFixtureTestTask.java | 8 + .../env/PyProcessWithConsoleTestTask.java | 28 ++++ .../PyUnitTestProcessWithConsoleTestTask.java | 7 +- .../python/testing/PythonNoseTestingTest.java | 22 ++- .../python/testing/PythonPyTestingTest.java | 33 +++- .../python/testing/PythonUnitTestingTest.java | 20 ++- 16 files changed, 275 insertions(+), 86 deletions(-) diff --git a/python/helpers/pycharm/_jb_runner_tools.py b/python/helpers/pycharm/_jb_runner_tools.py index ceec9f695c7a..9f3c5cf798ee 100644 --- a/python/helpers/pycharm/_jb_runner_tools.py +++ b/python/helpers/pycharm/_jb_runner_tools.py @@ -163,7 +163,8 @@ class NewTeamcityServiceMessages(_old_service_messages): return try: - properties["locationHint"] = "python://{0}".format(properties["name"]) + # Report directory so Java site knows which folder to resolve names against + properties["locationHint"] = "python<{0}>://{1}".format(os.getcwd(), properties["name"]) except KeyError: # If message does not have name, then it is not test # Simply pass it @@ -324,27 +325,12 @@ class _SymbolName2KSplitter(_SymbolNameSplitter): def check_is_importable(self, parts, current_step, separator): import imp module_to_import = parts[current_step] - try: - (fil, self._path, desc) = imp.find_module(module_to_import, [self._path] if self._path else None) - self._symbol_processed = True - if desc[2] == imp.PKG_DIRECTORY: - # Package - self._path = imp.load_module(module_to_import, fil, self._path, desc).__path__[0] - except ImportError as error: - if not self._symbol_processed: - # First ImportError means there could be folder with out for __init__.py - # See class doc for more info - subdir = os.path.sep.join(parts[:current_step + 1]) - dirs = [path for path in map( lambda p: os.path.join(p, subdir), sys.path) if os.path.isdir(path)] - if not dirs: - raise error - elif len(dirs) == 1: - # can be folder with out of __init__.py - self._path = dirs[0] - return - else: - raise Exception("Several folders on sys.path with same name, rename folder: {0}", ",".join(dirs)) - raise error + (fil, self._path, desc) = imp.find_module(module_to_import, [self._path] if self._path else None) + self._symbol_processed = True + if desc[2] == imp.PKG_DIRECTORY: + # Package + self._path = imp.load_module(module_to_import, fil, self._path, desc).__path__[0] + class _SymbolName3KSplitter(_SymbolNameSplitter): @@ -414,6 +400,9 @@ def jb_start_tests(): _jb_utils.OptionDescription('--target', 'Python target to run', "append")) del sys.argv[1:] # Remove all args NewTeamcityServiceMessages().message('enteredTheMatrix') + + # Working dir should be on path, that is how runners work when launched from command line + sys.path.append(os.getcwd()) return namespace.path, namespace.target, additional_args @@ -429,4 +418,4 @@ def jb_doc_args(framework_name, args): Runner encouraged to report its arguments to user with aid of this function """ - print("Launching {0} with arguments {1}".format(framework_name, " ".join(args))) + print("Launching {0} with arguments {1} in {2}".format(framework_name, " ".join(args), os.getcwd())) diff --git a/python/helpers/pycharm/_jb_unittest_runner.py b/python/helpers/pycharm/_jb_unittest_runner.py index 9b532ccea36a..11c019ffe7f6 100644 --- a/python/helpers/pycharm/_jb_unittest_runner.py +++ b/python/helpers/pycharm/_jb_unittest_runner.py @@ -18,6 +18,7 @@ if __name__ == '__main__': discovery_args += [os.path.dirname(path), "-p", os.path.basename(path)] else: discovery_args.append(path) + discovery_args += ["-t", os.getcwd()] # To force unit calculate path relative to this folder additional_args = discovery_args + additional_args elif targets: additional_args += targets diff --git a/python/psi-api/src/com/jetbrains/python/psi/resolve/PyQualifiedNameResolveContext.java b/python/psi-api/src/com/jetbrains/python/psi/resolve/PyQualifiedNameResolveContext.java index bde854324fdb..e68a776c519b 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/resolve/PyQualifiedNameResolveContext.java +++ b/python/psi-api/src/com/jetbrains/python/psi/resolve/PyQualifiedNameResolveContext.java @@ -31,6 +31,12 @@ import org.jetbrains.annotations.Nullable; * @author vlan */ public interface PyQualifiedNameResolveContext { + /** + * For mode when context resolves element relative to this directory + * @see #copyWithRelative(PsiDirectory) + */ + @Nullable + PsiDirectory getRelativeDirectory(); @Nullable PsiElement getFoothold(); int getRelativeLevel(); @@ -71,4 +77,10 @@ public interface PyQualifiedNameResolveContext { PyQualifiedNameResolveContext copyWithRoots(); @NotNull PyQualifiedNameResolveContext copyWithoutStubs(); + + /** + * @see #getRelativeDirectory() + */ + @NotNull + PyQualifiedNameResolveContext copyWithRelative(@NotNull PsiDirectory directory); } \ No newline at end of file diff --git a/python/src/com/jetbrains/extenstions/QualifiedNameExt.kt b/python/src/com/jetbrains/extenstions/QualifiedNameExt.kt index 8c7e7e17f5df..35db3fe94f64 100644 --- a/python/src/com/jetbrains/extenstions/QualifiedNameExt.kt +++ b/python/src/com/jetbrains/extenstions/QualifiedNameExt.kt @@ -16,17 +16,21 @@ package com.jetbrains.extenstions import com.intellij.openapi.module.Module +import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement +import com.intellij.psi.PsiManager import com.intellij.psi.util.QualifiedName +import com.jetbrains.extensions.getSdk import com.jetbrains.python.psi.PyClass import com.jetbrains.python.psi.resolve.fromModule import com.jetbrains.python.psi.resolve.resolveQualifiedName import com.jetbrains.python.psi.types.TypeEvalContext +import com.jetbrains.python.sdk.PythonSdkType /** * Resolves qname of any symbol to appropriate PSI element. */ -fun QualifiedName.toElement(module: Module, context: TypeEvalContext): PsiElement? { +fun QualifiedName.toElement(module: Module, context: TypeEvalContext, folderToStart: VirtualFile? = null): PsiElement? { var currentName = QualifiedName.fromComponents(this.components) @@ -34,9 +38,23 @@ fun QualifiedName.toElement(module: Module, context: TypeEvalContext): PsiElemen // Drill as deep, as we can var lastElement: String? = null - while (currentName.componentCount > 0 && element == null) { - element = resolveQualifiedName(currentName, fromModule(module).copyWithMembers()).firstOrNull() + var resolveContext = fromModule(module) + if (folderToStart != null) { + val psiDirectory = PsiManager.getInstance(module.project).findDirectory(folderToStart) + if (psiDirectory != null) { + resolveContext = resolveContext.copyWithRelative(psiDirectory) + } + } + + // check for module and set if py3k + if (PythonSdkType.getLanguageLevelForSdk(module.getSdk()).isPy3K) { + resolveContext = resolveContext.copyWithPlainDirectories() + } + + + while (currentName.componentCount > 0 && element == null) { + element = resolveQualifiedName(currentName, resolveContext.copyWithMembers()).firstOrNull() if (element != null) { break } diff --git a/python/src/com/jetbrains/python/psi/resolve/PyQualifiedNameResolveContextImpl.kt b/python/src/com/jetbrains/python/psi/resolve/PyQualifiedNameResolveContextImpl.kt index e50a213cbded..eb41d46b23ee 100644 --- a/python/src/com/jetbrains/python/psi/resolve/PyQualifiedNameResolveContextImpl.kt +++ b/python/src/com/jetbrains/python/psi/resolve/PyQualifiedNameResolveContextImpl.kt @@ -27,6 +27,7 @@ import com.jetbrains.python.psi.PyUtil data class PyQualifiedNameResolveContextImpl(private val psiManager: PsiManager, private val module: Module?, private val foothold: PsiElement?, private val sdk: Sdk?, private val relativeLevel : Int = -1, + private val relativeDirectory: PsiDirectory? = null, private val withoutRoots : Boolean = false, private val withoutForeign: Boolean = false, private val withMembers : Boolean = false, @@ -48,6 +49,8 @@ data class PyQualifiedNameResolveContextImpl(private val psiManager: PsiManager, override fun getPsiManager() = psiManager + override fun getRelativeDirectory() = relativeDirectory + override fun getWithMembers() = withMembers override fun getWithPlainDirectories() = withPlainDirectories @@ -77,6 +80,8 @@ data class PyQualifiedNameResolveContextImpl(private val psiManager: PsiManager, return if (relativeLevel > 0) ResolveImportUtil.stepBackFrom(file, relativeLevel) else file.containingDirectory } + override fun copyWithRelative(directory: PsiDirectory) = copy(relativeDirectory = directory) + override fun getFootholdFile() = when (foothold) { is PsiDirectory -> foothold.findFile(PyNames.INIT_DOT_PY) else -> foothold?.containingFile?.originalFile diff --git a/python/src/com/jetbrains/python/psi/resolve/PyResolveImportUtil.kt b/python/src/com/jetbrains/python/psi/resolve/PyResolveImportUtil.kt index d8d3d61330bf..e258faa63a94 100644 --- a/python/src/com/jetbrains/python/psi/resolve/PyResolveImportUtil.kt +++ b/python/src/com/jetbrains/python/psi/resolve/PyResolveImportUtil.kt @@ -228,7 +228,10 @@ private fun isNamespacePackage(element: PsiElement): Boolean { return false } -private fun resolveWithRelativeLevel(name: QualifiedName, context : PyQualifiedNameResolveContext): List { +private fun resolveWithRelativeLevel(name: QualifiedName, context: PyQualifiedNameResolveContext): List { + if (context.relativeDirectory != null) { + return resolveModuleAt(name, context.relativeDirectory, context) + } val footholdFile = context.footholdFile if (context.relativeLevel >= 0 && footholdFile != null && !PyUserSkeletonsUtil.isUnderUserSkeletonsDirectory(footholdFile)) { return resolveModuleAt(name, context.containingDirectory, context) + relativeResultsForStubsFromRoots(name, context) diff --git a/python/src/com/jetbrains/python/testing/universalTests/PyUniversalNoseTest.kt b/python/src/com/jetbrains/python/testing/universalTests/PyUniversalNoseTest.kt index fba8e316f66b..b64ea37755dc 100644 --- a/python/src/com/jetbrains/python/testing/universalTests/PyUniversalNoseTest.kt +++ b/python/src/com/jetbrains/python/testing/universalTests/PyUniversalNoseTest.kt @@ -22,6 +22,7 @@ import com.intellij.execution.configurations.RunProfileState import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.openapi.options.SettingsEditor import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement import com.jetbrains.python.PythonHelper import com.jetbrains.python.testing.PythonTestConfigurationsModel import com.jetbrains.python.testing.VFSTestFrameworkListener @@ -58,6 +59,9 @@ class PyUniversalNoseTestConfiguration(project: Project, factory: PyUniversalNos } override fun isFrameworkInstalled() = VFSTestFrameworkListener.getInstance().isNoseTestInstalled(sdk) + + //https://github.com/nose-devs/nose/issues/1042 + override fun treatFoldersAsPackages(anchor: PsiElement) = true } object PyUniversalNoseTestFactory : PyUniversalTestFactory() { diff --git a/python/src/com/jetbrains/python/testing/universalTests/PyUniversalTests.kt b/python/src/com/jetbrains/python/testing/universalTests/PyUniversalTests.kt index 3387a8d09071..7f5549cc8dfe 100644 --- a/python/src/com/jetbrains/python/testing/universalTests/PyUniversalTests.kt +++ b/python/src/com/jetbrains/python/testing/universalTests/PyUniversalTests.kt @@ -38,10 +38,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.util.JDOMExternalizerUtil import com.intellij.openapi.util.Pair import com.intellij.openapi.util.Ref -import com.intellij.openapi.vfs.LocalFileSystem -import com.intellij.openapi.vfs.VfsUtil -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.openapi.vfs.VirtualFileSystem +import com.intellij.openapi.vfs.* import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiElement import com.intellij.psi.PsiFileSystemItem @@ -53,11 +50,8 @@ import com.intellij.refactoring.listeners.RefactoringElementListener import com.intellij.refactoring.listeners.UndoRefactoringElementAdapter import com.jetbrains.extenstions.toElement import com.jetbrains.python.PyBundle -import com.jetbrains.python.psi.PyClass -import com.jetbrains.python.psi.PyFile -import com.jetbrains.python.psi.PyFunction -import com.jetbrains.python.psi.PyQualifiedNameOwner -import com.jetbrains.python.psi.stubs.PyFunctionNameIndex +import com.jetbrains.python.PyNames +import com.jetbrains.python.psi.* import com.jetbrains.python.psi.types.TypeEvalContext import com.jetbrains.python.run.AbstractPythonRunConfiguration import com.jetbrains.python.run.CommandLinePatcher @@ -70,6 +64,7 @@ import com.jetbrains.reflection.getProperties import org.jdom.Element import java.io.File import java.util.* +import java.util.regex.Pattern import javax.swing.JComponent @@ -82,24 +77,56 @@ val factories: Array = arrayOf(PyUniversalUnitTe internal fun getAdditionalArgumentsPropertyName() = PyUniversalTestConfiguration::additionalArguments.name +/** + * Checks if configuration can run with provided qname against provided working dir. + * Fixes name and configuration if can't + */ +private fun configureRelative(name: QualifiedName, + configuration: PyUniversalTestConfiguration): QualifiedName { + val module = configuration.module?: return name + val fileSystem = LocalFileSystem.getInstance() + val context = TypeEvalContext.userInitiated(module.project, null) + + // Working dir points to project root for newly created configuration + var path = fileSystem.findFileByPath(configuration.workingDirectorySafe)?:return name + + // If some element can't be resolved that means one of its parts is not package. + // foo.spam.bar does not work if "foo" is not package + // convert it to "spam.bar" and set "foo" as working dir instead + + var currentName = name + while (currentName.toElement(module, context, path) !is PyElement) { + val head = currentName.firstComponent ?: return name + currentName = currentName.removeHead(1) + path = path.findFileByRelativePath(head) ?: return name + } + configuration.workingDirectory = path.canonicalPath + return currentName +} /** - * For cases like "module.class.test_name.subtest_name" situated somewhere deep in folder which is not package, - * this function tries to resolve test_name using index. + * Checks if configuration can run with provided path against provided working dir. + * Fixes configuration workdir if it can't */ -private fun findFunctionByPartialName(qualifiedName: QualifiedName, project: Project): PyFunction? { - // TODO: Add to background if too slow - val components = ArrayList(qualifiedName.components) - components.reverse() - components.forEach { - for (function in PyFunctionNameIndex.find(it, project)) { - val name = function.qualifiedName - if (name != null && name.contains(qualifiedName.toString())) { - return function - } - } +private fun configureRelative(path: VirtualFile, configuration: PyUniversalTestConfiguration) { + val workDir = LocalFileSystem.getInstance().findFileByPath(configuration.workingDirectorySafe)?:return + if (! VfsUtil.isAncestor(workDir, path, false)) { + return } - return null + + var currentPath = if(path.isDirectory) { + path + } else { + path.parent + } + + //Ensure path is __init__.py based for all between path and configuration + // no init py -- not a package + + while (currentPath.findFileByRelativePath(PyNames.INIT_DOT_PY) != null && currentPath != workDir) { + currentPath = currentPath.parent + } + configuration.workingDirectory = currentPath.path } /** @@ -131,19 +158,27 @@ private fun findConfigurationFactoryFromSettings(module: Module): ConfigurationF } +// folder provided by python side. Resolve test names versus it +private val PATH_URL = Pattern.compile("^python<([^<>]+)>$") + private object PyUniversalTestsLocator : SMTestLocator { override fun getLocation(protocol: String, path: String, project: Project, scope: GlobalSearchScope): List> { if (scope !is ModuleWithDependenciesScope) { return listOf() } + val matcher = PATH_URL.matcher(protocol) + + val folder = if (matcher.matches()) { + LocalFileSystem.getInstance().findFileByPath(matcher.group(1)) + } + else { + null + } + val qualifiedName = QualifiedName.fromDottedString(path) // Assume qname id good and resolve it directly - var element = qualifiedName.toElement(scope.module, - TypeEvalContext.codeAnalysis(project, null)) - if (element == null) { - // If no luck then resolve it using heuristic - element = findFunctionByPartialName(qualifiedName, project) - } + val element = qualifiedName.toElement(scope.module, + TypeEvalContext.codeAnalysis(project, null), folder) if (element != null) { // Path is qualified name of python test according to runners protocol // Parentheses are part of generators / parametrized tests @@ -218,9 +253,9 @@ data class ConfigurationTarget(@ConfigField var target: String, @ConfigField var /** * Converts target to PSI element if possible */ - fun asPsiElement(module: Module, context: TypeEvalContext): PsiElement? { + fun asPsiElement(module: Module, context: TypeEvalContext, folderToStart: VirtualFile? = null): PsiElement? { if (targetType == TestTargetType.PYTHON) { - return QualifiedName.fromDottedString(target).toElement(module, context) + return QualifiedName.fromDottedString(target).toElement(module, context, folderToStart) } return null } @@ -307,13 +342,6 @@ abstract class PyUniversalTestConfiguration(project: Project, override fun getRefactoringElementListener(element: PsiElement?): RefactoringElementListener? { val myModule = module val targetElement: PsiElement? - if (myModule != null) { - targetElement = target.asPsiElement(myModule, TypeEvalContext.userInitiated(project, null)) - } - else { - targetElement = null - } - val targetFile = target.asVirtualFile(LocalFileSystem.getInstance()) val workingDirectoryFile = if (workingDirectory.isNotEmpty()) { LocalFileSystem.getInstance().findFileByPath(workingDirectory) @@ -322,6 +350,16 @@ abstract class PyUniversalTestConfiguration(project: Project, null } + if (myModule != null) { + targetElement = target.asPsiElement(myModule, TypeEvalContext.userInitiated(project, null), workingDirectoryFile) + } + else { + targetElement = null + } + val targetFile = target.asVirtualFile(LocalFileSystem.getInstance()) + + + if (targetElement != null && PsiTreeUtil.isAncestor(element, targetElement, false)) { return PyElementTargetRenamer(targetElement, workingDirectoryFile) } @@ -487,11 +525,16 @@ abstract class PyUniversalTestConfiguration(project: Project, is PyClass -> PythonUnitTestUtil.isTestCaseClass(element, TypeEvalContext.userInitiated(element.project, element.containingFile)) else -> false } + + /** + * When checking if configuration is ok we need to know if folders could be packages: i.e. if foo.bar requires init.py in foo to work + */ + open fun treatFoldersAsPackages(anchor: PsiElement) = (!LanguageLevel.forElement(anchor).isPy3K) } private fun isTestFile(file: PyFile): Boolean { return PythonUnitTestUtil.isUnitTestFile(file) || - PythonUnitTestUtil.getTestCaseClassesFromFile(file, TypeEvalContext.userInitiated(file.project, file)).isNotEmpty() + PythonUnitTestUtil.getTestCaseClassesFromFile(file, TypeEvalContext.userInitiated(file.project, file)).isNotEmpty() } abstract class PyUniversalTestFactory : PythonConfigurationFactoryBase( @@ -537,7 +580,7 @@ object PyUniversalTestsConfigurationProducer : AbstractPythonTestConfigurationPr location.target.copyTo(configuration.target) } else { - val targetForConfig = getTargetForConfig(configuration, sourceElement.get()) ?: return false + val targetForConfig = getTargetForConfig(configuration, sourceElement.get(), true) ?: return false targetForConfig.copyTo(configuration.target) } configuration.setGeneratedName() @@ -550,7 +593,10 @@ object PyUniversalTestsConfigurationProducer : AbstractPythonTestConfigurationPr * @return configuration name and its target */ private fun getTargetForConfig(configuration: PyUniversalTestConfiguration, - baseElement: PsiElement): ConfigurationTarget? { + baseElement: PsiElement, fixConfiguration: Boolean = false): ConfigurationTarget? { + + val setRelative = (fixConfiguration && configuration.treatFoldersAsPackages(baseElement)) + var element = baseElement // Go up until we reach top of the file // asking configuration about each element if it is supported or not @@ -559,14 +605,23 @@ object PyUniversalTestsConfigurationProducer : AbstractPythonTestConfigurationPr if (configuration.couldBeTestTarget(element)) { when (element) { is PyQualifiedNameOwner -> { // Function, class, method - val qualifiedName = element.qualifiedName + var qualifiedName = element.qualifiedName if (qualifiedName == null) { Logger.getInstance(PyUniversalTestConfiguration::class.java).warn("$element has no qualified name") return null } + if (setRelative) { + qualifiedName = configureRelative(QualifiedName.fromDottedString(qualifiedName), configuration).toString() + } return ConfigurationTarget(qualifiedName, TestTargetType.PYTHON) } - is PsiFileSystemItem -> return ConfigurationTarget(element.virtualFile.path, TestTargetType.PATH) + is PsiFileSystemItem -> { + val path = element.virtualFile + if (setRelative) { + configureRelative(path, configuration).toString() + } + return ConfigurationTarget(path.path, TestTargetType.PATH) + } } } element = element.parent diff --git a/python/testData/testRunner/env/testsInFolder/tests/test_spam.py b/python/testData/testRunner/env/testsInFolder/tests/test_spam.py index a2e9ab2c06a6..af7d39a30ab4 100644 --- a/python/testData/testRunner/env/testsInFolder/tests/test_spam.py +++ b/python/testData/testRunner/env/testsInFolder/tests/test_spam.py @@ -8,3 +8,17 @@ def test_funeggs(): class EggsTest(TestCase): def test_metheggs(self): print("I am method") + + + +class Parent: + + def test_first(self): + assert True + + def test_second(self): + assert False + + +class Child(TestCase, Parent): + pass \ No newline at end of file diff --git a/python/testData/testRunner/env/unit/dependentTests/test_my_class.py b/python/testData/testRunner/env/unit/dependentTests/test_my_class.py index fa388125795f..4ab6cc78a868 100644 --- a/python/testData/testRunner/env/unit/dependentTests/test_my_class.py +++ b/python/testData/testRunner/env/unit/dependentTests/test_my_class.py @@ -1,5 +1,5 @@ import unittest -from testedCode.my_class import * +from .testedCode.my_class import * class MyClassTest(unittest.TestCase): def test_foo(self): diff --git a/python/testSrc/com/jetbrains/env/PyExecutionFixtureTestTask.java b/python/testSrc/com/jetbrains/env/PyExecutionFixtureTestTask.java index 2ea9c698d957..c98ebe9dcd73 100644 --- a/python/testSrc/com/jetbrains/env/PyExecutionFixtureTestTask.java +++ b/python/testSrc/com/jetbrains/env/PyExecutionFixtureTestTask.java @@ -17,9 +17,12 @@ import com.intellij.testFramework.builders.ModuleFixtureBuilder; import com.intellij.testFramework.fixtures.*; import com.intellij.testFramework.fixtures.impl.ModuleFixtureBuilderImpl; import com.intellij.testFramework.fixtures.impl.ModuleFixtureImpl; +import com.jetbrains.extensions.ModuleExtKt; import com.jetbrains.python.PythonModuleTypeBase; import com.jetbrains.python.PythonTestUtil; +import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.sdk.InvalidSdkException; +import com.jetbrains.python.sdk.PythonSdkType; import com.jetbrains.python.sdkTools.PyTestSdkTools; import com.jetbrains.python.sdkTools.SdkCreationType; import org.jetbrains.annotations.NotNull; @@ -136,6 +139,11 @@ public abstract class PyExecutionFixtureTestTask extends PyTestTask { } } + @NotNull + public LanguageLevel getLevelForSdk() { + return PythonSdkType.getLanguageLevelForSdk(ModuleExtKt.getSdk(myFixture.getModule())); + } + /** * @return additional content roots */ diff --git a/python/testSrc/com/jetbrains/env/PyProcessWithConsoleTestTask.java b/python/testSrc/com/jetbrains/env/PyProcessWithConsoleTestTask.java index db4552b7e779..3e9484796149 100644 --- a/python/testSrc/com/jetbrains/env/PyProcessWithConsoleTestTask.java +++ b/python/testSrc/com/jetbrains/env/PyProcessWithConsoleTestTask.java @@ -22,6 +22,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.xdebugger.XDebuggerTestUtil; import com.jetbrains.python.sdkTools.SdkCreationType; import org.jetbrains.annotations.NotNull; @@ -58,6 +59,11 @@ public abstract class PyProcessWithConsoleTestTask methodLocation = method.getLocation(getProject(), GlobalSearchScope.moduleScope(myFixture.getModule())); - Assert.assertNotNull("Failed to resolve method location", methodLocation); + Assert.assertNotNull("Failed to resolve method location " + method, methodLocation); final PsiElement methodPsiElement = methodLocation.getPsiElement(); Assert.assertNotNull("Failed to get PSI for method location", methodPsiElement); Assert.assertThat("Wrong test returned", methodPsiElement, Matchers.instanceOf(PyFunction.class)); @@ -131,8 +131,11 @@ abstract class PyUnitTestProcessWithConsoleTestTask extends PyProcessWithConsole Assert.assertThat("Function output is broken", MockPrinter.fillPrinter(method).getStdOut().trim(), Matchers.containsString("I am function")); } + else if (functionName.endsWith("test_first") || functionName.endsWith("test_second")) { + // No output expected + } else { - throw new AssertionError("Unknown function" + functionName); + throw new AssertionError("Unknown function " + functionName); } } } diff --git a/python/testSrc/com/jetbrains/env/python/testing/PythonNoseTestingTest.java b/python/testSrc/com/jetbrains/env/python/testing/PythonNoseTestingTest.java index 947e7306eb0a..3e90a697828b 100644 --- a/python/testSrc/com/jetbrains/env/python/testing/PythonNoseTestingTest.java +++ b/python/testSrc/com/jetbrains/env/python/testing/PythonNoseTestingTest.java @@ -13,6 +13,8 @@ import com.jetbrains.python.testing.universalTests.PyUniversalNoseTestFactory; import org.jetbrains.annotations.NotNull; import org.junit.Test; +import java.io.IOException; + import static org.junit.Assert.assertEquals; /** @@ -28,11 +30,17 @@ public final class PythonNoseTestingTest extends PyEnvTestCase { @Test public void testTestsInSubFolderResolvable() throws Exception { runPythonTest( - new PyUnitTestProcessWithConsoleTestTask.PyTestsInSubFolderRunner("test_metheggs", "test_funeggs") { + new PyUnitTestProcessWithConsoleTestTask.PyTestsInSubFolderRunner("test_metheggs", "test_funeggs", "test_first") { @NotNull @Override protected PyNoseTestProcessRunner createProcessRunner() throws Exception { - return new PyNoseTestProcessRunner("tests", 0); + return new PyNoseTestProcessRunner(toFullPath("tests"), 0) { + @Override + protected void configurationCreatedAndWillLaunch(@NotNull PyUniversalNoseTestConfiguration configuration) throws IOException { + super.configurationCreatedAndWillLaunch(configuration); + configuration.setWorkingDirectory(getWorkingFolderForScript()); + } + }; } }); } @@ -43,11 +51,17 @@ public final class PythonNoseTestingTest extends PyEnvTestCase { @Test public void testOutput() throws Exception { runPythonTest( - new PyUnitTestProcessWithConsoleTestTask.PyTestsOutputRunner("test_metheggs", "test_funeggs") { + new PyUnitTestProcessWithConsoleTestTask.PyTestsOutputRunner("test_metheggs", "test_funeggs", "test_first") { @NotNull @Override protected PyNoseTestProcessRunner createProcessRunner() throws Exception { - return new PyNoseTestProcessRunner("tests", 0); + return new PyNoseTestProcessRunner(toFullPath("tests"), 0) { + @Override + protected void configurationCreatedAndWillLaunch(@NotNull PyUniversalNoseTestConfiguration configuration) throws IOException { + super.configurationCreatedAndWillLaunch(configuration); + configuration.setWorkingDirectory(getWorkingFolderForScript()); + } + }; } }); } diff --git a/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java b/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java index 3bce18627584..cd54c18eecd5 100644 --- a/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java +++ b/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java @@ -43,11 +43,17 @@ public final class PythonPyTestingTest extends PyEnvTestCase { @Test public void testTestsInSubFolderResolvable() throws Exception { runPythonTest( - new PyUnitTestProcessWithConsoleTestTask.PyTestsInSubFolderRunner("test_metheggs", "test_funeggs") { + new PyUnitTestProcessWithConsoleTestTask.PyTestsInSubFolderRunner("test_metheggs", "test_funeggs", "test_first") { @NotNull @Override protected PyTestTestProcessRunner createProcessRunner() throws Exception { - return new PyTestTestProcessRunner("tests", 0); + return new PyTestTestProcessRunner(toFullPath("tests"), 0) { + @Override + protected void configurationCreatedAndWillLaunch(@NotNull PyUniversalPyTestConfiguration configuration) throws IOException { + super.configurationCreatedAndWillLaunch(configuration); + configuration.setWorkingDirectory(getWorkingFolderForScript()); + } + }; } }); } @@ -58,11 +64,17 @@ public final class PythonPyTestingTest extends PyEnvTestCase { @Test public void testOutput() throws Exception { runPythonTest( - new PyUnitTestProcessWithConsoleTestTask.PyTestsOutputRunner("test_metheggs", "test_funeggs") { + new PyUnitTestProcessWithConsoleTestTask.PyTestsOutputRunner("test_metheggs", "test_funeggs", "test_first") { @NotNull @Override protected PyTestTestProcessRunner createProcessRunner() throws Exception { - return new PyTestTestProcessRunner("tests", 0); + return new PyTestTestProcessRunner(toFullPath("tests"), 0) { + @Override + protected void configurationCreatedAndWillLaunch(@NotNull PyUniversalPyTestConfiguration configuration) throws IOException { + super.configurationCreatedAndWillLaunch(configuration); + configuration.setWorkingDirectory(getWorkingFolderForScript()); + } + }; } }); } @@ -225,7 +237,18 @@ public final class PythonPyTestingTest extends PyEnvTestCase { @NotNull @Override protected PyTestTestProcessRunner createProcessRunner() throws Exception { - return new PyTestTestProcessRunner("folder_no_init_py/test_test.py", 2); + if (getLevelForSdk().isPy3K()) { + return new PyTestTestProcessRunner("folder_no_init_py/test_test.py", 2); + } + else { + return new PyTestTestProcessRunner(toFullPath("folder_no_init_py/test_test.py"), 2) { + @Override + protected void configurationCreatedAndWillLaunch(@NotNull PyUniversalPyTestConfiguration configuration) throws IOException { + super.configurationCreatedAndWillLaunch(configuration); + configuration.setWorkingDirectory(getWorkingFolderForScript()); + } + }; + } } @Override diff --git a/python/testSrc/com/jetbrains/env/python/testing/PythonUnitTestingTest.java b/python/testSrc/com/jetbrains/env/python/testing/PythonUnitTestingTest.java index 4ce035e46e32..86d490bd4b87 100644 --- a/python/testSrc/com/jetbrains/env/python/testing/PythonUnitTestingTest.java +++ b/python/testSrc/com/jetbrains/env/python/testing/PythonUnitTestingTest.java @@ -58,11 +58,17 @@ public final class PythonUnitTestingTest extends PyEnvTestCase { @Test public void testTestsInSubFolderResolvable() throws Exception { runPythonTest( - new PyUnitTestProcessWithConsoleTestTask.PyTestsInSubFolderRunner("test_metheggs") { + new PyUnitTestProcessWithConsoleTestTask.PyTestsInSubFolderRunner("test_metheggs", "test_first") { @NotNull @Override protected PyUnitTestProcessRunner createProcessRunner() throws Exception { - return new PyUnitTestProcessRunner("tests", 0); + return new PyUnitTestProcessRunner(toFullPath("tests"), 0) { + @Override + protected void configurationCreatedAndWillLaunch(@NotNull PyUniversalUnitTestConfiguration configuration) throws IOException { + super.configurationCreatedAndWillLaunch(configuration); + configuration.setWorkingDirectory(getWorkingFolderForScript()); + } + }; } }); } @@ -73,11 +79,17 @@ public final class PythonUnitTestingTest extends PyEnvTestCase { @Test public void testOutput() throws Exception { runPythonTest( - new PyUnitTestProcessWithConsoleTestTask.PyTestsOutputRunner("test_metheggs") { + new PyUnitTestProcessWithConsoleTestTask.PyTestsOutputRunner("test_metheggs", "test_first") { @NotNull @Override protected PyUnitTestProcessRunner createProcessRunner() throws Exception { - return new PyUnitTestProcessRunner("tests", 0); + return new PyUnitTestProcessRunner(toFullPath("tests"), 0) { + @Override + protected void configurationCreatedAndWillLaunch(@NotNull PyUniversalUnitTestConfiguration configuration) throws IOException { + super.configurationCreatedAndWillLaunch(configuration); + configuration.setWorkingDirectory(getWorkingFolderForScript()); + } + }; } }); }