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()); + } + }; } }); }