diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegate.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegate.java index ddbfb262..ac8721d7 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegate.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegate.java @@ -151,34 +151,39 @@ private void addTestItemArgs(List arguments) throws CoreException { method.getParameters().length > 0) { final ICompilationUnit unit = method.getCompilationUnit(); if (unit == null) { - throw new CoreException(new Status(IStatus.ERROR, JUnitPlugin.PLUGIN_ID, IStatus.ERROR, - "Cannot get compilation unit of method" + method.getElementName(), null)); //$NON-NLS-1$ - } - final CompilationUnit root = (CompilationUnit) TestSearchUtils.parseToAst(unit, - false /*fromCache*/, new NullProgressMonitor()); - final MethodDeclaration methodDeclaration = ASTNodeSearchUtil.getMethodDeclarationNode(method, root); - if (methodDeclaration == null) { - throw new CoreException(new Status(IStatus.ERROR, JUnitPlugin.PLUGIN_ID, IStatus.ERROR, + // binary method + if (method.getDeclaringType() == null) { + throw new CoreException(new Status(IStatus.ERROR, JUnitPlugin.PLUGIN_ID, IStatus.ERROR, + "Cannot get compilation unit of method" + method.getElementName(), null)); //$NON-NLS-1$ + } + } else { + final CompilationUnit root = (CompilationUnit) TestSearchUtils.parseToAst(unit, + false /* fromCache */, new NullProgressMonitor()); + final MethodDeclaration methodDeclaration = ASTNodeSearchUtil.getMethodDeclarationNode(method, + root); + if (methodDeclaration == null) { + throw new CoreException(new Status(IStatus.ERROR, JUnitPlugin.PLUGIN_ID, IStatus.ERROR, "Cannot get method declaration of method" + method.getElementName(), null)); //$NON-NLS-1$ - } + } - final List parameters = new LinkedList<>(); - for (final Object obj : methodDeclaration.parameters()) { - if (obj instanceof SingleVariableDeclaration) { - final ITypeBinding paramTypeBinding = ((SingleVariableDeclaration) obj) - .getType().resolveBinding(); - if (paramTypeBinding == null) { - throw new CoreException(new Status(IStatus.ERROR, JUnitPlugin.PLUGIN_ID, IStatus.ERROR, - "Cannot set set argument for method" + methodDeclaration.toString(), null)); - } else if (paramTypeBinding.isPrimitive()) { - parameters.add(paramTypeBinding.getQualifiedName()); - } else { - parameters.add(paramTypeBinding.getBinaryName()); + final List parameters = new LinkedList<>(); + for (final Object obj : methodDeclaration.parameters()) { + if (obj instanceof SingleVariableDeclaration) { + final ITypeBinding paramTypeBinding = ((SingleVariableDeclaration) obj).getType() + .resolveBinding(); + if (paramTypeBinding == null) { + throw new CoreException(new Status(IStatus.ERROR, JUnitPlugin.PLUGIN_ID, IStatus.ERROR, + "Cannot set set argument for method" + methodDeclaration.toString(), null)); + } else if (paramTypeBinding.isPrimitive()) { + parameters.add(paramTypeBinding.getQualifiedName()); + } else { + parameters.add(paramTypeBinding.getBinaryName()); + } } } - } - if (parameters.size() > 0) { - testName += "(" + String.join(",", parameters) + ")"; + if (parameters.size() > 0) { + testName += "(" + String.join(",", parameters) + ")"; + } } } arguments.add(method.getDeclaringType().getFullyQualifiedName() + ':' + testName); diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/builder/JavaTestItemBuilder.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/builder/JavaTestItemBuilder.java index 0285b826..54feb9ac 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/builder/JavaTestItemBuilder.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/builder/JavaTestItemBuilder.java @@ -17,15 +17,33 @@ import com.microsoft.java.test.plugin.util.TestItemUtils; import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; +import org.eclipse.jdt.core.IClassFile; +import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.internal.core.BinaryMember; +import org.eclipse.jdt.internal.core.BinaryMethod; +import org.eclipse.jdt.internal.core.BinaryType; +import org.eclipse.jdt.internal.core.PackageFragmentRoot; import org.eclipse.jdt.internal.core.manipulation.JavaElementLabelsCore; import org.eclipse.jdt.ls.core.internal.JDTUtils; +import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; import org.eclipse.jdt.ls.core.internal.ProjectUtils; +import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; /** * Builder class to build {@link com.microsoft.java.test.plugin.model.JavaTestItem} @@ -36,6 +54,7 @@ public class JavaTestItemBuilder { private IJavaElement element; private TestLevel level; private TestKind kind; + private String displayName; public JavaTestItemBuilder setJavaElement(IJavaElement element) { this.element = element; @@ -52,34 +71,124 @@ public JavaTestItemBuilder setKind(TestKind kind) { return this; } + public JavaTestItemBuilder setDisplayName(String displayName) { + this.displayName = displayName; + return this; + } + public JavaTestItem build() throws JavaModelException { if (this.element == null || this.level == null || this.kind == null) { throw new IllegalArgumentException("Failed to build Java test item due to missing arguments"); } - final String displayName; String uri = null; - if (this.element instanceof IJavaProject) { - final IJavaProject javaProject = (IJavaProject) this.element; - final IProject project = javaProject.getProject(); - if (ProjectUtils.isVisibleProject(project)) { - displayName = project.getName(); + if (this.displayName == null) { + if (this.element instanceof IJavaProject) { + final IJavaProject javaProject = (IJavaProject) this.element; + final IProject project = javaProject.getProject(); + if (ProjectUtils.isVisibleProject(project)) { + displayName = project.getName(); + } else { + final IPath realPath = ProjectUtils.getProjectRealFolder(project); + displayName = realPath.lastSegment(); + uri = realPath.toFile().toURI().toString(); + } + } else if (this.element instanceof IPackageFragment && + ((IPackageFragment) this.element).isDefaultPackage()) { + displayName = DEFAULT_PACKAGE_NAME; + final IResource resource = getResource((IPackageFragment) this.element); + if (resource == null || !resource.exists()) { + return null; + } + uri = JDTUtils.getFileURI(resource); } else { - final IPath realPath = ProjectUtils.getProjectRealFolder(project); - displayName = realPath.lastSegment(); - uri = realPath.toFile().toURI().toString(); + displayName = JavaElementLabelsCore.getElementLabel(this.element, JavaElementLabelsCore.ALL_DEFAULT); } - } else if (this.element instanceof IPackageFragment && ((IPackageFragment) this.element).isDefaultPackage()) { - displayName = DEFAULT_PACKAGE_NAME; - } else { - displayName = JavaElementLabelsCore.getElementLabel(this.element, JavaElementLabelsCore.ALL_DEFAULT); } + Range range = null; final String fullName = TestItemUtils.parseFullName(this.element, this.level, this.kind); if (uri == null) { - uri = JDTUtils.getFileURI(this.element.getResource()); + IResource resource = this.element.getResource(); + if (resource == null && this.element instanceof IPackageFragment) { + resource = getResource((IPackageFragment) this.element); + } + if (resource == null || !resource.exists()) { + return null; + } + if (element instanceof BinaryMember) { + final String[] sources = new String[1]; + final int[] lines = {-1}; + final IClassFile classFile = ((BinaryMember) element).getClassFile(); + try (InputStream is = new ByteArrayInputStream(classFile.getBytes())) { + final ClassReader cr = new ClassReader(is); + if (element instanceof BinaryType) { + cr.accept(new ClassVisitor(Opcodes.ASM9) { + @Override + public void visitSource(String source, String debug) { + sources[0] = source; + } + }, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); + } else if (element instanceof BinaryMethod) { + final String methodDescriptor = ((BinaryMethod) element).getSignature(); + cr.accept(new ClassVisitor(Opcodes.ASM9) { + @Override + public void visitSource(String source, String debug) { + sources[0] = source; + } + + public MethodVisitor visitMethod(int access, String name, String descriptor, + String signature, String[] exceptions) { + if (name.equals(element.getElementName()) && descriptor.equals(methodDescriptor)) { + return new MethodVisitor(Opcodes.ASM9) { + @Override + public void visitLineNumber(int line, Label start) { + if (lines[0] < 0) { + lines[0] = line; + if (line > 0) { + lines[0]--; + } + } + } + }; + } + return null; + } + }, ClassReader.SKIP_FRAMES); + } + } catch (Exception e) { + JavaLanguageServerPlugin.logException(e); + } + if (sources[0] != null) { + final IPackageFragment packageFragment = (IPackageFragment) element + .getAncestor(IJavaElement.PACKAGE_FRAGMENT); + if (packageFragment != null) { + final String packageName = packageFragment.getElementName(); + final IJavaProject project = element.getJavaProject(); + final String packagePath = packageName.replace('.', '/'); + for (final IClasspathEntry entry : project.getRawClasspath()) { + if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { + final IPath sourceFolderPath = entry.getPath(); + final IPath fullPath = sourceFolderPath.append(packagePath).append(sources[0]); + final IResource candidate = ResourcesPlugin.getWorkspace().getRoot() + .findMember(fullPath); + if (candidate != null && candidate.exists()) { + resource = candidate; + break; + } + } + } + } + } + if (lines[0] > 0) { + final Position line = new Position(lines[0] - 1, 0); + range = new Range(line, line); + } + } + if (uri == null) { + uri = JDTUtils.getFileURI(resource); + } } - Range range = null; - if (this.level == TestLevel.CLASS || this.level == TestLevel.METHOD) { + if (range == null && (this.level == TestLevel.CLASS || this.level == TestLevel.METHOD)) { range = TestItemUtils.parseTestItemRange(this.element); } @@ -89,4 +198,22 @@ public JavaTestItem build() throws JavaModelException { return result; } + + private IResource getResource(IPackageFragment packageFragment) { + if (packageFragment == null) { + return null; + } + IResource resource = packageFragment.getResource(); + if (resource == null) { + final IJavaElement e = packageFragment.getParent(); + if (e instanceof PackageFragmentRoot) { + final PackageFragmentRoot root = (PackageFragmentRoot) e; + resource = root.getResource(); + if (resource == null) { + resource = root.resource(root); + } + } + } + return resource; + } } 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 66c44e3f..a5be5b7d 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 @@ -77,4 +77,9 @@ public Set findTestItemsInContainer(IJavaElement element, IProgressMonito return types; } + + @Override + public String getDisplayName(IMethodBinding methodBinding) { + return null; + } } 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 9ae301e8..ac080659 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 @@ -21,6 +21,7 @@ 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.IMemberValuePairBinding; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.Modifier; @@ -40,9 +41,11 @@ public class JUnit5TestSearcher extends BaseFrameworkSearcher { protected static final String DISPLAY_NAME_ANNOTATION_JUNIT5 = "org.junit.jupiter.api.DisplayName"; + protected static final String SPOCK_FEATURE_METADATA = "org.spockframework.runtime.model.FeatureMetadata"; + public JUnit5TestSearcher() { super(); - this.testMethodAnnotations = new String[] { JUNIT_PLATFORM_TESTABLE }; + this.testMethodAnnotations = new String[] { JUNIT_PLATFORM_TESTABLE, SPOCK_FEATURE_METADATA }; } @Override @@ -133,4 +136,19 @@ public Set findTestItemsInContainer(IJavaElement element, IProgressMonito } return types; } + + @Override + public String getDisplayName(IMethodBinding methodBinding) { + for (final IAnnotationBinding annotation : methodBinding.getAnnotations()) { + if (matchesName(annotation.getAnnotationType(), SPOCK_FEATURE_METADATA)) { + final IMemberValuePairBinding[] pairs = annotation.getDeclaredMemberValuePairs(); + for (final IMemberValuePairBinding pair : pairs) { + if ("name".equals(pair.getName()) && (pair.getValue() instanceof String)) { + return (String) pair.getValue(); + } + } + } + } + return null; + } } diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/JUnit6TestFinder.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/JUnit6TestFinder.java index 2580fa10..056ad13f 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/JUnit6TestFinder.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/JUnit6TestFinder.java @@ -34,6 +34,7 @@ import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.RecordDeclaration; import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.internal.junit.JUnitCorePlugin; import org.eclipse.jdt.internal.junit.launcher.ITestFinder; import org.eclipse.jdt.internal.junit.util.CoreTestSearchEngine; @@ -67,6 +68,157 @@ public class JUnit6TestFinder implements ITestFinder { */ private static final String JUNIT6_LOADER = "org.eclipse.jdt.junit.loader.junit6"; + private static class Annotation { + + private static final Annotation RUN_WITH = new Annotation("org.junit.runner.RunWith"); //$NON-NLS-1$ + + private static final Annotation TEST_4 = new Annotation("org.junit.Test"); //$NON-NLS-1$ + + private static final Annotation SUITE = new Annotation("org.junit.platform.suite.api.Suite"); //$NON-NLS-1$ + + private static final Annotation TESTABLE = new Annotation(JUnitCorePlugin.JUNIT5_TESTABLE_ANNOTATION_NAME); + + private static final Annotation NESTED = new Annotation(JUnitCorePlugin.JUNIT5_JUPITER_NESTED_ANNOTATION_NAME); + + private final String fName; + + private Annotation(String name) { + fName = name; + } + + String getName() { + return fName; + } + + boolean annotatesAtLeastOneInnerClass(ITypeBinding type) { + if (type == null) { + return false; + } + if (annotatesDeclaredTypes(type)) { + return true; + } + final ITypeBinding superClass = type.getSuperclass(); + if (annotatesAtLeastOneInnerClass(superClass)) { + return true; + } + final ITypeBinding[] interfaces = type.getInterfaces(); + for (final ITypeBinding intf : interfaces) { + if (annotatesAtLeastOneInnerClass(intf)) { + return true; + } + } + return false; + } + + private boolean annotatesDeclaredTypes(ITypeBinding type) { + final ITypeBinding[] declaredTypes = type.getDeclaredTypes(); + for (final ITypeBinding declaredType : declaredTypes) { + if (isNestedClass(declaredType)) { + return true; + } + } + return false; + } + + private boolean isNestedClass(ITypeBinding type) { + final int modifiers = type.getModifiers(); + if (type.isInterface() || Modifier.isPrivate(modifiers) || Modifier.isStatic(modifiers)) { + return false; + } + if (annotates(type.getAnnotations())) { + return true; + } + return false; + } + + boolean annotatesTypeOrSuperTypes(ITypeBinding type) { + while (type != null) { + if (annotates(type.getAnnotations())) { + return true; + } + type = type.getSuperclass(); + } + return false; + } + + boolean annotatesAtLeastOneMethod(ITypeBinding type) { + if (type == null) { + return false; + } + if (annotatesDeclaredMethods(type)) { + return true; + } + final ITypeBinding superClass = type.getSuperclass(); + if (annotatesAtLeastOneMethod(superClass)) { + return true; + } + final ITypeBinding[] interfaces = type.getInterfaces(); + for (final ITypeBinding intf : interfaces) { + if (annotatesAtLeastOneMethod(intf)) { + return true; + } + } + return false; + } + + private boolean annotatesDeclaredMethods(ITypeBinding type) { + final IMethodBinding[] declaredMethods = type.getDeclaredMethods(); + for (final IMethodBinding curr : declaredMethods) { + if (annotates(curr.getAnnotations())) { + return true; + } + } + return false; + } + + // See JUnitLaunchConfigurationTab#isAnnotatedWithTestable also. + private boolean annotates(IAnnotationBinding[] annotations) { + for (final IAnnotationBinding annotation : annotations) { + if (annotation == null) { + continue; + } + if (matchesName(annotation.getAnnotationType())) { + return true; + } + if (TESTABLE.getName().equals(fName) || NESTED.getName().equals(fName)) { + final Set hierarchy = new HashSet<>(); + if (matchesNameInAnnotationHierarchy(annotation, hierarchy)) { + return true; + } + } + } + return false; + } + + private boolean matchesName(ITypeBinding annotationType) { + if (annotationType != null) { + final String qualifiedName = annotationType.getQualifiedName(); + if (qualifiedName.equals(fName)) { + return true; + } + } + return false; + } + + private boolean matchesNameInAnnotationHierarchy(IAnnotationBinding annotation, Set hierarchy) { + final ITypeBinding type = annotation.getAnnotationType(); + if (type != null) { + for (final IAnnotationBinding annotationBinding : type.getAnnotations()) { + if (annotationBinding != null) { + final ITypeBinding annotationType = annotationBinding.getAnnotationType(); + if (annotationType != null && hierarchy.add(annotationType)) { + if (matchesName(annotationType) || + matchesNameInAnnotationHierarchy(annotationBinding, hierarchy)) { + return true; + } + } + } + } + } + return false; + } + } + public JUnit6TestFinder() { } @@ -172,32 +324,20 @@ private static boolean isAvailable(ISourceRange range) { return range != null && range.getOffset() != -1; } - private boolean isTest(ITypeBinding typeBinding) { - if (typeBinding == null || Modifier.isAbstract(typeBinding.getModifiers())) { + private boolean isTest(ITypeBinding binding) { + if (binding == null || Modifier.isAbstract(binding.getModifiers())) { return false; } - // Check if the type itself has test methods - if (hasTestMethods(typeBinding)) { + if (Annotation.RUN_WITH.annotatesTypeOrSuperTypes(binding) || + Annotation.SUITE.annotatesTypeOrSuperTypes(binding) || + Annotation.TEST_4.annotatesAtLeastOneMethod(binding) || + Annotation.TESTABLE.annotatesAtLeastOneMethod(binding) || + Annotation.TESTABLE.annotatesTypeOrSuperTypes(binding) || + Annotation.NESTED.annotatesAtLeastOneInnerClass(binding)) { return true; } - - // Check nested classes with @Nested annotation - for (final ITypeBinding nestedType : typeBinding.getDeclaredTypes()) { - if (isNestedTestClass(nestedType)) { - return true; - } - } - - // Check superclass - final ITypeBinding superclass = typeBinding.getSuperclass(); - if (superclass != null && !superclass.getQualifiedName().equals("java.lang.Object")) { - if (isTest(superclass)) { - return true; - } - } - - return false; + return CoreTestSearchEngine.isTestImplementor(binding); } private boolean hasTestMethods(ITypeBinding typeBinding) { 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 914e7bcd..f3e54aaf 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 @@ -31,6 +31,8 @@ public interface TestFrameworkSearcher { boolean isTestMethod(IMethodBinding methodBinding); + String getDisplayName(IMethodBinding methodBinding); + boolean isTestClass(IType type) throws JavaModelException; String[] getTestMethodAnnotations(); 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 78208349..6ff73aa8 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 @@ -227,6 +227,11 @@ public Set findTestItemsInContainer(IJavaElement element, IProgressMonito } return types; } + + @Override + public String getDisplayName(IMethodBinding methodBinding) { + return null; + } } /* diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestItemUtils.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestItemUtils.java index 6a43b3cd..0da167a8 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestItemUtils.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestItemUtils.java @@ -37,7 +37,7 @@ public static Range parseTestItemRange(IJavaElement element) throws JavaModelExc sourceRange.getLength() - nameRange.getOffset() + sourceRange.getOffset()); } } - return null; + return JDTUtils.newRange(); } public static String parseFullName(IJavaElement element, TestLevel level, TestKind kind) { 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 c8542223..c8c9e028 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 @@ -25,14 +25,12 @@ import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.core.runtime.jobs.Job; -import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaModel; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IPackageFragment; -import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeHierarchy; import org.eclipse.jdt.core.JavaCore; @@ -41,6 +39,7 @@ 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.IBinding; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.TypeDeclaration; @@ -120,8 +119,10 @@ public static List findJavaProjects(List arguments, IProgr .setLevel(TestLevel.PROJECT) .setKind(testKind) .build(); - item.setNatureIds(project.getProject().getDescription().getNatureIds()); - resultList.add(item); + if (item != null) { + item.setNatureIds(project.getProject().getDescription().getNatureIds()); + resultList.add(item); + } } catch (CoreException e) { JUnitPlugin.logError("Failed to parse project item: " + project.getElementName()); } @@ -158,20 +159,16 @@ public static List findTestPackagesAndTypes(List arguments if (monitor != null && monitor.isCanceled()) { return Collections.emptyList(); } - final TestFrameworkSearcher searcher = TestFrameworkUtils.getSearcherByTestKind(kind); final Set testTypes = new HashSet<>(); - final List testEntries = ProjectTestUtils.getTestEntries(javaProject); - for (final IClasspathEntry entry : testEntries) { - final IPackageFragmentRoot[] packageRoots = javaProject.findPackageFragmentRoots(entry); - for (final IPackageFragmentRoot root : packageRoots) { - try { - testTypes.addAll(searcher.findTestItemsInContainer(root, monitor)); - } catch (CoreException e) { - JUnitPlugin.logException("failed to search tests in: " + root.getElementName(), e); - } - } + final TestFrameworkSearcher searcher = TestFrameworkUtils.getSearcherByTestKind(kind); + if (searcher == null) { + continue; + } + try { + testTypes.addAll(searcher.findTestItemsInContainer(element, monitor)); + } catch (CoreException e) { + JavaLanguageServerPlugin.logException(e); } - for (final IType type : testTypes) { JavaTestItem classItem = testItemMapping.get(type.getHandleIdentifier()); if (classItem == null) { @@ -179,7 +176,9 @@ public static List findTestPackagesAndTypes(List arguments .setLevel(TestLevel.CLASS) .setKind(kind) .build(); - testItemMapping.put(classItem.getJdtHandler(), classItem); + if (classItem != null) { + testItemMapping.put(classItem.getJdtHandler(), classItem); + } } else { // 1. We suppose a class can only use one test framework // 2. If more accurate kind is available, use it. @@ -187,7 +186,6 @@ public static List findTestPackagesAndTypes(List arguments classItem.setTestKind(TestKind.JUnit); } } - final IType declaringType = type.getDeclaringType(); if (declaringType == null) { // it's a root type, we find its declaring package @@ -199,7 +197,9 @@ public static List findTestPackagesAndTypes(List arguments .setLevel(TestLevel.PACKAGE) .setKind(TestKind.None) .build(); - testItemMapping.put(packageIdentifier, packageItem); + if (packageItem != null) { + testItemMapping.put(packageIdentifier, packageItem); + } } if (packageItem.getChildren() == null || !packageItem.getChildren().contains(classItem)) { packageItem.addChild(classItem); @@ -212,7 +212,9 @@ public static List findTestPackagesAndTypes(List arguments .setLevel(TestLevel.CLASS) .setKind(kind) .build(); - testItemMapping.put(declaringTypeIdentifier, declaringTypeItem); + if (declaringTypeItem != null) { + testItemMapping.put(declaringTypeIdentifier, declaringTypeItem); + } } if (declaringTypeItem.getChildren() == null || !declaringTypeItem.getChildren().contains(classItem)) { @@ -254,18 +256,50 @@ public static List findDirectTestChildrenForClass(List arg } final ICompilationUnit unit = testType.getCompilationUnit(); + final List testKinds = TestKindProvider.getTestKindsFromCache(testType.getJavaProject()); if (unit == null) { - return Collections.emptyList(); + final List result = new LinkedList<>(); + for (final TestKind kind : testKinds) { + final Set testTypes = new HashSet<>(); + final TestFrameworkSearcher searcher = TestFrameworkUtils.getSearcherByTestKind(kind); + if (searcher == null) { + continue; + } + try { + testTypes.addAll(searcher.findTestItemsInContainer(testType, monitor)); + } catch (CoreException e) { + JavaLanguageServerPlugin.logException(e); + } + for (final IType type : testTypes) { + final ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); + parser.setProject(type.getJavaProject()); + parser.setResolveBindings(true); + parser.setIgnoreMethodBodies(true); + final IBinding[] bindings = parser.createBindings(new IJavaElement[] { type }, null); + if (bindings != null && bindings.length > 0 && bindings[0] instanceof ITypeBinding) { + for (final IMethodBinding methodBinding : ((ITypeBinding) bindings[0]).getDeclaredMethods()) { + if (searcher.isTestMethod(methodBinding)) { + final String displayName = searcher.getDisplayName(methodBinding); + final JavaTestItem item = new JavaTestItemBuilder() + .setJavaElement(methodBinding.getJavaElement()).setLevel(TestLevel.METHOD) + .setDisplayName(displayName) + .setKind(kind).build(); + if (item != null) { + result.add(item); + } + } + } + } + } + } + return result; } - final List testKinds = TestKindProvider.getTestKindsFromCache(unit.getJavaProject()); - final List result = new LinkedList<>(); final CompilationUnit root = (CompilationUnit) parseToAst(unit, true /* fromCache */, monitor); for (final IType type : unit.getAllTypes()) { if (monitor != null && monitor.isCanceled()) { return result; } - final IType declaringType = type.getDeclaringType(); if (declaringType != null && declaringType.getFullyQualifiedName().equals(testType.getFullyQualifiedName())) { @@ -276,13 +310,14 @@ public static List findDirectTestChildrenForClass(List arg .setLevel(TestLevel.CLASS) .setKind(kind) .build(); - result.add(typeItem); + if (typeItem != null) { + result.add(typeItem); + } break; } } continue; } - if (!type.getFullyQualifiedName().equals(testType.getFullyQualifiedName())) { continue; } @@ -297,7 +332,6 @@ public static List findDirectTestChildrenForClass(List arg continue; } - for (final IMethodBinding methodBinding : binding.getDeclaredMethods()) { for (final TestKind kind: testKinds) { final TestFrameworkSearcher searcher = TestFrameworkUtils.getSearcherByTestKind(kind); @@ -307,12 +341,13 @@ public static List findDirectTestChildrenForClass(List arg .setLevel(TestLevel.METHOD) .setKind(kind) .build(); - result.add(item); + if (item != null) { + result.add(item); + } } } } } - return result; } @@ -403,7 +438,9 @@ private static void findTestItemsInTypeBinding(ITypeBinding typeBinding, JavaTes .setLevel(TestLevel.METHOD) .setKind(searcher.getTestKind()) .build(); - testMethods.add(methodItem); + if (methodItem != null) { + testMethods.add(methodItem); + } break; } } @@ -474,7 +511,9 @@ public static List resolvePath(List arguments, IProgressMo .setLevel(TestLevel.PROJECT) .setKind(testKind) .build(); - result.add(projectItem); + if (projectItem != null) { + result.add(projectItem); + } final IPackageFragment packageFragment = (IPackageFragment) unit.getParent(); if (packageFragment == null || !(packageFragment instanceof IPackageFragment)) { @@ -484,9 +523,10 @@ public static List resolvePath(List arguments, IProgressMo .setLevel(TestLevel.PACKAGE) .setKind(testKind) .build(); - result.add(packageItem); + if (packageItem != null) { + result.add(packageItem); + } } - return result; } @@ -593,7 +633,6 @@ public void acceptTypeNameMatch(TypeNameMatch match) { } catch (JavaModelException e) { JUnitPlugin.log(e); } - return result[0]; } diff --git a/package.json b/package.json index a5c55c99..b7ccba78 100644 --- a/package.json +++ b/package.json @@ -56,18 +56,18 @@ "contributes": { "javaExtensions": [ "./server/com.microsoft.java.test.plugin-0.43.1.jar", - "./server/junit-jupiter-api_5.14.1.jar", + "./server/junit-jupiter-api_5.14.2.jar", "./server/junit-jupiter-api_6.0.1.jar", - "./server/junit-jupiter-engine_5.14.1.jar", + "./server/junit-jupiter-engine_5.14.2.jar", "./server/junit-jupiter-engine_6.0.1.jar", "./server/junit-jupiter-migrationsupport_5.14.1.jar", - "./server/junit-jupiter-params_5.14.1.jar", + "./server/junit-jupiter-params_5.14.2.jar", "./server/junit-jupiter-params_6.0.1.jar", - "./server/junit-platform-commons_1.14.1.jar", + "./server/junit-platform-commons_1.14.2.jar", "./server/junit-platform-commons_6.0.1.jar", - "./server/junit-platform-engine_1.14.1.jar", + "./server/junit-platform-engine_1.14.2.jar", "./server/junit-platform-engine_6.0.1.jar", - "./server/junit-platform-launcher_1.14.1.jar", + "./server/junit-platform-launcher_1.14.2.jar", "./server/junit-platform-launcher_6.0.1.jar", "./server/junit-platform-runner_1.14.1.jar", "./server/junit-platform-suite-api_1.14.1.jar", diff --git a/src/constants.ts b/src/constants.ts index 2023c384..23b6bbae 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -81,4 +81,6 @@ export namespace JUnitTestPart { export const TEST_TEMPLATE_INVOCATION: string = 'test-template-invocation:'; export const DYNAMIC_CONTAINER: string = 'dynamic-container:'; export const DYNAMIC_TEST: string = 'dynamic-test:'; + export const SPEC: string = 'spec:'; + export const FEATURE: string = 'feature:'; } diff --git a/src/runners/junitRunner/JUnitRunnerResultAnalyzer.ts b/src/runners/junitRunner/JUnitRunnerResultAnalyzer.ts index 4dc9f9c3..730a2435 100644 --- a/src/runners/junitRunner/JUnitRunnerResultAnalyzer.ts +++ b/src/runners/junitRunner/JUnitRunnerResultAnalyzer.ts @@ -191,7 +191,7 @@ export class JUnitRunnerResultAnalyzer extends RunnerResultAnalyzer { } protected getTestId(message: string): string { - if (message.includes('engine:junit5') || message.includes('engine:junit-jupiter') || message.includes('engine:jqwik')) { + if (message.includes('engine:junit5') || message.includes('engine:junit-jupiter') || message.includes('engine:jqwik') || message.includes('engine:spock')) { return this.getTestIdForJunit5Method(message); } else { return this.getTestIdForNonJunit5Method(message); @@ -218,14 +218,20 @@ export class JUnitRunnerResultAnalyzer extends RunnerResultAnalyzer { if (part.startsWith(JUnitTestPart.CLASS)) { className = part.substring(JUnitTestPart.CLASS.length); + } else if (part.startsWith(JUnitTestPart.SPEC)) { + className = part.substring(JUnitTestPart.SPEC.length); } else if (part.startsWith(JUnitTestPart.METHOD)) { const rawMethodName: string = part.substring(JUnitTestPart.METHOD.length); // If the method name exists then we want to include the '#' qualifier. methodName = `#${this.getJUnit5MethodName(rawMethodName)}`; - } else if (part.startsWith(JUnitTestPart.TEST_FACTORY)) { - const rawMethodName: string = part.substring(JUnitTestPart.TEST_FACTORY.length); + } else if (part.startsWith(JUnitTestPart.METHOD)) { + const rawMethodName: string = part.substring(JUnitTestPart.METHOD.length); // If the method name exists then we want to include the '#' qualifier. methodName = `#${this.getJUnit5MethodName(rawMethodName)}`; + } else if (part.startsWith(JUnitTestPart.FEATURE)) { + const rawMethodName: string = part.substring(JUnitTestPart.FEATURE.length); + // If the method name exists then we want to include the '#' qualifier. + methodName = '#' + rawMethodName + '()'; } else if (part.startsWith(JUnitTestPart.NESTED_CLASS)) { const nestedClassName: string = part.substring(JUnitTestPart.NESTED_CLASS.length); className = `${className}$${nestedClassName}`;