You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by pk...@apache.org on 2026/07/09 20:05:57 UTC
(commons-xml) 01/01: Run the test suite as a GraalVM native image
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 <pk...@apache.org>
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 <no...@anthropic.com>
---
.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>