ConcatWorker constructed ConcatEngine in place while ConversionWorker reached its engines through ConversionDependencies. That asymmetry is the whole reason one worker had a tested failure path and the other had none: everything past setForeground was untested on every source set, JVM and device alike.
The repo had already measured the cost and written it down. PerJobStagingTest's KDoc records that reverting ConcatWorker to a constant staging name left all 257 tests green, because nothing could reach the line that names the file. RefusedJobTest says it from the other side — "the next thing past the count guard is ConcatEngine, which is native."
That mutation is red now.
The seam
ConversionDependencies.concat: (Context) -> ConcatJoiner, beside .hardware and .software. ConcatEngine implements the interface; its Result type stays nested in the implementation, because moving it would touch every call site to buy nothing — what a test needs is the ability to not run FFmpeg, and that is the method, not the type.
Five tests, five mutations
behaviour
mutation
the engine's own reason reaches the user
replace e.message with the generic string
a failure with no message still says one
drop the ?: GENERIC_FAILURE_MESSAGE fallback
a failed join deletes its partial
drop staged.delete() from the catch
no input array at all is refused
swap in TOO_FEW_INPUTS_MESSAGE
a join reports its own staged file
revert to the constant staging name
The delete test was vacuous on its first draft, and the mutation caught it. It scanned the staging directory for a "join-" prefix that StagingNames.forJob does not produce — it names files <jobId>.<ext> — so the assertion was trivially true and the mutation walked straight through. Rewritten to assert against the handle the joiner was actually given.
Two small fixes ride along
Both are the repo's own conventions, not new opinions:
"No input files." becomes NO_INPUTS_MESSAGE, per #158: a message the user can see is named once, so a test asserts the string the worker writes rather than a copy that can drift.
That arm now has a JVM test. It ran before staging and before any native code and had no business being a device-only test.
The line denominator moved 2348 → 2352: that is the ConcatJoiner interface, not new untested code. Said plainly because CLAUDE.md's coverage entry has a history of explaining its own numbers wrongly.
Closes #176. Stacked on #187. Last of the wave.
`ConcatWorker` constructed `ConcatEngine` in place while `ConversionWorker` reached its engines through `ConversionDependencies`. **That asymmetry is the whole reason one worker had a tested failure path and the other had none**: everything past `setForeground` was untested on *every* source set, JVM and device alike.
The repo had already measured the cost and written it down. `PerJobStagingTest`'s KDoc records that **reverting `ConcatWorker` to a constant staging name left all 257 tests green**, because nothing could reach the line that names the file. `RefusedJobTest` says it from the other side — *"the next thing past the count guard is `ConcatEngine`, which is native."*
That mutation is red now.
### The seam
`ConversionDependencies.concat: (Context) -> ConcatJoiner`, beside `.hardware` and `.software`. `ConcatEngine` implements the interface; its `Result` type stays nested in the implementation, because moving it would touch every call site to buy nothing — what a test needs is the ability to *not run FFmpeg*, and that is the method, not the type.
### Five tests, five mutations
| behaviour | mutation |
|---|---|
| the engine's own reason reaches the user | replace `e.message` with the generic string |
| a failure with no message still says one | drop the `?: GENERIC_FAILURE_MESSAGE` fallback |
| a failed join deletes its partial | drop `staged.delete()` from the catch |
| no input array at all is refused | swap in `TOO_FEW_INPUTS_MESSAGE` |
| a join reports its own staged file | revert to the constant staging name |
**The delete test was vacuous on its first draft, and the mutation caught it.** It scanned the staging directory for a `"join-"` prefix that `StagingNames.forJob` does not produce — it names files `<jobId>.<ext>` — so the assertion was trivially true and the mutation walked straight through. Rewritten to assert against the handle the joiner was actually given.
### Two small fixes ride along
Both are the repo's own conventions, not new opinions:
- `"No input files."` becomes `NO_INPUTS_MESSAGE`, per **#158**: a message the user can see is named once, so a test asserts the string the worker writes rather than a copy that can drift.
- That arm now has a JVM test. It ran before staging and before any native code and had no business being a device-only test.
### Numbers
579 → 584 JVM tests, 0 failures. `ConcatWorker`: 14 → 5 missed lines, 2 → **0** missed branches.
Line 2173/2348 → **2183/2352**; branch numerator unchanged at 1091/1342.
The line **denominator** moved 2348 → 2352: that is the `ConcatJoiner` interface, not new untested code. Said plainly because `CLAUDE.md`'s coverage entry has a history of explaining its own numbers wrongly.
🤖 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 #176. Stacked on #187. Last of the wave.
ConcatWorkerconstructedConcatEnginein place whileConversionWorkerreached its engines throughConversionDependencies. That asymmetry is the whole reason one worker had a tested failure path and the other had none: everything pastsetForegroundwas untested on every source set, JVM and device alike.The repo had already measured the cost and written it down.
PerJobStagingTest's KDoc records that revertingConcatWorkerto a constant staging name left all 257 tests green, because nothing could reach the line that names the file.RefusedJobTestsays it from the other side — "the next thing past the count guard isConcatEngine, which is native."That mutation is red now.
The seam
ConversionDependencies.concat: (Context) -> ConcatJoiner, beside.hardwareand.software.ConcatEngineimplements the interface; itsResulttype stays nested in the implementation, because moving it would touch every call site to buy nothing — what a test needs is the ability to not run FFmpeg, and that is the method, not the type.Five tests, five mutations
e.messagewith the generic string?: GENERIC_FAILURE_MESSAGEfallbackstaged.delete()from the catchTOO_FEW_INPUTS_MESSAGEThe delete test was vacuous on its first draft, and the mutation caught it. It scanned the staging directory for a
"join-"prefix thatStagingNames.forJobdoes not produce — it names files<jobId>.<ext>— so the assertion was trivially true and the mutation walked straight through. Rewritten to assert against the handle the joiner was actually given.Two small fixes ride along
Both are the repo's own conventions, not new opinions:
"No input files."becomesNO_INPUTS_MESSAGE, per #158: a message the user can see is named once, so a test asserts the string the worker writes rather than a copy that can drift.Numbers
579 → 584 JVM tests, 0 failures.
ConcatWorker: 14 → 5 missed lines, 2 → 0 missed branches.Line 2173/2348 → 2183/2352; branch numerator unchanged at 1091/1342.
The line denominator moved 2348 → 2352: that is the
ConcatJoinerinterface, not new untested code. Said plainly becauseCLAUDE.md's coverage entry has a history of explaining its own numbers wrongly.🤖 Generated with Claude Code