Repository navigation
test: kill eight surviving mutants in formatter, parser and blocker analysis - #52
Merged
Merged
Conversation
…nalysis Found by mutation-testing lash 0.3.1 with Flawd 0.8.0 (400 mutants sampled from 3528, per-test targeting, every survivor re-confirmed against the full suite). These eight survivors are all cases where a conditional's two branches produce the same test outcome, so inverting it changed nothing that any test observed. Three sit in code 0.3.1 just changed: - formatter: `normalize_whitespace` was only ever exercised through the helper it gates, never through the flag itself, so inverting the flag passed. The trailing-blank-line trim and the "don't re-emit a description that is already in the source" guard were likewise unasserted. - parser: a blank line between a checkbox and its annotation block. The parsed values did not pin this — annotations are recovered downstream either way — but `annotation_line_count` silently drops to 0, and that count is what the write paths use to find where a task's annotations end. A wrong count there is how `lash add` splices a task into a previous task's block, the data-loss class fixed in 0.3.1 (#49). - blocker analysis: `find_blocker_chains` was only tested on a chain one hop deep, where the walk exits on the "current task is not itself blocked" check before its own progress guard is ever what stops it. A four-node chain distinguishes them. The report's per-blocker status word and its chains section were also unpinned. Each test was verified to fail against its mutation and pass without it. No production code changed. One further survivor is left deliberately untested: in the parser's blank-line branch, `} else if !annotation_lines.is_empty() { break; }` is followed by an unconditional `break`, so both paths break identically and no test can observe the difference. It is an equivalent mutant, not a gap; the redundant branch could be removed but that is a change to production code and not this commit's business.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Eight new tests in lash-core. No production code changes.
Each one covers a conditional whose two branches produced the same result for the existing suite, so inverting the condition left every test green.
Where these came from
A mutation testing run against 0.3.1 sampled 400 mutants out of 3528 and left 86 survivors once each had been re-confirmed against the full suite. These eight are the ones in three files I thought were worth acting on first.
Formatter
normalize_whitespacealready had a test, but it calls the helper directly. Nothing exercised the flag that decides whether the helper runs at all, so inverting the flag passed. Two nearby conditions were also unasserted: the trim that stops a formatted file ending on a blank line, and the guard that stops a synthesized tasks section re-emitting a description the source already has.Parser
A blank line is allowed between a checkbox and its annotation block. Asserting the parsed values does not pin that, because the annotations are recovered further down either way, which is why the existing test still passes with the condition inverted. What actually changes is
annotation_line_count, which drops to zero.That count is how the write paths find where a task's annotations end, and a wrong count there is how
lash addused to splice a new task into the middle of the previous task's block (#49). The new test asserts the count rather than the values.Blocker analysis
find_blocker_chainswas tested on task1 to task2 to task3. That chain is only one hop deep: the walk pushes task3 and stops because task3 is not itself blocked, so the loop's own "did I find anything this pass" guard is never what ends it. Inverting that guard produced the same two-element chain and nothing failed. A fourth node forces a second hop, which is the first case that tells them apart.The per-blocker status word and the chains section in
BlockerReport::formatwere unasserted too, so two tests now cover it.Verification
Every test was checked in both directions. Applied to its mutation it fails, and without the mutation it passes. The workspace suite, clippy and fmt are all clean.
One survivor left alone
In the parser's blank-line branch,
} else if !annotation_lines.is_empty() { break; }is followed by an unconditionalbreak. Both paths break, so nothing can observe the difference. That is an equivalent mutant rather than a gap in coverage. The redundant branch could go, but that is a production change and does not belong in a test PR.Found with Flawd, the mutation testing tool we build at Fixture. It is available now, with a 14-day evaluation, at https://fixture.dev/flawd.