From a239ed439615eadd7d7503640c9be8ce4e0e60d5 Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Tue, 17 Dec 2013 17:51:10 +0400 Subject: [PATCH] fixed PY-11617 Nosetest runner: simple function tests cannot jump to source --- python/helpers/pycharm/nose_utils.py | 6 +- python/src/META-INF/python-core.xml | 1 + .../PythonUnitTestTestIdUrlProvider.java | 51 +------------ .../python/testing/PythonUnitTestUtil.java | 73 ++++++++++++++++++- .../nosetest/PythonNoseTestUrlProvider.java | 72 ++++++++++++++++++ 5 files changed, 151 insertions(+), 52 deletions(-) create mode 100644 python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestUrlProvider.java diff --git a/python/helpers/pycharm/nose_utils.py b/python/helpers/pycharm/nose_utils.py index 62c77a2e1e4f..bed318a50bf0 100644 --- a/python/helpers/pycharm/nose_utils.py +++ b/python/helpers/pycharm/nose_utils.py @@ -107,7 +107,7 @@ class TeamcityPlugin(ErrorClassPlugin, TextTestResult, TeamcityTestResult): location = location + ":" + str(test.test.lineno) else: suite = strclass(test.__class__) - suite_location = "python_uttestid://" + suite + suite_location = "python_nosetestid://" + suite try: from nose_helper.util import func_lineno @@ -124,8 +124,8 @@ class TeamcityPlugin(ErrorClassPlugin, TextTestResult, TeamcityTestResult): except: test_id = test.id() suite_id = test_id[:test_id.rfind(".")] - suite_location = "python_uttestid://" + str(suite_id) - location = "python_uttestid://" + str(test_id) + suite_location = "python_nosetestid://" + str(suite_id) + location = "python_nosetestid://" + str(test_id) return (location, suite_location) diff --git a/python/src/META-INF/python-core.xml b/python/src/META-INF/python-core.xml index cf37b6961b17..f11f009b3a5c 100644 --- a/python/src/META-INF/python-core.xml +++ b/python/src/META-INF/python-core.xml @@ -114,6 +114,7 @@ + diff --git a/python/src/com/jetbrains/python/testing/PythonUnitTestTestIdUrlProvider.java b/python/src/com/jetbrains/python/testing/PythonUnitTestTestIdUrlProvider.java index d0dd2c21f6f1..a53828a9eaf1 100644 --- a/python/src/com/jetbrains/python/testing/PythonUnitTestTestIdUrlProvider.java +++ b/python/src/com/jetbrains/python/testing/PythonUnitTestTestIdUrlProvider.java @@ -16,22 +16,12 @@ package com.jetbrains.python.testing; import com.intellij.execution.Location; -import com.intellij.execution.PsiLocation; -import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiFile; import com.intellij.testIntegration.TestLocationProvider; -import com.jetbrains.python.psi.PyClass; -import com.jetbrains.python.psi.PyFunction; -import com.jetbrains.python.psi.stubs.PyClassNameIndex; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -55,55 +45,20 @@ public class PythonUnitTestTestIdUrlProvider implements TestLocationProvider { // parse path as [ns.]*fileName.className[.methodName] if (listSize == 2) { - return findLocations(project, list.get(0), list.get(1), null); + return PythonUnitTestUtil.findLocations(project, list.get(0), list.get(1), null); } if (listSize > 2) { final String className = list.get(listSize - 2); final String methodName = list.get(listSize - 1); String fileName = list.get(listSize - 3); - final List locations = findLocations(project, fileName, className, methodName); + final List locations = PythonUnitTestUtil.findLocations(project, fileName, className, methodName); if (locations.size() > 0) { return locations; } - return findLocations(project, list.get(listSize-2), list.get(listSize-1), null); + return PythonUnitTestUtil.findLocations(project, list.get(listSize-2), list.get(listSize-1), null); } return Collections.emptyList(); } - - private static List findLocations(Project project, - String fileName, - String className, - @Nullable String methodName) { - if (fileName.indexOf("%") >= 0) { - fileName = fileName.substring(0, fileName.lastIndexOf("%")); - } - - final List locations = new ArrayList(); - for (PyClass cls : PyClassNameIndex.find(className, project, false)) { - ProgressManager.checkCanceled(); - - final PsiFile containingFile = cls.getContainingFile(); - final VirtualFile virtualFile = containingFile.getVirtualFile(); - final String clsFileName = virtualFile == null? containingFile.getName() : virtualFile.getPath(); - final String clsFileNameWithoutExt = FileUtil.getNameWithoutExtension(clsFileName); - if (!clsFileNameWithoutExt.endsWith(fileName)) { - continue; - } - if (methodName == null) { - locations.add(new PsiLocation(project, cls)); - } - else { - final PyFunction method = cls.findMethodByName(methodName, true); - if (method == null) { - continue; - } - - locations.add(new PsiLocation(project, method)); - } - } - - return locations; - } } diff --git a/python/src/com/jetbrains/python/testing/PythonUnitTestUtil.java b/python/src/com/jetbrains/python/testing/PythonUnitTestUtil.java index 93941439e7d7..08308973c1f1 100644 --- a/python/src/com/jetbrains/python/testing/PythonUnitTestUtil.java +++ b/python/src/com/jetbrains/python/testing/PythonUnitTestUtil.java @@ -17,14 +17,25 @@ package com.jetbrains.python.testing; import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import com.intellij.execution.Location; +import com.intellij.execution.PsiLocation; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; import com.intellij.util.containers.Stack; import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.stubs.PyClassNameIndex; +import com.jetbrains.python.psi.stubs.PyFunctionNameIndex; import com.jetbrains.python.psi.types.PyClassLikeType; import com.jetbrains.python.psi.types.TypeEvalContext; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.regex.Pattern; @@ -148,7 +159,9 @@ public class PythonUnitTestUtil { return true; } String clsName = cls.getQualifiedName(); - String[] names = clsName.split("\\."); + String[] names = new String[0]; + if (clsName != null) + names = clsName.split("\\."); clsName = names[names.length - 1]; if (TEST_MATCH_PATTERN.matcher(clsName).find()) { return true; @@ -157,4 +170,62 @@ public class PythonUnitTestUtil { } return false; } + + public static List findLocations(@NotNull final Project project, + @NotNull String fileName, + @Nullable String className, + @Nullable String methodName) { + if (fileName.contains("%")) { + fileName = fileName.substring(0, fileName.lastIndexOf("%")); + } + final List locations = new ArrayList(); + if (methodName == null && className == null) { + final VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(fileName); + if (virtualFile == null) return locations; + final PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile); + if (psiFile != null) + locations.add(new PsiLocation(project, psiFile)); + } + + if (className != null) { + for (PyClass cls : PyClassNameIndex.find(className, project, false)) { + ProgressManager.checkCanceled(); + + final PsiFile containingFile = cls.getContainingFile(); + final VirtualFile virtualFile = containingFile.getVirtualFile(); + final String clsFileName = virtualFile == null? containingFile.getName() : virtualFile.getPath(); + final String clsFileNameWithoutExt = FileUtil.getNameWithoutExtension(clsFileName); + if (!clsFileNameWithoutExt.endsWith(fileName)) { + continue; + } + if (methodName == null) { + locations.add(new PsiLocation(project, cls)); + } + else { + final PyFunction method = cls.findMethodByName(methodName, true); + if (method == null) { + continue; + } + + locations.add(new PsiLocation(project, method)); + } + } + } + else if (methodName != null) { + for (PyFunction function : PyFunctionNameIndex.find(methodName, project)) { + ProgressManager.checkCanceled(); + if (function.getContainingClass() == null) { + final PsiFile containingFile = function.getContainingFile(); + final VirtualFile virtualFile = containingFile.getVirtualFile(); + final String clsFileName = virtualFile == null? containingFile.getName() : virtualFile.getPath(); + final String clsFileNameWithoutExt = FileUtil.getNameWithoutExtension(clsFileName); + if (!clsFileNameWithoutExt.endsWith(fileName)) { + continue; + } + locations.add(new PsiLocation(project, function)); + } + } + } + return locations; + } } diff --git a/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestUrlProvider.java b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestUrlProvider.java new file mode 100644 index 000000000000..684a09bad563 --- /dev/null +++ b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestUrlProvider.java @@ -0,0 +1,72 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.testing.nosetest; + +import com.intellij.execution.Location; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.testIntegration.TestLocationProvider; +import com.jetbrains.python.testing.PythonUnitTestUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; + +public class PythonNoseTestUrlProvider implements TestLocationProvider { + @NonNls + private static final String PROTOCOL_ID = "python_nosetestid"; + + @NotNull + public List getLocation(@NotNull final String protocolId, @NotNull final String path, + final Project project) { + if (!PROTOCOL_ID.equals(protocolId)) { + return Collections.emptyList(); + } + + final List list = StringUtil.split(path, "."); + if (list.isEmpty()) { + return Collections.emptyList(); + } + final int listSize = list.size(); + + // parse path as [ns.]*fileName.className[.methodName] + if (listSize == 2) { + final List classes = PythonUnitTestUtil.findLocations(project, list.get(0), list.get(1), null); + if (classes.size() > 0) + return classes; + } + + if (listSize > 2) { + final String className = list.get(listSize - 2); + final String methodName = list.get(listSize - 1); + + String fileName = list.get(listSize - 3); + List locations = PythonUnitTestUtil.findLocations(project, fileName, className, methodName); + if (locations.size() > 0) { + return locations; + } + locations = PythonUnitTestUtil.findLocations(project, list.get(listSize - 2), list.get(listSize - 1), null); + if (locations.size() > 0) { + return locations; + } + final List functions = PythonUnitTestUtil.findLocations(project, list.get(listSize - 2), null, list.get(listSize - 1)); + if (functions.size() > 0) + return functions; + } + return PythonUnitTestUtil.findLocations(project, project.getBasePath() + "/" + StringUtil.join(list, "/") + ".py", null, null); + } +}