[#3393] Support multiple Java modules per POM with Maven 4 module source hierarchy - #3394
[#3393] Support multiple Java modules per POM with Maven 4 module source hierarchy#3394ascheman wants to merge 6 commits into
Conversation
The IT fixture defaults to embedded launching via Embedded3xLauncher
(maven-shared-verifier), which looks up org.apache.maven.cli.MavenCli.
Maven 4 renamed the entry point to org.apache.maven.cling.MavenCling
with a different signature, so every embedded launch against Maven 4
aborts with NoSuchMethodException before any test runs.
Detect Maven 4 by the presence of maven-api-core in ${maven.home}/lib
and default forkJvm to true in that case; Maven 3 keeps embedded mode.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Maven 4 with maven-compiler-plugin 4.x compiles module source hierarchy projects to a nested layout (target/classes/<module>/, target/test-classes/<module>/) and emits a runtime handoff file META-INF/maven/module-info-patch.args derived from module-info-patch.maven. Surefire handled neither: - findModuleDescriptor() only looked for module-info.class at the build output root, so detection failed and execution silently fell back to the classpath. - DirectoryScanner read the module directory as a package prefix, producing doubled FQCNs and "Tests run: 0" or "Unable to create test class" failures. - The compiler-generated module-info-patch.args was ignored, dropping the --add-exports directives declared in module-info-patch.maven. Detect the nested module directory, scan its test classes, patch the module with target/test-classes/<module>, and merge the handoff file into the fork argfile. --add-reads/--add-modules lines from the file are skipped (one-line and two-line argfile forms): they may reference named modules that surefire places on the classpath; surefire keeps generating --add-reads <module>=ALL-UNNAMED, --add-opens for test packages and --add-modules ALL-MODULE-PATH itself. The nested layout is decided by the MAIN build output only (no root module-info.class plus a module subdirectory containing one): a classic modular project whose module is named after its root package (module "it", package "it") has target/test-classes/it/ as a package directory, which must not be mistaken for a nested test output and patched into the module (would break class loading of every test). Covered by unit tests and two ITs: ModulePathWhiteboxIT (classic layout, runs on Maven 3+4) and Surefire3345ModuleSourceHierarchyIT (nested layout, skips itself below Maven 4). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
052fcc4 to
4b03dae
Compare
There was a problem hiding this comment.
Pull request overview
Adds Surefire support for multiple JPMS modules produced from a single Maven 4 Module Source Hierarchy POM, enabling one test execution to discover and run tests across all nested target/test-classes/<module>/ outputs while producing a single, unified JPMS boot layer for the forked JVM.
Changes:
- Detect and resolve all nested module descriptors under
target/classes/<module>/, selecting a primary module and carrying sibling module descriptors alongside it. - Scan and union test classes from each nested
target/test-classes/<module>/directory, and generate per-sibling--patch-module/--add-reads/--add-opensJPMS arguments. - Add/extend IT fixtures and integration tests covering single-module MSH and multi-module-in-one-POM MSH scenarios (Maven 4-only where appropriate).
Reviewed changes
Copilot reviewed 35 out of 35 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.extra/test/java/module-info-patch.maven | Adds compiler patch input for the com.example.extra module (whitebox access). |
| surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.extra/test/java/com/example/extra/internal/TwiceHelperWhiteboxTest.java | Adds whitebox test validating internal-package access in com.example.extra. |
| surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.extra/test/java/com/example/extra/DoublerTest.java | Adds regular test for com.example.extra exported API. |
| surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.extra/main/java/module-info.java | Declares the com.example.extra module and its dependency on com.example.core. |
| surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.extra/main/java/com/example/extra/internal/TwiceHelper.java | Adds internal helper used by the extra module and its whitebox test. |
| surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.extra/main/java/com/example/extra/Doubler.java | Adds exported class exercising a cross-module call into com.example.core. |
| surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.core/test/java/module-info-patch.maven | Adds compiler patch input for the com.example.core module (whitebox access). |
| surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.core/test/java/com/example/core/internal/MathHelperWhiteboxTest.java | Adds whitebox test validating internal-package access in com.example.core. |
| surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.core/test/java/com/example/core/CalculatorTest.java | Adds tests exercising both basic behavior and an external module dependency (jakarta.json). |
| surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.core/main/java/module-info.java | Declares the com.example.core module and a transitive dependency on jakarta.json. |
| surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.core/main/java/com/example/core/internal/MathHelper.java | Adds internal helper used for whitebox coverage in core. |
| surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/src/com.example.core/main/java/com/example/core/Calculator.java | Adds exported class referencing jakarta.json to validate module-path dependency placement. |
| surefire-its/src/test/resources/surefire-3393-multi-module-source-hierarchy/pom.xml | Defines a Maven 4.1.0 multi-module-source-hierarchy test project with two modules in one POM. |
| surefire-its/src/test/resources/surefire-3345-module-source-hierarchy/src/com.example/test/java/module-info-patch.maven | Adds compiler patch input for the single-module MSH IT fixture. |
| surefire-its/src/test/resources/surefire-3345-module-source-hierarchy/src/com.example/test/java/com/example/internal/MathHelperWhiteboxTest.java | Adds whitebox tests for the single-module MSH IT fixture. |
| surefire-its/src/test/resources/surefire-3345-module-source-hierarchy/src/com.example/test/java/com/example/CalculatorTest.java | Adds a basic exported-API test for the single-module MSH IT fixture. |
| surefire-its/src/test/resources/surefire-3345-module-source-hierarchy/src/com.example/main/java/module-info.java | Declares the module for the single-module MSH IT fixture. |
| surefire-its/src/test/resources/surefire-3345-module-source-hierarchy/src/com.example/main/java/com/example/internal/MathHelper.java | Adds internal helper for whitebox coverage in the single-module MSH fixture. |
| surefire-its/src/test/resources/surefire-3345-module-source-hierarchy/src/com.example/main/java/com/example/Calculator.java | Adds exported class used by tests in the single-module MSH fixture. |
| surefire-its/src/test/resources/surefire-3345-module-source-hierarchy/pom.xml | Defines the Maven 4.1.0 single-module-source-hierarchy test project fixture. |
| surefire-its/src/test/resources/modulepath-whitebox/src/test/java/com/example/internal/MathHelperWhiteboxTest.java | Adds/updates baseline modular whitebox fixture test content (classic layout). |
| surefire-its/src/test/resources/modulepath-whitebox/src/test/java/com/example/CalculatorTest.java | Adds/updates baseline modular fixture test content (classic layout). |
| surefire-its/src/test/resources/modulepath-whitebox/src/main/java/module-info.java | Declares the module for the baseline modular whitebox fixture. |
| surefire-its/src/test/resources/modulepath-whitebox/src/main/java/com/example/internal/MathHelper.java | Adds internal helper used by baseline whitebox tests. |
| surefire-its/src/test/resources/modulepath-whitebox/src/main/java/com/example/Calculator.java | Adds exported class used by baseline tests. |
| surefire-its/src/test/resources/modulepath-whitebox/pom.xml | Defines the baseline modular whitebox fixture POM (Maven 3-compatible). |
| surefire-its/src/test/java/org/apache/maven/surefire/its/ModulePathWhiteboxIT.java | Adds an integration test validating classic modular whitebox behavior via --patch-module. |
| surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire3393MultiModuleSourceHierarchyIT.java | Adds Maven 4-only IT validating multi-module-in-one-POM MSH behavior (single execution). |
| surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire3345ModuleSourceHierarchyIT.java | Adds/updates Maven 4-only IT validating single-module MSH + module-info-patch args handling. |
| surefire-its/src/test/java/org/apache/maven/surefire/its/fixture/MavenLauncher.java | Switches IT launcher default to forked mode when running under Maven 4+. |
| maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfigurationTest.java | Adds unit tests around reading/skipping directives from module-info-patch.args. |
| maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoJava7PlusTest.java | Adds unit tests for nested module descriptor detection and multi-descriptor ordering. |
| maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/ResolvePathResultWrapper.java | Extends wrapper to carry sibling module descriptor resolutions. |
| maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ModularClasspathForkConfiguration.java | Reads compiler-generated module-info-patch.args and appends JPMS args into the fork args file. |
| maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java | Resolves multiple nested module descriptors, unions nested test scans, and generates per-module JPMS args. |
| /** | ||
| * Reads the module-info-patch.args file and appends its --add-exports and --add-opens | ||
| * directives to the args builder. The --add-reads directive is handled by surefire itself | ||
| * (always adds ALL-UNNAMED) to avoid referencing modules that may be on the classpath | ||
| * rather than the module-path. | ||
| * |
Address Copilot review feedback on apache#3392: - Reword the inline comment and the appendModuleInfoPatchArgs Javadoc: the method forwards ALL directives from module-info-patch.args except --add-reads/--add-modules, not only --add-exports. - Sort the nested module directory candidates so the picked module no longer depends on filesystem iteration order. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4b03dae to
0203470
Compare
The compiler-generated handoff file is UTF-8; FileReader uses the platform default charset and could misparse non-ASCII module or package names. Addresses Copilot review feedback on apache#3394. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0203470 to
abce838
Compare
Address desruisseaux's review comments on apache#3392: - Javadoc for createArgsFile, documenting in particular the patchFile parameter (test output directory, or its per-module subdirectory for a Maven 4 module source hierarchy build). - Correct the findModuleInfoPatchArgs documentation: there is exactly one module-info-patch.args per project, at target/test-classes/META-INF/maven/; the parent lookup exists because the patch directory is the nested per-module directory in a module source hierarchy build. - Drop the explicit charset: Files.newBufferedReader(Path) already reads UTF-8. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A Maven 4 module source hierarchy build may declare several Java modules in one POM; the single-module support from apache#3345 detected only the first nested module, scanned only its tests, and decided classpath-vs-module-path dependency placement with one descriptor, so the fork died at the Java Modules boot layer when a sibling module required a dependency that stayed on the classpath. - findModuleDescriptor() resolves ALL nested module descriptors; the primary module (driving scanning and --patch-module) is the first one with a nested test output directory, siblings travel as additional results in ResolvePathResultWrapper. - scanDirectories() unions the scan over every nested target/test-classes/<module>/ directory. - The classpath/module-path split is the union over all module descriptors: an element required on the module path by ANY module goes there, since the fork has a single boot layer. - The primary module only opens its own test packages; each sibling module with tests gets --patch-module, --add-reads and per-package --add-opens, passed through the existing StartupConfiguration#getJpmsArguments() channel - no change to the surefire-booter API or its serialization. Covered by a unit test for the multi-descriptor detection and the IT Surefire3393MultiModuleSourceHierarchyIT: two modules in one POM (com.example.extra requires com.example.core, core requires transitive jakarta.json to cover the boot-layer failure), whitebox tests in both modules, five tests in one execution. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
abce838 to
ec267cb
Compare
|
Merged into #3392 as requested by @desruisseaux (via Slack) — the stacked split created review noise without adding value: the shared commits showed up in both diffs and review comments landed on either PR. The multi-module commit now lives directly on the #3392 branch ( |
The compiler-generated handoff file is UTF-8; FileReader uses the platform default charset and could misparse non-ASCII module or package names. Addresses Copilot review feedback on apache#3394. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes #3393. Stacked on #3392 (single-module Module Source Hierarchy support) — draft until #3392 is merged; only the last commit is specific to this PR.
Changes
ResolvePathResultWrapperadditionally carries the descriptors of sibling modules found undertarget/classes/<module>/.AbstractSurefireMojo.findModuleDescriptor(...)resolves ALL nested module descriptors; the primary module (driving scanning and--patch-module) is the first one that has a nested test output directory.scanDirectories()unions the test-class scan over every nestedtarget/test-classes/<module>/directory.newStartupConfigWithModularPath(...):--add-openslist is restricted to its own test packages;--patch-module <m>=<its test dir>,--add-reads <m>=ALL-UNNAMEDand--add-opens <m>/<pkg>=ALL-UNNAMED, passed to the fork through the existingStartupConfiguration#getJpmsArguments()channel (no change to the surefire-booter API or its serialization).Tests
shouldFindAllNestedModuleDescriptors(all nested modules, deterministic order).Surefire3393MultiModuleSourceHierarchyIT— two modules in one POM (com.example.extrarequirescom.example.core, corerequires transitive jakarta.jsonto cover the external-modular-dependency boot failure), whitebox tests in both modules, 5 tests in ONE execution; skips itself under Maven 3.Verification (local, JDK 17 for surefire build/ITs, macOS)
maven-surefire-commonunit suite: 849 tests, 0 failures.mvn testruns 602 tests, 0 failures in ONE surefire execution. With the single-module state ([#3345/#3393] Support Maven 4 module source hierarchy (single + multiple modules per POM) #3392) the same run fails at the JPMS boot layer (FindException: Module jakarta.json.bind not found); before Support Maven 4 Module Source Hierarchy and module-info-patch.args for modular whitebox testing #3345 it silently ran 0 tests. Fork argsfile verified: two--patch-moduleentries, per-module--add-reads/--add-opens,--add-exportsfrommodule-info-patch.args, and the union dependency split placingjakarta.jsonon the module path.Following this checklist to help us incorporate your contribution quickly and easily:
mvn clean installto make sure basic checks pass. A more thorough check will be performed on your pull request automatically.