Build patch against c6570f142, candidate manifest (4 files), repro,
mutant (killed, 2 of 191), targeted gate (cli 191/0, webui 23/0) and
build.md.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
60 lines
2.7 KiB
Python
Executable File
60 lines
2.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""Row 130 mutant: drop the extra stderr line from run() in cli.mjs, run the
|
||
cli suite, and restore the file from .orig with a sha256 check."""
|
||
import hashlib, os, re, shutil, signal, subprocess
|
||
|
||
WT = os.path.expanduser('~/darkwing-scratch/r130/wt')
|
||
PKT = os.path.expanduser('~/darkwing-scratch/r130/pkt')
|
||
TMP = os.path.expanduser('~/darkwing-scratch/r130/tmp')
|
||
F = 'packages/cli/src/cli.mjs'
|
||
LINE = ' if (OTHER_TREE_CODES.has(error.code)) io.stderr.write(`mosaic: ${OTHER_TREE}\\n`);\n'
|
||
M = [('m1-drop-other-tree-line', LINE)]
|
||
|
||
def sha(p):
|
||
return hashlib.sha256(open(p, 'rb').read()).hexdigest()
|
||
|
||
# Kill only processes left with a cwd under this row's TMPDIR.
|
||
def sweep():
|
||
left = []
|
||
for pid in filter(str.isdigit, os.listdir('/proc')):
|
||
try:
|
||
if os.readlink(f'/proc/{pid}/cwd').startswith(TMP + '/'):
|
||
left.append(int(pid)); os.kill(int(pid), signal.SIGKILL)
|
||
except OSError:
|
||
pass
|
||
return left
|
||
|
||
os.chdir(WT)
|
||
env = dict(os.environ, TMPDIR=TMP, NO_COLOR='1')
|
||
base = sha(F)
|
||
os.makedirs(os.path.join(PKT, 'mutants'), exist_ok=True)
|
||
log = open(os.path.join(PKT, 'mutants-run.log'), 'a')
|
||
for name, drop in M:
|
||
src = open(F).read()
|
||
if src.count(drop) != 1:
|
||
print(f'{name}: pattern not found once, skipped', file=log, flush=True); print(name, 'BAD PATTERN'); continue
|
||
shutil.copy2(F, F + '.orig')
|
||
try:
|
||
open(F, 'w').write(src.replace(drop, ''))
|
||
p = subprocess.Popen('node --test tests/*.test.mjs', shell=True, cwd='packages/cli', stdout=subprocess.PIPE,
|
||
stderr=subprocess.STDOUT, env=env, start_new_session=True, text=True)
|
||
try:
|
||
res, _ = p.communicate(timeout=900)
|
||
except subprocess.TimeoutExpired:
|
||
os.killpg(p.pid, signal.SIGKILL); res, _ = p.communicate(); res += '\nTIMEOUT\n'
|
||
open(os.path.join(PKT, 'mutants', f'mutant-{name}.log'), 'w').write(res)
|
||
fails = sorted({l.strip()[2:] for l in res.splitlines() if l.strip().startswith('✖ ') and l.strip() != '✖ failing tests:'})
|
||
tally = dict(re.findall(r'^ℹ (pass|fail) (\d+)$', res, re.M))
|
||
verdict = 'killed' if p.returncode != 0 else 'SURVIVED'
|
||
print(f'{name}: {verdict} (rc {p.returncode}, pass {tally.get("pass", "?")}, fail {tally.get("fail", "?")})', file=log, flush=True)
|
||
for l in fails:
|
||
print(f' fail: {l}', file=log, flush=True)
|
||
left = sweep()
|
||
if left:
|
||
print(f' swept {len(left)} leftover process(es) under TMPDIR: {left}', file=log, flush=True)
|
||
print(name, verdict, tally, flush=True)
|
||
finally:
|
||
shutil.move(F + '.orig', F)
|
||
assert sha(F) == base, f'{F} not restored'
|
||
print('restored:', sha(F) == base, file=log, flush=True)
|