From 0ba0e6c8e635b12c20d13ef5ad87b0fd68861632 Mon Sep 17 00:00:00 2001 From: Vinay Kumar Maheshwaram <54681400+sudovinay01@users.noreply.github.com> Date: Sun, 27 Sep 2026 12:09:13 +0530 Subject: [PATCH 1/7] fix: discover inherited test methods when running child classes --- .../java/example/InheritanceBaseTest.java | 9 ++ .../java/example/InheritanceChildTest.java | 9 ++ .../java/example/InheritanceOverrideTest.java | 14 +++ .../test/plugin/util/TestSearchUtilsTest.java | 38 +++++++- .../java/test/plugin/model/JavaTestItem.java | 4 + .../test/plugin/util/TestSearchUtils.java | 93 +++++++++++++++++++ 6 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/InheritanceBaseTest.java create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/InheritanceChildTest.java create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/InheritanceOverrideTest.java diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/InheritanceBaseTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/InheritanceBaseTest.java new file mode 100644 index 000000000..8568f7e15 --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/InheritanceBaseTest.java @@ -0,0 +1,9 @@ +package example; + +import org.junit.jupiter.api.Test; + +public class InheritanceBaseTest { + @Test + void base() { + } +} diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/InheritanceChildTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/InheritanceChildTest.java new file mode 100644 index 000000000..6188c65d9 --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/InheritanceChildTest.java @@ -0,0 +1,9 @@ +package example; + +import org.junit.jupiter.api.Test; + +public class InheritanceChildTest extends InheritanceBaseTest { + @Test + void child() { + } +} diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/InheritanceOverrideTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/InheritanceOverrideTest.java new file mode 100644 index 000000000..e9411368a --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/InheritanceOverrideTest.java @@ -0,0 +1,14 @@ +package example; + +import org.junit.jupiter.api.Test; + +public class InheritanceOverrideTest extends InheritanceBaseTest { + @Test + @Override + void base() { + } + + @Test + void extra() { + } +} diff --git a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java index f88fe4706..bd36f6c65 100644 --- a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java +++ b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java @@ -77,10 +77,12 @@ public void testDiscoverJUnit5TestsWithOlderPreviewSource() throws Exception { final List packages = TestSearchUtils.findTestPackagesAndTypes( Arrays.asList(javaProject.getHandleIdentifier()), new NullProgressMonitor()); assertEquals(1, packages.size()); - assertEquals(3, packages.get(0).getChildren().size()); + assertEquals(6, packages.get(0).getChildren().size()); assertTrue(packages.get(0).getChildren().stream().anyMatch(item -> "MiniTest".equals(item.getLabel()))); assertTrue(packages.get(0).getChildren().stream().anyMatch(item -> "SiblingTest".equals(item.getLabel()))); assertTrue(packages.get(0).getChildren().stream().anyMatch(item -> "NestedOnlyTest".equals(item.getLabel()))); + assertTrue(packages.get(0).getChildren().stream() + .anyMatch(item -> "InheritanceChildTest".equals(item.getLabel()))); final List methods = TestSearchUtils.findDirectTestChildrenForClass( Arrays.asList(type.getHandleIdentifier()), new NullProgressMonitor()); @@ -94,4 +96,38 @@ public void testDiscoverJUnit5TestsWithOlderPreviewSource() throws Exception { assertEquals("Child", nestedChildren.get(0).getLabel()); assertEquals(JavaCore.ENABLED, javaProject.getOption(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, true)); } + + @Test + public void testDiscoverInheritedTestMethods() throws Exception { + final IProject project = importProjects("preview-junit").get(0); + final IJavaProject javaProject = JavaCore.create(project); + final IType childType = javaProject.findType("example.InheritanceChildTest"); + assertNotNull(childType); + + final List childMethods = TestSearchUtils.findDirectTestChildrenForClass( + Arrays.asList(childType.getHandleIdentifier()), new NullProgressMonitor()); + assertEquals(2, childMethods.size()); + assertTrue(childMethods.stream().anyMatch( + item -> "example.InheritanceChildTest#child()".equals(item.getFullName()))); + assertTrue(childMethods.stream().anyMatch( + item -> "example.InheritanceChildTest#base()".equals(item.getFullName()))); + for (final JavaTestItem item : childMethods) { + assertTrue(item.getId().startsWith(item.getProjectName() + "@example.InheritanceChildTest#")); + } + + final IType baseType = javaProject.findType("example.InheritanceBaseTest"); + final List baseMethods = TestSearchUtils.findDirectTestChildrenForClass( + Arrays.asList(baseType.getHandleIdentifier()), new NullProgressMonitor()); + assertEquals(1, baseMethods.size()); + assertEquals("example.InheritanceBaseTest#base()", baseMethods.get(0).getFullName()); + + final IType overrideType = javaProject.findType("example.InheritanceOverrideTest"); + final List overrideMethods = TestSearchUtils.findDirectTestChildrenForClass( + Arrays.asList(overrideType.getHandleIdentifier()), new NullProgressMonitor()); + assertEquals(2, overrideMethods.size()); + assertTrue(overrideMethods.stream().anyMatch( + item -> "example.InheritanceOverrideTest#base()".equals(item.getFullName()))); + assertTrue(overrideMethods.stream().anyMatch( + item -> "example.InheritanceOverrideTest#extra()".equals(item.getFullName()))); + } } diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/JavaTestItem.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/JavaTestItem.java index 6dababc34..04e999201 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/JavaTestItem.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/JavaTestItem.java @@ -89,6 +89,10 @@ public String getId() { return id; } + public void setId(String id) { + this.id = id; + } + public String getLabel() { return label; } 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 de9b67342..61b35ad76 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 @@ -47,6 +47,7 @@ import org.eclipse.jdt.core.dom.CompilationUnit; 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.TypeDeclaration; import org.eclipse.jdt.core.manipulation.CoreASTProvider; import org.eclipse.jdt.core.search.IJavaSearchConstants; @@ -360,6 +361,8 @@ public static List findDirectTestChildrenForClass(List arg } } } + + addInheritedTestMethodsForKinds(binding, type, testKinds, result); } return result; @@ -496,6 +499,8 @@ private static void findTestItemsInTypeBinding(ITypeBinding typeBinding, JavaTes } } + addInheritedTestMethods(typeBinding, type, searchers, testMethods); + JavaTestItem classItem = null; if (testMethods.size() > 0) { classItem = new JavaTestItemBuilder().setJavaElement(type) @@ -543,6 +548,94 @@ private static void findTestItemsInTypeBinding(ITypeBinding typeBinding, JavaTes } } + /** + * Collect test methods inherited from superclasses and append them to {@code accumulator}. + * Test frameworks (JUnit, TestNG) execute inherited {@code @Test} methods when running a + * child class, so discovery must list them under the child. Methods overridden by the child + * and non-inheritable (private) superclass methods are skipped. + */ + private static void addInheritedTestMethodsForKinds(ITypeBinding classBinding, IType childType, + List testKinds, List accumulator) throws JavaModelException { + final List searchers = new LinkedList<>(); + for (final TestKind kind : testKinds) { + final TestFrameworkSearcher searcher = TestFrameworkUtils.getSearcherByTestKind(kind); + if (searcher != null) { + searchers.add(searcher); + } + } + addInheritedTestMethods(classBinding, childType, searchers, accumulator); + } + + private static void addInheritedTestMethods(ITypeBinding classBinding, IType childType, + List searchers, List accumulator) throws JavaModelException { + if (classBinding == null || childType == null || searchers.isEmpty()) { + return; + } + + final Set seenSignatures = new HashSet<>(); + for (final IMethodBinding declared : classBinding.getDeclaredMethods()) { + seenSignatures.add(getMethodSignatureKey(declared)); + } + + ITypeBinding superClass = classBinding.getSuperclass(); + while (superClass != null && !"java.lang.Object".equals(superClass.getBinaryName())) { + for (final IMethodBinding methodBinding : superClass.getDeclaredMethods()) { + if (Modifier.isPrivate(methodBinding.getModifiers())) { + continue; + } + final String key = getMethodSignatureKey(methodBinding); + if (!seenSignatures.add(key)) { + continue; + } + for (final TestFrameworkSearcher searcher : searchers) { + if (searcher.isTestMethod(methodBinding)) { + final IJavaElement element = methodBinding.getJavaElement(); + if (element == null) { + break; + } + final JavaTestItem methodItem = new JavaTestItemBuilder().setJavaElement(element) + .setLevel(TestLevel.METHOD) + .setKind(searcher.getTestKind()) + .build(); + rebaseMethodItemToChild(methodItem, childType); + accumulator.add(methodItem); + break; + } + } + } + superClass = superClass.getSuperclass(); + } + } + + private static String getMethodSignatureKey(IMethodBinding methodBinding) { + final StringBuilder key = new StringBuilder(methodBinding.getName()).append('('); + for (final ITypeBinding param : methodBinding.getParameterTypes()) { + final ITypeBinding erasure = param.getErasure() != null ? param.getErasure() : param; + key.append(erasure.getQualifiedName()).append(','); + } + return key.append(')').toString(); + } + + /** + * Rewrite an inherited method item so its full name and id are anchored at the child class + * (e.g. {@code Child#test}) instead of the declaring parent class. The display label, source + * range, and JDT handler still point at the parent declaration. + */ + private static void rebaseMethodItemToChild(JavaTestItem methodItem, IType childType) + throws JavaModelException { + final String fullName = methodItem.getFullName(); + final int separator = fullName != null ? fullName.indexOf('#') : -1; + if (separator < 0) { + return; + } + final String childFullName = childType.getFullyQualifiedName() + fullName.substring(separator); + if (childFullName.equals(fullName)) { + return; + } + methodItem.setFullName(childFullName); + methodItem.setId(methodItem.getProjectName() + "@" + childFullName); + } + /** * Given a file Uri, resolve its belonging project and package node in the test explorer, this is * used to find the test item node when a test file is changed From 17e6b992d43e51a1144b9776070d7bc2f32ffde6 Mon Sep 17 00:00:00 2001 From: Vinay Kumar Maheshwaram <54681400+sudovinay01@users.noreply.github.com> Date: Mon, 28 Sep 2026 20:52:16 +0530 Subject: [PATCH 2/7] fix: address inherited test review feedback --- .../projects/inheritance-child/.classpath | 13 +++++ .../projects/inheritance-child/.project | 17 +++++++ .../java/example/InheritanceChildTest.java | 9 ++++ .../projects/inheritance-junit4/.classpath | 12 +++++ .../projects/inheritance-junit4/.project | 15 ++++++ .../src/test/java/example/JUnit4BaseTest.java | 9 ++++ .../test/java/example/JUnit4ChildTest.java | 7 +++ .../projects/inheritance-parent/.classpath | 12 +++++ .../projects/inheritance-parent/.project | 15 ++++++ .../java/example/InheritanceBaseTest.java | 9 ++++ .../InheritanceUnannotatedOverrideTest.java | 7 +++ .../JUnitLaunchConfigurationDelegateTest.java | 25 +++++++++ .../test/plugin/util/TestSearchUtilsTest.java | 51 ++++++++++++++++++- .../JUnitLaunchConfigurationDelegate.java | 17 +++++-- .../plugin/launchers/JUnitLaunchUtils.java | 11 +++- .../java/test/plugin/model/JavaTestItem.java | 10 ++++ .../test/plugin/util/TestSearchUtils.java | 47 ++++++++++------- src/controller/testItemDataCache.ts | 1 + src/controller/utils.ts | 2 + src/types.ts | 1 + src/utils/launchUtils.ts | 9 +++- 21 files changed, 272 insertions(+), 27 deletions(-) create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-child/.classpath create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-child/.project create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-child/src/test/java/example/InheritanceChildTest.java create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/.classpath create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/.project create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4BaseTest.java create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4ChildTest.java create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-parent/.classpath create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-parent/.project create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-parent/src/test/java/example/InheritanceBaseTest.java create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/InheritanceUnannotatedOverrideTest.java diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-child/.classpath b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-child/.classpath new file mode 100644 index 000000000..8d3b8c727 --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-child/.classpath @@ -0,0 +1,13 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-child/.project b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-child/.project new file mode 100644 index 000000000..8d1c76df2 --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-child/.project @@ -0,0 +1,17 @@ + + + inheritance-child + + + inheritance-parent + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.jdt.core.javanature + + \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-child/src/test/java/example/InheritanceChildTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-child/src/test/java/example/InheritanceChildTest.java new file mode 100644 index 000000000..3e68a261c --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-child/src/test/java/example/InheritanceChildTest.java @@ -0,0 +1,9 @@ +package example; + +import org.junit.jupiter.api.Test; + +public class InheritanceChildTest extends InheritanceBaseTest { + @Test + void child() { + } +} \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/.classpath b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/.classpath new file mode 100644 index 000000000..fad9f65c7 --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/.project b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/.project new file mode 100644 index 000000000..034166d6c --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/.project @@ -0,0 +1,15 @@ + + + inheritance-junit4 + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.jdt.core.javanature + + \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4BaseTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4BaseTest.java new file mode 100644 index 000000000..481ba2e2c --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4BaseTest.java @@ -0,0 +1,9 @@ +package example; + +import org.junit.Test; + +public class JUnit4BaseTest { + @Test + public void inherited() { + } +} \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4ChildTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4ChildTest.java new file mode 100644 index 000000000..73b17310d --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4ChildTest.java @@ -0,0 +1,7 @@ +package example; + +public class JUnit4ChildTest extends JUnit4BaseTest { + @Override + public void inherited() { + } +} \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-parent/.classpath b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-parent/.classpath new file mode 100644 index 000000000..91ae659e5 --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-parent/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-parent/.project b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-parent/.project new file mode 100644 index 000000000..48ff273b1 --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-parent/.project @@ -0,0 +1,15 @@ + + + inheritance-parent + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.jdt.core.javanature + + \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-parent/src/test/java/example/InheritanceBaseTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-parent/src/test/java/example/InheritanceBaseTest.java new file mode 100644 index 000000000..d98c12396 --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-parent/src/test/java/example/InheritanceBaseTest.java @@ -0,0 +1,9 @@ +package example; + +import org.junit.jupiter.api.Test; + +public class InheritanceBaseTest { + @Test + void base() { + } +} \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/InheritanceUnannotatedOverrideTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/InheritanceUnannotatedOverrideTest.java new file mode 100644 index 000000000..fcd2d51fd --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/InheritanceUnannotatedOverrideTest.java @@ -0,0 +1,7 @@ +package example; + +public class InheritanceUnannotatedOverrideTest extends InheritanceBaseTest { + @Override + void base() { + } +} \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java index 6baaf304f..0f1fe0aab 100644 --- a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java +++ b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java @@ -28,6 +28,7 @@ import org.junit.Test; import com.microsoft.java.test.plugin.AbstractProjectsManagerBasedTest; +import com.microsoft.java.test.plugin.WorkspaceHelper; import com.microsoft.java.test.plugin.model.Response; import com.google.gson.Gson; @@ -82,4 +83,28 @@ public void testAddOpensForAllSelectedPackagesInModularProject() throws Exceptio assertEquals("--add-opens", vmArguments.get(vmArguments.indexOf(secondPackageOpen) - 1)); } + @Test + public void testInheritedMethodLaunchesAgainstChildClass() throws Exception { + importProjects(Arrays.asList("inheritance-parent", "inheritance-child")); + final IProject childProject = WorkspaceHelper.getProject("inheritance-child"); + final IJavaProject childJavaProject = JavaCore.create(childProject); + final IType parentType = JavaCore.create(WorkspaceHelper.getProject("inheritance-parent")) + .findType("example.InheritanceBaseTest"); + + final Map request = new LinkedHashMap<>(); + request.put("projectName", childProject.getName()); + request.put("testLevel", 6); + request.put("testKind", 0); + request.put("testNames", Arrays.asList(parentType.getMethod("base", new String[0]).getHandleIdentifier())); + request.put("executionClassNames", Arrays.asList("example.InheritanceChildTest")); + + final Response response = JUnitLaunchUtils.resolveLaunchArgument( + Arrays.asList(new Gson().toJson(request)), new NullProgressMonitor()); + + assertEquals(0, response.getStatus()); + assertEquals(childJavaProject.getElementName(), response.getBody().projectName); + assertTrue(Arrays.asList(response.getBody().programArguments) + .contains("example.InheritanceChildTest:base")); + } + } diff --git a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java index bd36f6c65..9e727de61 100644 --- a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java +++ b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java @@ -34,6 +34,7 @@ import com.microsoft.java.test.plugin.AbstractProjectsManagerBasedTest; import com.microsoft.java.test.plugin.model.JavaTestItem; +import com.microsoft.java.test.plugin.WorkspaceHelper; public class TestSearchUtilsTest extends AbstractProjectsManagerBasedTest { @@ -77,7 +78,7 @@ public void testDiscoverJUnit5TestsWithOlderPreviewSource() throws Exception { final List packages = TestSearchUtils.findTestPackagesAndTypes( Arrays.asList(javaProject.getHandleIdentifier()), new NullProgressMonitor()); assertEquals(1, packages.size()); - assertEquals(6, packages.get(0).getChildren().size()); + assertEquals(7, packages.get(0).getChildren().size()); assertTrue(packages.get(0).getChildren().stream().anyMatch(item -> "MiniTest".equals(item.getLabel()))); assertTrue(packages.get(0).getChildren().stream().anyMatch(item -> "SiblingTest".equals(item.getLabel()))); assertTrue(packages.get(0).getChildren().stream().anyMatch(item -> "NestedOnlyTest".equals(item.getLabel()))); @@ -130,4 +131,52 @@ public void testDiscoverInheritedTestMethods() throws Exception { assertTrue(overrideMethods.stream().anyMatch( item -> "example.InheritanceOverrideTest#extra()".equals(item.getFullName()))); } + + @Test + public void testInheritedMethodUsesChildProjectAndExecutionClass() throws Exception { + importProjects(Arrays.asList("inheritance-parent", "inheritance-child")); + final IProject childProject = WorkspaceHelper.getProject("inheritance-child"); + final IJavaProject javaProject = JavaCore.create(childProject); + final IType childType = javaProject.findType("example.InheritanceChildTest"); + final IType parentType = JavaCore.create(WorkspaceHelper.getProject("inheritance-parent")) + .findType("example.InheritanceBaseTest"); + assertNotNull(childType); + assertNotNull(parentType); + + final List methods = TestSearchUtils.findDirectTestChildrenForClass( + Arrays.asList(childType.getHandleIdentifier()), new NullProgressMonitor()); + final JavaTestItem inherited = methods.stream() + .filter(item -> item.getFullName().endsWith("#base()")) + .findFirst().orElse(null); + assertNotNull(inherited); + assertEquals(childProject.getName(), inherited.getProjectName()); + assertEquals(childProject.getName() + "@example.InheritanceChildTest#base()", inherited.getId()); + assertEquals("example.InheritanceChildTest", inherited.getExecutionClassName()); + assertEquals(parentType.getMethod("base", new String[0]).getHandleIdentifier(), inherited.getJdtHandler()); + } + + @Test + public void testJUnit4TestSurvivesUnannotatedOverride() throws Exception { + importProjects("inheritance-junit4"); + final IJavaProject javaProject = JavaCore.create(WorkspaceHelper.getProject("inheritance-junit4")); + final IType childType = javaProject.findType("example.JUnit4ChildTest"); + assertNotNull(childType); + + final List methods = TestSearchUtils.findDirectTestChildrenForClass( + Arrays.asList(childType.getHandleIdentifier()), new NullProgressMonitor()); + assertEquals(1, methods.size()); + assertEquals("example.JUnit4ChildTest#inherited", methods.get(0).getFullName()); + assertEquals("example.JUnit4ChildTest", methods.get(0).getExecutionClassName()); + } + + @Test + public void testJUnit5UnannotatedOverrideSuppressesInheritedTest() throws Exception { + final IProject project = importProjects("preview-junit").get(0); + final IType childType = JavaCore.create(project).findType("example.InheritanceUnannotatedOverrideTest"); + assertNotNull(childType); + + final List methods = TestSearchUtils.findDirectTestChildrenForClass( + Arrays.asList(childType.getHandleIdentifier()), new NullProgressMonitor()); + assertTrue(methods.isEmpty()); + } } 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 c0711c5c7..d0e2d6857 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 @@ -192,7 +192,7 @@ private void addTestItemArgs(List arguments) throws CoreException { arguments.add(fileName); } else { arguments.add("-test"); - arguments.add(resolveMethodTestName(this.args.testNames[0])); + arguments.add(resolveMethodTestName(this.args.testNames[0], getExecutionClassName(0))); if (StringUtils.isNotBlank(this.args.uniqueId)) { arguments.add("-uniqueId"); @@ -202,7 +202,7 @@ private void addTestItemArgs(List arguments) throws CoreException { } } - private String resolveMethodTestName(String handleId) throws CoreException { + private String resolveMethodTestName(String handleId, String executionClassName) throws CoreException { final IMethod method = (IMethod) JavaCore.create(handleId); String testName = method.getElementName(); if ((this.args.testKind == TestKind.JUnit5 || this.args.testKind == TestKind.JUnit6) && @@ -239,7 +239,14 @@ private String resolveMethodTestName(String handleId) throws CoreException { testName += "(" + String.join(",", parameters) + ")"; } } - return method.getDeclaringType().getFullyQualifiedName() + ':' + testName; + final String className = StringUtils.isNotBlank(executionClassName) ? executionClassName : + method.getDeclaringType().getFullyQualifiedName(); + return className + ':' + testName; + } + + private String getExecutionClassName(int index) { + return this.args.executionClassNames != null && index < this.args.executionClassNames.length ? + this.args.executionClassNames[index] : null; } private String createTestNamesFile(String[] testNames) throws CoreException { @@ -266,8 +273,8 @@ private String createMethodTestNamesFile(String[] testNames) throws CoreExceptio file.deleteOnExit(); try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(file), StandardCharsets.UTF_8));) { - for (final String handleId : testNames) { - bw.write(resolveMethodTestName(handleId)); + for (int index = 0; index < testNames.length; index++) { + bw.write(resolveMethodTestName(testNames[index], getExecutionClassName(index))); bw.newLine(); } } 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 738ba41a7..55d3c5aed 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 @@ -18,6 +18,7 @@ import com.microsoft.java.test.plugin.util.JUnitPlugin; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; import org.eclipse.core.resources.IFolder; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; @@ -120,8 +121,13 @@ public static Response resolveLaunchArgument(List if (args.testLevel == TestLevel.CLASS) { info.mainType = args.testNames[0].substring(args.testNames[0].indexOf("@") + 1); } else if (args.testLevel == TestLevel.METHOD) { - final IMethod method = (IMethod) JavaCore.create(args.testNames[0]); - info.mainType = method.getDeclaringType().getFullyQualifiedName(); + if (ArrayUtils.isNotEmpty(args.executionClassNames) && + StringUtils.isNotBlank(args.executionClassNames[0])) { + info.mainType = args.executionClassNames[0]; + } else { + final IMethod method = (IMethod) JavaCore.create(args.testNames[0]); + info.mainType = method.getDeclaringType().getFullyQualifiedName(); + } } } } @@ -259,6 +265,7 @@ class Argument { public TestKind testKind; public String[] testNames; public String[] testHandles; + public String[] executionClassNames; public String uniqueId; } } diff --git a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/JavaTestItem.java b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/JavaTestItem.java index 04e999201..818f215d8 100644 --- a/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/JavaTestItem.java +++ b/java-extension/com.microsoft.java.test.plugin/src/main/java/com/microsoft/java/test/plugin/model/JavaTestItem.java @@ -37,6 +37,8 @@ public class JavaTestItem { private String jdtHandler; + private String executionClassName; + /** * Optional field for project item. */ @@ -85,6 +87,14 @@ public void setJdtHandler(String jdtHandler) { this.jdtHandler = jdtHandler; } + public String getExecutionClassName() { + return executionClassName; + } + + public void setExecutionClassName(String executionClassName) { + this.executionClassName = executionClassName; + } + public String getId() { return id; } 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 61b35ad76..332e9d4ba 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 @@ -572,11 +572,12 @@ private static void addInheritedTestMethods(ITypeBinding classBinding, IType chi return; } - final Set seenSignatures = new HashSet<>(); + final Map moreSpecificMethods = new HashMap<>(); for (final IMethodBinding declared : classBinding.getDeclaredMethods()) { - seenSignatures.add(getMethodSignatureKey(declared)); + moreSpecificMethods.put(getMethodSignatureKey(declared), declared); } + final Set inheritedTestSignatures = new HashSet<>(); ITypeBinding superClass = classBinding.getSuperclass(); while (superClass != null && !"java.lang.Object".equals(superClass.getBinaryName())) { for (final IMethodBinding methodBinding : superClass.getDeclaredMethods()) { @@ -584,23 +585,33 @@ private static void addInheritedTestMethods(ITypeBinding classBinding, IType chi continue; } final String key = getMethodSignatureKey(methodBinding); - if (!seenSignatures.add(key)) { + if (inheritedTestSignatures.contains(key)) { continue; } + final IMethodBinding overridingMethod = moreSpecificMethods.get(key); for (final TestFrameworkSearcher searcher : searchers) { - if (searcher.isTestMethod(methodBinding)) { - final IJavaElement element = methodBinding.getJavaElement(); - if (element == null) { - break; - } - final JavaTestItem methodItem = new JavaTestItemBuilder().setJavaElement(element) - .setLevel(TestLevel.METHOD) - .setKind(searcher.getTestKind()) - .build(); - rebaseMethodItemToChild(methodItem, childType); - accumulator.add(methodItem); + if (!searcher.isTestMethod(methodBinding)) { + continue; + } + if (overridingMethod != null && + (searcher.getTestKind() != TestKind.JUnit || searcher.isTestMethod(overridingMethod))) { + break; + } + final IJavaElement element = methodBinding.getJavaElement(); + if (element == null) { break; } + final JavaTestItem methodItem = new JavaTestItemBuilder().setJavaElement(element) + .setLevel(TestLevel.METHOD) + .setKind(searcher.getTestKind()) + .build(); + rebaseMethodItemToChild(methodItem, childType); + accumulator.add(methodItem); + inheritedTestSignatures.add(key); + break; + } + if (overridingMethod == null) { + moreSpecificMethods.put(key, methodBinding); } } superClass = superClass.getSuperclass(); @@ -629,11 +640,11 @@ private static void rebaseMethodItemToChild(JavaTestItem methodItem, IType child return; } final String childFullName = childType.getFullyQualifiedName() + fullName.substring(separator); - if (childFullName.equals(fullName)) { - return; - } methodItem.setFullName(childFullName); - methodItem.setId(methodItem.getProjectName() + "@" + childFullName); + final String childProjectName = childType.getJavaProject().getProject().getName(); + methodItem.setProjectName(childProjectName); + methodItem.setId(childProjectName + "@" + childFullName); + methodItem.setExecutionClassName(childType.getFullyQualifiedName()); } /** diff --git a/src/controller/testItemDataCache.ts b/src/controller/testItemDataCache.ts index 570e07c56..311de0c13 100644 --- a/src/controller/testItemDataCache.ts +++ b/src/controller/testItemDataCache.ts @@ -39,6 +39,7 @@ export function invalidateResolutionVersion(item: TestItem): void { export interface ITestItemData { jdtHandler: string; + executionClassName?: string; fullName: string; projectName: string; testLevel: TestLevel; diff --git a/src/controller/utils.ts b/src/controller/utils.ts index 451073364..c9bb78d7b 100644 --- a/src/controller/utils.ts +++ b/src/controller/utils.ts @@ -143,6 +143,7 @@ function updateTestItem(testItem: TestItem, metaInfo: IJavaTestItem): void { testItem.label = metaInfo.label; dataCache.set(testItem, { jdtHandler: metaInfo.jdtHandler, + executionClassName: metaInfo.executionClassName, fullName: metaInfo.fullName, projectName: metaInfo.projectName, testLevel: metaInfo.testLevel, @@ -173,6 +174,7 @@ export function createTestItem(metaInfo: IJavaTestItem, parent?: TestItem): Test item.tags = [runnableTag]; dataCache.set(item, { jdtHandler: metaInfo.jdtHandler, + executionClassName: metaInfo.executionClassName, fullName: metaInfo.fullName, projectName: metaInfo.projectName, testLevel: metaInfo.testLevel, diff --git a/src/types.ts b/src/types.ts index d614c8d72..1726fc60a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,6 +9,7 @@ export interface IJavaTestItem { uri: string | undefined; range: Range | undefined; jdtHandler: string; + executionClassName?: string; fullName: string; label: string; id: string; diff --git a/src/utils/launchUtils.ts b/src/utils/launchUtils.ts index d22e4eb63..b13a69020 100644 --- a/src/utils/launchUtils.ts +++ b/src/utils/launchUtils.ts @@ -97,6 +97,7 @@ async function getLaunchArguments(testContext: IRunTestContext): Promise dataCache.get(item)?.executionClassName); +} + async function resolveJUnitLaunchArguments(projectName: string, testLevel: TestLevel, testKind: TestKind, - testNames: string[], testHandles: string[], uniqueId: string | undefined): Promise { + testNames: string[], testHandles: string[], executionClassNames: (string | undefined)[], + uniqueId: string | undefined): Promise { const argument: Response | undefined = await executeJavaLanguageServerCommand>( JavaTestRunnerDelegateCommands.RESOLVE_JUNIT_ARGUMENT, JSON.stringify({ projectName, @@ -145,6 +151,7 @@ async function resolveJUnitLaunchArguments(projectName: string, testLevel: TestL testKind, testNames, testHandles, + executionClassNames, uniqueId }), ); From 57344f1e742970ade10cfdb3ee0e16f36896f1d6 Mon Sep 17 00:00:00 2001 From: Vinay Kumar Maheshwaram <54681400+sudovinay01@users.noreply.github.com> Date: Tue, 29 Sep 2026 19:46:59 +0530 Subject: [PATCH 3/7] fix: resolve binary inherited method parameters --- .../inheritance-binary-child/.classpath | 13 ++++ .../inheritance-binary-child/.project | 15 ++++ .../example/InheritanceBinaryChildTest.java | 4 ++ .../inheritance-binary-parent/.classpath | 12 ++++ .../inheritance-binary-parent/.project | 15 ++++ .../example/InheritanceBinaryBaseTest.java | 10 +++ .../JUnitLaunchConfigurationDelegateTest.java | 61 ++++++++++++++++ .../JUnitLaunchConfigurationDelegate.java | 71 ++++++++++++------- 8 files changed, 176 insertions(+), 25 deletions(-) create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-child/.classpath create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-child/.project create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-child/src/test/java/example/InheritanceBinaryChildTest.java create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-parent/.classpath create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-parent/.project create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-parent/src/test/java/example/InheritanceBinaryBaseTest.java diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-child/.classpath b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-child/.classpath new file mode 100644 index 000000000..15b6944de --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-child/.classpath @@ -0,0 +1,13 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-child/.project b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-child/.project new file mode 100644 index 000000000..cbbce0823 --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-child/.project @@ -0,0 +1,15 @@ + + + inheritance-binary-child + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.jdt.core.javanature + + \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-child/src/test/java/example/InheritanceBinaryChildTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-child/src/test/java/example/InheritanceBinaryChildTest.java new file mode 100644 index 000000000..a255512ff --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-child/src/test/java/example/InheritanceBinaryChildTest.java @@ -0,0 +1,4 @@ +package example; + +public class InheritanceBinaryChildTest extends InheritanceBinaryBaseTest { +} \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-parent/.classpath b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-parent/.classpath new file mode 100644 index 000000000..91ae659e5 --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-parent/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-parent/.project b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-parent/.project new file mode 100644 index 000000000..5e91282e9 --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-parent/.project @@ -0,0 +1,15 @@ + + + inheritance-binary-parent + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.jdt.core.javanature + + \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-parent/src/test/java/example/InheritanceBinaryBaseTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-parent/src/test/java/example/InheritanceBinaryBaseTest.java new file mode 100644 index 000000000..e73b27ebb --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-parent/src/test/java/example/InheritanceBinaryBaseTest.java @@ -0,0 +1,10 @@ +package example; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +public class InheritanceBinaryBaseTest { + @Test + void base(TestInfo info) { + } +} \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java index 0f1fe0aab..81e346e99 100644 --- a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java +++ b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java @@ -23,6 +23,7 @@ import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; import org.junit.Test; @@ -30,6 +31,8 @@ import com.microsoft.java.test.plugin.AbstractProjectsManagerBasedTest; import com.microsoft.java.test.plugin.WorkspaceHelper; import com.microsoft.java.test.plugin.model.Response; +import com.microsoft.java.test.plugin.model.JavaTestItem; +import com.microsoft.java.test.plugin.util.TestSearchUtils; import com.google.gson.Gson; public class JUnitLaunchConfigurationDelegateTest extends AbstractProjectsManagerBasedTest { @@ -107,4 +110,62 @@ public void testInheritedMethodLaunchesAgainstChildClass() throws Exception { .contains("example.InheritanceChildTest:base")); } + @Test + public void testBinaryMethodWithParametersLaunchesAgainstChildClass() throws Exception { + final IProject project = importProjects("preview-junit").get(0); + final IJavaProject javaProject = JavaCore.create(project); + final IType assertions = javaProject.findType("org.junit.jupiter.api.Assertions"); + assertNotNull(assertions); + final IMethod binaryMethod = assertions.getMethod("assertEquals", + new String[] { "Ljava.lang.Object;", "Ljava.lang.Object;" }); + assertTrue(binaryMethod.exists()); + assertTrue(binaryMethod.getCompilationUnit() == null); + + final Map request = new LinkedHashMap<>(); + request.put("projectName", javaProject.getElementName()); + request.put("testLevel", 6); + request.put("testKind", 0); + request.put("testNames", Arrays.asList(binaryMethod.getHandleIdentifier())); + request.put("executionClassNames", Arrays.asList("example.InheritanceChildTest")); + + final Response response = JUnitLaunchUtils.resolveLaunchArgument( + Arrays.asList(new Gson().toJson(request)), new NullProgressMonitor()); + + assertEquals(0, response.getStatus()); + assertTrue(Arrays.asList(response.getBody().programArguments) + .contains("example.InheritanceChildTest:assertEquals(java.lang.Object,java.lang.Object)")); + } + + @Test + public void testInheritedBinaryMethodWithParametersLaunchesAgainstChildClass() throws Exception { + importProjects(Arrays.asList("inheritance-binary-parent", "inheritance-binary-child")); + final IJavaProject javaProject = JavaCore.create(WorkspaceHelper.getProject("inheritance-binary-child")); + final IType childType = javaProject.findType("example.InheritanceBinaryChildTest"); + assertNotNull(childType); + + final List methods = TestSearchUtils.findDirectTestChildrenForClass( + Arrays.asList(childType.getHandleIdentifier()), new NullProgressMonitor()); + final JavaTestItem inherited = methods.stream() + .filter(item -> item.getFullName().contains("#base")) + .findFirst().orElse(null); + assertNotNull(inherited); + final IMethod binaryMethod = (IMethod) + JavaCore.create(inherited.getJdtHandler()); + assertTrue(binaryMethod.getCompilationUnit() == null); + + final Map request = new LinkedHashMap<>(); + request.put("projectName", javaProject.getElementName()); + request.put("testLevel", 6); + request.put("testKind", 0); + request.put("testNames", Arrays.asList(inherited.getJdtHandler())); + request.put("executionClassNames", Arrays.asList("example.InheritanceBinaryChildTest")); + + final Response response = JUnitLaunchUtils.resolveLaunchArgument( + Arrays.asList(new Gson().toJson(request)), new NullProgressMonitor()); + + assertEquals(0, response.getStatus()); + assertTrue(Arrays.asList(response.getBody().programArguments) + .contains("example.InheritanceBinaryChildTest:base(org.junit.jupiter.api.TestInfo)")); + } + } 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 d0e2d6857..a196a79da 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 @@ -34,7 +34,11 @@ import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.dom.AST; +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.MethodDeclaration; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; @@ -209,34 +213,39 @@ private String resolveMethodTestName(String handleId, String executionClassName) 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, - "Cannot get method declaration of method" + method.getElementName(), null)); //$NON-NLS-1$ - } + final ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); + parser.setProject(method.getJavaProject()); + final IBinding[] bindings = parser.createBindings(new IJavaElement[] { method }, + new NullProgressMonitor()); + if (bindings.length != 1 || !(bindings[0] instanceof IMethodBinding)) { + throw new CoreException(new Status(IStatus.ERROR, JUnitPlugin.PLUGIN_ID, IStatus.ERROR, + "Cannot resolve binary method " + method.getElementName(), null)); //$NON-NLS-1$ + } + testName += formatParameterTypes((IMethodBinding) bindings[0]); + } 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 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 argument for method" + methodDeclaration.toString(), null)); + } + parameters.add(formatParameterType(paramTypeBinding)); } } - } - if (parameters.size() > 0) { - testName += "(" + String.join(",", parameters) + ")"; + if (!parameters.isEmpty()) { + testName += "(" + String.join(",", parameters) + ")"; + } } } final String className = StringUtils.isNotBlank(executionClassName) ? executionClassName : @@ -244,6 +253,18 @@ private String resolveMethodTestName(String handleId, String executionClassName) return className + ':' + testName; } + private String formatParameterTypes(IMethodBinding methodBinding) { + final List parameters = new LinkedList<>(); + for (final ITypeBinding parameterType : methodBinding.getParameterTypes()) { + parameters.add(formatParameterType(parameterType)); + } + return parameters.isEmpty() ? "" : "(" + String.join(",", parameters) + ")"; + } + + private String formatParameterType(ITypeBinding parameterType) { + return parameterType.isPrimitive() ? parameterType.getQualifiedName() : parameterType.getBinaryName(); + } + private String getExecutionClassName(int index) { return this.args.executionClassNames != null && index < this.args.executionClassNames.length ? this.args.executionClassNames[index] : null; From de1d289062eca05366734e56df7f567343dd0058 Mon Sep 17 00:00:00 2001 From: Vinay Kumar Maheshwaram <54681400+sudovinay01@users.noreply.github.com> Date: Sat, 31 Oct 2026 13:27:30 +0530 Subject: [PATCH 4/7] fix: detect inherited test overrides accurately Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../test/java/example/JUnit4ChildTest.java | 2 +- .../test/java/example/JUnit4MiddleTest.java | 10 ++++++ .../child/CrossPackageSameSignatureTest.java | 8 +++++ .../parent/PackagePrivateBaseTest.java | 9 +++++ .../test/plugin/util/TestSearchUtilsTest.java | 27 ++++++++++---- .../test/plugin/util/TestSearchUtils.java | 35 ++++++++++++++----- 6 files changed, 75 insertions(+), 16 deletions(-) create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4MiddleTest.java create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/child/CrossPackageSameSignatureTest.java create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/parent/PackagePrivateBaseTest.java diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4ChildTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4ChildTest.java index 73b17310d..7ee71013e 100644 --- a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4ChildTest.java +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4ChildTest.java @@ -1,6 +1,6 @@ package example; -public class JUnit4ChildTest extends JUnit4BaseTest { +public class JUnit4ChildTest extends JUnit4MiddleTest { @Override public void inherited() { } diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4MiddleTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4MiddleTest.java new file mode 100644 index 000000000..8884120dd --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4MiddleTest.java @@ -0,0 +1,10 @@ +package example; + +import org.junit.Test; + +public class JUnit4MiddleTest extends JUnit4BaseTest { + @Override + @Test + public void inherited() { + } +} diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/child/CrossPackageSameSignatureTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/child/CrossPackageSameSignatureTest.java new file mode 100644 index 000000000..e9d787af8 --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/child/CrossPackageSameSignatureTest.java @@ -0,0 +1,8 @@ +package example.child; + +import example.parent.PackagePrivateBaseTest; + +public class CrossPackageSameSignatureTest extends PackagePrivateBaseTest { + void packagePrivateTest() { + } +} diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/parent/PackagePrivateBaseTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/parent/PackagePrivateBaseTest.java new file mode 100644 index 000000000..fa01667e7 --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/parent/PackagePrivateBaseTest.java @@ -0,0 +1,9 @@ +package example.parent; + +import org.junit.jupiter.api.Test; + +public class PackagePrivateBaseTest { + @Test + void packagePrivateTest() { + } +} diff --git a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java index 9e727de61..2fb56a285 100644 --- a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java +++ b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java @@ -77,12 +77,15 @@ public void testDiscoverJUnit5TestsWithOlderPreviewSource() throws Exception { final List packages = TestSearchUtils.findTestPackagesAndTypes( Arrays.asList(javaProject.getHandleIdentifier()), new NullProgressMonitor()); - assertEquals(1, packages.size()); - assertEquals(7, packages.get(0).getChildren().size()); - assertTrue(packages.get(0).getChildren().stream().anyMatch(item -> "MiniTest".equals(item.getLabel()))); - assertTrue(packages.get(0).getChildren().stream().anyMatch(item -> "SiblingTest".equals(item.getLabel()))); - assertTrue(packages.get(0).getChildren().stream().anyMatch(item -> "NestedOnlyTest".equals(item.getLabel()))); - assertTrue(packages.get(0).getChildren().stream() + final JavaTestItem examplePackage = packages.stream() + .filter(item -> "example".equals(item.getLabel())) + .findFirst().orElse(null); + assertNotNull(examplePackage); + assertEquals(7, examplePackage.getChildren().size()); + assertTrue(examplePackage.getChildren().stream().anyMatch(item -> "MiniTest".equals(item.getLabel()))); + assertTrue(examplePackage.getChildren().stream().anyMatch(item -> "SiblingTest".equals(item.getLabel()))); + assertTrue(examplePackage.getChildren().stream().anyMatch(item -> "NestedOnlyTest".equals(item.getLabel()))); + assertTrue(examplePackage.getChildren().stream() .anyMatch(item -> "InheritanceChildTest".equals(item.getLabel()))); final List methods = TestSearchUtils.findDirectTestChildrenForClass( @@ -179,4 +182,16 @@ public void testJUnit5UnannotatedOverrideSuppressesInheritedTest() throws Except Arrays.asList(childType.getHandleIdentifier()), new NullProgressMonitor()); assertTrue(methods.isEmpty()); } + + @Test + public void testPackagePrivateTestMethodIsNotInheritedAcrossPackages() throws Exception { + final IProject project = importProjects("preview-junit").get(0); + final IType childType = JavaCore.create(project) + .findType("example.child.CrossPackageSameSignatureTest"); + assertNotNull(childType); + + final List methods = TestSearchUtils.findDirectTestChildrenForClass( + Arrays.asList(childType.getHandleIdentifier()), new NullProgressMonitor()); + assertTrue(methods.isEmpty()); + } } 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 332e9d4ba..62f609b4a 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 @@ -551,8 +551,8 @@ private static void findTestItemsInTypeBinding(ITypeBinding typeBinding, JavaTes /** * Collect test methods inherited from superclasses and append them to {@code accumulator}. * Test frameworks (JUnit, TestNG) execute inherited {@code @Test} methods when running a - * child class, so discovery must list them under the child. Methods overridden by the child - * and non-inheritable (private) superclass methods are skipped. + * child class, so discovery must list them under the child. Override handling follows each + * framework's behavior; non-inheritable superclass methods are skipped. */ private static void addInheritedTestMethodsForKinds(ITypeBinding classBinding, IType childType, List testKinds, List accumulator) throws JavaModelException { @@ -572,23 +572,29 @@ private static void addInheritedTestMethods(ITypeBinding classBinding, IType chi return; } - final Map moreSpecificMethods = new HashMap<>(); + final List moreSpecificMethods = new LinkedList<>(); for (final IMethodBinding declared : classBinding.getDeclaredMethods()) { - moreSpecificMethods.put(getMethodSignatureKey(declared), declared); + moreSpecificMethods.add(declared); } final Set inheritedTestSignatures = new HashSet<>(); ITypeBinding superClass = classBinding.getSuperclass(); while (superClass != null && !"java.lang.Object".equals(superClass.getBinaryName())) { for (final IMethodBinding methodBinding : superClass.getDeclaredMethods()) { - if (Modifier.isPrivate(methodBinding.getModifiers())) { + if (!isInheritedBy(methodBinding, classBinding)) { continue; } final String key = getMethodSignatureKey(methodBinding); if (inheritedTestSignatures.contains(key)) { continue; } - final IMethodBinding overridingMethod = moreSpecificMethods.get(key); + IMethodBinding overridingMethod = null; + for (final IMethodBinding moreSpecificMethod : moreSpecificMethods) { + if (moreSpecificMethod.overrides(methodBinding)) { + overridingMethod = moreSpecificMethod; + break; + } + } for (final TestFrameworkSearcher searcher : searchers) { if (!searcher.isTestMethod(methodBinding)) { continue; @@ -610,14 +616,25 @@ private static void addInheritedTestMethods(ITypeBinding classBinding, IType chi inheritedTestSignatures.add(key); break; } - if (overridingMethod == null) { - moreSpecificMethods.put(key, methodBinding); - } } + Collections.addAll(moreSpecificMethods, superClass.getDeclaredMethods()); superClass = superClass.getSuperclass(); } } + private static boolean isInheritedBy(IMethodBinding methodBinding, ITypeBinding childBinding) { + final int modifiers = methodBinding.getModifiers(); + if (Modifier.isPrivate(modifiers)) { + return false; + } + if (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)) { + return true; + } + return methodBinding.getDeclaringClass().getPackage() != null && + childBinding.getPackage() != null && + methodBinding.getDeclaringClass().getPackage().getName().equals(childBinding.getPackage().getName()); + } + private static String getMethodSignatureKey(IMethodBinding methodBinding) { final StringBuilder key = new StringBuilder(methodBinding.getName()).append('('); for (final ITypeBinding param : methodBinding.getParameterTypes()) { From 31943b72eb7dfa055e9471f58e20cfb7b1736b7b Mon Sep 17 00:00:00 2001 From: Vinay Kumar Maheshwaram <54681400+sudovinay01@users.noreply.github.com> Date: Wed, 11 Nov 2026 21:30:46 +0530 Subject: [PATCH 5/7] Fix inherited Java test discovery Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../META-INF/MANIFEST.MF | 3 +- .../example/InheritanceBinaryChildTest.java | 4 +- .../example/InheritanceBinaryBaseTest.java | 4 +- .../example/GenericInheritanceBaseTest.java | 10 ++++ .../example/GenericInheritanceChildTest.java | 6 ++ .../JUnitLaunchConfigurationDelegateTest.java | 30 ++++++++++ .../test/plugin/util/TestSearchUtilsTest.java | 25 ++++++++- .../JUnitLaunchConfigurationDelegate.java | 53 ++++-------------- .../test/plugin/util/TestSearchUtils.java | 55 ++++--------------- 9 files changed, 97 insertions(+), 93 deletions(-) create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/GenericInheritanceBaseTest.java create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/GenericInheritanceChildTest.java diff --git a/java-extension/com.microsoft.java.test.plugin.test/META-INF/MANIFEST.MF b/java-extension/com.microsoft.java.test.plugin.test/META-INF/MANIFEST.MF index cc743c327..ee695a30d 100644 --- a/java-extension/com.microsoft.java.test.plugin.test/META-INF/MANIFEST.MF +++ b/java-extension/com.microsoft.java.test.plugin.test/META-INF/MANIFEST.MF @@ -9,8 +9,7 @@ Import-Package: org.osgi.framework;version="1.3.0" Bundle-Localization: plugin Bundle-ActivationPolicy: lazy Require-Bundle: org.eclipse.jdt.ls.core, - com.microsoft.java.test.plugin, - com.google.gson, + com.microsoft.java.test.plugin, org.eclipse.lsp4j, com.google.gson, org.junit, org.apache.commons.commons-io;bundle-version="2.12.0", org.eclipse.core.resources, diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-child/src/test/java/example/InheritanceBinaryChildTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-child/src/test/java/example/InheritanceBinaryChildTest.java index a255512ff..adfd19fe4 100644 --- a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-child/src/test/java/example/InheritanceBinaryChildTest.java +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-child/src/test/java/example/InheritanceBinaryChildTest.java @@ -1,4 +1,6 @@ package example; -public class InheritanceBinaryChildTest extends InheritanceBinaryBaseTest { +import org.junit.jupiter.api.TestInfo; + +public class InheritanceBinaryChildTest extends InheritanceBinaryBaseTest { } \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-parent/src/test/java/example/InheritanceBinaryBaseTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-parent/src/test/java/example/InheritanceBinaryBaseTest.java index e73b27ebb..62844964e 100644 --- a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-parent/src/test/java/example/InheritanceBinaryBaseTest.java +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-binary-parent/src/test/java/example/InheritanceBinaryBaseTest.java @@ -3,8 +3,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; -public class InheritanceBinaryBaseTest { +public class InheritanceBinaryBaseTest { @Test - void base(TestInfo info) { + void base(T info) { } } \ No newline at end of file diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/GenericInheritanceBaseTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/GenericInheritanceBaseTest.java new file mode 100644 index 000000000..464e0a75c --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/GenericInheritanceBaseTest.java @@ -0,0 +1,10 @@ +package example; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +public class GenericInheritanceBaseTest { + @Test + void generic(T info) { + } +} diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/GenericInheritanceChildTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/GenericInheritanceChildTest.java new file mode 100644 index 000000000..5573a6b7f --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/GenericInheritanceChildTest.java @@ -0,0 +1,6 @@ +package example; + +import org.junit.jupiter.api.TestInfo; + +public class GenericInheritanceChildTest extends GenericInheritanceBaseTest { +} diff --git a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java index 81e346e99..a164d08d4 100644 --- a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java +++ b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java @@ -13,8 +13,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import java.net.URI; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; @@ -136,6 +138,32 @@ public void testBinaryMethodWithParametersLaunchesAgainstChildClass() throws Exc .contains("example.InheritanceChildTest:assertEquals(java.lang.Object,java.lang.Object)")); } + @Test + public void testGenericSourceMethodLaunchesWithErasedParameterType() throws Exception { + final IProject project = importProjects("preview-junit").get(0); + final IJavaProject javaProject = JavaCore.create(project); + final IType parentType = javaProject.findType("example.GenericInheritanceBaseTest"); + assertNotNull(parentType); + final IMethod genericMethod = Arrays.stream(parentType.getMethods()) + .filter(method -> "generic".equals(method.getElementName())) + .findFirst().orElse(null); + assertNotNull(genericMethod); + + final Map request = new LinkedHashMap<>(); + request.put("projectName", javaProject.getElementName()); + request.put("testLevel", 6); + request.put("testKind", 0); + request.put("testNames", Arrays.asList(genericMethod.getHandleIdentifier())); + request.put("executionClassNames", Arrays.asList("example.GenericInheritanceChildTest")); + + final Response response = JUnitLaunchUtils.resolveLaunchArgument( + Arrays.asList(new Gson().toJson(request)), new NullProgressMonitor()); + + assertEquals(0, response.getStatus()); + assertTrue(Arrays.asList(response.getBody().programArguments) + .contains("example.GenericInheritanceChildTest:generic(org.junit.jupiter.api.TestInfo)")); + } + @Test public void testInheritedBinaryMethodWithParametersLaunchesAgainstChildClass() throws Exception { importProjects(Arrays.asList("inheritance-binary-parent", "inheritance-binary-child")); @@ -149,6 +177,8 @@ public void testInheritedBinaryMethodWithParametersLaunchesAgainstChildClass() t .filter(item -> item.getFullName().contains("#base")) .findFirst().orElse(null); assertNotNull(inherited); + assertEquals(childType.getResource().getLocationURI(), URI.create(inherited.getUri())); + assertNull(inherited.getRange()); final IMethod binaryMethod = (IMethod) JavaCore.create(inherited.getJdtHandler()); assertTrue(binaryMethod.getCompilationUnit() == null); diff --git a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java index 2fb56a285..caebb9c9d 100644 --- a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java +++ b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java @@ -16,6 +16,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import java.net.URI; import java.util.Arrays; import java.util.List; @@ -81,12 +82,14 @@ public void testDiscoverJUnit5TestsWithOlderPreviewSource() throws Exception { .filter(item -> "example".equals(item.getLabel())) .findFirst().orElse(null); assertNotNull(examplePackage); - assertEquals(7, examplePackage.getChildren().size()); + assertEquals(9, examplePackage.getChildren().size()); assertTrue(examplePackage.getChildren().stream().anyMatch(item -> "MiniTest".equals(item.getLabel()))); assertTrue(examplePackage.getChildren().stream().anyMatch(item -> "SiblingTest".equals(item.getLabel()))); assertTrue(examplePackage.getChildren().stream().anyMatch(item -> "NestedOnlyTest".equals(item.getLabel()))); assertTrue(examplePackage.getChildren().stream() .anyMatch(item -> "InheritanceChildTest".equals(item.getLabel()))); + assertTrue(examplePackage.getChildren().stream() + .anyMatch(item -> "GenericInheritanceChildTest".equals(item.getLabel()))); final List methods = TestSearchUtils.findDirectTestChildrenForClass( Arrays.asList(type.getHandleIdentifier()), new NullProgressMonitor()); @@ -156,6 +159,8 @@ public void testInheritedMethodUsesChildProjectAndExecutionClass() throws Except assertEquals(childProject.getName() + "@example.InheritanceChildTest#base()", inherited.getId()); assertEquals("example.InheritanceChildTest", inherited.getExecutionClassName()); assertEquals(parentType.getMethod("base", new String[0]).getHandleIdentifier(), inherited.getJdtHandler()); + assertEquals(childType.getResource().getLocationURI(), URI.create(inherited.getUri())); + assertNull(inherited.getRange()); } @Test @@ -184,7 +189,7 @@ public void testJUnit5UnannotatedOverrideSuppressesInheritedTest() throws Except } @Test - public void testPackagePrivateTestMethodIsNotInheritedAcrossPackages() throws Exception { + public void testPackagePrivateTestMethodIsInheritedAcrossPackages() throws Exception { final IProject project = importProjects("preview-junit").get(0); final IType childType = JavaCore.create(project) .findType("example.child.CrossPackageSameSignatureTest"); @@ -192,6 +197,20 @@ public void testPackagePrivateTestMethodIsNotInheritedAcrossPackages() throws Ex final List methods = TestSearchUtils.findDirectTestChildrenForClass( Arrays.asList(childType.getHandleIdentifier()), new NullProgressMonitor()); - assertTrue(methods.isEmpty()); + assertEquals(1, methods.size()); + assertEquals("example.child.CrossPackageSameSignatureTest#packagePrivateTest()", + methods.get(0).getFullName()); + } + + @Test + public void testDiscoverGenericInheritedMethod() throws Exception { + final IProject project = importProjects("preview-junit").get(0); + final IType childType = JavaCore.create(project).findType("example.GenericInheritanceChildTest"); + assertNotNull(childType); + + final List methods = TestSearchUtils.findDirectTestChildrenForClass( + Arrays.asList(childType.getHandleIdentifier()), new NullProgressMonitor()); + assertEquals(1, methods.size()); + assertEquals("example.GenericInheritanceChildTest", methods.get(0).getExecutionClassName()); } } 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 a196a79da..2c20699c2 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 @@ -16,7 +16,6 @@ import com.microsoft.java.test.plugin.model.TestKind; import com.microsoft.java.test.plugin.model.TestLevel; import com.microsoft.java.test.plugin.util.JUnitPlugin; -import com.microsoft.java.test.plugin.util.TestSearchUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.core.runtime.CoreException; @@ -27,7 +26,6 @@ import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.Launch; -import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMember; @@ -36,13 +34,9 @@ import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.dom.AST; 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.MethodDeclaration; -import org.eclipse.jdt.core.dom.SingleVariableDeclaration; -import org.eclipse.jdt.internal.corext.refactoring.structure.ASTNodeSearchUtil; import org.eclipse.jdt.launching.VMRunnerConfiguration; import java.io.BufferedWriter; @@ -211,42 +205,15 @@ private String resolveMethodTestName(String handleId, String executionClassName) String testName = method.getElementName(); if ((this.args.testKind == TestKind.JUnit5 || this.args.testKind == TestKind.JUnit6) && method.getParameters().length > 0) { - final ICompilationUnit unit = method.getCompilationUnit(); - if (unit == null) { - final ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); - parser.setProject(method.getJavaProject()); - final IBinding[] bindings = parser.createBindings(new IJavaElement[] { method }, - new NullProgressMonitor()); - if (bindings.length != 1 || !(bindings[0] instanceof IMethodBinding)) { - throw new CoreException(new Status(IStatus.ERROR, JUnitPlugin.PLUGIN_ID, IStatus.ERROR, - "Cannot resolve binary method " + method.getElementName(), null)); //$NON-NLS-1$ - } - testName += formatParameterTypes((IMethodBinding) bindings[0]); - } 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 argument for method" + methodDeclaration.toString(), null)); - } - parameters.add(formatParameterType(paramTypeBinding)); - } - } - if (!parameters.isEmpty()) { - testName += "(" + String.join(",", parameters) + ")"; - } + final ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); + parser.setProject(method.getJavaProject()); + final IBinding[] bindings = parser.createBindings(new IJavaElement[] { method }, + new NullProgressMonitor()); + if (bindings.length != 1 || !(bindings[0] instanceof IMethodBinding)) { + throw new CoreException(new Status(IStatus.ERROR, JUnitPlugin.PLUGIN_ID, IStatus.ERROR, + "Cannot resolve method " + method.getElementName(), null)); //$NON-NLS-1$ } + testName += formatParameterTypes((IMethodBinding) bindings[0]); } final String className = StringUtils.isNotBlank(executionClassName) ? executionClassName : method.getDeclaringType().getFullyQualifiedName(); @@ -262,7 +229,9 @@ private String formatParameterTypes(IMethodBinding methodBinding) { } private String formatParameterType(ITypeBinding parameterType) { - return parameterType.isPrimitive() ? parameterType.getQualifiedName() : parameterType.getBinaryName(); + final ITypeBinding erasedType = parameterType.getErasure(); + final ITypeBinding type = erasedType != null ? erasedType : parameterType; + return type.isPrimitive() ? type.getQualifiedName() : type.getBinaryName(); } private String getExecutionClassName(int index) { 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 62f609b4a..4ae7b632c 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 @@ -573,39 +573,27 @@ private static void addInheritedTestMethods(ITypeBinding classBinding, IType chi } final List moreSpecificMethods = new LinkedList<>(); - for (final IMethodBinding declared : classBinding.getDeclaredMethods()) { - moreSpecificMethods.add(declared); - } - - final Set inheritedTestSignatures = new HashSet<>(); + Collections.addAll(moreSpecificMethods, classBinding.getDeclaredMethods()); ITypeBinding superClass = classBinding.getSuperclass(); while (superClass != null && !"java.lang.Object".equals(superClass.getBinaryName())) { for (final IMethodBinding methodBinding : superClass.getDeclaredMethods()) { - if (!isInheritedBy(methodBinding, classBinding)) { + if (Modifier.isPrivate(methodBinding.getModifiers())) { continue; } - final String key = getMethodSignatureKey(methodBinding); - if (inheritedTestSignatures.contains(key)) { - continue; - } - IMethodBinding overridingMethod = null; - for (final IMethodBinding moreSpecificMethod : moreSpecificMethods) { - if (moreSpecificMethod.overrides(methodBinding)) { - overridingMethod = moreSpecificMethod; - break; - } - } for (final TestFrameworkSearcher searcher : searchers) { if (!searcher.isTestMethod(methodBinding)) { continue; } - if (overridingMethod != null && - (searcher.getTestKind() != TestKind.JUnit || searcher.isTestMethod(overridingMethod))) { + final boolean overriddenBySubclass = moreSpecificMethods.stream() + .filter(candidate -> candidate.overrides(methodBinding)) + .anyMatch(candidate -> searcher.getTestKind() != TestKind.JUnit || + searcher.isTestMethod(candidate)); + if (overriddenBySubclass) { break; } final IJavaElement element = methodBinding.getJavaElement(); if (element == null) { - break; + continue; } final JavaTestItem methodItem = new JavaTestItemBuilder().setJavaElement(element) .setLevel(TestLevel.METHOD) @@ -613,7 +601,6 @@ private static void addInheritedTestMethods(ITypeBinding classBinding, IType chi .build(); rebaseMethodItemToChild(methodItem, childType); accumulator.add(methodItem); - inheritedTestSignatures.add(key); break; } } @@ -622,28 +609,6 @@ private static void addInheritedTestMethods(ITypeBinding classBinding, IType chi } } - private static boolean isInheritedBy(IMethodBinding methodBinding, ITypeBinding childBinding) { - final int modifiers = methodBinding.getModifiers(); - if (Modifier.isPrivate(modifiers)) { - return false; - } - if (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)) { - return true; - } - return methodBinding.getDeclaringClass().getPackage() != null && - childBinding.getPackage() != null && - methodBinding.getDeclaringClass().getPackage().getName().equals(childBinding.getPackage().getName()); - } - - private static String getMethodSignatureKey(IMethodBinding methodBinding) { - final StringBuilder key = new StringBuilder(methodBinding.getName()).append('('); - for (final ITypeBinding param : methodBinding.getParameterTypes()) { - final ITypeBinding erasure = param.getErasure() != null ? param.getErasure() : param; - key.append(erasure.getQualifiedName()).append(','); - } - return key.append(')').toString(); - } - /** * Rewrite an inherited method item so its full name and id are anchored at the child class * (e.g. {@code Child#test}) instead of the declaring parent class. The display label, source @@ -662,6 +627,10 @@ private static void rebaseMethodItemToChild(JavaTestItem methodItem, IType child methodItem.setProjectName(childProjectName); methodItem.setId(childProjectName + "@" + childFullName); methodItem.setExecutionClassName(childType.getFullyQualifiedName()); + if (childType.getResource() != null) { + methodItem.setUri(JDTUtils.getFileURI(childType.getResource())); + } + methodItem.setRange(null); } /** From 7af9f03bb60899a5d890c35521fd23e356193ad9 Mon Sep 17 00:00:00 2001 From: Vinay Kumar Maheshwaram <54681400+sudovinay01@users.noreply.github.com> Date: Thu, 12 Nov 2026 01:57:33 +0530 Subject: [PATCH 6/7] test: cover inherited test regression scenarios Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../java/example/JUnit4GrandparentTest.java | 9 +++++++ .../java/example/JUnit4IntermediateTest.java | 7 +++++ .../example/JUnit4ReannotatedChildTest.java | 10 +++++++ .../UnboundedGenericInheritanceBaseTest.java | 9 +++++++ .../UnboundedGenericInheritanceChildTest.java | 4 +++ .../JUnitLaunchConfigurationDelegateTest.java | 26 +++++++++++++++++++ .../test/plugin/util/TestSearchUtilsTest.java | 19 +++++++++++++- 7 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4GrandparentTest.java create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4IntermediateTest.java create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4ReannotatedChildTest.java create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/UnboundedGenericInheritanceBaseTest.java create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/UnboundedGenericInheritanceChildTest.java diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4GrandparentTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4GrandparentTest.java new file mode 100644 index 000000000..75eaf23ee --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4GrandparentTest.java @@ -0,0 +1,9 @@ +package example; + +import org.junit.Test; + +public class JUnit4GrandparentTest { + @Test + public void reannotated() { + } +} diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4IntermediateTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4IntermediateTest.java new file mode 100644 index 000000000..507dc8e0a --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4IntermediateTest.java @@ -0,0 +1,7 @@ +package example; + +public class JUnit4IntermediateTest extends JUnit4GrandparentTest { + @Override + public void reannotated() { + } +} diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4ReannotatedChildTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4ReannotatedChildTest.java new file mode 100644 index 000000000..d938bb44f --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/inheritance-junit4/src/test/java/example/JUnit4ReannotatedChildTest.java @@ -0,0 +1,10 @@ +package example; + +import org.junit.Test; + +public class JUnit4ReannotatedChildTest extends JUnit4IntermediateTest { + @Override + @Test + public void reannotated() { + } +} diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/UnboundedGenericInheritanceBaseTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/UnboundedGenericInheritanceBaseTest.java new file mode 100644 index 000000000..ef4111981 --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/UnboundedGenericInheritanceBaseTest.java @@ -0,0 +1,9 @@ +package example; + +import org.junit.jupiter.api.Test; + +public class UnboundedGenericInheritanceBaseTest { + @Test + void generic(T info) { + } +} diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/UnboundedGenericInheritanceChildTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/UnboundedGenericInheritanceChildTest.java new file mode 100644 index 000000000..472a2ded1 --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/preview-junit/src/test/java/example/UnboundedGenericInheritanceChildTest.java @@ -0,0 +1,4 @@ +package example; + +public class UnboundedGenericInheritanceChildTest extends UnboundedGenericInheritanceBaseTest { +} diff --git a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java index a164d08d4..91a9d7889 100644 --- a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java +++ b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java @@ -164,6 +164,32 @@ public void testGenericSourceMethodLaunchesWithErasedParameterType() throws Exce .contains("example.GenericInheritanceChildTest:generic(org.junit.jupiter.api.TestInfo)")); } + @Test + public void testUnboundedGenericSourceMethodLaunchesWithObjectParameterType() throws Exception { + final IProject project = importProjects("preview-junit").get(0); + final IJavaProject javaProject = JavaCore.create(project); + final IType parentType = javaProject.findType("example.UnboundedGenericInheritanceBaseTest"); + assertNotNull(parentType); + final IMethod genericMethod = Arrays.stream(parentType.getMethods()) + .filter(method -> "generic".equals(method.getElementName())) + .findFirst().orElse(null); + assertNotNull(genericMethod); + + final Map request = new LinkedHashMap<>(); + request.put("projectName", javaProject.getElementName()); + request.put("testLevel", 6); + request.put("testKind", 0); + request.put("testNames", Arrays.asList(genericMethod.getHandleIdentifier())); + request.put("executionClassNames", Arrays.asList("example.UnboundedGenericInheritanceChildTest")); + + final Response response = JUnitLaunchUtils.resolveLaunchArgument( + Arrays.asList(new Gson().toJson(request)), new NullProgressMonitor()); + + assertEquals(0, response.getStatus()); + assertTrue(Arrays.asList(response.getBody().programArguments) + .contains("example.UnboundedGenericInheritanceChildTest:generic(java.lang.Object)")); + } + @Test public void testInheritedBinaryMethodWithParametersLaunchesAgainstChildClass() throws Exception { importProjects(Arrays.asList("inheritance-binary-parent", "inheritance-binary-child")); diff --git a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java index caebb9c9d..a98f48ab4 100644 --- a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java +++ b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/util/TestSearchUtilsTest.java @@ -82,7 +82,7 @@ public void testDiscoverJUnit5TestsWithOlderPreviewSource() throws Exception { .filter(item -> "example".equals(item.getLabel())) .findFirst().orElse(null); assertNotNull(examplePackage); - assertEquals(9, examplePackage.getChildren().size()); + assertEquals(11, examplePackage.getChildren().size()); assertTrue(examplePackage.getChildren().stream().anyMatch(item -> "MiniTest".equals(item.getLabel()))); assertTrue(examplePackage.getChildren().stream().anyMatch(item -> "SiblingTest".equals(item.getLabel()))); assertTrue(examplePackage.getChildren().stream().anyMatch(item -> "NestedOnlyTest".equals(item.getLabel()))); @@ -90,6 +90,8 @@ public void testDiscoverJUnit5TestsWithOlderPreviewSource() throws Exception { .anyMatch(item -> "InheritanceChildTest".equals(item.getLabel()))); assertTrue(examplePackage.getChildren().stream() .anyMatch(item -> "GenericInheritanceChildTest".equals(item.getLabel()))); + assertTrue(examplePackage.getChildren().stream() + .anyMatch(item -> "UnboundedGenericInheritanceChildTest".equals(item.getLabel()))); final List methods = TestSearchUtils.findDirectTestChildrenForClass( Arrays.asList(type.getHandleIdentifier()), new NullProgressMonitor()); @@ -177,6 +179,21 @@ public void testJUnit4TestSurvivesUnannotatedOverride() throws Exception { assertEquals("example.JUnit4ChildTest", methods.get(0).getExecutionClassName()); } + @Test + public void testJUnit4AnnotatedChildOverrideSurvivesUnannotatedMiddleOverride() throws Exception { + importProjects("inheritance-junit4"); + final IJavaProject javaProject = JavaCore.create(WorkspaceHelper.getProject("inheritance-junit4")); + final IType childType = javaProject.findType("example.JUnit4ReannotatedChildTest"); + assertNotNull(childType); + + final List methods = TestSearchUtils.findDirectTestChildrenForClass( + Arrays.asList(childType.getHandleIdentifier()), new NullProgressMonitor()); + assertEquals(1, methods.size()); + assertEquals("example.JUnit4ReannotatedChildTest#reannotated", methods.get(0).getFullName()); + assertEquals(childType.getMethod("reannotated", new String[0]).getHandleIdentifier(), + methods.get(0).getJdtHandler()); + } + @Test public void testJUnit5UnannotatedOverrideSuppressesInheritedTest() throws Exception { final IProject project = importProjects("preview-junit").get(0); From 6d596cdad50039afca45f65e2d3326f9b881fda8 Mon Sep 17 00:00:00 2001 From: Vinay Kumar Maheshwaram <54681400+sudovinay01@users.noreply.github.com> Date: Thu, 12 Nov 2026 08:43:54 +0530 Subject: [PATCH 7/7] docs: fix inherited test comments and formatting Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../JUnitLaunchConfigurationDelegateTest.java | 46 +++++++++---------- .../test/plugin/util/TestSearchUtils.java | 5 +- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java index 91a9d7889..5f68ed0b2 100644 --- a/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java +++ b/java-extension/com.microsoft.java.test.plugin.test/src/com/microsoft/java/test/plugin/launchers/JUnitLaunchConfigurationDelegateTest.java @@ -88,29 +88,29 @@ public void testAddOpensForAllSelectedPackagesInModularProject() throws Exceptio assertEquals("--add-opens", vmArguments.get(vmArguments.indexOf(secondPackageOpen) - 1)); } - @Test - public void testInheritedMethodLaunchesAgainstChildClass() throws Exception { - importProjects(Arrays.asList("inheritance-parent", "inheritance-child")); - final IProject childProject = WorkspaceHelper.getProject("inheritance-child"); - final IJavaProject childJavaProject = JavaCore.create(childProject); - final IType parentType = JavaCore.create(WorkspaceHelper.getProject("inheritance-parent")) - .findType("example.InheritanceBaseTest"); - - final Map request = new LinkedHashMap<>(); - request.put("projectName", childProject.getName()); - request.put("testLevel", 6); - request.put("testKind", 0); - request.put("testNames", Arrays.asList(parentType.getMethod("base", new String[0]).getHandleIdentifier())); - request.put("executionClassNames", Arrays.asList("example.InheritanceChildTest")); - - final Response response = JUnitLaunchUtils.resolveLaunchArgument( - Arrays.asList(new Gson().toJson(request)), new NullProgressMonitor()); - - assertEquals(0, response.getStatus()); - assertEquals(childJavaProject.getElementName(), response.getBody().projectName); - assertTrue(Arrays.asList(response.getBody().programArguments) - .contains("example.InheritanceChildTest:base")); - } + @Test + public void testInheritedMethodLaunchesAgainstChildClass() throws Exception { + importProjects(Arrays.asList("inheritance-parent", "inheritance-child")); + final IProject childProject = WorkspaceHelper.getProject("inheritance-child"); + final IJavaProject childJavaProject = JavaCore.create(childProject); + final IType parentType = JavaCore.create(WorkspaceHelper.getProject("inheritance-parent")) + .findType("example.InheritanceBaseTest"); + + final Map request = new LinkedHashMap<>(); + request.put("projectName", childProject.getName()); + request.put("testLevel", 6); + request.put("testKind", 0); + request.put("testNames", Arrays.asList(parentType.getMethod("base", new String[0]).getHandleIdentifier())); + request.put("executionClassNames", Arrays.asList("example.InheritanceChildTest")); + + final Response response = JUnitLaunchUtils.resolveLaunchArgument( + Arrays.asList(new Gson().toJson(request)), new NullProgressMonitor()); + + assertEquals(0, response.getStatus()); + assertEquals(childJavaProject.getElementName(), response.getBody().projectName); + assertTrue(Arrays.asList(response.getBody().programArguments) + .contains("example.InheritanceChildTest:base")); + } @Test public void testBinaryMethodWithParametersLaunchesAgainstChildClass() throws Exception { 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 4ae7b632c..513fb25e2 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 @@ -611,8 +611,9 @@ private static void addInheritedTestMethods(ITypeBinding classBinding, IType chi /** * Rewrite an inherited method item so its full name and id are anchored at the child class - * (e.g. {@code Child#test}) instead of the declaring parent class. The display label, source - * range, and JDT handler still point at the parent declaration. + * (e.g. {@code Child#test}) instead of the declaring parent class. The display label and JDT + * handler still point at the parent declaration; the URI points to the child type, and the + * range is cleared because the parent declaration's range must not be used in the child. */ private static void rebaseMethodItemToChild(JavaTestItem methodItem, IType childType) throws JavaModelException {