From cbc9fc62ef781c77a4ecf244103492dc5df096c8 Mon Sep 17 00:00:00 2001 From: Jason Ross Date: Fri, 3 Jul 2026 12:04:36 -0500 Subject: [PATCH] wip: recover work from interrupted session (issue #192) Co-Authored-By: Claude Opus 4.8 --- app/build.gradle.kts | 70 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5c0e84e..2518467 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later +import org.gradle.testing.jacoco.tasks.JacocoReport import java.util.Properties plugins { @@ -9,6 +10,11 @@ plugins { id("org.jetbrains.kotlin.plugin.compose") id("com.google.devtools.ksp") id("com.google.dagger.hilt.android") + // JaCoCo (Gradle built-in) — unit-test code-coverage reporting (issue #192). The base `jacoco` + // plugin auto-instruments the JVM `testDebugUnitTest` task; the jacocoTestReport task below turns + // its exec data into XML + HTML. AGP-9-safe: it does NOT apply org.jetbrains.kotlin.android (which + // ClassCastExceptions against AGP 9's built-in-Kotlin DSL — see CLAUDE.md) and touches no variant DSL. + jacoco // Lint/format — resolved from the Gradle Plugin Portal (not the buildscript classpath). alias(libs.plugins.ktlint) alias(libs.plugins.detekt) @@ -164,6 +170,70 @@ detekt { config.setFrom(rootProject.file("config/detekt/detekt.yml")) } +// Pin a modern JaCoCo so the coverage agent understands Kotlin 2.4.0 bytecode on JDK 21. +jacoco { + toolVersion = "0.8.13" +} + +// Unit-test coverage report (issue #192). Reads the exec data the base `jacoco` plugin records for +// the JVM `testDebugUnitTest` task, mapped against the debug variant's compiled Kotlin classes and +// the hand-written main sources. Produces machine-readable XML + human-readable HTML under +// build/reports/jacoco/jacocoTestReport/. Instrumented/E2E coverage is out of scope (issue #192). +tasks.register("jacocoTestReport") { + // Ensure the unit tests (and thus their coverage exec data) have run first. + dependsOn("testDebugUnitTest") + group = "verification" + description = "Generates JaCoCo XML + HTML coverage for the debug JVM unit tests." + + reports { + xml.required.set(true) + html.required.set(true) + } + + // Strip generated code from the denominator so the % reflects hand-written Kotlin: Room (*_Impl*), + // Hilt/Dagger, Compose singletons/synthetics, and Android BuildConfig/R/Manifest scaffolding. + val generated = listOf( + "**/R.class", + "**/R\$*.class", + "**/BuildConfig.*", + "**/Manifest*.*", + "**/*_Hilt*", + "**/hilt_aggregated_deps/**", + "**/dagger/**", + "**/*_Factory*", + "**/*_MembersInjector*", + "**/*_Provide*", + "**/*_Impl_*", + "**/*_Impl*", + "**/ComposableSingletons*", + "**/*\$\$*", + "**/*\$*Lambda*", + ) + + // Classes = the debug variant's compiled Kotlin (AGP 9 built-in Kotlin output). All hand-written + // code here is Kotlin, so the javac output (purely Hilt/Dagger/BuildConfig generated) is omitted. + val debugKotlinClasses = layout.buildDirectory.dir( + "intermediates/built_in_kotlinc/debug/compileDebugKotlin/classes", + ) + classDirectories.setFrom( + fileTree(debugKotlinClasses) { exclude(generated) }, + ) + + // Sources = hand-written main Kotlin. + sourceDirectories.setFrom(files("src/main/kotlin")) + + // Exec data written by the instrumented testDebugUnitTest task. Accept the base `jacoco` plugin's + // default location and AGP's enableUnitTestCoverage location so the wiring is robust either way. + executionData.setFrom( + fileTree(layout.buildDirectory) { + include( + "jacoco/testDebugUnitTest.exec", + "outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec", + ) + }, + ) +} + dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)