fix(mutants): a surviving mutant exits 1 (H4)
`verdict(survived, errors)` is the one place the run's exit code is decided: 2 when a mutant could not be applied (it was never measured, and that outranks everything), 1 when one survived, 0 otherwise. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
6abe4b4358
commit
f5b263f5ef
1 changed files with 20 additions and 1 deletions
|
|
@ -18,6 +18,10 @@ edited, so an interrupted run cannot leave a mutant behind. A pattern that does
|
||||||
not match the expected number of times is reported as an ERROR and the run
|
not match the expected number of times is reported as an ERROR and the run
|
||||||
exits 2 -- a mutant that could not be applied was never measured, and counting
|
exits 2 -- a mutant that could not be applied was never measured, and counting
|
||||||
it as killed is the same mistake as reading an empty search as an absence.
|
it as killed is the same mistake as reading an empty search as an absence.
|
||||||
|
|
||||||
|
A SURVIVOR EXITS 1. Until 2026-09-19 the run ended `2 if errors else 0`, so
|
||||||
|
`killed 0 of 1` with the survivor printed beside it was an exit 0 and nothing
|
||||||
|
could fail on the finding this harness exists to produce (H4).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
@ -26,6 +30,7 @@ import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from collections.abc import Sequence
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
@ -267,6 +272,20 @@ def _apply(text: str, mutant: Mutant) -> str:
|
||||||
return text.replace(mutant.old, mutant.new, 1 if mutant.first_only else -1)
|
return text.replace(mutant.old, mutant.new, 1 if mutant.first_only else -1)
|
||||||
|
|
||||||
|
|
||||||
|
def verdict(survived: Sequence[str], errors: Sequence[str]) -> int:
|
||||||
|
"""The run's exit code: 0 clean, 1 a mutant survived, 2 one was not measured.
|
||||||
|
|
||||||
|
Until 2026-09-19 this was `2 if errors else 0`, so a run that printed
|
||||||
|
`killed 0 of 1` and named its survivor exited 0 and no caller could fail
|
||||||
|
on it (H4). A mutant that survived names a check nothing holds, which is
|
||||||
|
the finding this harness exists to produce; a mutant that could not be
|
||||||
|
applied was never measured at all, and that outranks it.
|
||||||
|
"""
|
||||||
|
if errors:
|
||||||
|
return 2
|
||||||
|
return 1 if survived else 0
|
||||||
|
|
||||||
|
|
||||||
def main(argv: list[str] | None = None) -> int:
|
def main(argv: list[str] | None = None) -> int:
|
||||||
del argv
|
del argv
|
||||||
survived: list[str] = []
|
survived: list[str] = []
|
||||||
|
|
@ -311,7 +330,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||||
print(f" survived: {label}")
|
print(f" survived: {label}")
|
||||||
for problem in errors:
|
for problem in errors:
|
||||||
print(f" ERROR: {problem}")
|
print(f" ERROR: {problem}")
|
||||||
return 2 if errors else 0
|
return verdict(survived, errors)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue