Closes#157. Independent of the rest of wave 2 (#153).
The gap
setPreset was covered. The six beside it — setContainer, setVideoCodec, setAudioCodec, applySuggestion, setQuality, setEnginePreference — and cancel() had no coverage at all. That asymmetry is the tell: they are reachable from the JVM suite by exactly the route setPreset already takes, and nothing had asked.
What is asserted
Not "the setter sets something". Each copies into a nested OutputSpec, so the failure worth catching is a setter that writes the right value into the wrong field, or that rebuilds the spec and quietly discards the other two. Every test asserts the field it changed and that the rest survived.
Three tests did not bite, and the reason is the interesting part
The first mutation pass came back with two green:
mutation
first run
setContainer rebuilds the spec from OutputFormat.MP4_H265.spec
GREEN
setQuality also resets enginePreference to AUTO
GREEN
Both for one mistake of mine. ConversionSettings starts at MP4_H265.spec, QualityTier.FAST and EnginePreference.AUTO — and I asserted "the rest survived" against values still sitting at those defaults. So a mutation that RESET a neighbouring field to its default was indistinguishable from one that left it alone. The tests were checking a value, not a behaviour.
Fixed by moving each neighbour off its default before the call under test. A third test had the same latent hazard — it asserted quality == FAST — and was corrected with the others rather than left to fail later on someone else's change.
This is exactly the shape CLAUDE.md warns about:
A review of this codebase ran 46 mutations against a 257-test suite and 9 were vacuous — five of them passing the whole suite over a completely unguarded code path. Green is not evidence.
Second time in this wave the mutation pass has earned its keep.
Mutations, after the fix
mutation
red
setContainer rebuilds from a preset
1 test
setQuality resets the engine preference
1 test
setEnginePreference resets the quality
2 tests
applySuggestion resets quality
1 test
setVideoCodec writes nothing
1 test
setAudioCodec writes nothing
2 tests
setEnginePreference writes nothing
2 tests
cancel() dereferences a null activeWorkId
1 test
cancel()'s null side matters on its own: a user can reach Cancel through a state that has already finished, and taking the app down for it would be worse than doing nothing.
Note the modest branch movement, in contrast to W1 and W2: these are one-line assignments with no decisions in them, so line coverage is what moves. That is the expected shape here, and the inverse of what #153's parent records for decision code.
Closes #157. Independent of the rest of wave 2 (#153).
## The gap
`setPreset` was covered. The six beside it — `setContainer`, `setVideoCodec`, `setAudioCodec`, `applySuggestion`, `setQuality`, `setEnginePreference` — and `cancel()` had **no coverage at all**. That asymmetry is the tell: they are reachable from the JVM suite by exactly the route `setPreset` already takes, and nothing had asked.
## What is asserted
Not "the setter sets something". Each copies into a nested `OutputSpec`, so the failure worth catching is **a setter that writes the right value into the wrong field, or that rebuilds the spec and quietly discards the other two.** Every test asserts the field it changed *and* that the rest survived.
## Three tests did not bite, and the reason is the interesting part
The first mutation pass came back with two green:
| mutation | first run |
|---|---|
| `setContainer` rebuilds the spec from `OutputFormat.MP4_H265.spec` | **GREEN** |
| `setQuality` also resets `enginePreference` to `AUTO` | **GREEN** |
Both for one mistake of mine. `ConversionSettings` starts at `MP4_H265.spec`, `QualityTier.FAST` and `EnginePreference.AUTO` — and I asserted "the rest survived" against values still sitting at those defaults. **So a mutation that RESET a neighbouring field to its default was indistinguishable from one that left it alone.** The tests were checking a value, not a behaviour.
Fixed by moving each neighbour off its default before the call under test. A third test had the same latent hazard — it asserted `quality == FAST` — and was corrected with the others rather than left to fail later on someone else's change.
This is exactly the shape `CLAUDE.md` warns about:
> A review of this codebase ran 46 mutations against a 257-test suite and **9 were vacuous** — five of them passing the whole suite over a completely unguarded code path. Green is not evidence.
Second time in this wave the mutation pass has earned its keep.
## Mutations, after the fix
| mutation | red |
|---|---|
| `setContainer` rebuilds from a preset | 1 test |
| `setQuality` resets the engine preference | 1 test |
| `setEnginePreference` resets the quality | 2 tests |
| `applySuggestion` resets quality | 1 test |
| `setVideoCodec` writes nothing | 1 test |
| `setAudioCodec` writes nothing | 2 tests |
| `setEnginePreference` writes nothing | 2 tests |
| `cancel()` dereferences a null `activeWorkId` | 1 test |
`cancel()`'s null side matters on its own: a user can reach Cancel through a state that has already finished, and taking the app down for it would be worse than doing nothing.
**516 → 525 tests, 87.7% → 88.0% line, 70.4% → 70.5% branch.** Gate green: `assembleDebug`, `testDebugUnitTest`, `compileDebugAndroidTestKotlin`, `ktlintCheck`, `detekt`, `lintDebug`.
Note the modest branch movement, in contrast to W1 and W2: these are one-line assignments with no decisions in them, so line coverage is what moves. That is the expected shape here, and the inverse of what #153's parent records for decision 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 #157. Independent of the rest of wave 2 (#153).
The gap
setPresetwas covered. The six beside it —setContainer,setVideoCodec,setAudioCodec,applySuggestion,setQuality,setEnginePreference— andcancel()had no coverage at all. That asymmetry is the tell: they are reachable from the JVM suite by exactly the routesetPresetalready takes, and nothing had asked.What is asserted
Not "the setter sets something". Each copies into a nested
OutputSpec, so the failure worth catching is a setter that writes the right value into the wrong field, or that rebuilds the spec and quietly discards the other two. Every test asserts the field it changed and that the rest survived.Three tests did not bite, and the reason is the interesting part
The first mutation pass came back with two green:
setContainerrebuilds the spec fromOutputFormat.MP4_H265.specsetQualityalso resetsenginePreferencetoAUTOBoth for one mistake of mine.
ConversionSettingsstarts atMP4_H265.spec,QualityTier.FASTandEnginePreference.AUTO— and I asserted "the rest survived" against values still sitting at those defaults. So a mutation that RESET a neighbouring field to its default was indistinguishable from one that left it alone. The tests were checking a value, not a behaviour.Fixed by moving each neighbour off its default before the call under test. A third test had the same latent hazard — it asserted
quality == FAST— and was corrected with the others rather than left to fail later on someone else's change.This is exactly the shape
CLAUDE.mdwarns about:Second time in this wave the mutation pass has earned its keep.
Mutations, after the fix
setContainerrebuilds from a presetsetQualityresets the engine preferencesetEnginePreferenceresets the qualityapplySuggestionresets qualitysetVideoCodecwrites nothingsetAudioCodecwrites nothingsetEnginePreferencewrites nothingcancel()dereferences a nullactiveWorkIdcancel()'s null side matters on its own: a user can reach Cancel through a state that has already finished, and taking the app down for it would be worse than doing nothing.516 → 525 tests, 87.7% → 88.0% line, 70.4% → 70.5% branch. Gate green:
assembleDebug,testDebugUnitTest,compileDebugAndroidTestKotlin,ktlintCheck,detekt,lintDebug.Note the modest branch movement, in contrast to W1 and W2: these are one-line assignments with no decisions in them, so line coverage is what moves. That is the expected shape here, and the inverse of what #153's parent records for decision code.