README invalid-options cell lists only count, key, text and duplicates; a
non-{key,text} option refuses invalid-request (Sage 28378, Filbert C1 28379).
Broker test pins the option shape cases. m1 and m2 killed; full gate passed
15:53:56-15:59:33Z on BASE 96656dd21.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
55 lines
2.7 KiB
Python
55 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
||
# Row 112 (#1606): mutants on the decision.raise choice checks in broker.mjs.
|
||
# m1 restores the old shared line, so a recommendation miss refuses
|
||
# invalid-options again (the brief's required mutant).
|
||
# m2 checks the recommendation before duplicate keys, so a bad option list
|
||
# with a missing recommendation reports invalid-recommendation.
|
||
# Each runs the FULL bus suite (node --test tests/*.test.mjs in packages/bus)
|
||
# and restores broker.mjs 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]
|
||
SRC = os.path.join(WT, 'packages/bus/src/broker.mjs')
|
||
CUR = (" if (new Set(choices).size !== choices.length) fail('invalid-options');\n"
|
||
" if (!choices.includes(a.recommendation)) fail('invalid-recommendation');\n")
|
||
MUTANTS = {
|
||
'm1': (" if (new Set(choices).size !== choices.length || !choices.includes(a.recommendation))\n"
|
||
" fail('invalid-options');\n"),
|
||
'm2': (" if (!choices.includes(a.recommendation)) fail('invalid-recommendation');\n"
|
||
" if (new Set(choices).size !== choices.length) fail('invalid-options');\n"),
|
||
}
|
||
os.makedirs(LOGS, exist_ok=True)
|
||
text = open(SRC).read()
|
||
if text.count(CUR) != 1:
|
||
sys.exit('STOP the choice checks in broker.mjs are not unique or have moved')
|
||
if os.path.exists(SRC + '.orig'):
|
||
sys.exit('STOP broker.mjs.orig already exists')
|
||
env = dict(os.environ, TMPDIR=os.path.expanduser('~/darkwing-scratch/r112/tmp'))
|
||
for name, new in MUTANTS.items():
|
||
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('STOP broker.mjs was not restored')
|