From fbd12119d567b536d6d35586d8a234b63e9f3c0d Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Tue, 1 Sep 2026 19:28:29 +0200 Subject: [PATCH 1/3] Report on the site the tests the build already ran The site's Surefire report counted zero tests: the eight surefire executions write into target/surefire-reports/, while the report plugin scans target/surefire-reports itself. List the eight directories. Take the non-forking goals while here. The `report` goal forks the test phase, and in the forked lifecycle the first execution is configured with a mixture of the other executions' settings (Xerces added, Saxon excluded, xpath3 skipped, failures ignored), so `mvn test site` ran a ninth, misconfigured test round that overwrote the real Saxon reports. `report-only` reads what the build produced instead. The directory list is scoped to that report set so `failsafe-report-only` keeps its own default, target/failsafe-reports. Assisted-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XVaEa2R2sHtBgJh8Mhv841 --- pom.xml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pom.xml b/pom.xml index 443148c..76853ab 100644 --- a/pom.xml +++ b/pom.xml @@ -453,6 +453,39 @@ limitations under the License. + + org.apache.maven.plugins + maven-surefire-report-plugin + + + + surefire + + report-only + + + + + ${project.build.directory}/surefire-reports/jdk-xerces + ${project.build.directory}/surefire-reports/saxon + ${project.build.directory}/surefire-reports/saxon-xerces + ${project.build.directory}/surefire-reports/stockjdk + ${project.build.directory}/surefire-reports/woodstox + ${project.build.directory}/surefire-reports/xalan + ${project.build.directory}/surefire-reports/xalan-xerces + ${project.build.directory}/surefire-reports/xerces + + + + + failsafe + + + failsafe-report-only + + + + From 3bfdf6ce83bb2e974697dd05e75efa7783315148 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Tue, 1 Sep 2026 21:13:24 +0200 Subject: [PATCH 2/3] Count the Android instrumented run in the coverage report The device suite in android-tests exercises the platform-parser paths no JVM run reaches, but its results never reached JaCoCo: the Gradle build collected no coverage, and nothing merged it into the Maven data. Turn on the Android Gradle plugin's androidTest coverage, pinned to the JaCoCo version the Maven build reports with so the on-device agent and the report agree on the data format. The plugin instruments the whole test APK, the library JAR under test included, and leaves one execution data file per device under build/outputs/code_coverage. Merge those files into target/jacoco.exec in the site lifecycle, so jacoco:report shows the Android-only paths as covered. The merge stays out of the default lifecycle on purpose: jacoco:check keeps measuring the JVM run alone, so the coverage minimums mean the same thing whether or not a device run is lying around. With no Android run in the tree the merge is a no-op, and JaCoCo drops execution data whose class no longer matches, so a stale run cannot inflate the numbers. Document the round trip in the module README, along with the JDK 8 plus JDK 21 recipe that reaches the project's real coverage, and correct the excluded-tag list while there: the suite runs the schema tests against the Xerces on the androidTest classpath, and it is xpath that stays out, since XPathFactory.newDefaultInstance has no Android fallback and the leak controls expect an unconfigured XPath to fetch. Assisted-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XVaEa2R2sHtBgJh8Mhv841 --- android-tests/README.md | 84 +++++++++++++++++++++++++++++----- android-tests/build.gradle.kts | 11 +++++ pom.xml | 40 ++++++++++++++++ 3 files changed, 124 insertions(+), 11 deletions(-) diff --git a/android-tests/README.md b/android-tests/README.md index 729af19..af822c9 100644 --- a/android-tests/README.md +++ b/android-tests/README.md @@ -26,35 +26,97 @@ module; it is a standalone Gradle build kept separate so the default `mvn` goal - JDK 17 on `PATH` (AGP 8.x requires it). - Android SDK with `platforms/android-34` and `build-tools/34.0.0` installed; export `ANDROID_HOME` (or `ANDROID_SDK_ROOT`) to point at it. -- Either an attached emulator/device (`adb devices` shows it) or the AGP managed device - bundled into this build (`api31`, AOSP system image). -- The library JAR built by the parent Maven build: + ```shell + export ANDROID_HOME=/path/to/android ``` - cd .. && mvn -DskipTests package +- Either an attached emulator/device (`adb devices` shows it) or the AGP managed device bundled into this build (`api33`, AOSP system image). +- The library JAR built by the parent Maven build: + + ```shell + mvn -f.. -DskipTests package ``` ## Running Against an attached emulator/device: -``` +```shell ./gradlew connectedAndroidTest ``` -Against the bundled AGP managed device (downloads the AOSP API 31 system image on first -run, then provisions and tears down a headless emulator for each invocation): +Against the bundled AGP managed device +(downloads the AOSP API 33 system image on first run, +then provisions and tears down a headless emulator for each invocation): +```shell +./gradlew api33DebugAndroidTest ``` -./gradlew api31DebugAndroidTest + +## Coverage + +The run is instrumented with JaCoCo, +pinned to the version the Maven build reports with, +and leaves one execution data file per device under `build/outputs/code_coverage`. +The full round trip: + +1. Build the JAR the instrumented tests run against: + + ```shell + mvn -f.. -DskipTests package + ``` + +2. Run the tests on a device, either an attached one or the bundled managed device: + + ```shell + ./gradlew connectedDebugAndroidTest + ./gradlew api33DebugAndroidTest + ``` + +3. Fold the device data into the project's coverage: + + ```shell + mvn -f.. -Pjacoco verify site + ``` + + The JVM suite runs under the JaCoCo agent in this step, + so both halves end up in `target/jacoco.exec`: + the `jacoco` profile merges whatever this module produced into it in the site lifecycle, + just before the report is written. + The coverage check runs earlier, on the JVM data alone, + so its minimums mean the same thing whether a device run is lying around; + the device data widens the report, not the bar. + +Repeat step 2 whenever the library changes. +The execution data identifies each class by its bytecode, +and JaCoCo drops data that no longer matches the compiled class, +so a device run left over from an older JAR quietly lowers the numbers instead of inflating them. + +### Maximum coverage + +This library is written to work from JDK 8 through 25 and on Android. +Since new JAXP methods were introduced in JDK 9, 13, and 18, +maximum coverage is obtained by the following recipe: + +```shell +export JDK8=/path/to/jdk8 +export JDK21=/path/to/jdk21 +export ANDROID_HOME=/path/to/android +JAVA_HOME=$JDK21 mvn -f.. -Pjacoco clean package +./gradlew api33DebugAndroidTest +JAVA_HOME=$JDK8 mvn -f.. -Pjacoco test +JAVA_HOME=$JDK21 mvn -f.. -Pjacoco verify site ``` ## Excluded test groups -The build excludes JUnit 5 tags for JAXP types Android does not ship: +The build runs the `dom`, `sax`, `schema`, and `trax` tags and excludes the rest: - `stax`: there is no `XMLInputFactory` on Android. -- `schema`: there is no `SchemaFactory` on Android. +- `xpath`: Android ships an XPath implementation, + but it is currently untested. - `xpath3`: relies on Saxon, which is not on the Android classpath. -DOM, SAX, TrAX and XPath 1.0 paths are exercised in full. +DOM, SAX, TrAX, and schema paths are exercised in full; +the schema tests run against the Apache Xerces the `androidTest` classpath brings in, +since Android ships `javax.xml.validation` without a `SchemaFactory` implementation. diff --git a/android-tests/build.gradle.kts b/android-tests/build.gradle.kts index e8bfc34..845b1e1 100644 --- a/android-tests/build.gradle.kts +++ b/android-tests/build.gradle.kts @@ -42,6 +42,17 @@ android { targetCompatibility = JavaVersion.VERSION_1_8 } + // Match the JaCoCo the Maven build reports with, so the on-device agent understands the same execution data format. + testCoverage { + jacocoVersion = "0.8.15" + } + + buildTypes { + getByName("debug") { + enableAndroidTestCoverage = true + } + } + sourceSets { getByName("androidTest") { java.srcDirs("../src/test/java") diff --git a/pom.xml b/pom.xml index 76853ab..88b44ae 100644 --- a/pom.xml +++ b/pom.xml @@ -489,6 +489,46 @@ limitations under the License. + + + jacoco + + + + org.jacoco + jacoco-maven-plugin + + + merge-android + pre-site + + merge + + + + + ${project.build.directory} + + jacoco.exec + + + + ${project.basedir}/android-tests/build/outputs/code_coverage + + **/*.ec + + + + ${project.build.directory}/jacoco.exec + + + + + + + 2.16 - + true - 0.97 - 0.97 - 0.99 - 0.91 - 0.96 - 0.96 + 0.96 + 0.93 + 0.95 + 0.87 + 0.93 + 0.92