Count the Robolectric tests, which JaCoCo has never counted #76

Merged
JMR-dev merged 1 commits from fix/jacoco-robolectric-coverage into main 2026-08-24 23:53:43 +00:00
JMR-dev commented 2026-08-24 23:45:33 +00:00 (Migrated from github.com)

Closes #75.

The three #52 test PRs landed 56 new tests and coverage moved 29.8% → 29.7%. That looked like the tests being worthless. It was the measurement.

Robolectric loads classes through its own sandbox classloader, and those classes arrive with no source location. JaCoCo skips no-location classes unless told otherwise, and nothing here told it — so not one Robolectric test has ever contributed coverage in this repo, and Robolectric is what exercises the framework edge: both workers, the publisher, both ViewModels, every Compose screen.

Measured — same commit, same 335 tests, same 0 failures

before after
LINE 29.7% (652/2194) 69.2% (1519/2194)
BRANCH 29.8% (425/1424) 53.2% (758/1424)
OutputPublisher 0.0% 97.5%
ConversionViewModel 0.0% 85.4%
MainActivityKt 6.8% 86.4%
ConverterScreenKt 6.6% 62.8%

The discriminator, so this isn't cargo cult

Inside ConverterScreenKt, describe is the one non-@Composable and is exercised by a plain JVM test. It reported 8/8 covered while every @Composable in the same class reported 0 — including ones whose mutations demonstrably failed the build when reverted. Across files the split is exactly Robolectric-vs-not: StagingSweep (pure) 100%; OutputPublisher, ConversionViewModel, FailureOutcome (Robolectric) 0%.

excludes = listOf("jdk.internal.*") is not decoration — without it JaCoCo walks JDK-internal classes Robolectric has no location for either and the test JVM dies rather than reporting a number.

CLAUDE.md was wrong twice over

The figure was an artifact, and the explanation attached to it described a cause that doesn't exist: that coverage fell as the suite grew from 11 test files to 43 because the denominator outran the numerator on framework-edge code "the JVM cannot reach". The JVM reaches that code fine. The new tests were disproportionately Robolectric, so each added denominator and no numerator — the measurement was punishing precisely the tests that were hardest to write, and the conclusion drawn was that writing them hadn't helped.

Mutation

Run both ways on this branch: remove the block and jacocoTestReport collapses to 29.7% / 29.8%; restore it and it returns to 69.2% / 53.2%.

Gate green: assembleDebug + testDebugUnitTest + compileDebugAndroidTestKotlin + ktlintCheck + detekt + lintDebug --continue.

Still no coverage gate, and a floor still needs a settled baseline — this one just moved 39 points in one build change.

Closes #75. The three #52 test PRs landed 56 new tests and coverage moved **29.8% → 29.7%**. That looked like the tests being worthless. It was the measurement. Robolectric loads classes through its own sandbox classloader, and those classes arrive with **no source location**. JaCoCo skips no-location classes unless told otherwise, and nothing here told it — so **not one Robolectric test has ever contributed coverage in this repo**, and Robolectric is what exercises the framework edge: both workers, the publisher, both ViewModels, every Compose screen. ## Measured — same commit, same 335 tests, same 0 failures | | before | after | |---|---|---| | **LINE** | 29.7% (652/2194) | **69.2% (1519/2194)** | | **BRANCH** | 29.8% (425/1424) | **53.2% (758/1424)** | | `OutputPublisher` | 0.0% | **97.5%** | | `ConversionViewModel` | 0.0% | **85.4%** | | `MainActivityKt` | 6.8% | **86.4%** | | `ConverterScreenKt` | 6.6% | **62.8%** | ## The discriminator, so this isn't cargo cult Inside `ConverterScreenKt`, `describe` is the one **non**-`@Composable` and is exercised by a plain JVM test. It reported **8/8 covered** while every `@Composable` in the same class reported **0** — including ones whose mutations demonstrably failed the build when reverted. Across files the split is exactly Robolectric-vs-not: `StagingSweep` (pure) 100%; `OutputPublisher`, `ConversionViewModel`, `FailureOutcome` (Robolectric) 0%. `excludes = listOf("jdk.internal.*")` is not decoration — without it JaCoCo walks JDK-internal classes Robolectric has no location for either and the test JVM dies rather than reporting a number. ## CLAUDE.md was wrong twice over The figure was an artifact, **and the explanation attached to it described a cause that doesn't exist**: that coverage fell as the suite grew from 11 test files to 43 because the denominator outran the numerator on framework-edge code *"the JVM cannot reach"*. The JVM reaches that code fine. The new tests were disproportionately Robolectric, so each added denominator and no numerator — the measurement was punishing precisely the tests that were hardest to write, and the conclusion drawn was that writing them hadn't helped. ## Mutation Run both ways on this branch: remove the block and `jacocoTestReport` collapses to 29.7% / 29.8%; restore it and it returns to 69.2% / 53.2%. Gate green: `assembleDebug + testDebugUnitTest + compileDebugAndroidTestKotlin + ktlintCheck + detekt + lintDebug --continue`. Still no coverage gate, and a floor still needs a settled baseline — this one just moved 39 points in one build change.
Sign in to join this conversation.