Packet commit, agents/researcher/work/queue-97 only. Secrets scan clean. Not pushed. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
70 lines
3.3 KiB
Python
70 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
# Row 97 (#1591) mutants. Each edit must match exactly once. Every run starts in its own process
|
|
# group and the group is killed when the run ends or times out, so no node or chromium is left
|
|
# behind. The mutated file is restored from the bytes read before the edit, and every touched
|
|
# file's sha256 is checked against its start at the end.
|
|
# Usage: mutants.py <worktree> <out.txt> [timeout-seconds]
|
|
import hashlib, os, signal, subprocess, sys, time
|
|
|
|
WT, OUT = sys.argv[1], sys.argv[2]
|
|
TIMEOUT = int(sys.argv[3]) if len(sys.argv) > 3 else 120
|
|
A = 'packages/webui/src/public/app.js'
|
|
B = 'packages/webui/src/public/bus.js'
|
|
BOTH = ['tests/board-held.test.mjs', 'tests/browser-edge.test.mjs']
|
|
EDGE = ['tests/browser-edge.test.mjs']
|
|
|
|
# (name, file, old, new, tests, per-run timeout)
|
|
M = [
|
|
('13 schedule polls with the toggle off', A, 'if (!paused && machine && !out()) timer', 'if (!paused && !out()) timer', BOTH, TIMEOUT),
|
|
('27 bus schedule polls signed out', B, 'if (!paused() && !out()) timer', 'if (!paused()) timer', BOTH, TIMEOUT),
|
|
('12 refresh reads with the toggle off', A, 'if (busy || !machine || out()) return;', 'if (busy || out()) return;', BOTH, TIMEOUT),
|
|
('14 refresh without its signed-out guard', A, 'if (busy || !machine || out()) return;', 'if (busy || !machine) return;', BOTH, TIMEOUT),
|
|
('26 bus render without its signed-out guard', B, " clearTimeout(timer);\n if (out()) return;\n", " clearTimeout(timer);\n", BOTH, TIMEOUT),
|
|
('19 toggle on reads nothing', A, "fresh(); refresh(); } else hideMachine();", "fresh(); } else hideMachine();", EDGE, 30),
|
|
]
|
|
|
|
env = dict(os.environ, TMPDIR=os.environ.get('TMPDIR', '/mnt/storage/scratch/tmp'))
|
|
sha = lambda p: hashlib.sha256(open(os.path.join(WT, p), 'rb').read()).hexdigest()
|
|
start = {p: sha(p) for p in (A, B)}
|
|
|
|
def run(tests, limit):
|
|
t0 = time.time()
|
|
p = subprocess.Popen(['node', '--test', *tests], cwd=os.path.join(WT, 'packages/webui'), env=env,
|
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, start_new_session=True, text=True)
|
|
try:
|
|
out, _ = p.communicate(timeout=limit)
|
|
code = p.returncode
|
|
except subprocess.TimeoutExpired:
|
|
code, out = 'timeout', ''
|
|
finally:
|
|
try: os.killpg(p.pid, signal.SIGKILL)
|
|
except ProcessLookupError: pass
|
|
try: p.communicate(timeout=5)
|
|
except Exception: pass
|
|
return code, round(time.time() - t0, 1)
|
|
|
|
with open(OUT, 'w') as f:
|
|
def say(s): print(s); f.write(s + '\n'); f.flush()
|
|
say(f'worktree {WT}')
|
|
say(f'start {time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())}')
|
|
killed = 0
|
|
for name, path, old, new, tests, limit in M:
|
|
full = os.path.join(WT, path)
|
|
orig = open(full, 'rb').read()
|
|
src = orig.decode()
|
|
if src.count(old) != 1:
|
|
say(f'{name}: NO MATCH ({src.count(old)}), skipped'); continue
|
|
try:
|
|
open(full, 'w').write(src.replace(old, new))
|
|
code, secs = run(tests, limit)
|
|
finally:
|
|
open(full, 'wb').write(orig)
|
|
# A timeout is a stall, not a kill: the file never exited by itself.
|
|
dead = code not in (0, 'timeout')
|
|
killed += dead
|
|
verdict = 'killed' if dead else 'STALLED' if code == 'timeout' else 'ALIVE'
|
|
say(f'{name}: {verdict} (exit {code}, {secs}s, {" ".join(t.split("/")[-1] for t in tests)})')
|
|
ok = all(sha(p) == start[p] for p in (A, B))
|
|
say(f'killed {killed} of {len(M)}; sources restored byte for byte: {ok}')
|
|
sys.exit(0 if ok else 1)
|