test: 100% Kotlin test coverage of the Android layer; fix regressions from #388 - #477
Conversation
394d4af to
2d293ad
Compare
|
Static Analysis (Checkstyle + PMD) fails at the The failing step reports: Cause: the script, added in #478, only looks for packages under No fix is open yet. This patch makes the script scan both source roots. I checked it locally against current --- a/scripts/check-architecture-docs.sh
+++ b/scripts/check-architecture-docs.sh
@@ -18,7 +18,9 @@ set -euo pipefail
cd "$(dirname "$0")/.."
-SRC=logback-android/src/main/java
+# Library sources: the upstream-derived port is Java, the Android-specific
+# layer is Kotlin (#388). Package paths are relative to either root.
+SRC_ROOTS="logback-android/src/main/java logback-android/src/main/kotlin"
CATALOGUE=docs/architecture/components.md
@@ -30,7 +32,15 @@
-packages=$(find "$SRC" -name '*.java' -exec dirname {} \; | sed "s|^$SRC/||" | sort -u)
+# Every Java/Kotlin source file, as a path relative to its source root.
+sources() {
+ for root in $SRC_ROOTS; do
+ [ -d "$root" ] || continue
+ find "$root" \( -name '*.java' -o -name '*.kt' \) | sed "s|^$root/||"
+ done
+}
+
+packages=$(sources | xargs -n1 dirname | sort -u)
@@ -43,19 +53,24 @@
- echo "$CATALOGUE: entries for packages that no longer exist under $SRC:"
+ echo "$CATALOGUE: entries for packages that no longer exist under $SRC_ROOTS:"
@@
-core_to_classic=$(grep -rl 'import ch\.qos\.logback\.classic' "$SRC/ch/qos/logback/core" || true)
+core_to_classic=$(for root in $SRC_ROOTS; do
+ [ -d "$root/ch/qos/logback/core" ] && grep -rl 'import ch\.qos\.logback\.classic' "$root/ch/qos/logback/core"
+done || true)
@@
-android_users=$({ grep -rlE '^import (static )?android\.' "$SRC" || true; } | xargs -r -n1 dirname | sed "s|^$SRC/||" | sort -u)
+android_users=$(for root in $SRC_ROOTS; do
+ [ -d "$root" ] || continue
+ { grep -rlE '^import (static )?android\.' "$root" || true; } | xargs -r -n1 dirname | sed "s|^$root/||"
+done | sort -u)The failure is deterministic, so re-running the job won't help. I've left the fix out of this PR to keep it scoped to tests and the coverage gate. It can go in as its own Generated by Claude Code |
95f082b to
7c264e2
Compare
Adds what the Kotlin unit tests of the Android-specific layer need, and a
coverage gate so those tests keep pinning every path of that layer:
- JaCoCo 0.8.15 coverage of the debug unit tests (AGP's
enableUnitTestCoverage), recording classes that Robolectric's sandbox
classloader defines (includeNoLocationClasses).
- androidLayerCoverageVerification<Variant> / verifyAndroidLayerCoverage:
fail unless the unit tests cover every line and branch of each class in
ch.qos.logback.{classic,core}.android; run in CI after the unit tests,
with the jdk11Debug HTML/XML report uploaded.
- kotlin-test and mockito-kotlin 6.4.0 for the tests, which the existing
Kotlin plugin setup compiles from src/test/kotlin.
Kover was tried first, but with AGP 9's variants it never attached the unit
test tasks' coverage data to its reports (always 0%), although JaCoCo
recorded it correctly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AN8SJNGETCegfBbPy4iV5K
…nged The Kotlin tests of the Android-specific layer, written against the Java code #388 replaced, found these regressions in the conversion: - SystemPropertiesProxy.getBoolean returned a primitive boolean instead of java.lang.Boolean, which breaks Java callers compiled against the old signature (NoSuchMethodError). It again returns Boolean? and passes the SystemProperties result through. - SystemPropertiesProxy.get/getBoolean threw on a null key (Kotlin's parameter check) instead of returning the default; they accept null again. They also invoke SystemProperties with its class as the receiver again, so an IllegalArgumentException from the reflective call propagates as before. - The public members of AndroidContextUtil, LogcatAppender and SQLiteAppender had become final, breaking subclasses that override them (the classes are open, and these were overridable in the published Java API). They are open again; AndroidContextUtil.setupProperties once more tolerates null paths from such overrides. - LogcatAppender dropped (with an error status) events without a logger name; getTag returns String? again, so they're logged with a null tag. - AndroidContextUtil.getNoBackupFilesDirectoryPath lost its pre-Lollipop check. Also removes null checks that could never fail (the database handle once started, the history duration passed to the log cleaner), so that every branch of the layer is reachable by its tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AN8SJNGETCegfBbPy4iV5K
Ports LogcatAppenderTest, SQLiteAppenderTest and AndroidContextUtilTest to
Kotlin (keeping every case) and adds tests until every line and branch of
ch.qos.logback.{classic,core}.android is covered, asserting behavior rather
than just executing it:
- LogcatAppenderTest: level-to-priority mapping, isLoggable() checks and tag
truncation, start() validation, %nopex handling of tag layouts, and XML
configuration through Joran.
- BasicLogcatConfiguratorTest, SystemClockTest.
- SQLiteAppenderTest: the rows actually written (arguments, caller data,
properties, exceptions with causes), log cleanup timing, and every failure
path (database open/create/insert failures, rolled-back transactions).
- AndroidContextUtilTest: every path/property getter across SDK levels,
storage states, package info, and the application-context lookup.
- SystemPropertiesProxyTest (new).
- JavaApiSignaturesTest: pins the JVM-level API that Java callers and Joran
rely on, so a binary-incompatible change fails a test.
Uses kotlin-test assertions, mockito-kotlin and Robolectric.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AN8SJNGETCegfBbPy4iV5K
AGP compiles unit tests against android.jar's java.lang.ClassLoader, which has no getClassLoadingLock(); lock on the loader itself instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AN8SJNGETCegfBbPy4iV5K
0fadec9 to
f242957
Compare
|
The architecture-docs failure is fixed on Generated by Claude Code |
Summary
This PR adds Kotlin unit tests that cover every line and branch of the Android-specific layer (
ch.qos.logback.{classic,core}.android), and a CI gate that keeps coverage there.The tests were first written against the Java code that #388 replaced, then run against the Kotlin conversion to check it. They found regressions, which this PR also fixes.
Tests (
test:)LogcatAppenderTest,SQLiteAppenderTestandAndroidContextUtilTest, keeping every original case.BasicLogcatConfiguratorTest,SystemClockTestandSystemPropertiesProxyTest.JavaApiSignaturesTestpins the JVM-level API that Java callers and Joran rely on, so a binary-incompatible change fails a test.Regressions from #388 fixed (
fix:)SystemPropertiesProxy.getBooleanreturned a primitivebooleaninstead ofjava.lang.Boolean, which is binary-incompatible for Java callers (NoSuchMethodError).SystemPropertiesProxy.getandgetBooleanthrew on a null key instead of returning the default.AndroidContextUtil,LogcatAppenderandSQLiteAppenderhad becomefinal, which breaks subclasses that override them. They are open again.LogcatAppenderdropped events without a logger name; they are logged with a null tag again.AndroidContextUtil.getNoBackupFilesDirectoryPathlost its SDK check; it's restored.Kept as #388 changed them, since they're harmless:
SQLiteAppender.finalize()is removed;isNullOrBlank;AndroidContextUtil.getContext()is public;BasicLogcatConfiguratoris anobject;SystemPropertiesProxyis final (its constructor is private);LogcatAppenderfalls back to the logger name when a tag layout produces no tag.Build (
build:)enableUnitTestCoverage, withincludeNoLocationClassesso classes loaded by Robolectric are recorded.verifyAndroidLayerCoveragefails unless every class in the layer has 100% line and branch coverage.coverage-reportartifact.kotlin-testandmockito-kotlin6.4.0.Architecture: no packages, runtime dependencies or published outputs change;
docs/architecture/is untouched.Deferred work considered: the architecture-docs check failed on
mainafter #388, which made Static Analysis fail here too. #480 fixed it onmain, and this branch is rebased onto that fix.🤖 Generated with Claude Code
https://claude.ai/code/session_01AN8SJNGETCegfBbPy4iV5K