103 lines
3.5 KiB
Python
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())
|