56 lines
3.4 KiB
Bash
56 lines
3.4 KiB
Bash
#!/usr/bin/env bash
|
||
# Row 100 (#1594) mutants. Run from the candidate worktree's root:
|
||
# LOG=<dir> bash mutants.sh <queue-100 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"
|
||
}
|
||
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
|