#!/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()