73 lines
4.0 KiB
Bash
Executable File
73 lines
4.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# Row 109 (#1603), round 3 adds m7-m12: the two re-anchored tests (seat session, cli launcher) and the two N2 tests.
|
||
# Each new check must fail on a mutant. For each mutant this runs the named test
|
||
# file against the real source (control) and against the source with one exact-once text edit.
|
||
# Runs in the given worktree, one test file at a time, and restores every source file on exit.
|
||
# Usage: mutants.sh <worktree> <outdir>
|
||
set -u
|
||
WT=$(cd "$1" && pwd); OUT=$(mkdir -p "$2" && cd "$2" && pwd)
|
||
cd "$WT" || exit 2
|
||
BAK=$(mktemp -d "$OUT/.bak.XXXXXX")
|
||
declare -A SAVED
|
||
save() { [ -n "${SAVED[$1]:-}" ] || { mkdir -p "$BAK/$(dirname "$1")"; cp "$1" "$BAK/$1"; SAVED[$1]=1; }; }
|
||
restore() { for f in "${!SAVED[@]}"; do cp "$BAK/$f" "$f"; done; rm -rf "$BAK"; }
|
||
trap restore EXIT
|
||
edit() { # file old new: exact-once replace
|
||
save "$1"
|
||
node -e '
|
||
const fs = require("fs"); const [f, a, b] = process.argv.slice(1);
|
||
const s = fs.readFileSync(f, "utf8"); const n = s.split(a).length - 1;
|
||
if (n !== 1) { console.error("expected one match, found " + n); process.exit(3); }
|
||
fs.writeFileSync(f, s.replace(a, () => b));
|
||
' "$1" "$2" "$3"
|
||
}
|
||
run() { # name testfile
|
||
setsid -w timeout -k 10 400 node --test "$2" > "$OUT/$1.out" 2>&1; echo "$?" > "$OUT/$1.rc"
|
||
echo "$1: exit $(cat "$OUT/$1.rc"); $(grep -E '^ℹ (tests|pass|fail)' "$OUT/$1.out" | tr '\n' ' ')"
|
||
grep -E '^✖' "$OUT/$1.out" | sort -u | sed 's/ ([0-9.]*ms)$//' | sed "s/^/ failed: /"
|
||
}
|
||
mutant() { # name testfile file old new
|
||
edit "$3" "$4" "$5" || { echo "$1: edit failed"; return; }
|
||
git diff -- "$3" > "$OUT/$1.patch"
|
||
run "$1" "$2"
|
||
cp "$BAK/$3" "$3"
|
||
}
|
||
R=packages/harness/src/runner.mjs; RT=packages/harness/tests/runner.test.mjs
|
||
B=packages/bus/src/broker.mjs; BT=packages/bus/tests/state.test.mjs
|
||
run control-runner "$RT"
|
||
run control-bus "$BT"
|
||
# m1: the turn request no longer carries the state block
|
||
mutant m1-drop-block "$RT" "$R" 'request: `${block}\n${turnRequest(m)}`' 'request: turnRequest(m)'
|
||
# m2: the header no longer carries the message time
|
||
mutant m2-drop-time "$RT" "$R" ' if (m.at) head.push(`sent ${m.at}`);
|
||
' ''
|
||
# m3: a failed state read no longer fails the turn; the turn runs with no block
|
||
mutant m3-drop-failure-check "$RT" "$R" 'unread = `could not read the current state: ${e.code ?? e.message}`;' 'block = "";'
|
||
# m4: the block's launch line shows the older event instead of the latest
|
||
mutant m4-oldest-launch-event "$BT" "$B" "kind IN ('launch.revoked','launch.restored') ORDER BY seq DESC LIMIT 1" "kind IN ('launch.revoked','launch.restored') ORDER BY seq ASC LIMIT 1"
|
||
# m5: the open list includes closed tasks
|
||
mutant m5-closed-in-open "$BT" "$B" 'const open = this.#tasks(s.business, { done: false });' 'const open = this.#tasks(s.business, { limit: TASK_PAGE });'
|
||
# m6: the cap isn't stated in the output
|
||
mutant m6-cap-unstated "$BT" "$B" 'total: open.total, cap: TASK_PAGE }' 'total: open.total }'
|
||
# Round 3. SE and CL are the seat and cli tests that now anchor the new request shape.
|
||
SE=packages/seat/tests/session.test.mjs; CL=packages/cli/tests/launcher.test.mjs
|
||
run control-seat "$SE"
|
||
run control-cli "$CL"
|
||
# m7, m9: the turn request no longer carries the state block
|
||
mutant m7-seat-drop-block "$SE" "$R" 'request: `${block}\n${turnRequest(m)}`' 'request: turnRequest(m)'
|
||
mutant m9-cli-drop-block "$CL" "$R" 'request: `${block}\n${turnRequest(m)}`' 'request: turnRequest(m)'
|
||
# m8, m10: the header no longer carries the message time
|
||
mutant m8-seat-drop-time "$SE" "$R" ' if (m.at) head.push(`sent ${m.at}`);
|
||
' ''
|
||
mutant m10-cli-drop-time "$CL" "$R" ' if (m.at) head.push(`sent ${m.at}`);
|
||
' ''
|
||
# m11: a human session is checked as an agent, so it can't read state
|
||
mutant m11-human-checked-as-agent "$BT" "$B" 'if (!s.human && !s.reader) this.#agent(s);
|
||
const set' 'if (!s.reader) this.#agent(s);
|
||
const set'
|
||
# m12: the state read writes an event
|
||
mutant m12-state-writes-event "$BT" "$B" " case 'state': {
|
||
keys(a, []);" " case 'state': {
|
||
keys(a, []);
|
||
this.#event(s, 'action.allowed', {});"
|