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>

Reply via email to