Files

148 lines
11 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# Row 162 mutants M1-M46 (agents/darkwing/work/queue-162/BUILD.md). Each mutant is exact-string
# edits to one file in the r162 worktree; the named tests must fail. The file is restored from a
# pristine copy after every mutant and checked by sha256. Run with python3 -I.
import hashlib, os, re, shutil, signal, subprocess, sys, time
HOME = os.environ["HOME"]
R = f"{HOME}/darkwing-scratch/r162"
WT = f"{R}/wt"
OUT = f"{R}/out/mutants"
TEST = "packages/cli/tests/stack-release.test.mjs"
SR = "scripts/stack-release.sh"
SC = "scripts/stack-copy.sh"
def sha(p):
return hashlib.sha256(open(p, "rb").read()).hexdigest()
M = []
def mut(mid, pattern, edits, f=SR, expect=None):
M.append((mid, pattern, edits, f, expect))
mut("M1", "^T1 ", [(' [ ! -f "$POINTER" ] || { read_pointer; FROM="$P_COPY"; refuse already-adopted "$POINTER exists (copy $P_COPY)"; }\n', "")])
mut("M2", "^T[12] ", [(' if [ "${argv[i]}" = --checkout ]; then i=$((i + 1)); continue; fi\n', "")])
mut("M3", "^T2 ", [(' --claude "$claude" >> "$STEPS"', ' >> "$STEPS"')])
mut("M4", "^T3 ", [(' [ "${P_CONSOLE[*]}" = "${U_CONSOLE[*]}" ] || d+=$\'\\n\'" console:', ' true || d+=$\'\\n\'" console:')])
mut("M5", "^T4 ", [(" instance_busy\n drop_in_override\n", " drop_in_override\n")])
mut("M6", "^T4 ", [(" instance_busy\n drop_in_override\n", " instance_busy\n")])
mut("M7", "^T4 ", [('copy_ok "$TO" 2>/dev/null || refuse copy-unreachable', 'true || refuse copy-unreachable')])
mut("M8", "^T4 ", [('|| log_line result refused code "$code"', "|| true")])
mut("M9", "^T5 ", [(' systemctl --user stop "$CONSOLE_UNIT" || return 1\n systemctl --user stop "$BUS_UNIT" || return 1\n',
' systemctl --user stop "$BUS_UNIT" || return 1\n systemctl --user stop "$CONSOLE_UNIT" || return 1\n')])
mut("M10", "^T5 ", [('systemctl --user start "$BUS_UNIT" && systemctl --user start "$CONSOLE_UNIT"',
'systemctl --user start "$CONSOLE_UNIT" && systemctl --user start "$BUS_UNIT"')])
mut("M11", "^T6 ", [(' [ "$MIGRATE" = 1 ] || rollback_in_run migration-needs-flag\n', "")])
mut("M12", "^T7 ", [(' backup_store || rollback_in_run backup\n node "$COPIES/$TO/packages/bus/src/migrate-schema.mjs" "$DATA" >> "$STEPS" 2>&1 || rollback_in_run migrate-failed\n',
' node "$COPIES/$TO/packages/bus/src/migrate-schema.mjs" "$DATA" >> "$STEPS" 2>&1 || rollback_in_run migrate-failed\n backup_store || rollback_in_run backup\n')])
mut("M13", "^T8 ", [(' elif [ -n "$TAKEN" ] && ! restore_store "$TAKEN"; then rcode=restore\n', "")])
mut("M14", "^T9 ", [(' if [ "$STORE_SCHEMA" != "$T_SCHEMA" ]; then\n', ' if [ "$STORE_SCHEMA" != "$T_SCHEMA" ] && in_chain "$STORE_SCHEMA"; then\n')])
mut("M15", "^T10 ", [(' systemctl --user is-active --quiet "$BUS_UNIT" && systemctl --user is-active --quiet "$CONSOLE_UNIT" || return 1\n GATE=bus-status\n',
' true || return 1\n GATE=bus-status\n')], expect="units-inactive")
mut("M16", "^T10 ", [(" GATE=bus-status\n", " GATE=bus-status\n if false; then\n"),
(' [ -z "$GATE_WHY" ] || return 1\n GATE=healthz\n', ' fi\n GATE=healthz\n')], expect="bus-status")
mut("M17", "^T10 ", [('-- "$(console_port)" || return 1', '-- "$(console_port)" || true')], expect="healthz")
mut("M18", "^T10 ", [('--business "$BUSINESS" > /dev/null 2>&1 || return 1', '--business "$BUSINESS" > /dev/null 2>&1 || true')], expect="copy-check")
mut("M19", "^T10 ", [('pm_launched "$copy" || return 1', 'pm_launched "$copy" || true')], expect="pm-launch-unconfirmed")
mut("M20", "^T10 ", [(' gate "$TO" "$P_PM" || after_fail "$GATE"\n trap - INT TERM HUP\n write_pointer "$TO" "$P_PM" "$P_CLAUDE" "$T_SCHEMA" "${P_CONSOLE[@]}"\n',
' write_pointer "$TO" "$P_PM" "$P_CLAUDE" "$T_SCHEMA" "${P_CONSOLE[@]}"\n gate "$TO" "$P_PM" || after_fail "$GATE"\n trap - INT TERM HUP\n')])
mut("M21", "^T10 ", [(' tmp="$(mktemp "$BIN/.mosaic.XXXXXX")" && cp -p "$RS/wrapper" "$tmp" && chmod "$(cat "$RS/WRAPPER_MODE")" "$tmp" && mv -f "$tmp" "$BIN/mosaic" || return 1\n', ""),
(' [ "$(sha256sum < "$BIN/mosaic" | cut -d\' \' -f1)" = "$(sum_of "$RS" wrapper)" ] || return 1\n [ "$(stat -c %a "$BIN/mosaic")" = "$(cat "$RS/WRAPPER_MODE")" ] || return 1\n', "")])
mut("M22", "^T11 ", [(' elif ! gate "$P_COPY" "$P_PM"; then rcode="$GATE"\n',
' elif ! gate "$P_COPY" "$P_PM" && ! { start_both && gate "$P_COPY" "$P_PM"; }; then rcode="$GATE"\n')])
mut("M23", "^T12 ", [('if(mode==="previous"&&o.to!==current)', 'if(mode==="previous")')])
mut("M24", "^T13 ", [('[ "$P_SCHEMA" = "$T_SCHEMA" ] || [ -n "$RESTORE" ] || refuse schema-differs', 'true || refuse schema-differs')])
mut("M25", "^T14 ", [('[ "$COUNT" = 0 ] || [ "$EVENTS" = "$COUNT" ] || refuse post-backup-events', '[ "$COUNT" = 0 ] || [ -n "$EVENTS" ] || refuse post-backup-events')])
mut("M26", "^T15 ", [('if [ "$PLAN" = 1 ]; then check_target; plan; exit 0; fi', 'if [ "$PLAN" = 1 ]; then log_line result planned; check_target; plan; exit 0; fi', 2)])
mut("M27", "^T(8|13) ", [(' mv "$DATA/bus" "$POST" || return 1\n', ' mv "$DATA/bus" "$POST" && rm -rf "$POST" || return 1\n')])
mut("M28", "^T4 ", [('git -C "$REPO" merge-base --is-ancestor "$1" "$ref" || die', 'true || die')], f=SC)
mut("M29", "^T4 ", [('case "$state" in activating|deactivating|reloading) refuse instance-busy "$unit is $state" ;; esac', ":")])
mut("M30", "^T10b ", [(' [ "${#lines[@]}" -gt 0 ] || { GATE_WHY="no launched line since the start"; return 1; }\n',
' [ "${#lines[@]}" -gt 0 ] || lines=("launched x: run $("$1/scripts/mosaic" launches list --json 2>/dev/null | node -e \'const s=JSON.parse(require("fs").readFileSync(0,"utf8")).find((x)=>x.live);process.stdout.write(s?s.run:"none")\'), y")\n')],
expect="a human launch with no journal line")
mut("M31", "^T14b ", [(' [ -z "$RESTORED" ] || rollback_failed "$1" not-attempted\n', "")])
mut("M32", "^T19 ", [('[ -z "$open" ] || refuse interrupted-run', 'true || refuse interrupted-run')])
mut("M33", "^T20 ", [("flock -n 9 || refuse run-locked", "true || refuse run-locked")])
mut("M34", "^T21 ", [('[ -n "$U_CLAUDE" ] || refuse no-claude-pin', 'true || refuse no-claude-pin', 2)])
mut("M35", "^T10b ", [('--since "@$START_EPOCH"', '--since "$(date -d "@$START_EPOCH" \'+%Y-%m-%d %H:%M:%S\')"')], expect="one well-formed line and a live run pass")
mut("M36", "^T10b ", [(' [ "${#lines[@]}" = 1 ] || { GATE_WHY="${#lines[@]} launched lines since the start"; return 1; }\n', "")], expect="two lines")
mut("M37", "^T10b ", [(':!(Date.parse(s.startedAt)>=Number(since))?`run ${run} started before this start`', "")], expect="run started before the start")
mut("M38", "^T22 ", [('if [ -e "$f" ] || [ -L "$f" ]; then UNCLEAN="$f"; return 0; fi', ":")])
mut("M39", "^T22 ", [('new DatabaseSync(pathToFileURL(file).href+"?immutable=1",{readOnly:true})', "new DatabaseSync(file,{readOnly:true})")], expect="clean")
mut("M40", "^T14c ", [(' [ -z "$RESTORED" ] || rollback_failed "$1" not-attempted\n', ' [ -z "$RESTORED" ] && [ "$OP" != rollback ] || rollback_failed "$1" not-attempted\n')])
mut("M41", "^T23 ", [(' echo "needs: Jason present for this run (decision 96 condition 2)"\n', "")])
mut("M42", "^T(23|20) ", [(' [ "$PLAN" = 1 ] || lock\n', " lock\n", 2)])
mut("M43", "^T23 ", [(' S_SCHEMA="$(store_read version 2>/dev/null)" || S_SCHEMA=""\n', ' S_SCHEMA="$(node -e "$STORE_JS" -- heldversion "$STORE" 2>/dev/null)" || S_SCHEMA=""\n')])
mut("M44", "^T24 ", [(' capture || refuse capture-failed "could not write the rollback set $RS; nothing was stopped"\n',
' capture || { echo "stack-release: refusing capture-failed: could not write the rollback set $RS; nothing was stopped" >&2; exit 3; }\n')])
mut("M45a", "^T25 ", [(' else refuse copies-unrelated "neither $P_COPY nor $TO is an ancestor of the other, so the plan cannot list the commits"\n',
' else RANGE="$P_COPY..$TO" VERB=adds\n')])
mut("M45b", "^T26 ", [(" plan_commits\n", "")])
mut("M46", "^T(26|23) ", [(' echo "migration: $S_SCHEMA to $T_SCHEMA, after a backup of the stopped store"\n', "")])
mut("M47", "^T(26|23|27) ", [(' echo "migration steps: $(plan_steps)"\n', "")])
mut("M48", "^T27 ", [(' (( ${#l[@]} % 2 == 0 )) || { echo "cannot be read from $f"; return; }\n', "")])
mut("M49", "^T27 ", [(' if [ "$v" = "$T_SCHEMA" ] && [ -n "$out" ]; then', ' if [ -n "$out" ]; then')])
def main():
only = set(sys.argv[1:])
os.makedirs(OUT, exist_ok=True)
pristine = {}
for f in (SR, SC):
p = f"{R}/edit/pristine-{os.path.basename(f)}"
shutil.copy2(f"{WT}/{f}", p)
if sha(p) != sha(f"{WT}/{f}"):
sys.exit(f"ABORT: backup of {f} failed")
pristine[f] = (p, sha(p))
env = dict(os.environ, TMPDIR=f"{R}/tmp")
results = []
try:
for mid, pattern, edits, f, expect in M:
if only and mid not in only:
continue
src = open(pristine[f][0]).read()
text = src
for e in edits:
old, new = e[0], e[1]
n = e[2] if len(e) > 2 else 1
if text.count(old) != n:
sys.exit(f"ABORT {mid}: expected {n} match(es) of {old!r} in {f}, found {text.count(old)}")
text = text.replace(old, new)
if text == src:
sys.exit(f"ABORT {mid}: no change")
if os.environ.get("DRY"):
print(f"{mid} ok", flush=True); continue
open(f"{WT}/{f}", "w").write(text)
log = f"{OUT}/{mid}.log"
t0 = time.time()
with open(log, "w") as fh:
p = subprocess.Popen(["node", "--test", "--test-reporter=spec", f"--test-name-pattern={pattern}", TEST],
cwd=WT, env=env, stdout=fh, stderr=subprocess.STDOUT, start_new_session=True)
try:
rc = p.wait(timeout=600)
except subprocess.TimeoutExpired:
os.killpg(p.pid, signal.SIGKILL); p.wait(); rc = "timeout"
shutil.copy2(pristine[f][0], f"{WT}/{f}")
if sha(f"{WT}/{f}") != pristine[f][1]:
sys.exit(f"ABORT {mid}: restore of {f} failed")
body = open(log).read()
ran = re.search(r"^ℹ tests (\d+)", body, re.M)
fails = re.search(r"^ℹ fail (\d+)", body, re.M)
nfail = int(fails.group(1)) if fails else -1
failed_names = re.findall(r"^\s*✖ (.+?) \(\d", body, re.M)
killed = rc != 0 and nfail > 0
if expect and killed:
killed = any(n == expect for n in failed_names)
verdict = "KILLED" if killed else "SURVIVED"
line = f"{mid} {verdict} rc={rc} tests={ran.group(1) if ran else '?'} fail={nfail} {time.time()-t0:.0f}s failed={sorted(set(failed_names))[:6]}"
print(line, flush=True)
results.append((mid, verdict))
finally:
for f, (p, s) in pristine.items():
shutil.copy2(p, f"{WT}/{f}")
if sha(f"{WT}/{f}") != s:
print(f"RESTORE FAILED {f}", flush=True)
surv = [m for m, v in results if v != "KILLED"]
print(f"DONE {len(results)} mutants, {len(results)-len(surv)} killed, survived: {surv}", flush=True)
sys.exit(1 if surv else 0)
main()