Files
troupe-standup/wizard-work/forge/_mutant_scan.py
T

103 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""Safe mutation-test for the forge probe-refactor verifier.
Copies the verifier + the probe files + chunker into a TEMP dir (the real
artifact is never modified), applies each mutant to the temp copy of
``_probe_forge_refactored.py``, runs the temp copy of the verifier, and
reports SURVIVED / DIED.
Exit 0 = every mutant is killed (the verifier is robust).
Exit 1 = at least one mutant survived (verifier too weak).
"""
from __future__ import annotations
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
WORK = Path("/root/troupe-standup/wizard-work/forge")
SRC_FILES = [
"verify_probe_forge_refactor.py",
"_probe_forge.py",
"_probe_forge_refactored.py",
"chunker.py",
]
mutants = []
def mutant(label, fn):
mutants.append((label, fn))
mutant("guardless", lambda s: s.replace(
'if __name__ == "__main__":\n sys.exit(main())',
'main() # guard-less mutant\n'))
mutant("no_main_guard", lambda s: s.replace(
'if __name__ == "__main__":\n sys.exit(main())',
'sys.exit(main()) # no_main_guard mutant\n'))
mutant("ffd_flip", lambda s: s.replace(
"if actual[i] != expected[i]:", "if actual[i] == expected[i]:"))
mutant("ffd_return_none", lambda s: s.replace(
" return i\n", " return None # mutant\n"))
mutant("ffd_return_i_plus_1", lambda s: s.replace(
" return i\n", " return i + 1 # mutant\n"))
mutant("ffd_delete_return", lambda s: s.replace(
" return i\n", " pass # mutant\n"))
mutant("ffd_skip_index_zero", lambda s: s.replace(
"for i in range(min(len(actual), len(expected))):",
"for i in range(1, min(len(actual), len(expected))):"))
mutant("ffd_extend_range", lambda s: s.replace(
"for i in range(min(len(actual), len(expected))):",
"for i in range(min(len(actual), len(expected)) + 1):"))
mutant("ffd_always_return_none", lambda s: s.replace(
" for i in range(min(len(actual), len(expected))):\n",
" return None # mutant\n for i in range(min(len(actual), len(expected))):\n"))
mutant("hardcode_equal", lambda s: s.replace(
'print("equal:", actual == expected)', 'print("equal:", True)'))
mutant("reconstruct_overlap_zero", lambda s: s.replace(
"actual = chunker.reconstruct(cs, 8)", "actual = chunker.reconstruct(cs, 0)"))
def main() -> int:
tmp = Path(tempfile.mkdtemp(prefix="mutant_scan_"))
try:
for f in SRC_FILES:
shutil.copy2(WORK / f, tmp / f)
ref = tmp / "_probe_forge_refactored.py"
SRC = ref.read_text()
survivors = []
for label, fn in mutants:
mutant_src = fn(SRC)
if mutant_src == SRC:
print(f" {label:26s} SKIP (no change)")
continue
ref.write_text(mutant_src)
p = subprocess.run("python3 verify_probe_forge_refactor.py",
shell=True, cwd=tmp,
capture_output=True, text=True)
tail = (p.stdout + p.stderr).strip().splitlines()
tail = tail[-1] if tail else "(no output)"
died = p.returncode != 0
print(f" {label:26s} {'DIED' if died else 'SURVIVED':9s} "
f"exit={p.returncode} {tail}")
if not died:
survivors.append(label)
print("\nSurvivors:", survivors if survivors else "none")
return 1 if survivors else 0
finally:
shutil.rmtree(tmp, ignore_errors=True)
if __name__ == "__main__":
sys.exit(main())