#!/usr/bin/env bash # Row 100 (#1594) mutants. Run from the candidate worktree's root: # LOG= bash mutants.sh > 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" } WK=packages/seat/src/working.mjs GS=packages/cli/src/git-socket.mjs GTL=packages/harness/src/git-tools.mjs WT=packages/seat/tests/working.test.mjs GT=packages/cli/tests/git-socket.test.mjs HT=packages/harness/tests/git-tools.test.mjs # Timeouts: SIGTERM first, then SIGKILL after the grace period. run sigkill-first $WK $' child.kill("SIGTERM");\n kill = setTimeout(() => child.kill("SIGKILL"), limits.graceMs);' ' child.kill("SIGKILL");' $WT run no-escalation $WK $' kill = setTimeout(() => child.kill("SIGKILL"), limits.graceMs);\n' '' $WT # rollback-failed, refused and logged run no-rollback-check $WK $' if (!undone.ok) refuse("rollback-failed",' $' if (false) refuse("rollback-failed",' $WT $GT run rollback-not-logged $GS 'if (code === "git-failed" || code === "rollback-failed") log(' 'if (code === "git-failed") log(' $GT # The five untested refusals run no-root-check $WK ' if (!r.ok) refuse("root-unreadable",' ' if (false) refuse("root-unreadable",' $WT run no-role-check $GS 'if (!who.role || who.business !== b.id) throw' 'if (who.business !== b.id) throw' $GT run no-business-check $GS 'if (!who.role || who.business !== b.id) throw' 'if (!who.role) throw' $GT run no-project-check $GS ' if (!p) throw new Refusal("unknown-project");' '' $GT run no-busy-check $GS 'if (active.size >= maxActive) return' 'if (false) return' $GT run no-closing-answer $GS $' waiting.set(socket, () => done(new Refusal("host-closing")));\n' '' $GT # The body's total deadline run no-body-deadline $GS '), deadlineMs);' '), 2 ** 31 - 1);' $GT # fetch-aborted run abort-as-git-failed $GS 'const aborted = () => new Refusal("fetch-aborted",' 'const aborted = () => new Error(' $GT # A reviewer that hangs up once it has every byte was served. The first cut # filed any close before 'finish' as fetch-aborted; so does this mutant. run close-is-aborted $GS 'const served = () => !socket.errored;' 'const served = () => socket.writableFinished;' $GT # A queued write that fails is fetch-aborted. run errored-ignored $GS 'const served = () => !socket.errored;' 'const served = () => true;' $GT # have: working refs first, 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-uncapped $GTL 'if (OID.test(oid) && have.size < HAVE_MAX) have.add(oid);' 'if (OID.test(oid)) have.add(oid);' $HT