From 4fd98c2661acb708b1fd4d0e7c32be8559ceb275 Mon Sep 17 00:00:00 2001 From: Sheng Chen Date: Fri, 7 Aug 2020 13:22:23 +0800 Subject: [PATCH] Improve the perf when resolving code lens in large java file (#1042) --- .vscode/launch.json | 5 +- .../META-INF/MANIFEST.MF | 2 +- .../searcher/BaseFrameworkSearcher.java | 22 ++++ .../plugin/searcher/JUnit4TestSearcher.java | 22 ++++ .../plugin/searcher/JUnit5TestSearcher.java | 103 ++++++++++++++++++ .../searcher/TestFrameworkSearcher.java | 12 ++ .../plugin/searcher/TestNGTestSearcher.java | 20 ++++ .../test/plugin/util/TestFrameworkUtils.java | 78 ++++++++++++- .../test/plugin/util/TestSearchUtils.java | 83 +++++--------- .../target.target | 2 +- java-extension/pom.xml | 4 +- package-lock.json | 44 ++++---- test/gradle-junit5-suite/codelens.test.ts | 9 ++ test/shared.ts | 1 + .../junit5/src/test/java/junit5/FastTest.java | 16 +++ .../test/java/junit5/MetaAnnotationTest.java | 7 ++ 16 files changed, 345 insertions(+), 85 deletions(-) create mode 100644 test/test-projects/junit5/src/test/java/junit5/FastTest.java create mode 100644 test/test-projects/junit5/src/test/java/junit5/MetaAnnotationTest.java diff --git a/.vscode/launch.json b/.vscode/launch.json index a621079..bb7773e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,10 @@ "stopOnEntry": false, "sourceMaps": true, "outFiles": [ "${workspaceRoot}/dist/**/*.js" ], - "preLaunchTask": "npm: watch" + "preLaunchTask": "npm: watch", + "env": { + "DEBUG_VSCODE_JAVA":"true" + } }, { "type": "java", diff --git a/java-extension/com.microsoft.java.test.plugin/META-INF/MANIFEST.MF b/java-extension/com.microsoft.java.test.plugin/META-INF/MANIFEST.MF index b734714..e3d937c 100644 --- a/java-extension/com.microsoft.java.test.plugin/META-INF/MANIFEST.MF +++ b/java-extension/com.microsoft.java.test.plugin/META-INF/MANIFEST.MF @@ -4,7 +4,7 @@ Bundle-Name: com.microsoft.java.test.plugin Bundle-SymbolicName: com.microsoft.java.test.plugin;singleton:=true Bundle-Version: 0.24.0 Bundle-Activator: com.microsoft.java.test.plugin.util.JUnitPlugin -Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Bundle-RequiredExecutionEnvironment: JavaSE-11 Import-Package: org.eclipse.jdt.core, org.eclipse.jdt.launching, org.osgi.framework;version="1.3.0" diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/BaseFrameworkSearcher.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/BaseFrameworkSearcher.java index 2c671cf..e28ae83 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/BaseFrameworkSearcher.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/BaseFrameworkSearcher.java @@ -20,6 +20,9 @@ import com.microsoft.java.test.plugin.util.TestItemUtils; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.dom.IAnnotationBinding; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.search.IJavaSearchConstants; import org.eclipse.jdt.core.search.SearchPattern; @@ -74,6 +77,19 @@ public abstract class BaseFrameworkSearcher implements TestFrameworkSearcher { return searchPattern; } + @Override + public boolean findAnnotation(IAnnotationBinding[] annotations, String[] annotationNames) { + for (final IAnnotationBinding annotation : annotations) { + final ITypeBinding annotationType = annotation.getAnnotationType(); + for (final String annotationName : annotationNames) { + if (TestFrameworkUtils.isEquivalentAnnotationType(annotationType, annotationName)) { + return true; + } + } + } + return false; + } + @Override public TestItem parseTestItem(IMethod method) throws JavaModelException { return TestItemUtils.constructTestItem(method, TestLevel.METHOD, this.getTestKind()); @@ -83,4 +99,10 @@ public abstract class BaseFrameworkSearcher implements TestFrameworkSearcher { public TestItem parseTestItem(IType type) throws JavaModelException { return TestItemUtils.constructTestItem(type, TestLevel.CLASS, this.getTestKind()); } + + @Override + public TestItem parseTestItem(IMethodBinding methodBinding) throws JavaModelException { + return TestItemUtils.constructTestItem((IMethod) methodBinding.getJavaElement(), + TestLevel.METHOD, this.getTestKind()); + } } diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/JUnit4TestSearcher.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/JUnit4TestSearcher.java index 7e0cd21..63f45c3 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/JUnit4TestSearcher.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/JUnit4TestSearcher.java @@ -17,6 +17,9 @@ import com.microsoft.java.test.plugin.util.TestFrameworkUtils; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.Modifier; +import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry; public class JUnit4TestSearcher extends BaseFrameworkSearcher { @@ -33,6 +36,11 @@ public class JUnit4TestSearcher extends BaseFrameworkSearcher { return TestKind.JUnit; } + @Override + public String getJdtTestKind() { + return TestKindRegistry.JUNIT4_TEST_KIND_ID; + } + @Override public boolean isTestMethod(IMethod method) { try { @@ -55,4 +63,18 @@ public class JUnit4TestSearcher extends BaseFrameworkSearcher { return false; } } + + @Override + public boolean isTestMethod(IMethodBinding methodBinding) { + final int modifiers = methodBinding.getModifiers(); + if (Modifier.isAbstract(modifiers) || Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) { + return false; + } + + if (methodBinding.isConstructor() || !"void".equals(methodBinding.getReturnType().getName())) { + return false; + } + + return this.findAnnotation(methodBinding.getAnnotations(), this.getTestMethodAnnotations()); + } } diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/JUnit5TestSearcher.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/JUnit5TestSearcher.java index f102c59..36f7961 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/JUnit5TestSearcher.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/JUnit5TestSearcher.java @@ -23,14 +23,21 @@ import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.IAnnotationBinding; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.manipulation.CoreASTProvider; +import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Optional; +import java.util.Set; public class JUnit5TestSearcher extends BaseFrameworkSearcher { @@ -59,6 +66,11 @@ public class JUnit5TestSearcher extends BaseFrameworkSearcher { return TestKind.JUnit5; } + @Override + public String getJdtTestKind() { + return TestKindRegistry.JUNIT5_TEST_KIND_ID; + } + @Override public boolean isTestMethod(IMethod method) { try { @@ -81,6 +93,20 @@ public class JUnit5TestSearcher extends BaseFrameworkSearcher { } } + @Override + public boolean isTestMethod(IMethodBinding methodBinding) { + final int modifiers = methodBinding.getModifiers(); + if (Modifier.isAbstract(modifiers) || Modifier.isStatic(modifiers) || Modifier.isPrivate(modifiers)) { + return false; + } + + if (methodBinding.isConstructor()) { + return false; + } + + return this.findAnnotation(methodBinding.getAnnotations(), this.getTestMethodAnnotations()); + } + @SuppressWarnings("rawtypes") @Override public TestItem parseTestItem(IMethod method) throws JavaModelException { @@ -110,4 +136,81 @@ public class JUnit5TestSearcher extends BaseFrameworkSearcher { item.setParamTypes(result); return item; } + + @Override + public TestItem parseTestItem(IMethodBinding methodBinding) throws JavaModelException { + final TestItem item = super.parseTestItem(methodBinding); + + // deal with @DisplayName + for (final IAnnotationBinding annotation : methodBinding.getAnnotations()) { + if (annotation == null) { + continue; + } + if (matchesName(annotation.getAnnotationType(), DISPLAY_NAME_ANNOTATION_JUNIT5)) { + item.setDisplayName((String) annotation.getAllMemberValuePairs()[0].getValue()); + break; + } + } + + // parse the parameters + final ITypeBinding[] parameters = methodBinding.getParameterTypes(); + if (parameters.length > 0) { + final List result = new LinkedList<>(); + for (final ITypeBinding parameter : parameters) { + result.add(parameter.getQualifiedName()); + } + item.setParamTypes(result); + } + + return item; + } + + @Override + public boolean findAnnotation(IAnnotationBinding[] annotations, String[] annotationNames) { + for (final IAnnotationBinding annotation : annotations) { + if (annotation == null) { + continue; + } + for (final String annotationName : annotationNames) { + if (matchesName(annotation.getAnnotationType(), annotationName)) { + return true; + } + + if (JUPITER_NESTED.equals(annotationName) || JUNIT_PLATFORM_TESTABLE.equals(annotationName)) { + final Set hierarchy = new HashSet<>(); + if (matchesNameInAnnotationHierarchy(annotation, annotationName, hierarchy)) { + return true; + } + } + } + } + return false; + } + + private boolean matchesName(ITypeBinding annotationType, String annotationName) { + return TestFrameworkUtils.isEquivalentAnnotationType(annotationType, annotationName); + } + + private boolean matchesNameInAnnotationHierarchy(IAnnotationBinding annotation, String annotationName, + Set hierarchy) { + final ITypeBinding type = annotation.getAnnotationType(); + if (type == null) { + return false; + } + + for (final IAnnotationBinding annotationBinding : type.getAnnotations()) { + if (annotationBinding == null) { + continue; + } + final ITypeBinding annotationType = annotationBinding.getAnnotationType(); + if (annotationType != null && hierarchy.add(annotationType)) { + if (matchesName(annotationType, annotationName) || + matchesNameInAnnotationHierarchy(annotationBinding, annotationName, hierarchy)) { + return true; + } + } + } + + return false; + } } diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/TestFrameworkSearcher.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/TestFrameworkSearcher.java index 935e78d..f697c11 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/TestFrameworkSearcher.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/TestFrameworkSearcher.java @@ -17,14 +17,21 @@ import com.microsoft.java.test.plugin.model.TestKind; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.dom.IAnnotationBinding; +import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.search.SearchPattern; public interface TestFrameworkSearcher { TestKind getTestKind(); + String getJdtTestKind(); + + @Deprecated boolean isTestMethod(IMethod method); + boolean isTestMethod(IMethodBinding methodBinding); + boolean isTestClass(IType type); String[] getTestMethodAnnotations(); @@ -33,7 +40,12 @@ public interface TestFrameworkSearcher { SearchPattern getSearchPattern(); + boolean findAnnotation(IAnnotationBinding[] annotations, String[] annotationNames); + + @Deprecated TestItem parseTestItem(IMethod method) throws JavaModelException; + TestItem parseTestItem(IMethodBinding methodBinding) throws JavaModelException; + TestItem parseTestItem(IType type) throws JavaModelException; } diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/TestNGTestSearcher.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/TestNGTestSearcher.java index 6844a0b..02c9328 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/TestNGTestSearcher.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/TestNGTestSearcher.java @@ -17,6 +17,8 @@ import com.microsoft.java.test.plugin.util.TestFrameworkUtils; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.Modifier; public class TestNGTestSearcher extends BaseFrameworkSearcher { @@ -31,6 +33,11 @@ public class TestNGTestSearcher extends BaseFrameworkSearcher { return TestKind.TestNG; } + @Override + public String getJdtTestKind() { + return ""; + } + @Override public boolean isTestMethod(IMethod method) { try { @@ -53,4 +60,17 @@ public class TestNGTestSearcher extends BaseFrameworkSearcher { return false; } } + + @Override + public boolean isTestMethod(IMethodBinding methodBinding) { + final int modifiers = methodBinding.getModifiers(); + if (Modifier.isAbstract(modifiers) || Modifier.isStatic(modifiers)) { + return false; + } + + if (methodBinding.isConstructor() || !"void".equals(methodBinding.getReturnType().getName())) { + return false; + } + return this.findAnnotation(methodBinding.getAnnotations(), this.getTestMethodAnnotations()); + } } diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestFrameworkUtils.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestFrameworkUtils.java index 180bb27..4af1b86 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestFrameworkUtils.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestFrameworkUtils.java @@ -12,6 +12,8 @@ package com.microsoft.java.test.plugin.util; import com.microsoft.java.test.plugin.model.TestItem; +import com.microsoft.java.test.plugin.model.TestKind; +import com.microsoft.java.test.plugin.model.TestLevel; import com.microsoft.java.test.plugin.searcher.JUnit4TestSearcher; import com.microsoft.java.test.plugin.searcher.JUnit5TestSearcher; import com.microsoft.java.test.plugin.searcher.TestFrameworkSearcher; @@ -24,8 +26,17 @@ import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.internal.junit.launcher.JUnit4TestFinder; +import org.eclipse.jdt.internal.junit.launcher.JUnit5TestFinder; +import org.eclipse.jdt.internal.junit.util.CoreTestSearchEngine; +import java.util.ArrayList; import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -34,7 +45,70 @@ public class TestFrameworkUtils { public static final TestFrameworkSearcher[] FRAMEWORK_SEARCHERS = new TestFrameworkSearcher[] { new JUnit4TestSearcher(), new JUnit5TestSearcher(), new TestNGTestSearcher() }; - public static TestItem resoveTestItemForMethod(IMethod method) throws JavaModelException { + private static final JUnit4TestFinder JUNIT4_TEST_FINDER = new JUnit4TestFinder(); + private static final JUnit5TestFinder JUNIT5_TEST_FINDER = new JUnit5TestFinder(); + + public static void findTestItemsInTypeBinding(ITypeBinding typeBinding, List result, + TestItem parentClassTestItem) throws JavaModelException { + final List searchers = new ArrayList<>(); + final IType type = (IType) typeBinding.getJavaElement(); + for (final TestFrameworkSearcher searcher : FRAMEWORK_SEARCHERS) { + if (CoreTestSearchEngine.isAccessibleClass(type, searcher.getJdtTestKind())) { + searchers.add(searcher); + } + } + + if (searchers.size() == 0) { + return; + } + + final List testMethods = new LinkedList<>(); + final List testMethodIds = new LinkedList<>(); + for (final IMethodBinding methodBinding : typeBinding.getDeclaredMethods()) { + for (final TestFrameworkSearcher searcher : searchers) { + if (searcher.isTestMethod(methodBinding)) { + final TestItem methodItem = searcher.parseTestItem(methodBinding); + testMethods.add(methodItem); + testMethodIds.add(methodItem.getId()); + break; + } + } + } + TestItem classItem = null; + if (testMethods.size() > 0) { + result.addAll(testMethods); + classItem = TestItemUtils.constructTestItem((IType) typeBinding.getJavaElement(), + TestLevel.CLASS); + classItem.setChildren(testMethodIds); + classItem.setKind(testMethods.get(0).getKind()); + result.add(classItem); + } else { + if (JUNIT4_TEST_FINDER.isTest(type)) { + // Leverage JUnit4TestFinder to handle @RunWithclasses + classItem = TestItemUtils.constructTestItem(type, TestLevel.CLASS, TestKind.JUnit); + result.add(classItem); + } else if (JUNIT5_TEST_FINDER.isTest(type)) { + // Leverage JUnit5TestFinder to handle @Nested and @Testable classes + classItem = TestItemUtils.constructTestItem(type, TestLevel.CLASS, TestKind.JUnit5); + result.add(classItem); + } + } + + // set the class item as the child of its declaring type + if (classItem != null && parentClassTestItem != null) { + parentClassTestItem.addChild(classItem.getId()); + } + + for (final ITypeBinding childTypeBinding : typeBinding.getDeclaredTypes()) { + findTestItemsInTypeBinding(childTypeBinding, result, classItem); + } + } + + public static boolean isEquivalentAnnotationType(ITypeBinding annotationType, String annotationName) { + return annotationType != null && Objects.equals(annotationType.getQualifiedName(), annotationName); + } + + public static TestItem resolveTestItemForMethod(IMethod method) throws JavaModelException { for (final TestFrameworkSearcher searcher : FRAMEWORK_SEARCHERS) { if (searcher.isTestMethod(method)) { return searcher.parseTestItem(method); @@ -59,6 +133,7 @@ public class TestFrameworkUtils { * @param annotationToSearch The annotation string. * @param checkHierarchy Specify whether to search the whole annotation hierarchy. */ + @Deprecated public static Optional getAnnotation(IMember member, String annotationToSearch, boolean checkHierarchy) { if (!IAnnotatable.class.isInstance(member)) { @@ -110,6 +185,7 @@ public class TestFrameworkUtils { * @param annotationToSearch The annotation string. * @param checkHierarchy Specify whether to search the whole annotation hierarchy. */ + @Deprecated public static boolean hasAnnotation(IMember member, String annotationToSearch, boolean checkHierarchy) { return getAnnotation(member, annotationToSearch, checkHierarchy).isPresent(); } diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestSearchUtils.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestSearchUtils.java index 3d0022e..b422def 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestSearchUtils.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestSearchUtils.java @@ -14,9 +14,7 @@ package com.microsoft.java.test.plugin.util; import com.google.gson.Gson; import com.microsoft.java.test.plugin.model.SearchTestItemParams; import com.microsoft.java.test.plugin.model.TestItem; -import com.microsoft.java.test.plugin.model.TestKind; import com.microsoft.java.test.plugin.model.TestLevel; -import com.microsoft.java.test.plugin.searcher.JUnit4TestSearcher; import com.microsoft.java.test.plugin.searcher.JUnit5TestSearcher; import org.eclipse.core.resources.IFolder; @@ -37,6 +35,12 @@ import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeRoot; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.search.IJavaSearchConstants; import org.eclipse.jdt.core.search.IJavaSearchScope; import org.eclipse.jdt.core.search.SearchEngine; @@ -52,15 +56,12 @@ import org.eclipse.lsp4j.Location; import java.net.URISyntaxException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.Stream; @SuppressWarnings("restriction") @@ -88,61 +89,29 @@ public class TestSearchUtils { Job.getJobManager().join(DocumentLifeCycleHandler.DOCUMENT_LIFE_CYCLE_JOBS, monitor); final ICompilationUnit unit = JDTUtils.resolveCompilationUnit(uri); - // whether the file is on test path or not is guarded at client side cache - if (!isJavaElementExist(unit) || monitor.isCanceled()) { + final IType primaryType = unit.findPrimaryType(); + if (!isJavaElementExist(unit) || primaryType == null || monitor.isCanceled()) { return resultList; } - final IType[] childrenTypes = unit.getAllTypes(); - final Map classMapping = new HashMap<>(); - for (final IType type : childrenTypes) { - if (!isTestableClass(type)) { - continue; - } - final List testMethodList = Arrays.stream(type.getMethods()).map(method -> { - try { - return TestFrameworkUtils.resoveTestItemForMethod(method); - } catch (final JavaModelException e) { - return null; - } - }).filter(Objects::nonNull).collect(Collectors.toList()); - TestItem classItem = null; - if (testMethodList.size() > 0) { - classItem = TestItemUtils.constructTestItem(type, TestLevel.CLASS); - for (final TestItem method : testMethodList) { - resultList.add(method); - classItem.addChild(method.getId()); - } - // Assume the kinds of all methods are the same. - classItem.setKind(testMethodList.get(0).getKind()); - resultList.add(classItem); - } else { - // JUnit 5 supports nested test classes - if (isJunit5TestableClass(type)) { - classItem = TestItemUtils.constructTestItem(type, TestLevel.CLASS, TestKind.JUnit5); - resultList.add(classItem); - } - - // Class annotated by @RunWith should be considered as a Suite even it has no test method children - if (TestFrameworkUtils.hasAnnotation(type, JUnit4TestSearcher.RUN_WITH, false /*checkHierarchy*/)) { - classItem = TestItemUtils.constructTestItem(type, TestLevel.CLASS, TestKind.JUnit); - resultList.add(classItem); - } - } - if (classItem == null) { - continue; - } - classMapping.put(type.getFullyQualifiedName(), classItem); - final IType declarationType = type.getDeclaringType(); - if (declarationType != null) { - final TestItem declarationTypeItem = classMapping.get(declarationType.getFullyQualifiedName()); - if (declarationTypeItem != null) { - declarationTypeItem.addChild(classItem.getId()); - } - } - + final ASTParser parser = ASTParser.newParser(AST.JLS14); + parser.setSource(unit); + parser.setFocalPosition(0); + parser.setResolveBindings(true); + parser.setIgnoreMethodBodies(true); + final CompilationUnit root = (CompilationUnit) parser.createAST(monitor); + final ASTNode node = root.findDeclaringNode(primaryType.getKey()); + if (!(node instanceof TypeDeclaration)) { + return resultList; } + final ITypeBinding binding = ((TypeDeclaration) node).resolveBinding(); + if (binding == null) { + return resultList; + } + + TestFrameworkUtils.findTestItemsInTypeBinding(binding, resultList, null /*parentClassItem*/); + return resultList; } @@ -222,7 +191,7 @@ public class TestSearchUtils { if (!scope.encloses(method)) { return; } - final TestItem methodItem = TestFrameworkUtils.resoveTestItemForMethod(method); + final TestItem methodItem = TestFrameworkUtils.resolveTestItemForMethod(method); if (methodItem == null) { return; } @@ -409,7 +378,7 @@ public class TestSearchUtils { continue; } for (final IMethod method : type.getMethods()) { - final TestItem item = TestFrameworkUtils.resoveTestItemForMethod(method); + final TestItem item = TestFrameworkUtils.resolveTestItemForMethod(method); if (item != null) { resultList.add(item); } diff --git a/java-extension/com.microsoft.java.test.plugin/target.target b/java-extension/com.microsoft.java.test.plugin/target.target index 54550c1..aa23f87 100644 --- a/java-extension/com.microsoft.java.test.plugin/target.target +++ b/java-extension/com.microsoft.java.test.plugin/target.target @@ -10,7 +10,7 @@ - + diff --git a/java-extension/pom.xml b/java-extension/pom.xml index dbccaee..aac0993 100644 --- a/java-extension/pom.xml +++ b/java-extension/pom.xml @@ -53,9 +53,9 @@ - 202003 + 202006 p2 - https://download.eclipse.org/releases/2020-03/ + https://download.eclipse.org/releases/2020-06/ oss.sonatype.org diff --git a/package-lock.json b/package-lock.json index 4eeed01..105ead9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -343,7 +343,7 @@ }, "acorn": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "resolved": "http://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" }, "acorn-globals": { @@ -422,7 +422,7 @@ }, "ansi-colors": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { @@ -631,7 +631,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -845,7 +845,7 @@ }, "util": { "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, "requires": { @@ -1587,7 +1587,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -1758,7 +1758,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -1808,7 +1808,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -2460,7 +2460,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -3238,7 +3238,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -3296,7 +3296,7 @@ "dependencies": { "combined-stream": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "resolved": "http://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "dev": true, "requires": { @@ -4117,7 +4117,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -5121,7 +5121,7 @@ }, "json5": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", "dev": true }, @@ -5212,7 +5212,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -5572,7 +5572,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -5587,7 +5587,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -6558,7 +6558,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -6596,7 +6596,7 @@ }, "os-locale": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { @@ -7308,7 +7308,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -8478,7 +8478,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -8807,7 +8807,7 @@ }, "yargs": { "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", "requires": { "camelcase": "^1.0.2", @@ -9104,7 +9104,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -9748,7 +9748,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { diff --git a/test/gradle-junit5-suite/codelens.test.ts b/test/gradle-junit5-suite/codelens.test.ts index d2a11c7..4a81153 100644 --- a/test/gradle-junit5-suite/codelens.test.ts +++ b/test/gradle-junit5-suite/codelens.test.ts @@ -84,6 +84,15 @@ suite('Code Lens Tests', function() { assert.ok(failedDetail!.duration !== undefined, 'Should have execution time'); }); + test("Can show Code Lens for methods annotated with meta-annotation", async function() { + const document: TextDocument = await workspace.openTextDocument(Uris.GRADLE_JUNIT5_META_ANNOTATION_TEST); + await window.showTextDocument(document); + + const codeLensProvider: TestCodeLensProvider = new TestCodeLensProvider(); + let codeLens: CodeLens[] = await codeLensProvider.provideCodeLenses(document, Token.cancellationToken); + assert.equal(codeLens.length, 4); + }); + test("Can run test method annotated with @Nested", async function() { const document: TextDocument = await workspace.openTextDocument(Uris.GRADLE_JUNIT5_NESTED_TEST); await window.showTextDocument(document); diff --git a/test/shared.ts b/test/shared.ts index f5d9eef..6735c52 100644 --- a/test/shared.ts +++ b/test/shared.ts @@ -39,6 +39,7 @@ export namespace Uris { const GRADLE_JUNIT5_TEST_PACKAGE: string = path.join('junit5', 'src', 'test', 'java', 'junit5'); export const GRADLE_JUNIT5_PARAMETERIZED_TEST: Uri = Uri.file(path.join(TEST_PROJECT_BASE_PATH, GRADLE_JUNIT5_TEST_PACKAGE, 'ParameterizedAnnotationTest.java')); export const GRADLE_JUNIT5_NESTED_TEST: Uri = Uri.file(path.join(TEST_PROJECT_BASE_PATH, GRADLE_JUNIT5_TEST_PACKAGE, 'NestedTest.java')); + export const GRADLE_JUNIT5_META_ANNOTATION_TEST: Uri = Uri.file(path.join(TEST_PROJECT_BASE_PATH, GRADLE_JUNIT5_TEST_PACKAGE, 'MetaAnnotationTest.java')); export const GRADLE_JUNIT5_PROPERTY_TEST: Uri = Uri.file(path.join(TEST_PROJECT_BASE_PATH, GRADLE_JUNIT5_TEST_PACKAGE, 'PropertyTest.java')); export const GRADLE_CUCUMBER_TEST: Uri = Uri.file(path.join(TEST_PROJECT_BASE_PATH, GRADLE_JUNIT5_TEST_PACKAGE, 'cucumber', 'CucumberTest.java')); export const GRADLE_CUCUMBER_STEP: Uri = Uri.file(path.join(TEST_PROJECT_BASE_PATH, GRADLE_JUNIT5_TEST_PACKAGE, 'cucumber', 'CucumberSteps.java')); diff --git a/test/test-projects/junit5/src/test/java/junit5/FastTest.java b/test/test-projects/junit5/src/test/java/junit5/FastTest.java new file mode 100644 index 0000000..61ce7cf --- /dev/null +++ b/test/test-projects/junit5/src/test/java/junit5/FastTest.java @@ -0,0 +1,16 @@ +package junit5; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Tag("fast") +@Test +public @interface FastTest { +} \ No newline at end of file diff --git a/test/test-projects/junit5/src/test/java/junit5/MetaAnnotationTest.java b/test/test-projects/junit5/src/test/java/junit5/MetaAnnotationTest.java new file mode 100644 index 0000000..c4e7411 --- /dev/null +++ b/test/test-projects/junit5/src/test/java/junit5/MetaAnnotationTest.java @@ -0,0 +1,7 @@ +package junit5; + +public class MetaAnnotationTest { + @FastTest + void myFastTest() { + } +} \ No newline at end of file