Candidate on base 94ece491f: 5 files, manifest f37bc933. Three mutants
killed; gate passed with no flakes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
57 lines
2.7 KiB
Python
Executable File
57 lines
2.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
# Row 115 (#1609): the brief's three mutants of the decision close message.
|
||
# m1 drops the message: the close block never runs.
|
||
# m2 addresses the message to route_to instead of the raising role.
|
||
# m3 drops the raising-run check, so the raiser's own withdraw sends one too.
|
||
# Each runs the FULL bus suite (node --test tests/*.test.mjs in packages/bus)
|
||
# and restores the source from the .orig copy whatever happens.
|
||
# Usage: mutants.py <worktree> <log dir>
|
||
import os, shutil, subprocess, sys, signal
|
||
|
||
WT, LOGS = sys.argv[1], sys.argv[2]
|
||
MUTANTS = {
|
||
'm1': ('packages/bus/src/broker.mjs',
|
||
" if (op !== 'seen' && (s.human || s.run !== d.raised_by_run)) {\n",
|
||
" if (false) {\n"),
|
||
'm2': ('packages/bus/src/broker.mjs',
|
||
" d.raised_by_role,\n 'DECISION',\n",
|
||
" d.route_to,\n 'DECISION',\n"),
|
||
'm3': ('packages/bus/src/broker.mjs',
|
||
" if (op !== 'seen' && (s.human || s.run !== d.raised_by_run)) {\n",
|
||
" if (op !== 'seen') {\n"),
|
||
}
|
||
os.makedirs(LOGS, exist_ok=True)
|
||
env = dict(os.environ, TMPDIR=os.path.expanduser('~/darkwing-scratch/r115/tmp'))
|
||
for name, (rel, cur, new) in MUTANTS.items():
|
||
src = os.path.join(WT, rel)
|
||
text = open(src).read()
|
||
if text.count(cur) != 1:
|
||
sys.exit(f'STOP {name}: the target text in {rel} is not unique or has moved')
|
||
if os.path.exists(src + '.orig'):
|
||
sys.exit(f'STOP {rel}.orig already exists')
|
||
log_path = os.path.join(LOGS, f'mutant-{name}.log')
|
||
shutil.copy2(src, src + '.orig')
|
||
try:
|
||
open(src, 'w').write(text.replace(cur, new))
|
||
with open(log_path, 'w') as out:
|
||
p = subprocess.Popen('node --test tests/*.test.mjs', shell=True, cwd=os.path.join(WT, 'packages/bus'),
|
||
stdout=out, stderr=subprocess.STDOUT, env=env, start_new_session=True)
|
||
try:
|
||
rc = p.wait(timeout=600)
|
||
except subprocess.TimeoutExpired:
|
||
os.killpg(p.pid, signal.SIGKILL)
|
||
p.wait()
|
||
rc = 'timeout'
|
||
finally:
|
||
shutil.move(src + '.orig', src)
|
||
restored = open(src).read() == text
|
||
log = open(log_path).read()
|
||
counts = [l for l in log.splitlines() if l.startswith(('ℹ pass', 'ℹ fail', 'ℹ skipped'))]
|
||
failed = [l.strip() for l in log.splitlines() if l.lstrip().startswith('✖') and 'failing tests' not in l]
|
||
print(f'mutant {name} rc={rc} {" ".join(counts)} restored={restored}')
|
||
for f in sorted(set(failed)):
|
||
print(' ' + f)
|
||
print('KILLED' if rc != 0 else 'SURVIVED')
|
||
if not restored:
|
||
sys.exit(f'STOP {rel} was not restored')
|