Four coverage waves have been steered by JaCoCo, which measures testDebugUnitTest only and cannot see app/src/androidTest at all. Nothing had ever asked what the 60 device tests pin, only that they were green.
This is that read — a triage, not a test push, in the shape of docs/coverage-read-findings.md. It adds docs/e2e-read-findings.md (E1-E6) and a CLAUDE.md pointer. No production or test code changes.
The result
HardwareFallbackTest passes on every emulator leg without ever attempting the hardware path (#223).
It is the only automated check of the hardware→software fallback against a real codec failure. Measured on run 34004304566 — the API 33, 34, 35 and 37 legs each log:
I/AndroidDeviceCodecs: Hardware video encoders: []
I/ConversionWorker: Routing sample_h264_444.mp4 -> OutputSpec(container=MP4, videoCodec=H265,
audioCodec=AAC) via FFMPEG (NO_HARDWARE_ENCODER)
Emulators expose no hardware encoder, so the router sends the job straight to FFmpeg and runMedia3OrFallBack's catch is never entered. Its two assertions — succeeded, output non-empty — are true anyway. It finishes in 448 ms, which is not long enough to fail a hardware export and then software-encode a 3 s clip.
Deleting that catch reddens nothing, anywhere, on any leg.
The committed sample_h264_444.mp4 fixture — generated with x264 because Fedora's ffmpeg cannot produce High 4:4:4 — does nothing on any CI leg today.
Two things that generalise
A test can assert and still not reach. Neither a coverage number nor a "does it assert something" review catches this; it has two passing assertions. The filter that works is does this test's premise hold on the machine that runs it?
The codebase already knew.ForcedFailureTest pins DeviceCodecs.PERMISSIVE against this exact hazard and writes out why; ConversionWorkerTest records it a third time. Their assertions are about the path, so without the pin they fail loudly. HardwareFallbackTest's are about the output, so it passes quietly. That asymmetry is why nobody noticed.
Findings (no ticket — a test would not fix these)
ID
Finding
Action
E1
RemuxTest's KDoc argues for engine assertions three of its tests omit — and they are right to: those three produce MKV/AVI, which MEDIA3_CONTAINERS can never route to Media3
one line of KDoc
E2
Three of the 60 instrumented tests assert nothing; two never run
no action — deliberate, but 60 ≠ 60
E3
…AndReportsProgress does not assert progress fired
no action — the name overstates
E4
The API 37 marker's KDoc says "two"; three tests carry it
fix the sentence
E5
coverage-read-findings.md F7's "uncovered" half is stale — RemuxTest drives it on a device every leg
amend F7
E6
The one device-capability assertion asks the class under test what to expect
E1 is the candidate that looked strongest and dissolved on tracing, recorded so the next read does not re-file it. E5 is the structural argument for this document existing: a JaCoCo-derived document cannot see androidTest, so it will keep re-deriving "uncovered" for code the instrumented suite covers.
Tickets filed
Each names the mutation that must go red, not a coverage delta.
(spike) whether a running conversion's process can be killed under instrumentation
Checked and deliberately not filed
Real HEVC encode is not Pixel-only (Media3EngineTest runs on the API 33-36 gating legs; the marker excludes API 37 only). CLAUDE.md's "AndroidDeviceCodecs 20 / MediaProbe 14" is not stale — it dates the 2026-09-02 read and names #194/#195. Already tracked: #178, #190, #102, #108, F5.
Verification
./gradlew :app:ktlintCheck :app:detekt :app:lintDebug --continue — BUILD SUCCESSFUL. No .sh or workflow changes, so shellcheck/actionlint are unaffected.
Four coverage waves have been steered by JaCoCo, which measures `testDebugUnitTest` only and **cannot see `app/src/androidTest` at all**. Nothing had ever asked what the 60 device tests pin, only that they were green.
This is that read — a **triage, not a test push**, in the shape of `docs/coverage-read-findings.md`. It adds `docs/e2e-read-findings.md` (`E1`-`E6`) and a `CLAUDE.md` pointer. No production or test code changes.
## The result
**`HardwareFallbackTest` passes on every emulator leg without ever attempting the hardware path** (#223).
It is the only automated check of the hardware→software fallback against a *real* codec failure. Measured on run [`34004304566`](https://github.com/JMR-dev/LibreMediaConverter/actions/runs/34004304566) — the API 33, 34, 35 and 37 legs each log:
```
I/AndroidDeviceCodecs: Hardware video encoders: []
I/ConversionWorker: Routing sample_h264_444.mp4 -> OutputSpec(container=MP4, videoCodec=H265,
audioCodec=AAC) via FFMPEG (NO_HARDWARE_ENCODER)
```
Emulators expose no hardware encoder, so the router sends the job straight to FFmpeg and `runMedia3OrFallBack`'s `catch` is never entered. Its two assertions — succeeded, output non-empty — are true anyway. It finishes in **448 ms**, which is not long enough to fail a hardware export and then software-encode a 3 s clip.
**Deleting that `catch` reddens nothing, anywhere, on any leg.**
The committed `sample_h264_444.mp4` fixture — generated with x264 because Fedora's ffmpeg cannot produce High 4:4:4 — does nothing on any CI leg today.
## Two things that generalise
- **A test can assert and still not reach.** Neither a coverage number nor a "does it assert something" review catches this; it has two passing assertions. The filter that works is *does this test's premise hold on the machine that runs it?*
- **The codebase already knew.** `ForcedFailureTest` pins `DeviceCodecs.PERMISSIVE` against this exact hazard and writes out why; `ConversionWorkerTest` records it a third time. Their assertions are about the **path**, so without the pin they fail loudly. `HardwareFallbackTest`'s are about the **output**, so it passes quietly. That asymmetry is why nobody noticed.
## Findings (no ticket — a test would not fix these)
| ID | Finding | Action |
|---|---|---|
| E1 | `RemuxTest`'s KDoc argues for engine assertions three of its tests omit — and they are **right** to: those three produce MKV/AVI, which `MEDIA3_CONTAINERS` can never route to Media3 | one line of KDoc |
| E2 | Three of the 60 instrumented tests assert nothing; two never run | no action — deliberate, but 60 ≠ 60 |
| E3 | `…AndReportsProgress` does not assert progress fired | no action — the name overstates |
| E4 | The API 37 marker's KDoc says "two"; three tests carry it | fix the sentence |
| E5 | `coverage-read-findings.md` F7's "uncovered" half is stale — `RemuxTest` drives it on a device every leg | amend F7 |
| E6 | The one device-capability assertion asks the class under test what to expect | no action — read with #223 |
**E1 is the candidate that looked strongest and dissolved on tracing**, recorded so the next read does not re-file it. **E5 is the structural argument for this document existing**: a JaCoCo-derived document cannot see `androidTest`, so it will keep re-deriving "uncovered" for code the instrumented suite covers.
## Tickets filed
Each names the mutation that must go red, not a coverage delta.
| # | Gap |
|---|---|
| #223 | `HardwareFallbackTest` never attempts the hardware path |
| #224 | Cancelling a *running* native session, in any of the three engines |
| #225 | No `content://` input has reached a *successful* conversion — the ffkitsaf bridge |
| #226 | `OutputPublisher.publish` against a real `DocumentsProvider`, and the SAF premise it rests on |
| #227 | The notification's Cancel action has never been fired |
| #228 | `encodesFlacLosslessAudio` and `encodesOpus` pass on any non-empty file |
| #229 | FFmpeg's progress percentage is computed everywhere, asserted nowhere |
| #230 | *(spike)* whether a running conversion's process can be killed under instrumentation |
## Checked and deliberately not filed
Real HEVC encode is **not** Pixel-only (`Media3EngineTest` runs on the API 33-36 gating legs; the marker excludes API 37 only). `CLAUDE.md`'s "AndroidDeviceCodecs 20 / MediaProbe 14" is **not** stale — it dates the 2026-09-02 read and names #194/#195. Already tracked: #178, #190, #102, #108, F5.
## Verification
`./gradlew :app:ktlintCheck :app:detekt :app:lintDebug --continue` — BUILD SUCCESSFUL. No `.sh` or workflow changes, so shellcheck/actionlint are unaffected.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Four coverage waves have been steered by JaCoCo, which measures
testDebugUnitTestonly and cannot seeapp/src/androidTestat all. Nothing had ever asked what the 60 device tests pin, only that they were green.This is that read — a triage, not a test push, in the shape of
docs/coverage-read-findings.md. It addsdocs/e2e-read-findings.md(E1-E6) and aCLAUDE.mdpointer. No production or test code changes.The result
HardwareFallbackTestpasses on every emulator leg without ever attempting the hardware path (#223).It is the only automated check of the hardware→software fallback against a real codec failure. Measured on run
34004304566— the API 33, 34, 35 and 37 legs each log:Emulators expose no hardware encoder, so the router sends the job straight to FFmpeg and
runMedia3OrFallBack'scatchis never entered. Its two assertions — succeeded, output non-empty — are true anyway. It finishes in 448 ms, which is not long enough to fail a hardware export and then software-encode a 3 s clip.Deleting that
catchreddens nothing, anywhere, on any leg.The committed
sample_h264_444.mp4fixture — generated with x264 because Fedora's ffmpeg cannot produce High 4:4:4 — does nothing on any CI leg today.Two things that generalise
ForcedFailureTestpinsDeviceCodecs.PERMISSIVEagainst this exact hazard and writes out why;ConversionWorkerTestrecords it a third time. Their assertions are about the path, so without the pin they fail loudly.HardwareFallbackTest's are about the output, so it passes quietly. That asymmetry is why nobody noticed.Findings (no ticket — a test would not fix these)
RemuxTest's KDoc argues for engine assertions three of its tests omit — and they are right to: those three produce MKV/AVI, whichMEDIA3_CONTAINERScan never route to Media3…AndReportsProgressdoes not assert progress firedcoverage-read-findings.mdF7's "uncovered" half is stale —RemuxTestdrives it on a device every legE1 is the candidate that looked strongest and dissolved on tracing, recorded so the next read does not re-file it. E5 is the structural argument for this document existing: a JaCoCo-derived document cannot see
androidTest, so it will keep re-deriving "uncovered" for code the instrumented suite covers.Tickets filed
Each names the mutation that must go red, not a coverage delta.
HardwareFallbackTestnever attempts the hardware pathcontent://input has reached a successful conversion — the ffkitsaf bridgeOutputPublisher.publishagainst a realDocumentsProvider, and the SAF premise it rests onencodesFlacLosslessAudioandencodesOpuspass on any non-empty fileChecked and deliberately not filed
Real HEVC encode is not Pixel-only (
Media3EngineTestruns on the API 33-36 gating legs; the marker excludes API 37 only).CLAUDE.md's "AndroidDeviceCodecs 20 / MediaProbe 14" is not stale — it dates the 2026-09-02 read and names #194/#195. Already tracked: #178, #190, #102, #108, F5.Verification
./gradlew :app:ktlintCheck :app:detekt :app:lintDebug --continue— BUILD SUCCESSFUL. No.shor workflow changes, so shellcheck/actionlint are unaffected.🤖 Generated with Claude Code