Record the wave-4 coverage read, and correct the filter that missed the biggest gap #205

Merged
JMR-dev merged 1 commits from docs/wave4-coverage-findings into main 2026-09-02 21:41:50 +00:00
JMR-dev commented 2026-09-02 12:53:01 +00:00 (Migrated from github.com)

Documentation only — no Kotlin, Gradle or shell file is touched.

What this is

The wave-4 coverage read on main @ 54ca2dd (92.8% line, 81.3% branch, 584 tests in 87 classes — the committed figures, re-verified counter for counter against the report on disk rather than quoted).

It produced three things. The twelve test tickets are #192–#203 and the four deferred candidates are #204; this PR is the third part — the five findings a test would not fix, plus one correction to how the next read should be done.

The correction, which is the reusable half

Wave 3 filtered candidates on mi > 0, and CLAUDE.md recommended it. It fails in both directions:

  • Over-reports on Compose. JoinScreen.kt:222 reads mi=10 — and also ci=38, and JoinStateAffordancesTest already clicks that Save button and asserts save:joined.mp4. The missed instructions are the synthesized $changed/$dirty recomposition-skip path — the same codegen this repo already knew inflated the branch count, showing up in the instruction count too. Every onClick lambda flagged that way turned out covered at method level.
  • Under-reports on the case that mattered more. Both ViewModels' cancel() miss no line, so no line-level filter can see them — yet only the null arm of activeWorkId?.let(workManager::cancelWorkById) had ever been entered, and nothing in 584 tests connected the Cancel button to WorkManager. That is #192.

Use both: ci == 0 (JaCoCo's own missed-line definition, no judgement needed) plus ci > 0 && mb > 0 at method level.

The five findings

F6 Four more unreachable arms, each traced to the upstream guard that makes it so. One (ConversionRouter:214-217) carries a KDoc describing a hazard :117 already removed — F2's shape, and a one-line fix.
F7 probeWithExtractor's catch is unreachable for the same measured reason probeForConcat's is. The measurement was on record for one site and not the other, three lines apart in the same file.
F8 Three more dead members and six unused defaults.
F9 Both getForegroundInfo overrides are dead — getForegroundInfoAsync is called only for expedited work, and grep setExpedited returns nothing. Sharpens #88's close rather than reopening it.
F10 Three arms that are reachable and still cannot be made to bite. Recorded because all three were picked up as candidates and put down again.

Six of the ten findings are now "no action" or "not a test gap". That shape is the honest summary of what is left: arms nothing can reach, members nothing calls, and arms a test can reach but not pin — and a coverage number tells none of them apart.

One close qualified, not overturned

#86 and #133 ruled AndroidDeviceCodecs.probe() out through ShadowMediaCodecList, because MediaCodecInfoBuilder cannot set isAlias or canonicalName. A pure seam does not have that constraint and #133 did not evaluate one — so #194 is a different mechanism, not a third run of the same spike. Both this document and CLAUDE.md now say so, since #133's text currently reads as a close on the whole question.

Its argument is not coverage, either: the runCatching fallback logs "assuming permissive" while returning empty sets, which makes canEncode and canDecode answer no for everything.

Verification

Documentation-only, so the code gates have nothing to act on. What was checked instead is every factual claim the entries make — grep setExpedited (empty), ConcatWorker never reading Result.output, all three Transcoders call sites passing every argument, JobSnapshots.kt passing all seven fields, and each unreachable arm traced to its guard. Line citations were re-read rather than carried over.

🤖 Generated with Claude Code

Documentation only — no Kotlin, Gradle or shell file is touched. ## What this is The wave-4 coverage read on `main` @ `54ca2dd` (**92.8% line, 81.3% branch, 584 tests in 87 classes** — the committed figures, re-verified counter for counter against the report on disk rather than quoted). It produced three things. The twelve test tickets are **#192–#203** and the four deferred candidates are **#204**; this PR is the third part — the five findings a test would not fix, plus one correction to how the next read should be done. ## The correction, which is the reusable half Wave 3 filtered candidates on `mi > 0`, and `CLAUDE.md` recommended it. It fails in both directions: - **Over-reports on Compose.** `JoinScreen.kt:222` reads `mi=10` — and also `ci=38`, and `JoinStateAffordancesTest` already clicks that Save button and asserts `save:joined.mp4`. The missed instructions are the synthesized `$changed`/`$dirty` recomposition-skip path — the same codegen this repo already knew inflated the *branch* count, showing up in the instruction count too. Every `onClick` lambda flagged that way turned out covered at method level. - **Under-reports on the case that mattered more.** Both ViewModels' `cancel()` miss no line, so no line-level filter can see them — yet only the null arm of `activeWorkId?.let(workManager::cancelWorkById)` had ever been entered, and nothing in 584 tests connected the Cancel button to WorkManager. That is #192. Use both: `ci == 0` (JaCoCo's own missed-line definition, no judgement needed) plus `ci > 0 && mb > 0` at method level. ## The five findings | | | |---|---| | **F6** | Four more unreachable arms, each traced to the upstream guard that makes it so. One (`ConversionRouter:214-217`) carries a KDoc describing a hazard `:117` already removed — F2's shape, and a one-line fix. | | **F7** | `probeWithExtractor`'s catch is unreachable for the same measured reason `probeForConcat`'s is. The measurement was on record for one site and not the other, three lines apart in the same file. | | **F8** | Three more dead members and six unused defaults. | | **F9** | Both `getForegroundInfo` overrides are dead — `getForegroundInfoAsync` is called only for expedited work, and `grep setExpedited` returns nothing. **Sharpens #88's close rather than reopening it.** | | **F10** | Three arms that *are* reachable and still cannot be made to bite. Recorded because all three were picked up as candidates and put down again. | Six of the ten findings are now "no action" or "not a test gap". That shape is the honest summary of what is left: arms nothing can reach, members nothing calls, and arms a test can reach but not pin — and a coverage number tells none of them apart. ## One close qualified, not overturned #86 and #133 ruled `AndroidDeviceCodecs.probe()` out **through `ShadowMediaCodecList`**, because `MediaCodecInfoBuilder` cannot set `isAlias` or `canonicalName`. A pure seam does not have that constraint and #133 did not evaluate one — so #194 is a different mechanism, not a third run of the same spike. Both this document and `CLAUDE.md` now say so, since #133's text currently reads as a close on the whole question. Its argument is not coverage, either: the `runCatching` fallback logs *"assuming permissive"* while returning empty sets, which makes `canEncode` and `canDecode` answer **no** for everything. ## Verification Documentation-only, so the code gates have nothing to act on. What was checked instead is every factual claim the entries make — `grep setExpedited` (empty), `ConcatWorker` never reading `Result.output`, all three `Transcoders` call sites passing every argument, `JobSnapshots.kt` passing all seven fields, and each unreachable arm traced to its guard. Line citations were re-read rather than carried over. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.