Author SHA1 Message Date
Jason Ross 9b3fce0c10 Merge main into build-290-jacoco-scope 2026-07-04 04:00:46 -05:00
Jason Ross 2d4076fa96 Merge main into build-290-jacoco-scope 2026-07-04 03:43:44 -05:00
Jason Ross 552a365ab0 Merge main into build-290-jacoco-scope 2026-07-04 03:21:28 -05:00
Jason Ross 74cbc9c343 Merge main into build-290-jacoco-scope 2026-07-04 03:01:20 -05:00
Jason Ross 41f4babc04 Merge main into build-290-jacoco-scope 2026-07-04 02:45:00 -05:00
Jason Ross 74bd273b70 Merge main into build-290-jacoco-scope 2026-07-04 02:26:51 -05:00
Jason Ross c66b81d31f Merge main into build-290-jacoco-scope 2026-07-04 01:56:45 -05:00
Jason Ross d57c42e789 Merge main into build-290-jacoco-scope 2026-07-04 01:22:26 -05:00
JMR-devandClaude Opus 4.8 f7e2cbdbe5 build(jacoco): scope coverage report to the JVM-testable surface
The jacocoTestReport classDirectories filter only stripped generated
code, so Compose screen/component render code, Android framework
entry points, DI wiring, and the src/debug cold-open probe all sat in
the coverage denominator and dragged the reported line% to ~50% even
though the hand-written, JVM-testable logic is much better covered.

Extends the exclusion list (issue #290, Phase-2 coverage audit) with
Compose UI render files, Activity/Service/Worker/Application/
BackupAgent, di/**, and the coldopen probe — while deliberately
keeping ViewModels, repos, and four Screen/component files that mix
tested pure logic with their Composable code (RichTextEditor,
AccountReorderList, HtmlBody, ReportReviewScreen) fully in scope, so
their dedicated unit tests keep counting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 01:07:32 -05:00
+76 -1
View File
@@ -248,13 +248,88 @@ tasks.register<JacocoReport>("jacocoTestReport") {
"**/ComposableSingletons*",
)
// Scope the denominator to the JVM-testable surface (issue #290, following the Phase-2 coverage
// audit): unlike `generated` above, none of this is generated code — it is hand-written but
// structurally unreachable from a JVM unit test, so counting it against the metric just measures
// how much Compose/framework glue exists rather than how well the logic is tested. Four buckets:
// 1. Compose screen/component render code — only exercisable via a Compose UI test or an emulator.
// 2. Android framework entry points the OS instantiates directly (Activity/Service/Worker/
// Application/BackupAgent) rather than the app's own code constructing them.
// 3. Hilt DI modules — `@Provides`/`@Binds` one-liners with no branching logic.
// 4. The `src/debug` cold-open probe (issue #221), a `ContentProvider` that only runs in a forked
// instrumented process (see its kdoc) and is never packaged in a release build anyway.
//
// Deliberately NOT excluded, even though each sits in a package/pattern above and renders UI: files
// that carry plain, unit-tested logic alongside their `@Composable` functions. JaCoCo has no finer
// granularity than a class file, and Kotlin compiles every top-level function in a .kt file —
// `@Composable` or not — into the SAME facade class (`<File>Kt.class`); excluding that class would
// silently zero out the tested function's coverage too, not just the render code's. Confirmed
// against these files' own dedicated tests before leaving them out of the list below:
// - ui/compose/RichTextEditor.kt (RichTextEditorTest) — the AnnotatedString<->RichTextContent
// editor-op functions (applyStyle/applyBlock/applyLink/toRichContent/toAnnotatedString/...).
// - ui/settings/AccountReorderList.kt (AccountReorderListTest) — commitDrag's reorder maths.
// - ui/reader/HtmlBody.kt (HtmlBodyTest, InlineImageResolverTest) — cidKey/resolveInlineImage/
// wrapHtml/toCssHex.
// - ui/reporting/ReportReviewScreen.kt (ReportReviewClipboardTest) — copyReportPayloadToClipboard.
// (ui/compose/format/FontRegistry.kt and ui/mailbox/FolderLabels.kt are plain logic files with no
// `@Composable` at all — never at risk — but sit right next to excluded files below.) For the same
// reason this list names each Screen/component file individually rather than a package-wide
// "**/ui/**": a blanket pattern can't carve the four files above back out, and would also reach
// every `*ViewModel*`.
//
// Tradeoff called out for review rather than silently applied: `**/*Worker*` excludes SyncWorker,
// BackfillWorker, PruneWorker, SendWorker, ReportPurgeWorker and ReportUploadWorker as framework
// entry points, per issue #290 — but all six are directly unit-tested today (construct-the-worker-
// and-call-doWork(), e.g. SyncWorkerTest, SendWorkerTest), so this also removes that already-tested
// coverage from both the numerator and the denominator, not just untested render/glue code.
val nonJvmTestableSurface = listOf(
// --- Compose UI render code: one glob per screen/component file (see the exceptions above) ---
"**/LibreMailApp*",
"**/AccountPickerScreen*",
"**/AppPasswordSetupScreen*",
"**/ManualSetupScreen*",
"**/ComposeScreen*",
"**/ColorSwatch*",
"**/FontPicker*",
"**/FontSizePicker*",
"**/ParagraphAlignmentControl*",
"**/DraftsScreen*",
"**/LockScreen*",
"**/AppLockGateHost*",
"**/FolderDrawer*",
"**/MailboxScreen*",
"**/AddAnotherAccountScreen*",
"**/BatteryOptimizationScreen*",
"**/ContactsAccessScreen*",
"**/LicenseScreen*",
"**/OnboardingWelcomeScreen*",
"**/OutboxScreen*",
"**/ReaderScreen*",
"**/ProblemReportsScreen*",
"**/AccountSettingsScreen*",
"**/SettingsScreen*",
"**/SettingsComponents*",
"**/SignatureEditScreen*",
"**/SignaturesScreen*",
// --- Android framework entry points ---
"**/*Activity*",
"**/*Service*",
"**/*Worker*",
"**/LibreMailApplication*",
"**/*BackupAgent*",
// --- Hilt DI wiring ---
"**/di/**",
// --- src/debug cold-open probe (issue #221) ---
"**/data/local/coldopen/**",
)
// 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) },
fileTree(debugKotlinClasses) { exclude(generated + nonJvmTestableSurface) },
)
// Sources = hand-written main Kotlin.