S1: cut MediaProbe's track walk into a pure seam, and test the matrix #150

Merged
JMR-dev merged 3 commits from test/mediaprobe-track-seam into test/refused-jobs 2026-08-27 13:57:08 +00:00
JMR-dev commented 2026-08-27 03:51:12 +00:00 (Migrated from github.com)

Closes #141.

This revises a closed ticket's boundary, so that part first. #84 classified probeWithExtractor and probeForConcat as device-bound and explicitly not a gap:

These are exercised by RemuxTest, ConcatEngineTest and RealMediaBenchmark in androidTest … Do not read their 0% as untested, and do not try to fix it by mocking FFprobe.

Right about FFprobe. Right about the measurement boundary. Not right that these are only orchestration. The track walk is a branch matrix, and androidTest reaches it only through whatever the committed fixtures happen to contain — so none of its rules is chosen by any test there. A fixture with two video tracks, a track that omits its duration, or an audio-before-video ordering is not something a device test produces on purpose.

I've left a note on #84 pointing here.

The decision #133 asked for

Two ways in: drive ShadowMediaExtractor (verified reachable — it shadows the exact setDataSource(Context, Uri, Map) overload), or cut the loop into a pure function. Taking the second, which is the pattern CLAUDE.md names and work/FailureOutcome.kt documents.

extractedFrom(List<MediaFormat>) and concatInputFrom(List<MediaFormat>) hold the rules. What is left needing a device — setDataSource, getTrackFormat, release — is one three-line extension function, which is the thin edge androidTest should be covering.

The two are deliberately not merged despite the overlap. One reads duration and not frame rate; the other reads frame rate and not duration. A merged version would compute both for every caller, and ConcatPlanner treats an unknown frame rate as "cannot prove a match" — so a field the join flow does not need must not start arriving as a number.

Eleven tests, over cases no fixture provides

Two video tracks · two audio tracks · audio outlasting video · a track with no KEY_DURATION · audio declared before video · a subtitle track · no tracks at all.

Mutations — six run, six red

mutation reddens
last video track wins first-video test
last audio track wins first-audio test
duration = last rather than maxOf longest-track test
drop the containsKey guard six tests — getLong throws on a missing key
guess a frame rate of 30 no-frame-rate test
join takes the last video track join frame-rate test

Behaviour preservation

This is the only one of these PRs that changes production code, so the check that matters is the e2e legs — RemuxTest.probeIdentifiesTheSourceContainerOfEachFixture, probeDistinguishesAudioFromImagesFromRubbish and ConcatEngineTest.probeReadsThePropertiesTheStrategyDependsOn all drive these paths over real files on a device. The JVM suite passing is necessary, not sufficient; please read the e2e results here as the real gate.

Coverage

MediaProbe's missed branches 91 → 70. What remains is the FFprobe half and the two catch arms — native and device-bound, exactly as #84 said.

Local gate green: ktlintCheck, detekt, lintDebug, testDebugUnitTest, compileDebugAndroidTestKotlin.

🤖 Generated with Claude Code

Closes #141. **This revises a closed ticket's boundary, so that part first.** #84 classified `probeWithExtractor` and `probeForConcat` as device-bound and explicitly not a gap: > These are exercised by `RemuxTest`, `ConcatEngineTest` and `RealMediaBenchmark` in `androidTest` … **Do not read their 0% as untested**, and do not try to fix it by mocking FFprobe. Right about FFprobe. Right about the measurement boundary. **Not right that these are only orchestration.** The track walk is a branch matrix, and `androidTest` reaches it only through whatever the committed fixtures happen to contain — so none of its rules is *chosen* by any test there. A fixture with two video tracks, a track that omits its duration, or an audio-before-video ordering is not something a device test produces on purpose. I've left a note on #84 pointing here. ### The decision #133 asked for Two ways in: drive `ShadowMediaExtractor` (verified reachable — it shadows the exact `setDataSource(Context, Uri, Map)` overload), or cut the loop into a pure function. **Taking the second**, which is the pattern `CLAUDE.md` names and `work/FailureOutcome.kt` documents. `extractedFrom(List<MediaFormat>)` and `concatInputFrom(List<MediaFormat>)` hold the rules. What is left needing a device — `setDataSource`, `getTrackFormat`, `release` — is one three-line extension function, which is the thin edge `androidTest` *should* be covering. **The two are deliberately not merged despite the overlap.** One reads duration and not frame rate; the other reads frame rate and not duration. A merged version would compute both for every caller, and `ConcatPlanner` treats an unknown frame rate as "cannot prove a match" — so a field the join flow does not need must not start arriving as a number. ### Eleven tests, over cases no fixture provides Two video tracks · two audio tracks · audio outlasting video · a track with no `KEY_DURATION` · audio declared before video · a subtitle track · no tracks at all. ### Mutations — six run, six red | mutation | reddens | |---|---| | last video track wins | first-video test | | last audio track wins | first-audio test | | duration = last rather than `maxOf` | longest-track test | | drop the `containsKey` guard | six tests — `getLong` throws on a missing key | | guess a frame rate of 30 | no-frame-rate test | | join takes the last video track | join frame-rate test | ### Behaviour preservation This is the only one of these PRs that changes production code, so the check that matters is the **e2e legs** — `RemuxTest.probeIdentifiesTheSourceContainerOfEachFixture`, `probeDistinguishesAudioFromImagesFromRubbish` and `ConcatEngineTest.probeReadsThePropertiesTheStrategyDependsOn` all drive these paths over real files on a device. The JVM suite passing is necessary, not sufficient; please read the e2e results here as the real gate. ### Coverage `MediaProbe`'s missed branches 91 → **70**. What remains is the FFprobe half and the two `catch` arms — native and device-bound, exactly as #84 said. Local gate green: `ktlintCheck`, `detekt`, `lintDebug`, `testDebugUnitTest`, `compileDebugAndroidTestKotlin`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
JMR-dev commented 2026-08-27 04:01:29 +00:00 (Migrated from github.com)

Behaviour preservation, confirmed on device. This is the only PR in the batch that touches production code, and the PR body asked reviewers to read the e2e legs rather than the JVM suite as the real gate. They are in:

leg shape
API 33 expected: 60, received: 60, failed: 0, completed cleanly: yes
API 34 same
API 35 same
API 36 same

Zero failures matching RemuxTest, ConcatEngineTest or MediaProbe on any leg — those are the tests that drive probeWithExtractor and probeForConcat over real files, and they are what would break if extracting the walk had changed its answer.

API 37 is red on SafPickerRoundTripTest.pickingAFileThroughTheSystemPickerFillsInTheFileCard — "the system picker would not close: after 4 back presses … com.google.android.documentsui is in front" — which is an emulator/system-UI flake unrelated to this diff. Same failure appeared today on #146, a PR that adds only JVM model tests. Tallied on #102. Re-running that leg.

**Behaviour preservation, confirmed on device.** This is the only PR in the batch that touches production code, and the PR body asked reviewers to read the e2e legs rather than the JVM suite as the real gate. They are in: | leg | shape | |---|---| | API 33 | `expected: 60, received: 60, failed: 0, completed cleanly: yes` | | API 34 | same | | API 35 | same | | API 36 | same | Zero failures matching `RemuxTest`, `ConcatEngineTest` or `MediaProbe` on any leg — those are the tests that drive `probeWithExtractor` and `probeForConcat` over real files, and they are what would break if extracting the walk had changed its answer. API 37 is red on **`SafPickerRoundTripTest.pickingAFileThroughTheSystemPickerFillsInTheFileCard`** — "the system picker would not close: after 4 back presses … `com.google.android.documentsui` is in front" — which is an emulator/system-UI flake unrelated to this diff. Same failure appeared today on #146, a PR that adds only JVM model tests. Tallied on #102. Re-running that leg.
Sign in to join this conversation.