79 lines
5.4 KiB
Bash
79 lines
5.4 KiB
Bash
#!/usr/bin/env bash
|
||
# Row 103 (#1597) mutants. Run from the candidate worktree's root:
|
||
# LOG=<dir> bash mutants.sh <queue-103 dir> > mutants.txt
|
||
# Each mutant edits one site with mut.mjs (exactly one match, else exit 3),
|
||
# runs the named test files (DRY=1 only checks the match) in their own
|
||
# process group (run.py, killed after 240 s) and restores the file. "killed"
|
||
# means the run failed; "survived" means it passed.
|
||
set -u
|
||
Q=$1
|
||
run() {
|
||
local name=$1 file=$2 from=$3 to=$4; shift 4
|
||
cp -- "$file" "$file.orig"
|
||
if ! node "$Q/mut.mjs" "$file" "$from" "$to"; then
|
||
echo "$name: NO MATCH"; mv -- "$file.orig" "$file"; return
|
||
fi
|
||
if [ -n "${DRY:-}" ]; then echo "$name: match"; mv -- "$file.orig" "$file"; return; fi
|
||
local log="${LOG:-.}/mut-$name.log"
|
||
if python3 "$Q/run.py" 240 "$log" node --test "$@"; then
|
||
echo "$name: survived"
|
||
else
|
||
echo "$name: killed ($(grep -m1 -o 'ℹ fail [0-9]*' "$log"; grep -m1 -o 'TIMEOUT.*' "$log"), $(grep -m1 '^✖' "$log" | cut -c1-90))"
|
||
fi
|
||
mv -- "$file.orig" "$file"
|
||
}
|
||
CO=packages/seat/src/checkout.mjs
|
||
GTL=packages/harness/src/git-tools.mjs
|
||
GS=packages/cli/src/git-socket.mjs
|
||
HM=packages/cli/src/host.mjs
|
||
CT=packages/seat/tests/checkout.test.mjs
|
||
HT=packages/harness/tests/git-tools.test.mjs
|
||
GT=packages/cli/tests/git-socket.test.mjs
|
||
HOT=packages/cli/tests/host.test.mjs
|
||
TERM_THEN_KILL=$' group("SIGTERM");\n kill = setTimeout(() => group("SIGKILL"), limits.graceMs);'
|
||
ESCALATE=$' kill = setTimeout(() => group("SIGKILL"), limits.graceMs);\n'
|
||
# Host git (checkout.mjs runGit, which working.mjs uses too): SIGTERM, the
|
||
# grace period, SIGKILL, all to git's process group, and the sweep.
|
||
run co-sigkill-first $CO "$TERM_THEN_KILL" ' group("SIGKILL");' $CT
|
||
run co-no-escalation $CO "$ESCALATE" '' $CT
|
||
run co-not-detached $CO 'spawn("git", argv, { env, detached: true,' 'spawn("git", argv, { env, detached: false,' $CT
|
||
run co-pid-not-group $CO 'process.kill(-child.pid, signal);' 'process.kill(child.pid, signal);' $CT
|
||
run co-no-sweep $CO ' if (signalled) group("SIGKILL");' '' $CT
|
||
# Runner git (git-tools.mjs): the same sequence.
|
||
run gt-sigkill-first $GTL "$TERM_THEN_KILL" ' group("SIGKILL");' $HT
|
||
run gt-no-escalation $GTL "$ESCALATE" '' $HT
|
||
run gt-not-detached $GTL ' detached: true,' ' detached: false,' $HT
|
||
run gt-pid-not-group $GTL 'process.kill(-child.pid, signal);' 'process.kill(child.pid, signal);' $HT
|
||
run gt-no-sweep $GTL ' if (signalled) group("SIGKILL");' '' $HT
|
||
# have: working refs first, newest first within a clone, at most HAVE_MAX.
|
||
run have-origin-first $GTL 'for (const remote of ["refs/remotes/working", "refs/remotes/origin"])' 'for (const remote of ["refs/remotes/origin", "refs/remotes/working"])' $HT
|
||
run have-oldest-first $GTL '"--sort=-committerdate"' '"--sort=committerdate"' $HT
|
||
run have-uncapped $GTL 'if (OID.test(oid) && have.size < HAVE_MAX) have.add(oid);' 'if (OID.test(oid)) have.add(oid);' $HT
|
||
# close(): the cap on the wait for told sockets, then destroy.
|
||
run letgo-no-cap $GS 'const capped = new Promise((r) => (timer = setTimeout(r, ms)));' 'const capped = new Promise(() => {});' $GT
|
||
run letgo-no-destroy $GS ' for (const s of sockets) s.destroy();' '' $GT
|
||
# Expected to survive: a told socket on a real unix socket flushes its few
|
||
# bytes whether or not the peer reads (build.md).
|
||
run close-skips-letgo $GS ' await letGo([...told]);' '' $GT
|
||
# send()'s post-loop arm, and a failed write anywhere.
|
||
run postloop-always-served $GS $' if (socket.destroyed) {\n if (served()) return;\n throw aborted();\n }' ' if (socket.destroyed) return;' $GT
|
||
run postloop-always-aborted $GS $' if (socket.destroyed) {\n if (served()) return;\n throw aborted();\n }' ' if (socket.destroyed) throw aborted();' $GT
|
||
run errored-ignored $GS 'const served = () => !socket.errored;' 'const served = () => true;' $GT
|
||
# The idle limit on bundle bytes.
|
||
run no-idle $GS ' socket.setTimeout(idleMs);' ' socket.setTimeout(0);' $GT
|
||
# host.lock: an atomic claim, the live check, the identity check on the
|
||
# moved-aside file, the release on each boot failure and on close, the lock
|
||
# kept apart from host.json, and the check for a host that holds only host.json.
|
||
run claim-rename $HM ' linkSync(tmp, file);' ' renameSync(tmp, file);' $HOT
|
||
run claim-no-live-check $HM ' if (prior.live) throw' ' if (false) throw' $HOT
|
||
run claim-dead-blocks $HM ' if (prior.live) throw' ' if (prior) throw' $HOT
|
||
run claim-no-identity-check $HM ' if (!same(readState(aside), prior)) {' ' if (false) {' $HOT
|
||
run claim-no-release $HM $' if (same(held, state)) rmSync(hostLock(dataRoot), { force: true });' '' $HOT
|
||
run release-boot-timeout $HM $' await ended(broker, 5000);\n release();\n throw e;' $' await ended(broker, 5000);\n throw e;' $HOT
|
||
run release-broker-refused $HM $' release();\n throw new CliError(`broker refused' $' throw new CliError(`broker refused' $HOT
|
||
run release-notifier-dies $HM $' release();\n throw e;' ' throw e;' $HOT
|
||
run release-notifier-refused $HM $' release();\n throw new CliError(`notifier refused' $' throw new CliError(`notifier refused' $HOT
|
||
run release-on-close $HM $' release();\n finish(result);' ' finish(result);' $HOT
|
||
run lock-is-host-json $HM 'join(hostDir(dataRoot), "host.lock")' 'join(hostDir(dataRoot), "host.json")' $HOT
|
||
run no-host-json-check $HM ' if (prior?.live) throw new CliError(' ' if (false) throw new CliError(' $HOT
|