diff --git a/android-tests/README.md b/android-tests/README.md
index 729af19c..af822c99 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 e8bfc34c..845b1e1e 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 443148c6..e63ea551 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,14 +75,19 @@ limitations under the License.
2.12.2
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
+
+ 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
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+