From 3357d6ef35e97ab1efe35c489e076c94a8f1a9c5 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Fri, 10 Jul 2026 16:12:43 +0800 Subject: [PATCH 1/2] fix: add module opens for all selected test classes Pass selected class JDT handles to the launch delegate so Eclipse JDT can generate VM arguments for every test package without rediscovering the project. Fixes #1883 Copilot-Session: c3cd113b-dad4-4d4d-90c2-60abfd204405 --- .../projects/modular-junit/.classpath | 14 ++++++ .../projects/modular-junit/.project | 17 ++++++++ .../src/main/java/module-info.java | 2 + .../src/test/java/p1/FirstTest.java | 9 ++++ .../src/test/java/p2/SecondTest.java | 9 ++++ .../JUnitLaunchConfigurationDelegateTest.java | 43 +++++++++++++++++++ .../JUnitLaunchConfigurationDelegate.java | 27 ++++++++++++ .../plugin/launchers/JUnitLaunchUtils.java | 1 + src/utils/launchUtils.ts | 22 +++++++++- 9 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/.classpath create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/.project create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/src/main/java/module-info.java create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/src/test/java/p1/FirstTest.java create mode 100644 java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/src/test/java/p2/SecondTest.java diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/.classpath b/java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/.classpath new file mode 100644 index 00000000..b6d99b1f --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/.classpath @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/.project b/java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/.project new file mode 100644 index 00000000..1615557d --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/.project @@ -0,0 +1,17 @@ + + + modular-junit + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/src/main/java/module-info.java b/java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/src/main/java/module-info.java new file mode 100644 index 00000000..47c99b7e --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/src/main/java/module-info.java @@ -0,0 +1,2 @@ +module com.example.modular { +} diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/src/test/java/p1/FirstTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/src/test/java/p1/FirstTest.java new file mode 100644 index 00000000..b94a787c --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/src/test/java/p1/FirstTest.java @@ -0,0 +1,9 @@ +package p1; + +import org.junit.jupiter.api.Test; + +class FirstTest { + @Test + void testFirstPackage() { + } +} diff --git a/java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/src/test/java/p2/SecondTest.java b/java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/src/test/java/p2/SecondTest.java new file mode 100644 index 00000000..93df5d89 --- /dev/null +++ b/java-extension/com.microsoft.java.test.plugin.test/projects/modular-junit/src/test/java/p2/SecondTest.java @@ -0,0 +1,9 @@ +package p2; + +import org.junit.jupiter.api.Test; + +class SecondTest { + @Test + void testSecondPackage() { + } +} 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 d5c97416..3c4b7fec 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 @@ -12,6 +12,7 @@ package com.microsoft.java.test.plugin.launchers; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.util.Arrays; @@ -19,6 +20,9 @@ import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaCore; import org.junit.Test; import com.microsoft.java.test.plugin.AbstractProjectsManagerBasedTest; @@ -39,4 +43,43 @@ public void testWorkingDirectoryForTestNgUnmanagedFolder() throws Exception { assertEquals(0, response.getStatus()); assertTrue(response.getBody().workingDirectory.endsWith("simple")); } + + @Test + public void testAddOpensForAllSelectedPackagesInModularProject() throws Exception { + final IProject project = importProjects("modular-junit").get(0); + final IJavaProject javaProject = JavaCore.create(project); + final IType firstTest = javaProject.findType("p1.FirstTest"); + final IType secondTest = javaProject.findType("p2.SecondTest"); + assertNotNull(firstTest); + assertNotNull(secondTest); + + final String launchRequest = String.format( + "{\"projectName\":%s,\"testLevel\":5,\"testKind\":0,\"testNames\":[%s,%s]," + + "\"testHandles\":[%s,%s]}", + toJsonString(javaProject.getElementName()), + toJsonString(firstTest.getFullyQualifiedName()), + toJsonString(secondTest.getFullyQualifiedName()), + toJsonString(firstTest.getHandleIdentifier()), + toJsonString(secondTest.getHandleIdentifier())); + + final Response response = JUnitLaunchUtils.resolveLaunchArgument( + Arrays.asList(launchRequest), new NullProgressMonitor()); + + assertEquals(0, response.getStatus()); + final List vmArguments = Arrays.asList(response.getBody().vmArguments); + final String firstPackagePrefix = "com.example.modular/p1="; + final String firstPackageOpen = vmArguments.stream() + .filter(argument -> argument.startsWith(firstPackagePrefix)) + .findFirst() + .orElseThrow(() -> new AssertionError("Missing --add-opens for p1")); + final String targets = firstPackageOpen.substring(firstPackagePrefix.length()); + final String secondPackageOpen = "com.example.modular/p2=" + targets; + assertEquals("--add-opens", vmArguments.get(vmArguments.indexOf(firstPackageOpen) - 1)); + assertTrue(vmArguments.contains(secondPackageOpen)); + assertEquals("--add-opens", vmArguments.get(vmArguments.indexOf(secondPackageOpen) - 1)); + } + + private static String toJsonString(String value) { + return "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\""; + } } 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 65cfffbb..c0711c5c 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 @@ -28,8 +28,11 @@ 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; import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ITypeBinding; @@ -65,6 +68,30 @@ public JUnitLaunchConfigurationDelegate(Argument args) { this.args = args; } + @Override + protected IMember[] evaluateTests(ILaunchConfiguration configuration, IProgressMonitor monitor) + throws CoreException { + if (this.args.testLevel != TestLevel.CLASS || this.args.testNames == null || + this.args.testNames.length < 2 || this.args.testHandles == null) { + return super.evaluateTests(configuration, monitor); + } + if (this.args.testHandles.length != this.args.testNames.length) { + throw new CoreException(new Status(IStatus.ERROR, JUnitPlugin.PLUGIN_ID, + "Cannot resolve all selected test classes. Refresh the Test Explorer and retry.")); + } + + final List testTypes = new ArrayList<>(); + for (final String handle : this.args.testHandles) { + final IJavaElement element = JavaCore.create(handle); + if (!(element instanceof IType) || !element.exists()) { + throw new CoreException(new Status(IStatus.ERROR, JUnitPlugin.PLUGIN_ID, + "Cannot resolve a selected test class. Refresh the Test Explorer and retry.")); + } + testTypes.add((IType) element); + } + return testTypes.toArray(new IMember[testTypes.size()]); + } + public Response getJUnitLaunchArguments(ILaunchConfiguration configuration, String mode, IProgressMonitor monitor) { final ILaunch launch = new Launch(configuration, mode, null); 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 f09703a3..738ba41a 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 @@ -258,6 +258,7 @@ class Argument { public TestLevel testLevel; public TestKind testKind; public String[] testNames; + public String[] testHandles; public String uniqueId; } } diff --git a/src/utils/launchUtils.ts b/src/utils/launchUtils.ts index 0fb746e4..d22e4eb6 100644 --- a/src/utils/launchUtils.ts +++ b/src/utils/launchUtils.ts @@ -96,6 +96,7 @@ async function getLaunchArguments(testContext: IRunTestContext): Promise { +function getTestHandles(testContext: IRunTestContext): string[] { + if (dataCache.get(testContext.testItems[0])?.testLevel !== TestLevel.Class || + testContext.testItems.length < 2) { + return []; + } + + const handles: (string | undefined)[] = testContext.testItems.map((item: TestItem) => { + return dataCache.get(item)?.jdtHandler; + }); + if (handles.some((handle: string | undefined) => !handle)) { + const error: Error = new Error('Failed to resolve all selected test classes. Refresh the Test Explorer and retry.'); + sendError(error); + throw error; + } + return handles as string[]; +} + +async function resolveJUnitLaunchArguments(projectName: string, testLevel: TestLevel, testKind: TestKind, + testNames: string[], testHandles: string[], uniqueId: string | undefined): Promise { const argument: Response | undefined = await executeJavaLanguageServerCommand>( JavaTestRunnerDelegateCommands.RESOLVE_JUNIT_ARGUMENT, JSON.stringify({ projectName, testLevel, testKind, testNames, + testHandles, uniqueId }), ); From 6ed580db468abe4de2a426a0deb6dd9159d98818 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Mon, 13 Jul 2026 10:21:35 +0800 Subject: [PATCH 2/2] test: serialize launch request with Gson Copilot-Session: c3cd113b-dad4-4d4d-90c2-60abfd204405 --- .../META-INF/MANIFEST.MF | 1 + .../JUnitLaunchConfigurationDelegateTest.java | 24 +++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) 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 4e03aee2..cc743c32 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 @@ -10,6 +10,7 @@ Bundle-Localization: plugin Bundle-ActivationPolicy: lazy Require-Bundle: org.eclipse.jdt.ls.core, com.microsoft.java.test.plugin, + 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/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 3c4b7fec..6baaf304 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 @@ -16,7 +16,9 @@ import static org.junit.Assert.assertTrue; import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.NullProgressMonitor; @@ -27,6 +29,7 @@ import com.microsoft.java.test.plugin.AbstractProjectsManagerBasedTest; import com.microsoft.java.test.plugin.model.Response; +import com.google.gson.Gson; public class JUnitLaunchConfigurationDelegateTest extends AbstractProjectsManagerBasedTest { @@ -53,17 +56,17 @@ public void testAddOpensForAllSelectedPackagesInModularProject() throws Exceptio assertNotNull(firstTest); assertNotNull(secondTest); - final String launchRequest = String.format( - "{\"projectName\":%s,\"testLevel\":5,\"testKind\":0,\"testNames\":[%s,%s]," - + "\"testHandles\":[%s,%s]}", - toJsonString(javaProject.getElementName()), - toJsonString(firstTest.getFullyQualifiedName()), - toJsonString(secondTest.getFullyQualifiedName()), - toJsonString(firstTest.getHandleIdentifier()), - toJsonString(secondTest.getHandleIdentifier())); + final Map request = new LinkedHashMap<>(); + request.put("projectName", javaProject.getElementName()); + request.put("testLevel", 5); + request.put("testKind", 0); + request.put("testNames", Arrays.asList( + firstTest.getFullyQualifiedName(), secondTest.getFullyQualifiedName())); + request.put("testHandles", Arrays.asList( + firstTest.getHandleIdentifier(), secondTest.getHandleIdentifier())); final Response response = JUnitLaunchUtils.resolveLaunchArgument( - Arrays.asList(launchRequest), new NullProgressMonitor()); + Arrays.asList(new Gson().toJson(request)), new NullProgressMonitor()); assertEquals(0, response.getStatus()); final List vmArguments = Arrays.asList(response.getBody().vmArguments); @@ -79,7 +82,4 @@ public void testAddOpensForAllSelectedPackagesInModularProject() throws Exceptio assertEquals("--add-opens", vmArguments.get(vmArguments.indexOf(secondPackageOpen) - 1)); } - private static String toJsonString(String value) { - return "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\""; - } }