HardwareFallbackTest is the only automated check of the hardware→software fallback against a real codec failure. It passed on every CI leg without ever attempting the hardware path.
What was wrong
Measured on run 34004304566 — the API 33, 34, 35 and 37 legs each log:
Routing sample_h264_444.mp4 -> ... via FFMPEG (NO_HARDWARE_ENCODER)
Emulators expose no hardware encoder, so the router never chooses Media3 and runMedia3OrFallBack's catch is never entered. The test's assertions — succeeded, output non-empty — are true of that conversion too. It finished in 448 ms. Deleting the catch reddened nothing.
The ticket left the fix open; trying it answered the question
#223 offered assumeTrue or asserting the routing decision. I tried a third option first — pin deviceCodecs to PERMISSIVE, exactly as ForcedFailureTest does — so the test would run on every leg instead of skipping.
It does not work, and why is the useful part. With the route forced, the export succeeds. On a local API 34 emulator MediaCodecInfo logs
for bothc2.goldfish.h264.decoder and c2.android.avc.decoder — and ExoPlayer allocates the goldfish decoder anyway, which decodes the High 4:4:4 fixture regardless of the profile it declares. c2.android.hevc.encoder encodes the result and the job reports MEDIA3.
So the class KDoc's "Media3 fails partway through the export on every device" is not true of the emulator images, and no routing pressure makes this fixture force a fallback there. Pinning also swaps in software codecs, which is not the path a real device takes — that is what made the forced run succeed.
That makes assumeTrue on the production premise the honest answer, now with a measurement behind it rather than a coin flip.
The change
assumeTrue(canEncode(H265)) — the test announces it cannot run instead of passing vacuously. Third permanent skip; recorded in docs/local-emulator.md and beside SafPickerRoundTripTest's run-shape note.
The assertion is a pair, because KEY_ENGINE_USED is FFMPEG whether the fallback fired or the router went straight there: the router chose MEDIA3 for this request on this device, and the worker reported FFMPEG. Together, and only together, that is the fallback.
The class KDoc carries both measurements, so the next person does not re-run the pinning experiment.
ForcedFailureTest.hardwareFailureFallsBackToSoftware still covers the fallback wiring on every leg with an ExplodingHardware double. What needs a real encoder is two real engines disagreeing about a real file.
Verification
Local API 34 emulator (tools/local-emulator/run-e2e.sh 34):
HardwareFallbackTest > aFileMedia3CannotDecodeStillConvertsViaFfmpeg SKIPPED
API 34: tests=... skipped=3
The first local run — with the assertion pair but no guard — failed exactly as intended: expected:<[FFMPEG]> but was:<[MEDIA3]>. That is the mutation, and it is what produced the finding above.
Closes #223.
`HardwareFallbackTest` is the only automated check of the hardware→software fallback against a *real* codec failure. It passed on every CI leg **without ever attempting the hardware path**.
## What was wrong
Measured on run [`34004304566`](https://github.com/JMR-dev/LibreMediaConverter/actions/runs/34004304566) — the API 33, 34, 35 and 37 legs each log:
```
Routing sample_h264_444.mp4 -> ... via FFMPEG (NO_HARDWARE_ENCODER)
```
Emulators expose no hardware encoder, so the router never chooses Media3 and `runMedia3OrFallBack`'s `catch` is never entered. The test's assertions — succeeded, output non-empty — are true of that conversion too. It finished in **448 ms**. **Deleting the `catch` reddened nothing.**
## The ticket left the fix open; trying it answered the question
#223 offered `assumeTrue` or asserting the routing decision. I tried a third option first — pin `deviceCodecs` to `PERMISSIVE`, exactly as `ForcedFailureTest` does — so the test would run on every leg instead of skipping.
**It does not work, and why is the useful part.** With the route forced, the export *succeeds*. On a local API 34 emulator `MediaCodecInfo` logs
```
NoSupport [codec.profileLevel, avc1.F4000C, video/avc]
```
for **both** `c2.goldfish.h264.decoder` and `c2.android.avc.decoder` — and ExoPlayer allocates the goldfish decoder anyway, which decodes the High 4:4:4 fixture regardless of the profile it declares. `c2.android.hevc.encoder` encodes the result and the job reports `MEDIA3`.
So the class KDoc's **"Media3 fails partway through the export on every device" is not true of the emulator images**, and no routing pressure makes this fixture force a fallback there. Pinning also swaps in *software* codecs, which is not the path a real device takes — that is what made the forced run succeed.
That makes `assumeTrue` on the production premise the honest answer, now with a measurement behind it rather than a coin flip.
## The change
- **`assumeTrue(canEncode(H265))`** — the test announces it cannot run instead of passing vacuously. Third permanent skip; recorded in `docs/local-emulator.md` and beside `SafPickerRoundTripTest`'s run-shape note.
- **The assertion is a pair**, because `KEY_ENGINE_USED` is `FFMPEG` whether the fallback fired *or* the router went straight there: the router chose `MEDIA3` for this request on this device, **and** the worker reported `FFMPEG`. Together, and only together, that is the fallback.
- The class KDoc carries both measurements, so the next person does not re-run the pinning experiment.
`ForcedFailureTest.hardwareFailureFallsBackToSoftware` still covers the fallback **wiring** on every leg with an `ExplodingHardware` double. What needs a real encoder is two real engines disagreeing about a real file.
## Verification
Local API 34 emulator (`tools/local-emulator/run-e2e.sh 34`):
```
HardwareFallbackTest > aFileMedia3CannotDecodeStillConvertsViaFfmpeg SKIPPED
API 34: tests=... skipped=3
```
The first local run — with the assertion pair but no guard — failed exactly as intended: `expected:<[FFMPEG]> but was:<[MEDIA3]>`. That is the mutation, and it is what produced the finding above.
🤖 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.
Closes #223.
HardwareFallbackTestis the only automated check of the hardware→software fallback against a real codec failure. It passed on every CI leg without ever attempting the hardware path.What was wrong
Measured on run
34004304566— the API 33, 34, 35 and 37 legs each log:Emulators expose no hardware encoder, so the router never chooses Media3 and
runMedia3OrFallBack'scatchis never entered. The test's assertions — succeeded, output non-empty — are true of that conversion too. It finished in 448 ms. Deleting thecatchreddened nothing.The ticket left the fix open; trying it answered the question
#223 offered
assumeTrueor asserting the routing decision. I tried a third option first — pindeviceCodecstoPERMISSIVE, exactly asForcedFailureTestdoes — so the test would run on every leg instead of skipping.It does not work, and why is the useful part. With the route forced, the export succeeds. On a local API 34 emulator
MediaCodecInfologsfor both
c2.goldfish.h264.decoderandc2.android.avc.decoder— and ExoPlayer allocates the goldfish decoder anyway, which decodes the High 4:4:4 fixture regardless of the profile it declares.c2.android.hevc.encoderencodes the result and the job reportsMEDIA3.So the class KDoc's "Media3 fails partway through the export on every device" is not true of the emulator images, and no routing pressure makes this fixture force a fallback there. Pinning also swaps in software codecs, which is not the path a real device takes — that is what made the forced run succeed.
That makes
assumeTrueon the production premise the honest answer, now with a measurement behind it rather than a coin flip.The change
assumeTrue(canEncode(H265))— the test announces it cannot run instead of passing vacuously. Third permanent skip; recorded indocs/local-emulator.mdand besideSafPickerRoundTripTest's run-shape note.KEY_ENGINE_USEDisFFMPEGwhether the fallback fired or the router went straight there: the router choseMEDIA3for this request on this device, and the worker reportedFFMPEG. Together, and only together, that is the fallback.ForcedFailureTest.hardwareFailureFallsBackToSoftwarestill covers the fallback wiring on every leg with anExplodingHardwaredouble. What needs a real encoder is two real engines disagreeing about a real file.Verification
Local API 34 emulator (
tools/local-emulator/run-e2e.sh 34):The first local run — with the assertion pair but no guard — failed exactly as intended:
expected:<[FFMPEG]> but was:<[MEDIA3]>. That is the mutation, and it is what produced the finding above.🤖 Generated with Claude Code