This is an automated email from the ASF dual-hosted git repository. ppkarwasz pushed a commit to branch feature/ci-matrix in repository https://gitbox.apache.org/repos/asf/commons-xml.git
commit b9964b3428a97bd89ecb2a225bf7d31efe213e80 Author: Piotr P. Karwasz <[email protected]> AuthorDate: Thu Jul 9 22:04:32 2026 +0200 Run the test suite as a GraalVM native image Add a `native-xalan` profile that builds the tests into a native image, and a GraalVM job to the CI matrix that activates it. This exercises a code path the JVM matrix cannot reach: native-image resolves the JAXP providers at build time under the closed-world assumption, rather than at run time through ServiceLoader. A native binary carries one classpath, so only `test-stockjdk` runs; the other JAXP combinations stay on the JVM matrix. Saxon-HE is dropped, since its JAXP entry points carry no reachability metadata and every ServiceLoader lookup would fail on a missing no-arg constructor. Its `xpath3` group is already excluded from that execution. Apache Xalan supplies TrAX. The stock JDK's XSLTC compiles each stylesheet into translet bytecode and defines the class at run time, which a closed-world image cannot do: with XSLTC the transform tests fail on `No classes have been predefined during the image build`. Predefining them does not help either, even though JDK-8274535 made the generated bytecode deterministic; the image builder rejects the class with `Cannot define class die.verwandlung.fixture`. Interpretive Xalan, the workaround named on that same issue, needs no bytecode at run time. Xalan and its serializer are declared as project dependencies rather than through surefire's additionalClasspathDependencies, which native-image never sees. Reachability metadata comes from the tracing agent rather than a committed config: Xalan reflects on its template setters and reads serializer defaults from property files, none of which static analysis sees, and a hand-written config would rot against every Xalan and JDK update. The attack fixtures are embedded explicitly, since the agent records only the resources a given run happened to open. Assisted-By: Claude Opus 4.8 <[email protected]> --- .github/workflows/maven.yml | 14 +++-- pom.xml | 122 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 3 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 21f86f6..11ff7f6 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -41,7 +41,9 @@ jobs: # # Temurin does not publish a JDK 8 build for ARM-based macOS runners, so that # one combination uses Zulu. - name: build (${{ matrix.os }}, JDK ${{ matrix.java-version }}) + # + # The GraalVM entry runs the test suite as a native image instead of on the JVM, via the `native-xalan` profile. + name: build (${{ matrix.os }}, ${{ matrix.distribution }} ${{ matrix.java-version }}) runs-on: ${{ matrix.os }} strategy: max-parallel: 20 @@ -66,6 +68,12 @@ jobs: - os: ubuntu-latest java-version: 21 distribution: temurin + # native-image resolves the JAXP providers at build time under the closed-world assumption, a different + # code path from the JVM's run-time ServiceLoader lookup. 25 is the latest GraalVM release. + - os: ubuntu-latest + java-version: 25 + distribution: graalvm + maven-args: -Pnative-xalan steps: - name: Checkout repository @@ -90,13 +98,13 @@ jobs: ${{ runner.os }}-maven- - name: Build with Maven - run: mvn --errors --show-version --batch-mode --no-transfer-progress + run: mvn --errors --show-version --batch-mode --no-transfer-progress ${{ matrix.maven-args }} - name: Upload Surefire reports if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: surefire-reports-${{ matrix.os }}-jdk${{ matrix.java-version }} + name: surefire-reports-${{ matrix.os }}-${{ matrix.distribution }}${{ matrix.java-version }} path: '**/target/surefire-reports/' if-no-files-found: ignore retention-days: 14 diff --git a/pom.xml b/pom.xml index 507d251..9c53326 100644 --- a/pom.xml +++ b/pom.xml @@ -64,6 +64,8 @@ limitations under the License. <!-- Java baseline --> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> + <!-- GraalVM native-image tooling; used by the `native` profile only. --> + <commons.graalvm.buildtools.version>1.1.3</commons.graalvm.buildtools.version> <!-- Optional JAXP implementations used by bundled providers. --> <commons.saxon.version>12.9</commons.saxon.version> <commons.woodstox.version>7.1.1</commons.woodstox.version> @@ -451,6 +453,126 @@ limitations under the License. <commons.saxon.version>13.0</commons.saxon.version> </properties> </profile> + <!-- + Runs the test suite as a GraalVM native image; activate with -Pnative-xalan on a GraalVM JDK. + + Worth a job because native-image resolves JAXP providers under the closed-world assumption: the lookup a JVM + performs at run time through ServiceLoader is decided at build time instead. + + A native binary carries one classpath, so only `test-stockjdk` runs; the other JAXP combinations stay on the + JVM matrix. Its `xpath3` group is excluded there already, and that group needs Saxon, which is dropped here. + --> + <profile> + <id>native-xalan</id> + <dependencies> + <dependency> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>junit-platform-native</artifactId> + <version>${commons.graalvm.buildtools.version}</version> + <scope>test</scope> + </dependency> + <!-- + Apache Xalan supplies TrAX. The stock JDK's XSLTC compiles each stylesheet into translet bytecode and + defines the class at run time, which a closed-world image cannot do; Xalan interprets instead. Declared as + project dependencies, not surefire's additionalClasspathDependencies, which native-image never sees. + --> + <dependency> + <groupId>xalan</groupId> + <artifactId>xalan</artifactId> + <version>${commons.xalan.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>xalan</groupId> + <artifactId>serializer</artifactId> + <version>${commons.xalan.version}</version> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <executions> + <!-- + Only `test-stockjdk` is left running, unconfigured: it registers the tests the native binary replays. + The profile's Xalan dependencies are on its classpath, so it is Xalan, not XSLTC, that supplies TrAX + in both the JVM and the native run. + + Every other execution is silenced; their classpath swaps have no native equivalent. + --> + <execution> + <id>test-xalan</id> + <phase>none</phase> + </execution> + <execution> + <id>test-saxon</id> + <phase>none</phase> + </execution> + <execution> + <id>test-saxon-xerces</id> + <phase>none</phase> + </execution> + <execution> + <id>test-woodstox</id> + <phase>none</phase> + </execution> + <execution> + <id>test-xalan-xerces</id> + <phase>none</phase> + </execution> + <execution> + <id>test-xerces</id> + <phase>none</phase> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>${commons.graalvm.buildtools.version}</version> + <extensions>true</extensions> + <configuration> + <!-- The bundled metadata pins older JUnit versions, whose class-initialization directives + conflict with the JUnit this project resolves. --> + <metadataRepository> + <enabled>false</enabled> + </metadataRepository> + <!-- Xalan reflects on org.apache.xalan.templates.* setters and reads its serializer defaults from + property files; static analysis sees none of it. The agent records what the JVM run touched, and + the native binary replays those same tests, so the metadata covers it by construction. --> + <agent> + <enabled>true</enabled> + </agent> + <!-- Match `test-stockjdk`, which drops Saxon-HE. Saxon's JAXP entry points carry no reachability + metadata, so every ServiceLoader lookup would fail on a missing no-arg constructor. --> + <exclusions> + <exclusion> + <groupId>net.sf.saxon</groupId> + <artifactId>Saxon-HE</artifactId> + </exclusion> + </exclusions> + <buildArgs> + <!-- The JUnit platform feature discovers tests while the image is built, which initializes TestTag. --> + <buildArg>--initialize-at-build-time=org.junit.platform.engine.TestTag</buildArg> + <!-- Embed the fixtures explicitly: the agent records only resources a run actually opened. --> + <buildArg>-H:IncludeResources=leaked/.*</buildArg> + </buildArgs> + </configuration> + <executions> + <execution> + <id>test-native</id> + <phase>test</phase> + <goals> + <goal>test</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> </profiles> <developers> <developer>
