mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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.
This commit is contained in:
@@ -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()))
|
||||
|
||||
@@ -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
|
||||
|
||||
+12
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -228,7 +228,10 @@ private fun isNamespacePackage(element: PsiElement): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
private fun resolveWithRelativeLevel(name: QualifiedName, context : PyQualifiedNameResolveContext): List<PsiElement> {
|
||||
private fun resolveWithRelativeLevel(name: QualifiedName, context: PyQualifiedNameResolveContext): List<PsiElement> {
|
||||
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)
|
||||
|
||||
@@ -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<PyUniversalNoseTestConfiguration>() {
|
||||
|
||||
@@ -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<PythonConfigurationFactoryBase> = 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<Location<out PsiElement>> {
|
||||
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<out CONF_T : PyUniversalTestConfiguration> : 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
|
||||
|
||||
@@ -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
|
||||
@@ -1,5 +1,5 @@
|
||||
import unittest
|
||||
from testedCode.my_class import *
|
||||
from .testedCode.my_class import *
|
||||
|
||||
class MyClassTest(unittest.TestCase):
|
||||
def test_foo(self):
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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<T extends ProcessWithConsoleR
|
||||
private static final Logger LOG = Logger.getInstance(PyProcessWithConsoleTestTask.class);
|
||||
@NotNull
|
||||
private final SdkCreationType myRequiredSdkType;
|
||||
/**
|
||||
* @see #toFullPath(String)
|
||||
*/
|
||||
@Nullable
|
||||
private VirtualFile myLatestUsedScript;
|
||||
|
||||
/**
|
||||
* @param requiredSdkType this task creates sdk and binds it to fixture module. Provide type of SDK your test needs.
|
||||
@@ -199,4 +205,26 @@ public abstract class PyProcessWithConsoleTestTask<T extends ProcessWithConsoleR
|
||||
* @param all joined stdout and stderr
|
||||
*/
|
||||
protected abstract void checkTestResults(@NotNull T runner, @NotNull String stdout, @NotNull String stderr, @NotNull String all);
|
||||
|
||||
/**
|
||||
* Converts script or folder name to full path and stores internally to retrived with {@link #getWorkingFolderForScript()}
|
||||
*/
|
||||
@NotNull
|
||||
public String toFullPath(@NotNull final String scriptName) {
|
||||
myLatestUsedScript = myFixture.getTempDirFixture().getFile(scriptName);
|
||||
assert myLatestUsedScript != null: "File not found " + scriptName;
|
||||
return myLatestUsedScript.getPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #toFullPath(String)
|
||||
*/
|
||||
@Nullable
|
||||
public String getWorkingFolderForScript() {
|
||||
final VirtualFile script = myLatestUsedScript;
|
||||
if (script == null) {
|
||||
return null;
|
||||
}
|
||||
return (script.isDirectory() ? script : script.getParent()).getPath();
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -104,7 +104,7 @@ abstract class PyUnitTestProcessWithConsoleTestTask extends PyProcessWithConsole
|
||||
|
||||
final Location<?> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-4
@@ -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<PyNoseTestProcessRunner>("test_metheggs", "test_funeggs") {
|
||||
new PyUnitTestProcessWithConsoleTestTask.PyTestsInSubFolderRunner<PyNoseTestProcessRunner>("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<PyNoseTestProcessRunner>("test_metheggs", "test_funeggs") {
|
||||
new PyUnitTestProcessWithConsoleTestTask.PyTestsOutputRunner<PyNoseTestProcessRunner>("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 +43,17 @@ public final class PythonPyTestingTest extends PyEnvTestCase {
|
||||
@Test
|
||||
public void testTestsInSubFolderResolvable() throws Exception {
|
||||
runPythonTest(
|
||||
new PyUnitTestProcessWithConsoleTestTask.PyTestsInSubFolderRunner<PyTestTestProcessRunner>("test_metheggs", "test_funeggs") {
|
||||
new PyUnitTestProcessWithConsoleTestTask.PyTestsInSubFolderRunner<PyTestTestProcessRunner>("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<PyTestTestProcessRunner>("test_metheggs", "test_funeggs") {
|
||||
new PyUnitTestProcessWithConsoleTestTask.PyTestsOutputRunner<PyTestTestProcessRunner>("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
|
||||
|
||||
+16
-4
@@ -58,11 +58,17 @@ public final class PythonUnitTestingTest extends PyEnvTestCase {
|
||||
@Test
|
||||
public void testTestsInSubFolderResolvable() throws Exception {
|
||||
runPythonTest(
|
||||
new PyUnitTestProcessWithConsoleTestTask.PyTestsInSubFolderRunner<PyUnitTestProcessRunner>("test_metheggs") {
|
||||
new PyUnitTestProcessWithConsoleTestTask.PyTestsInSubFolderRunner<PyUnitTestProcessRunner>("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<PyUnitTestProcessRunner>("test_metheggs") {
|
||||
new PyUnitTestProcessWithConsoleTestTask.PyTestsOutputRunner<PyUnitTestProcessRunner>("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());
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user