39 lines
2.7 KiB
Python
39 lines
2.7 KiB
Python
# Row 94 round 1 review mutants (Dewey), sites Rocko's 27 don't cover. Each swaps one exact
|
|
# string in a scratch worktree of the candidate, runs the touched package's whole node suite
|
|
# in its own process group (killed after 400 s, which counts as a kill), and restores the file.
|
|
import subprocess, sys, os, signal
|
|
M = os.path.expanduser('~/dewey-scratch/r94mut')
|
|
env = dict(os.environ, TMPDIR=os.path.expanduser('~/dewey-scratch/tmp'))
|
|
B = 'packages/bus/src/broker.mjs'; C = 'packages/cli/src/cli.mjs'; W = 'packages/webui/src/public/bus.js'
|
|
muts = [
|
|
('bus: read means delivered', B, "d.op='read') AS read FROM", "d.op IN ('delivered','read')) AS read FROM", 'bus'),
|
|
('bus: unread excludes delivered', B, "d.op='read')),0) AS unread", "d.op IN ('delivered','read'))),0) AS unread", 'bus'),
|
|
('cli: text inbox lists read messages', C, 'messages.push(...page.messages.filter(m => !m.read));', 'messages.push(...page.messages);', 'cli'),
|
|
('cli: plain talk takes Ctrl-C', C, 'if (follow) process.on("SIGINT", stop);', 'process.on("SIGINT", stop);', 'cli'),
|
|
('webui: count read only on Messages', W, "if (path !== '/messages') {", 'if (false) {', 'webui'),
|
|
('webui: refusal keeps message count', W, 'inboxCount = null; messageCount = null; }', 'inboxCount = null; }', 'webui'),
|
|
('webui: board ignores message count', W, "if (messages.status === 'fulfilled' && !refused) messageCount = messages.value.unread;", '', 'webui'),
|
|
('webui: pre-read swallows 403', W, "if (err.status === 403 || err.code === 'signed-out') throw err;", "if (err.code === 'signed-out') throw err;", 'webui'),
|
|
]
|
|
only = sys.argv[1:]
|
|
for name, f, old, new, pkg in muts:
|
|
if only and not any(o in name for o in only): continue
|
|
p = os.path.join(M, f); src = open(p).read()
|
|
n = src.count(old)
|
|
if n != 1: print(f'BAD-ANCHOR ({n}) {name}', flush=True); continue
|
|
open(p, 'w').write(src.replace(old, new))
|
|
d = os.path.join(M, 'packages', pkg)
|
|
tests = sorted('tests/' + t for t in os.listdir(os.path.join(d, 'tests')) if t.endswith('.test.mjs'))
|
|
proc = subprocess.Popen(['node', '--test', *tests], cwd=d, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, start_new_session=True)
|
|
try:
|
|
out, _ = proc.communicate(timeout=400)
|
|
except subprocess.TimeoutExpired:
|
|
os.killpg(proc.pid, signal.SIGKILL); proc.communicate()
|
|
out = None
|
|
finally:
|
|
open(p, 'w').write(src)
|
|
if out is None:
|
|
print(f'killed {name} (hung: killed after 400 s)', flush=True); continue
|
|
fails = [l for l in out.splitlines() if l.startswith('not ok')]
|
|
print(('killed ' if proc.returncode else 'SURVIVED') + f' {name}' + (f' ({len(fails)} failing: ' + ' | '.join(l[7:100] for l in fails) + ')' if fails else ''), flush=True)
|