From a3c6c0343773fd6d53537b3849f4c29393c4ae41 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Tue, 2 Dec 2025 17:00:40 +0800 Subject: [PATCH 1/5] feat: support junit 6 import --- .../META-INF/MANIFEST.MF | 35 ++- .../launchers/JUnitLaunchConfiguration.java | 1 + .../JUnitLaunchConfigurationTemplate.java | 1 + .../plugin/launchers/JUnitLaunchUtils.java | 3 + .../java/test/plugin/model/TestKind.java | 11 +- .../plugin/provider/TestKindProvider.java | 6 +- .../plugin/searcher/JUnit6TestFinder.java | 260 ++++++++++++++++++ .../plugin/searcher/JUnit6TestSearcher.java | 142 ++++++++++ .../java/test/plugin/util/JUnitPlugin.java | 52 ++++ .../test/plugin/util/TestFrameworkUtils.java | 4 + .../test/plugin/util/TestGenerationUtils.java | 1 + .../com.microsoft.java.test.tp.target | 6 +- package.json | 44 +-- scripts/buildJdtlsExt.js | 84 +++--- src/controller/testController.ts | 4 +- src/java-test-runner.api.ts | 1 + .../junitRunner/JUnitRunnerResultAnalyzer.ts | 2 +- src/utils/launchUtils.ts | 2 +- 18 files changed, 582 insertions(+), 77 deletions(-) create mode 100644 java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/JUnit6TestFinder.java create mode 100644 java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/JUnit6TestSearcher.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 14a3ed0d..0939e668 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 @@ -24,19 +24,28 @@ Require-Bundle: org.eclipse.jdt.core, org.eclipse.jdt.junit.runtime, org.eclipse.jdt.junit4.runtime, org.eclipse.jdt.junit5.runtime, - junit-jupiter-api;bundle-version="5.4.0", - junit-jupiter-engine;bundle-version="5.4.0", - junit-jupiter-migrationsupport;bundle-version="5.4.0", - junit-jupiter-params;bundle-version="5.4.0", - junit-vintage-engine;bundle-version="5.4.0", - org.opentest4j;bundle-version="1.1.1", - junit-platform-commons;bundle-version="1.4.0", - junit-platform-engine;bundle-version="1.4.0", - junit-platform-launcher;bundle-version="1.4.0", - junit-platform-runner;bundle-version="1.4.0", - junit-platform-suite-api;bundle-version="1.4.0", - junit-platform-suite-commons;bundle-version="1.8.1", - junit-platform-suite-engine;bundle-version="1.8.1", + junit-jupiter-api;bundle-version="5.14.0", + junit-jupiter-engine;bundle-version="5.14.0", + junit-jupiter-migrationsupport;bundle-version="5.14.0", + junit-jupiter-params;bundle-version="5.14.0", + junit-vintage-engine;bundle-version="5.14.0", + junit-platform-commons;bundle-version="1.14.1", + junit-platform-engine;bundle-version="1.14.1", + junit-platform-launcher;bundle-version="1.14.1", + junit-platform-runner;bundle-version="1.14.1", + junit-platform-suite-api;bundle-version="1.14.1", + junit-platform-suite-commons;bundle-version="1.14.1", + junit-platform-suite-engine;bundle-version="1.14.1", + org.eclipse.jdt.junit6.runtime, + junit-jupiter-api;bundle-version="6.0.1", + junit-jupiter-engine;bundle-version="6.0.1", + junit-jupiter-params;bundle-version="6.0.1", + org.opentest4j;bundle-version="1.3.0", + junit-platform-commons;bundle-version="6.0.1", + junit-platform-engine;bundle-version="6.0.1", + junit-platform-launcher;bundle-version="6.0.1", + junit-platform-suite-api;bundle-version="6.0.1", + junit-platform-suite-engine;bundle-version="6.0.1", org.apiguardian.api;bundle-version="1.0.0", org.apache.commons.lang3;bundle-version="3.1.0", com.google.gson;bundle-version="2.7.0", diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfiguration.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfiguration.java index dc1f597c..5377f3a8 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfiguration.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfiguration.java @@ -100,6 +100,7 @@ public Map toValueMap() { final Map valueMap = new HashMap<>(); valueMap.put("testKind", testKind); valueMap.put("mainType", mainType); + valueMap.put("projectName", project.getName()); return valueMap; } } diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationTemplate.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationTemplate.java index 4c882efa..2b32510f 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationTemplate.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationTemplate.java @@ -23,6 +23,7 @@ public class JUnitLaunchConfigurationTemplate { "\n" + "\n" + "\n" + + "\n" + "\n" + "\n"; //@formatter:on diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/launchers/JUnitLaunchUtils.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/launchers/JUnitLaunchUtils.java index b2aa18f1..a98dcc03 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/launchers/JUnitLaunchUtils.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/launchers/JUnitLaunchUtils.java @@ -47,6 +47,7 @@ public class JUnitLaunchUtils { private static final String TESTNG_LOADER = "com.microsoft.java.test.loader.testng"; + private static final String JUNIT6_LOADER = "org.eclipse.jdt.junit.loader.junit6"; private static final String JUNIT5_LOADER = "org.eclipse.jdt.junit.loader.junit5"; private static final String JUNIT4_LOADER = "org.eclipse.jdt.junit.loader.junit4"; @@ -205,6 +206,8 @@ private static String getEclipseTestKind(TestKind testKind) { return JUNIT4_LOADER; case JUnit5: return JUNIT5_LOADER; + case JUnit6: + return JUNIT6_LOADER; case TestNG: return TESTNG_LOADER; default: diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/TestKind.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/TestKind.java index 5bdd5087..c80ab75c 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/TestKind.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/TestKind.java @@ -24,19 +24,24 @@ public enum TestKind { @SerializedName("2") TestNG(2), + @SerializedName("3") + JUnit6(3), + @SerializedName("100") None(100); private int value; public static TestKind fromString(String s) { - switch(s) { + switch (s) { case "Unknown": return None; case "JUnit 4": return JUnit; case "JUnit 5": return JUnit5; + case "JUnit 6": + return JUnit6; case "TestNG": return TestNG; default: @@ -53,6 +58,8 @@ public String toString() { return "JUnit 4"; case JUnit5: return "JUnit 5"; + case JUnit6: + return "JUnit 6"; case TestNG: return "TestNG"; default: @@ -64,7 +71,7 @@ public int getValue() { return this.value; } - private TestKind(int value){ + private TestKind(int value) { this.value = value; } } diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/provider/TestKindProvider.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/provider/TestKindProvider.java index af89d11f..fed66c2e 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/provider/TestKindProvider.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/provider/TestKindProvider.java @@ -16,6 +16,7 @@ import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.internal.junit.util.CoreTestSearchEngine; import java.util.HashMap; import java.util.LinkedList; @@ -44,7 +45,10 @@ public static List getTestKindsFromCache(IJavaProject javaProject) { private static List getTestKinds(IJavaProject javaProject) { final List result = new LinkedList<>(); try { - if (javaProject.findType(JUNIT5_TEST) != null) { + // Check for JUnit 6 first using Eclipse JDT's built-in detection + if (CoreTestSearchEngine.hasJUnit6TestAnnotation(javaProject)) { + result.add(TestKind.JUnit6); + } else if (CoreTestSearchEngine.hasJUnit5TestAnnotation(javaProject)) { result.add(TestKind.JUnit5); } 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 new file mode 100644 index 00000000..b1c5c4c9 --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/JUnit6TestFinder.java @@ -0,0 +1,260 @@ +/******************************************************************************* + * Copyright (c) 2017-2025 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.test.plugin.searcher; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.jdt.core.IClassFile; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IRegion; +import org.eclipse.jdt.core.ISourceRange; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.ITypeHierarchy; +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.AbstractTypeDeclaration; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.IAnnotationBinding; +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.RecordDeclaration; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.internal.junit.launcher.ITestFinder; +import org.eclipse.jdt.internal.junit.util.CoreTestSearchEngine; + +import java.util.HashSet; +import java.util.Set; + +/** + * Test finder for JUnit 6 (Jupiter API 6.x). + * + * This class is similar to JUnit5TestFinder but uses the JUnit 6 loader + * to properly detect tests in JUnit 6 projects. + */ +public class JUnit6TestFinder implements ITestFinder { + + private static final String JUNIT6_LOADER = "org.eclipse.jdt.junit.loader.junit6"; + + public JUnit6TestFinder() { + } + + @Override + public void findTestsInContainer(IJavaElement element, Set result, IProgressMonitor pm) + throws CoreException { + if (element == null || result == null) { + throw new IllegalArgumentException(); + } + + if (element instanceof IType) { + final IType type = (IType) element; + if (internalIsTest(type, pm)) { + result.add(type); + } + return; + } + + final SubMonitor subMonitor = SubMonitor.convert(pm, "Searching for JUnit 6 tests...", 4); + + final IRegion region = CoreTestSearchEngine.getRegion(element); + final ITypeHierarchy hierarchy = JavaCore.newTypeHierarchy(region, null, subMonitor.split(1)); + final IType[] allClasses = hierarchy.getAllClasses(); + + for (final IType type : allClasses) { + if (region.contains(type) && internalIsTest(type, pm)) { + addTypeAndSubtypes(type, result, hierarchy); + } + } + + // Also find JUnit 3 style tests that implement junit.framework.Test + final IType testInterface = element.getJavaProject().findType("junit.framework.Test"); + if (testInterface != null) { + CoreTestSearchEngine.findTestImplementorClasses(hierarchy, testInterface, region, result); + } + + CoreTestSearchEngine.findSuiteMethods(element, result, subMonitor.split(1)); + } + + private void addTypeAndSubtypes(IType type, Set result, ITypeHierarchy hierarchy) { + if (result.add(type)) { + final IType[] subclasses = hierarchy.getSubclasses(type); + for (final IType subclass : subclasses) { + addTypeAndSubtypes(subclass, result, hierarchy); + } + } + } + + @Override + public boolean isTest(IType type) throws JavaModelException { + return internalIsTest(type, null); + } + + private boolean internalIsTest(IType type, IProgressMonitor pm) throws JavaModelException { + // Use JUnit 6 loader to check if the class is accessible + if (!CoreTestSearchEngine.isAccessibleClass(type, JUNIT6_LOADER)) { + return false; + } + + if (CoreTestSearchEngine.hasSuiteMethod(type)) { + return true; + } + + final ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); + + if (type.getCompilationUnit() != null) { + parser.setSource(type.getCompilationUnit()); + } else if (!isAvailable(type.getSourceRange())) { + parser.setProject(type.getJavaProject()); + final IBinding[] bindings = parser.createBindings(new IJavaElement[] { type }, pm); + if (bindings.length == 1 && bindings[0] instanceof ITypeBinding) { + final ITypeBinding typeBinding = (ITypeBinding) bindings[0]; + return isTest(typeBinding); + } + return false; + } else { + final IClassFile classFile = type.getClassFile(); + if (classFile != null) { + parser.setSource(classFile); + } else { + return false; + } + } + + parser.setFocalPosition(0); + parser.setResolveBindings(true); + + final CompilationUnit cu = (CompilationUnit) parser.createAST(pm); + final ASTNode node = cu.findDeclaringNode(type.getKey()); + + if (node instanceof TypeDeclaration || node instanceof RecordDeclaration) { + final AbstractTypeDeclaration typeDecl = (AbstractTypeDeclaration) node; + final ITypeBinding binding = typeDecl.resolveBinding(); + if (binding != null) { + return isTest(binding); + } + } + + return false; + } + + private static boolean isAvailable(ISourceRange range) { + return range != null && range.getOffset() != -1; + } + + private boolean isTest(ITypeBinding typeBinding) { + if (typeBinding == null) { + return false; + } + + // Check if the type itself has test methods + if (hasTestMethods(typeBinding)) { + 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; + } + + private boolean hasTestMethods(ITypeBinding typeBinding) { + for (final IMethodBinding method : typeBinding.getDeclaredMethods()) { + if (isTestMethod(method)) { + return true; + } + } + return false; + } + + private boolean isTestMethod(IMethodBinding method) { + for (final IAnnotationBinding annotation : method.getAnnotations()) { + if (annotation == null) { + continue; + } + final ITypeBinding annotationType = annotation.getAnnotationType(); + if (annotationType != null) { + // Check for @Testable meta-annotation + if (isTestableAnnotation(annotationType, new HashSet<>())) { + return true; + } + } + } + return false; + } + + private boolean isTestableAnnotation(ITypeBinding annotationType, Set visited) { + if (annotationType == null || !visited.add(annotationType)) { + return false; + } + + final String qualifiedName = annotationType.getQualifiedName(); + + // Direct check for @Testable + if ("org.junit.platform.commons.annotation.Testable".equals(qualifiedName)) { + return true; + } + + // Check for common JUnit Jupiter test annotations + if (qualifiedName.startsWith("org.junit.jupiter.api.")) { + if (qualifiedName.equals("org.junit.jupiter.api.Test") || + qualifiedName.equals("org.junit.jupiter.api.RepeatedTest") || + qualifiedName.equals("org.junit.jupiter.api.ParameterizedTest") || + qualifiedName.equals("org.junit.jupiter.api.TestFactory") || + qualifiedName.equals("org.junit.jupiter.api.TestTemplate")) { + return true; + } + } + + // Check meta-annotations + for (final IAnnotationBinding metaAnnotation : annotationType.getAnnotations()) { + if (metaAnnotation == null) { + continue; + } + final ITypeBinding metaAnnotationType = metaAnnotation.getAnnotationType(); + if (isTestableAnnotation(metaAnnotationType, visited)) { + return true; + } + } + + return false; + } + + private boolean isNestedTestClass(ITypeBinding nestedType) { + for (final IAnnotationBinding annotation : nestedType.getAnnotations()) { + if (annotation == null) { + continue; + } + final ITypeBinding annotationType = annotation.getAnnotationType(); + if (annotationType != null && + "org.junit.jupiter.api.Nested".equals(annotationType.getQualifiedName())) { + return isTest(nestedType); + } + } + return false; + } +} diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/JUnit6TestSearcher.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/JUnit6TestSearcher.java new file mode 100644 index 00000000..02395697 --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/searcher/JUnit6TestSearcher.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * Copyright (c) 2017-2025 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.microsoft.java.test.plugin.searcher; + +import com.microsoft.java.test.plugin.model.TestKind; +import com.microsoft.java.test.plugin.util.TestFrameworkUtils; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.jdt.core.IJavaElement; +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.dom.Modifier; +import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Test searcher for JUnit 6 (Jupiter API 6.x). + * + * JUnit 6 builds upon JUnit 5's Jupiter platform and shares the same core infrastructure. + * This searcher uses JUnit 6's test finder to properly detect tests in JUnit 6 projects. + */ +public class JUnit6TestSearcher extends BaseFrameworkSearcher { + + private static final JUnit6TestFinder JUNIT6_TEST_FINDER = new JUnit6TestFinder(); + + public static final String JUPITER_NESTED = "org.junit.jupiter.api.Nested"; + public static final String JUNIT_PLATFORM_TESTABLE = "org.junit.platform.commons.annotation.Testable"; + + protected static final String DISPLAY_NAME_ANNOTATION_JUNIT6 = "org.junit.jupiter.api.DisplayName"; + + public JUnit6TestSearcher() { + super(); + this.testMethodAnnotations = new String[] { JUNIT_PLATFORM_TESTABLE }; + } + + @Override + public TestKind getTestKind() { + return TestKind.JUnit6; + } + + @Override + public String getJdtTestKind() { + // JUnit 6 uses the same JDT test kind as JUnit 6 + return TestKindRegistry.JUNIT6_TEST_KIND_ID; + } + + @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()); + } + + @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; + } + + @Override + public boolean isTestClass(IType type) throws JavaModelException { + return JUNIT6_TEST_FINDER.isTest(type); + } + + 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; + } + + @Override + public Set findTestItemsInContainer(IJavaElement element, IProgressMonitor monitor) throws CoreException { + final Set types = new HashSet<>(); + try { + JUNIT6_TEST_FINDER.findTestsInContainer(element, types, monitor); + } catch (OperationCanceledException e) { + return Collections.emptySet(); + } + return types; + } +} diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/JUnitPlugin.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/JUnitPlugin.java index 1f3231a2..73949846 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/JUnitPlugin.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/JUnitPlugin.java @@ -19,6 +19,7 @@ import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; +import org.osgi.framework.Bundle; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; @@ -53,6 +54,57 @@ public class JUnitPlugin implements BundleActivator { public void start(BundleContext context) throws Exception { handler.addElementChangeListener(); JUnitPlugin.context = context; + + // Diagnostic logging for JUnit runtime bundles + logBundleDiagnostics(context); + } + + private void logBundleDiagnostics(BundleContext context) { + final String[] bundlesToCheck = { + "org.eclipse.jdt.junit.runtime", + "org.eclipse.jdt.junit4.runtime", + "org.eclipse.jdt.junit5.runtime", + "org.eclipse.jdt.junit6.runtime", + "junit-jupiter-api", + "junit-jupiter-engine", + "junit-platform-launcher", + "junit-platform-commons", + "junit-platform-engine", + "org.junit", + "org.opentest4j" + }; + + final StringBuilder sb = new StringBuilder(); + sb.append("\n====== JUnit Bundle Diagnostics ======\n"); + + for (final String symbolicName : bundlesToCheck) { + final Bundle[] bundles = Platform.getBundles(symbolicName, null); + if (bundles == null || bundles.length == 0) { + sb.append("MISSING: ").append(symbolicName).append("\n"); + } else { + for (final Bundle bundle : bundles) { + final String state = getBundleStateString(bundle.getState()); + sb.append("FOUND: ").append(symbolicName) + .append(" v").append(bundle.getVersion()) + .append(" [").append(state).append("]\n"); + } + } + } + + sb.append("======================================\n"); + log(new Status(IStatus.INFO, PLUGIN_ID, sb.toString())); + } + + private String getBundleStateString(int state) { + switch (state) { + case Bundle.UNINSTALLED: return "UNINSTALLED"; + case Bundle.INSTALLED: return "INSTALLED"; + case Bundle.RESOLVED: return "RESOLVED"; + case Bundle.STARTING: return "STARTING"; + case Bundle.STOPPING: return "STOPPING"; + case Bundle.ACTIVE: return "ACTIVE"; + default: return "UNKNOWN(" + state + ")"; + } } /* 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 00cc1223..b3e8677e 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 @@ -14,6 +14,7 @@ import com.microsoft.java.test.plugin.model.TestKind; import com.microsoft.java.test.plugin.searcher.JUnit4TestSearcher; import com.microsoft.java.test.plugin.searcher.JUnit5TestSearcher; +import com.microsoft.java.test.plugin.searcher.JUnit6TestSearcher; import com.microsoft.java.test.plugin.searcher.TestFrameworkSearcher; import com.microsoft.java.test.plugin.searcher.TestNGTestSearcher; @@ -25,6 +26,7 @@ public class TestFrameworkUtils { public static final TestFrameworkSearcher JUNIT4_TEST_SEARCHER = new JUnit4TestSearcher(); public static final TestFrameworkSearcher JUNIT5_TEST_SEARCHER = new JUnit5TestSearcher(); + public static final TestFrameworkSearcher JUNIT6_TEST_SEARCHER = new JUnit6TestSearcher(); public static final TestFrameworkSearcher TESTNG_TEST_SEARCHER = new TestNGTestSearcher(); public static boolean isEquivalentAnnotationType(ITypeBinding annotationType, String annotationName) { @@ -37,6 +39,8 @@ public static TestFrameworkSearcher getSearcherByTestKind(TestKind kind) { return JUNIT4_TEST_SEARCHER; case JUnit5: return JUNIT5_TEST_SEARCHER; + case JUnit6: + return JUNIT6_TEST_SEARCHER; case TestNG: return TESTNG_TEST_SEARCHER; default: diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestGenerationUtils.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestGenerationUtils.java index f75cab3d..c3509134 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestGenerationUtils.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/TestGenerationUtils.java @@ -764,6 +764,7 @@ private static List getLifecycleAnnotations(TestKind testKind) { list.add(JUNIT4_AFTER_CLASS_ANNOTATION); break; case JUnit5: + case JUnit6: list.add(JUNIT5_BEFORE_CLASS_ANNOTATION); list.add(JUNIT5_SET_UP_ANNOTATION); list.add(JUNIT5_TEAR_DOWN_ANNOTATION); diff --git a/java-extension/com.microsoft.java.test.target/com.microsoft.java.test.tp.target b/java-extension/com.microsoft.java.test.target/com.microsoft.java.test.tp.target index 5d387936..aa39c2f6 100644 --- a/java-extension/com.microsoft.java.test.target/com.microsoft.java.test.tp.target +++ b/java-extension/com.microsoft.java.test.target/com.microsoft.java.test.tp.target @@ -4,17 +4,15 @@ - - - + - + diff --git a/package.json b/package.json index e10303a6..b22d50cc 100644 --- a/package.json +++ b/package.json @@ -55,24 +55,36 @@ "main": "./main.js", "contributes": { "javaExtensions": [ - "./server/junit-jupiter-api_5.11.0.jar", - "./server/junit-jupiter-engine_5.11.0.jar", - "./server/junit-jupiter-migrationsupport_5.11.0.jar", - "./server/junit-jupiter-params_5.11.0.jar", - "./server/junit-platform-commons_1.11.0.jar", - "./server/junit-platform-engine_1.11.0.jar", - "./server/junit-platform-launcher_1.11.0.jar", - "./server/junit-platform-runner_1.11.0.jar", - "./server/junit-platform-suite-api_1.11.0.jar", - "./server/junit-platform-suite-commons_1.11.0.jar", - "./server/junit-platform-suite-engine_1.11.0.jar", - "./server/junit-vintage-engine_5.11.0.jar", + "./server/com.microsoft.java.test.plugin-0.43.1.jar", + "./server/junit-jupiter-api_5.14.1.jar", + "./server/junit-jupiter-api_6.0.1.jar", + "./server/junit-jupiter-engine_5.14.1.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_6.0.1.jar", + "./server/junit-platform-commons_1.14.1.jar", + "./server/junit-platform-commons_6.0.1.jar", + "./server/junit-platform-engine_1.14.1.jar", + "./server/junit-platform-engine_6.0.1.jar", + "./server/junit-platform-launcher_1.14.1.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", + "./server/junit-platform-suite-api_6.0.1.jar", + "./server/junit-platform-suite-commons_1.14.1.jar", + "./server/junit-platform-suite-engine_1.14.1.jar", + "./server/junit-platform-suite-engine_6.0.1.jar", + "./server/junit-vintage-engine_5.14.1.jar", "./server/org.apiguardian.api_1.1.2.jar", - "./server/org.eclipse.jdt.junit4.runtime_1.3.100.v20231214-1952.jar", - "./server/org.eclipse.jdt.junit5.runtime_1.1.300.v20231214-1952.jar", - "./server/org.opentest4j_1.3.0.jar", + "./server/org.eclipse.jdt.junit.core_3.14.0.v20251201-1407.jar", + "./server/org.eclipse.jdt.junit.runtime_3.8.0.v20251113-1434.jar", + "./server/org.eclipse.jdt.junit4.runtime_1.4.0.v20251113-1434.jar", + "./server/org.eclipse.jdt.junit5.runtime_1.2.0.v20251113-1434.jar", + "./server/org.eclipse.jdt.junit6.runtime_1.0.0.v20251112-1701.jar", "./server/org.jacoco.core_0.8.14.202510111229.jar", - "./server/com.microsoft.java.test.plugin-0.43.1.jar" + "./server/org.junit_4.13.2.v20240929-1000.jar", + "./server/org.opentest4j_1.3.0.jar" ], "viewsWelcome": [ { diff --git a/scripts/buildJdtlsExt.js b/scripts/buildJdtlsExt.js index 87c29f2d..7d60a288 100644 --- a/scripts/buildJdtlsExt.js +++ b/scripts/buildJdtlsExt.js @@ -9,24 +9,31 @@ const fse = require('fs-extra'); fse.removeSync('server'); const serverDir = path.resolve('java-extension'); +// Bundle prefixes to copy from the p2 repository. +// Each prefix may match multiple versions (e.g., junit-jupiter-api_5.x and junit-jupiter-api_6.x) +// to support both JUnit 5 and JUnit 6. const bundleList = [ 'org.eclipse.jdt.junit4.runtime_', 'org.eclipse.jdt.junit5.runtime_', - 'junit-jupiter-api', - 'junit-jupiter-engine', - 'junit-jupiter-migrationsupport', - 'junit-jupiter-params', - 'junit-vintage-engine', - 'org.opentest4j', - 'junit-platform-commons', - 'junit-platform-engine', - 'junit-platform-launcher', - 'junit-platform-runner', - 'junit-platform-suite-api', - 'junit-platform-suite-commons', - 'junit-platform-suite-engine', - 'org.apiguardian.api', - 'org.jacoco.core' + 'org.eclipse.jdt.junit6.runtime_', + 'org.eclipse.jdt.junit.runtime_', + 'org.eclipse.jdt.junit.core_', + 'org.junit_', + 'junit-jupiter-api_', + 'junit-jupiter-engine_', + 'junit-jupiter-migrationsupport_', + 'junit-jupiter-params_', + 'junit-vintage-engine_', + 'org.opentest4j_', + 'junit-platform-commons_', + 'junit-platform-engine_', + 'junit-platform-launcher_', + 'junit-platform-runner_', + 'junit-platform-suite-api_', + 'junit-platform-suite-commons_', + 'junit-platform-suite-engine_', + 'org.apiguardian.api_', + 'org.jacoco.core_' ]; cp.execSync(`${mvnw()} clean verify`, { cwd: serverDir, stdio: [0, 1, 2] }); copy(path.join(serverDir, 'com.microsoft.java.test.plugin/target'), path.resolve('server'), (file) => path.extname(file) === '.jar'); @@ -46,33 +53,34 @@ function copy(sourceFolder, targetFolder, fileFilter) { } function updateVersion() { - // Update the version + // Update the version - rebuild javaExtensions from actual server folder contents const packageJsonData = require('../package.json'); - const javaExtensions = packageJsonData.contributes.javaExtensions; - if (Array.isArray(javaExtensions)) { - packageJsonData.contributes.javaExtensions = javaExtensions.map((extensionString) => { - const ind = extensionString.indexOf('_'); - const fileName = findNewRequiredJar(extensionString.substring(extensionString.lastIndexOf('/') + 1, ind)); - if (ind >= 0) { - return extensionString.substring(0, extensionString.lastIndexOf('/') + 1) + fileName; + const destFolder = path.resolve('./server'); + const files = fs.readdirSync(destFolder); + + // Build new javaExtensions list from all jar files in server folder + // that match our bundleList prefixes, plus the plugin jar + const newJavaExtensions = []; + + for (const file of files) { + if (file.endsWith('.jar')) { + // Check if this file matches any bundle prefix or is the plugin jar + const isBundle = bundleList.some(prefix => file.startsWith(prefix)); + const isPlugin = file.startsWith('com.microsoft.java.test.plugin'); + + if (isBundle || isPlugin) { + newJavaExtensions.push('./server/' + file); } - return extensionString; - }); - - fs.writeFileSync(path.resolve('package.json'), JSON.stringify(packageJsonData, null, 4)); - fs.appendFileSync(path.resolve('package.json'), os.EOL); + } } -} + + // Sort for consistent ordering + newJavaExtensions.sort(); + + packageJsonData.contributes.javaExtensions = newJavaExtensions; -// The plugin jar follows the name convention: _.jar -function findNewRequiredJar(fileName) { - fileName = fileName + "_"; - const destFolder = path.resolve('./server'); - const files = fs.readdirSync(destFolder); - const f = files.find((file) => { - return file.indexOf(fileName) >= 0; - }); - return f; + fs.writeFileSync(path.resolve('package.json'), JSON.stringify(packageJsonData, null, 4)); + fs.appendFileSync(path.resolve('package.json'), os.EOL); } function downloadJacocoAgent() { diff --git a/src/controller/testController.ts b/src/controller/testController.ts index 4242f7ae..26cf4131 100644 --- a/src/controller/testController.ts +++ b/src/controller/testController.ts @@ -529,7 +529,7 @@ function mergeInvocations(testItems: TestItem[]): TestItem[] { const invocationsToMerge: TestItem[] = _.flatten([...invocationsPerMethod.entries()] .filter(([method, invs]) => method.children.size === invs.size) .map(([, invs]) => [...invs])); - /* eslint-enable @typescript-eslint/typedef */ + /* eslint-enable @typescript-eslint/typedef */ return _.uniq(testItems.map((item: TestItem) => invocationsToMerge.includes(item) ? item.parent! : item)); } @@ -696,6 +696,7 @@ function getRunnerByContext(testContext: IRunTestContext): BaseRunner | undefine switch (testContext.kind) { case TestKind.JUnit: case TestKind.JUnit5: + case TestKind.JUnit6: return new JUnitRunner(testContext); case TestKind.TestNG: return new TestNGRunner(testContext); @@ -711,6 +712,7 @@ function trackTestFrameworkVersion(testKind: TestKind, classpaths: string[], mod artifactPattern = /junit-(\d+\.\d+\.\d+(-[a-zA-Z\d]+)?).jar/; break; case TestKind.JUnit5: + case TestKind.JUnit6: artifactPattern = /junit-jupiter-api-(\d+\.\d+\.\d+(-[a-zA-Z\d]+)?).jar/; break; case TestKind.TestNG: diff --git a/src/java-test-runner.api.ts b/src/java-test-runner.api.ts index df35283b..7688e5d6 100644 --- a/src/java-test-runner.api.ts +++ b/src/java-test-runner.api.ts @@ -220,6 +220,7 @@ export enum TestKind { JUnit5 = 0, JUnit = 1, TestNG = 2, + JUnit6 = 3, None = 100, } diff --git a/src/runners/junitRunner/JUnitRunnerResultAnalyzer.ts b/src/runners/junitRunner/JUnitRunnerResultAnalyzer.ts index 5a376a2a..5146c1d1 100644 --- a/src/runners/junitRunner/JUnitRunnerResultAnalyzer.ts +++ b/src/runners/junitRunner/JUnitRunnerResultAnalyzer.ts @@ -349,7 +349,7 @@ export class JUnitRunnerResultAnalyzer extends RunnerResultAnalyzer { const isDynamic: boolean = result[4] === 'true'; const parentIndex: string = result[5]; const displayName: string = result[6].replace(/\\,/g, ','); - const uniqueId: string | undefined = this.testContext.kind === TestKind.JUnit5 ? + const uniqueId: string | undefined = (this.testContext.kind === TestKind.JUnit5 || this.testContext.kind === TestKind.JUnit6) ? result[8]?.replace(/\\,/g, ',') : undefined; let testItem: TestItem | undefined; diff --git a/src/utils/launchUtils.ts b/src/utils/launchUtils.ts index 53500e20..433b8486 100644 --- a/src/utils/launchUtils.ts +++ b/src/utils/launchUtils.ts @@ -60,7 +60,7 @@ export async function resolveLaunchConfigurationForRunner(runner: BaseRunner, te ], args: [ ...launchArguments.programArguments, - ...(testContext.kind === TestKind.JUnit5 ? parseTags(config) : []) + ...(testContext.kind === TestKind.JUnit5 || testContext.kind === TestKind.JUnit6 ? parseTags(config) : []) ], }); } From 413ab96bc568994b457718f1fa95c47a2a74b401 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Wed, 3 Dec 2025 16:16:53 +0800 Subject: [PATCH 2/5] test: update --- .../plugin/launchers/JUnitLaunchConfigurationDelegate.java | 3 ++- src/runners/junitRunner/JUnitRunnerResultAnalyzer.ts | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) 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 d0910410..ddbfb262 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 @@ -147,7 +147,8 @@ private void addTestItemArgs(List arguments) throws CoreException { arguments.add("-test"); final IMethod method = (IMethod) JavaCore.create(this.args.testNames[0]); String testName = method.getElementName(); - if (this.args.testKind == TestKind.JUnit5 && method.getParameters().length > 0) { + if ((this.args.testKind == TestKind.JUnit5 || this.args.testKind == TestKind.JUnit6) && + method.getParameters().length > 0) { final ICompilationUnit unit = method.getCompilationUnit(); if (unit == null) { throw new CoreException(new Status(IStatus.ERROR, JUnitPlugin.PLUGIN_ID, IStatus.ERROR, diff --git a/src/runners/junitRunner/JUnitRunnerResultAnalyzer.ts b/src/runners/junitRunner/JUnitRunnerResultAnalyzer.ts index 5146c1d1..4dc9f9c3 100644 --- a/src/runners/junitRunner/JUnitRunnerResultAnalyzer.ts +++ b/src/runners/junitRunner/JUnitRunnerResultAnalyzer.ts @@ -324,6 +324,7 @@ export class JUnitRunnerResultAnalyzer extends RunnerResultAnalyzer { 'org.eclipse.jdt.internal.junit.runner.', 'org.eclipse.jdt.internal.junit4.runner.', 'org.eclipse.jdt.internal.junit5.runner.', + 'org.eclipse.jdt.internal.junit6.runner.', 'org.eclipse.jdt.internal.junit.ui.', 'junit.framework.TestCase', 'junit.framework.TestResult', @@ -398,7 +399,8 @@ export class JUnitRunnerResultAnalyzer extends RunnerResultAnalyzer { } if (testItem) { - if (dataCache.get(testItem)?.testKind === TestKind.JUnit5 && + if ((dataCache.get(testItem)?.testKind === TestKind.JUnit5 || + dataCache.get(testItem)?.testKind === TestKind.JUnit6) && this.getLabelWithoutCodicon(testItem.label) !== displayName) { testItem.description = displayName; } else { From bc3b46276c1f704399fd3f0deda1e8c7596ba699 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Wed, 3 Dec 2025 16:19:13 +0800 Subject: [PATCH 3/5] test: update --- .../java/test/plugin/util/JUnitPlugin.java | 51 ------------------- package.json | 4 +- 2 files changed, 2 insertions(+), 53 deletions(-) diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/JUnitPlugin.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/JUnitPlugin.java index 73949846..52103b3f 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/JUnitPlugin.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/JUnitPlugin.java @@ -54,57 +54,6 @@ public class JUnitPlugin implements BundleActivator { public void start(BundleContext context) throws Exception { handler.addElementChangeListener(); JUnitPlugin.context = context; - - // Diagnostic logging for JUnit runtime bundles - logBundleDiagnostics(context); - } - - private void logBundleDiagnostics(BundleContext context) { - final String[] bundlesToCheck = { - "org.eclipse.jdt.junit.runtime", - "org.eclipse.jdt.junit4.runtime", - "org.eclipse.jdt.junit5.runtime", - "org.eclipse.jdt.junit6.runtime", - "junit-jupiter-api", - "junit-jupiter-engine", - "junit-platform-launcher", - "junit-platform-commons", - "junit-platform-engine", - "org.junit", - "org.opentest4j" - }; - - final StringBuilder sb = new StringBuilder(); - sb.append("\n====== JUnit Bundle Diagnostics ======\n"); - - for (final String symbolicName : bundlesToCheck) { - final Bundle[] bundles = Platform.getBundles(symbolicName, null); - if (bundles == null || bundles.length == 0) { - sb.append("MISSING: ").append(symbolicName).append("\n"); - } else { - for (final Bundle bundle : bundles) { - final String state = getBundleStateString(bundle.getState()); - sb.append("FOUND: ").append(symbolicName) - .append(" v").append(bundle.getVersion()) - .append(" [").append(state).append("]\n"); - } - } - } - - sb.append("======================================\n"); - log(new Status(IStatus.INFO, PLUGIN_ID, sb.toString())); - } - - private String getBundleStateString(int state) { - switch (state) { - case Bundle.UNINSTALLED: return "UNINSTALLED"; - case Bundle.INSTALLED: return "INSTALLED"; - case Bundle.RESOLVED: return "RESOLVED"; - case Bundle.STARTING: return "STARTING"; - case Bundle.STOPPING: return "STOPPING"; - case Bundle.ACTIVE: return "ACTIVE"; - default: return "UNKNOWN(" + state + ")"; - } } /* diff --git a/package.json b/package.json index b22d50cc..4f8afe69 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ "main": "./main.js", "contributes": { "javaExtensions": [ - "./server/com.microsoft.java.test.plugin-0.43.1.jar", "./server/junit-jupiter-api_5.14.1.jar", "./server/junit-jupiter-api_6.0.1.jar", "./server/junit-jupiter-engine_5.14.1.jar", @@ -84,7 +83,8 @@ "./server/org.eclipse.jdt.junit6.runtime_1.0.0.v20251112-1701.jar", "./server/org.jacoco.core_0.8.14.202510111229.jar", "./server/org.junit_4.13.2.v20240929-1000.jar", - "./server/org.opentest4j_1.3.0.jar" + "./server/org.opentest4j_1.3.0.jar", + "./server/com.microsoft.java.test.plugin-0.43.1.jar" ], "viewsWelcome": [ { From 32ccb893c87adce457fe0ed6e13ec7aed7edff14 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Wed, 3 Dec 2025 16:22:43 +0800 Subject: [PATCH 4/5] feat: update --- .../java/com/microsoft/java/test/plugin/util/JUnitPlugin.java | 1 - package.json | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/JUnitPlugin.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/JUnitPlugin.java index 52103b3f..1f3231a2 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/JUnitPlugin.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/util/JUnitPlugin.java @@ -19,7 +19,6 @@ import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; -import org.osgi.framework.Bundle; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; diff --git a/package.json b/package.json index 4f8afe69..b22d50cc 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "main": "./main.js", "contributes": { "javaExtensions": [ + "./server/com.microsoft.java.test.plugin-0.43.1.jar", "./server/junit-jupiter-api_5.14.1.jar", "./server/junit-jupiter-api_6.0.1.jar", "./server/junit-jupiter-engine_5.14.1.jar", @@ -83,8 +84,7 @@ "./server/org.eclipse.jdt.junit6.runtime_1.0.0.v20251112-1701.jar", "./server/org.jacoco.core_0.8.14.202510111229.jar", "./server/org.junit_4.13.2.v20240929-1000.jar", - "./server/org.opentest4j_1.3.0.jar", - "./server/com.microsoft.java.test.plugin-0.43.1.jar" + "./server/org.opentest4j_1.3.0.jar" ], "viewsWelcome": [ { From 13b5762b405ffe62c12a9bcdadb84ca5abd95332 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Wed, 3 Dec 2025 17:18:20 +0800 Subject: [PATCH 5/5] feat: updat code --- .../plugin/searcher/JUnit6TestFinder.java | 33 ++++++- .../plugin/searcher/JUnit6TestSearcher.java | 94 +++---------------- 2 files changed, 42 insertions(+), 85 deletions(-) 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 b1c5c4c9..2580fa10 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 @@ -31,6 +31,7 @@ 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.Modifier; import org.eclipse.jdt.core.dom.RecordDeclaration; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.internal.junit.launcher.ITestFinder; @@ -42,11 +43,28 @@ /** * Test finder for JUnit 6 (Jupiter API 6.x). * - * This class is similar to JUnit5TestFinder but uses the JUnit 6 loader + *

This class is similar to JUnit5TestFinder but uses the JUnit 6 loader * to properly detect tests in JUnit 6 projects. + * + *

Why this class exists: Eclipse JDT does not yet have built-in support for JUnit 6. + * This is a custom implementation that will be needed until Eclipse JDT adds official JUnit 6 support. + * + *

Key differences from JUnit5TestFinder: + *

    + *
  • Uses custom loader ID "org.eclipse.jdt.junit.loader.junit6"
  • + *
  • Filters out abstract classes explicitly
  • + *
  • Only supports JUnit Jupiter annotations (not JUnit 4 vintage)
  • + *
+ * + * @see org.eclipse.jdt.internal.junit.launcher.JUnit5TestFinder */ public class JUnit6TestFinder implements ITestFinder { + /** + * Custom loader ID for JUnit 6 tests. + * This is used by Eclipse JDT's classpath resolution mechanism to identify + * JUnit 6 test runtime dependencies. + */ private static final String JUNIT6_LOADER = "org.eclipse.jdt.junit.loader.junit6"; public JUnit6TestFinder() { @@ -155,7 +173,7 @@ private static boolean isAvailable(ISourceRange range) { } private boolean isTest(ITypeBinding typeBinding) { - if (typeBinding == null) { + if (typeBinding == null || Modifier.isAbstract(typeBinding.getModifiers())) { return false; } @@ -207,6 +225,15 @@ private boolean isTestMethod(IMethodBinding method) { return false; } + /** + * Checks if an annotation or its meta-annotations is a JUnit testable annotation. + * This supports JUnit Platform's meta-annotation model where custom annotations + * can be annotated with @Testable to make them test annotations. + * + * @param annotationType the annotation type to check + * @param visited set of already visited annotations to prevent infinite recursion + * @return true if this is a testable annotation or has @Testable in its hierarchy + */ private boolean isTestableAnnotation(ITypeBinding annotationType, Set visited) { if (annotationType == null || !visited.add(annotationType)) { return false; @@ -214,7 +241,7 @@ private boolean isTestableAnnotation(ITypeBinding annotationType, SetJUnit 6 is an evolutionary release built on top of JUnit 5's Jupiter platform. + * It maintains full backward compatibility with JUnit 5 while adding improvements + * and new features. This class extends JUnit5TestSearcher to inherit all the + * Jupiter test detection logic, only overriding the parts specific to JUnit 6: + *
    + *
  • Test kind identification (JUnit6 vs JUnit5)
  • + *
  • Test finder instance (uses JUnit6TestFinder for proper classpath resolution)
  • + *
+ * + * @see JUnit5TestSearcher + * @see JUnit6TestFinder */ -public class JUnit6TestSearcher extends BaseFrameworkSearcher { +public class JUnit6TestSearcher extends JUnit5TestSearcher { private static final JUnit6TestFinder JUNIT6_TEST_FINDER = new JUnit6TestFinder(); - public static final String JUPITER_NESTED = "org.junit.jupiter.api.Nested"; - public static final String JUNIT_PLATFORM_TESTABLE = "org.junit.platform.commons.annotation.Testable"; - - protected static final String DISPLAY_NAME_ANNOTATION_JUNIT6 = "org.junit.jupiter.api.DisplayName"; - - public JUnit6TestSearcher() { - super(); - this.testMethodAnnotations = new String[] { JUNIT_PLATFORM_TESTABLE }; - } - @Override public TestKind getTestKind() { return TestKind.JUnit6; @@ -57,78 +51,14 @@ public TestKind getTestKind() { @Override public String getJdtTestKind() { - // JUnit 6 uses the same JDT test kind as JUnit 6 return TestKindRegistry.JUNIT6_TEST_KIND_ID; } - @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()); - } - - @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; - } - @Override public boolean isTestClass(IType type) throws JavaModelException { return JUNIT6_TEST_FINDER.isTest(type); } - 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; - } - @Override public Set findTestItemsInContainer(IJavaElement element, IProgressMonitor monitor) throws CoreException { final Set types = new HashSet<>();