Skip to content

--add-opens VM args incomplete for modular projects when running a multi-class test suite #1883

Description

@xmourgues

--add-opens VM args incomplete for modular projects when running a multi-class test suite

Description

When running a test suite at the module level on a JPMS-modular project, VS Code Java Test only generates --add-opens for one package (the first test class selected), leaving all other packages in the module closed to the JUnit runtime. This causes InaccessibleObjectException failures that do not occur when running the same suite in Eclipse IDE.

Steps to reproduce

  1. Have a Maven multi-module project with JPMS (module-info.java in the module under test).
  2. In VS Code Test Explorer, run all tests at the module level (click the ▶ button next to the module node).
  3. Observe that some tests fail with:
    java.lang.reflect.InaccessibleObjectException:
    Unable to make ... accessible: module com.example.mymodule does not opens com.example.mymodule.somepackage to unnamed module
    
  4. Run the exact same suite in Eclipse IDE → all tests pass.

Root cause

The issue is in JUnitLaunchConfigurationDelegate.java.

The launch argument resolution follows this sequence:

Step 1getJUnitLaunchArguments calls the parent JDT delegate's getVMRunnerConfiguration via reflection (acknowledged by the TODO comment in the code):

// TODO: Make the getVMRunnerConfiguration() in super class protected.
final Method getVMRunnerConfiguration = getClass().getSuperclass().getDeclaredMethod(
    "getVMRunnerConfiguration", ILaunchConfiguration.class, ILaunch.class,
    String.class, IProgressMonitor.class);
getVMRunnerConfiguration.invoke(this, configuration, launch, mode, new NullProgressMonitor());

The parent's collectExecutionArguments runs here. It generates --add-opens com.example.mymodule/<package>=ALL-UNNAMED for each test element in fTestElements. However, at this point the JDT configuration only knows about one class — the "main type" derived from testNames[0] in JUnitLaunchUtils.resolveLaunchArgument. So --add-opens is emitted for exactly one package.

Step 2parseParameters strips out the test-selection arguments that the parent produced (-test, -testNameFile, -classNames, -packageNameFile):

private String[] parseParameters(String[] programArguments) throws CoreException {
    for (int i = 0; i < programArguments.length; i++) {
        if (testNameArgs.contains(programArguments[i])) {
            // skip this argument and its value(s)
            while (i + 1 < programArguments.length && !programArguments[i + 1].startsWith("-")) {
                i++;
            }
        } else {
            arguments.add(programArguments[i]);
            ...
        }
    }
    addTestItemArgs(arguments); // re-injects the full class list
}

addTestItemArgs re-injects the complete list of test classes (all selected classes in the module) into -testNameFile. This is correct — the test runner now receives all classes.

Step 3 – The --add-opens VM args were already frozen in Step 1 and are not updated. The full class list added in Step 2 contains classes from many different packages, all of which remain closed.

Result: The JUnit engine can run the first class (its package was opened), but fails with InaccessibleObjectException as soon as it tries to reflectively access any class from a different package.

Why Eclipse IDE does not have this problem

Eclipse IDE uses the upstream JDT JUnitLaunchConfigurationDelegate.collectExecutionArguments directly, which iterates all test elements and emits --add-opens for each package before launching. It also generates more --add-reads directives. There is no two-phase "generate for one class, then replace the list" pattern.

Workaround

Manually add --add-opens for every package in the module to java.test.config.vmArgs in .vscode/settings.json:

"java.test.config": [
  {
    "name": "my-config",
    "vmArgs": [
      "--add-modules", "ALL-MODULE-PATH",
      "--add-opens", "com.example.mymodule/com.example.mymodule.packagea=ALL-UNNAMED",
      "--add-opens", "com.example.mymodule/com.example.mymodule.packageb=ALL-UNNAMED"
    ]
  }
]

Expected behavior

When running a multi-class or module-level test suite, --add-opens should be generated for every package of every test class in the selection, not only for the first class used to set up the JDT launch configuration.

Proposed fix direction

After addTestItemArgs populates the full test class list, re-derive the set of packages from this.args.testNames and append the missing --add-opens entries to vmArguments. Alternatively, expose getVMRunnerConfiguration as protected in the upstream JDT delegate (the TODO comment already flags this) so that the full fTestElements can be populated correctly before collectExecutionArguments runs.

Environment

  • Extension: vscjava.vscode-java-test (replace with your installed version)
  • Java extension: redhat.java
  • Project type: Maven multi-module, JPMS (module-info.java present)
  • JDK: 21

Metadata

Metadata

Assignees

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions