carlos / platform Public

zdd-5 final review: mutate.sh refuses unknown mutants and cleans its scratch (T16 triage)

A selected name that is not in the mutant list is reported as ERROR before
any work, and the script exits 1. The scratch directory the script makes
with mktemp is removed whole on exit when the baseline PASSED and every
selected mutant was CAUGHT; on any other outcome only its tree copy goes,
and its path is printed again so the logs can be read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev f22e27524dd07aabb7a8c718867f96e8bdbf0a0a parent c602f63
1 file changed, +46 −15
  • internal/rollout/testdata/rolloutsim/mutate.sh +46 −15
diff --git a/internal/rollout/testdata/rolloutsim/mutate.sh b/internal/rollout/testdata/rolloutsim/mutate.sh
index 647639655..ac7b0a3cb 100755
--- a/internal/rollout/testdata/rolloutsim/mutate.sh
+++ b/internal/rollout/testdata/rolloutsim/mutate.sh
@@ -31,10 +31,16 @@
# The baseline is PASSED or, like a mutant, BROKEN or FAILED. Exit 1 on
# anything but a PASSED baseline and CAUGHT for every mutant.
#
+# A mutant named on the command line that is not in the list below is an
+# ERROR, reported before anything runs, and the script exits 1: a typo must
+# never pass as a selection that checked nothing.
+#
# Scratch: one fresh directory per run, made with mktemp -d under $TMPDIR
-# (named zdd5-mut.XXXXXX) and printed first. Its tree copy is removed on
-# exit; its logs stay. Remove that directory after. The script never touches
-# any other path.
+# (named zdd5-mut.XXXXXX) and printed first. When the run ends with a PASSED
+# baseline and every selected mutant CAUGHT, the whole directory is removed
+# on exit. On any other outcome (a failure, an interrupted run) only its tree
+# copy is removed, and its path is printed again: the logs stay there to be
+# read, and removed by hand after. The script never touches any other path.
set -u
if [ "$(nice)" -lt 19 ] && [ -z "${ROLLOUTSIM_RENICED:-}" ]; then
ROLLOUTSIM_RENICED=1 exec nice -n 19 "$0" "$@"
@@ -45,10 +51,46 @@ fi
SEEDS=${1:-200}
shift $(($# > 0 ? 1 : 0))
ONLY=" $* "
+
+# name|file|invariant|perl substitution applied to the marked line only. The
+# marker match is anchored (\b), so rolloutsim:M1 never matches
+# rolloutsim:M10. The substitution is the last field, so it may contain "|".
+MUTANTS='M1|queue.go|I2|s/if q\.running >= q\.concurrency \{/if false \&\& q.running >= q.concurrency {/
+M2|queue.go|I3|s/class := classRecover/class := classAdopt/
+M3|queue.go|I1|s/added, err = book\.Record\(rec\)/added, err = true, nil/
+M4|queue.go|I4|s/h\.buildRev = q\.nextRev\(\)/_ = q.nextRev()/
+M5|wakeplan.go|I1|s/if hasGood \&\& in\.Book\.Blocks\(/if false \&\& in.Book.Blocks(/
+M6|wakeplan.go|I1|s/plan\.Fallback = \&good/plan.Fallback = nil/'
+
+# Every name selected must be a mutant in the list, checked before any work.
+KNOWN=" $(printf '%s\n' "$MUTANTS" | cut -d'|' -f1 | tr '\n' ' ')"
+unknown=0
+for name in "$@"; do
+ if [[ "$KNOWN" != *" $name "* ]]; then
+ echo "ERROR $name: no such mutant (known:${KNOWN% })"
+ unknown=1
+ fi
+done
+[ $unknown -eq 0 ] || exit 1
+
export GOMAXPROCS=2
ROOT=$(git -C "$(dirname "$0")" rev-parse --show-toplevel) || exit 2
SCRATCH=$(mktemp -d "${TMPDIR:?}/zdd5-mut.XXXXXX") || exit 2
echo "scratch: $SCRATCH"
+# fail is the run's verdict so far; complete is set only once every
+# selected mutant has a verdict. The exit trap removes the scratch
+# directory whole only when both say the run passed.
+fail=0
+complete=0
+cleanup() {
+ if [ $complete -eq 1 ] && [ $fail -eq 0 ]; then
+ rm -rf "$SCRATCH"
+ else
+ rm -rf "$SCRATCH/tree" "$SCRATCH/orig" "$SCRATCH/gowork"
+ echo "scratch kept: $SCRATCH (logs in $SCRATCH/logs; remove it after reading)"
+ fi
+}
+trap cleanup EXIT
P=internal/rollout
# The default build cache is read-only inside a sandbox.
if [ -z "${GOCACHE:-}" ] && [ ! -w "$(go env GOCACHE)" ]; then
@@ -57,22 +99,11 @@ fi
# An archive has no .git, so VCS stamping must be off.
export GOFLAGS="${GOFLAGS:-} -buildvcs=false"
mkdir -p "$SCRATCH/tree" "$SCRATCH/orig" "$SCRATCH/logs" "$SCRATCH/gowork" || exit 2
-trap 'rm -rf "$SCRATCH/tree" "$SCRATCH/orig" "$SCRATCH/gowork"' EXIT
export GOTMPDIR=$SCRATCH/gowork
git -C "$ROOT" archive HEAD | tar -x -C "$SCRATCH/tree" || exit 2
echo "tree: $(git -C "$ROOT" rev-parse --short HEAD)"
cp -a "$SCRATCH/tree/$P/." "$SCRATCH/orig/" || exit 2
-# name|file|invariant|perl substitution applied to the marked line only. The
-# marker match is anchored (\b), so rolloutsim:M1 never matches
-# rolloutsim:M10. The substitution is the last field, so it may contain "|".
-MUTANTS='M1|queue.go|I2|s/if q\.running >= q\.concurrency \{/if false \&\& q.running >= q.concurrency {/
-M2|queue.go|I3|s/class := classRecover/class := classAdopt/
-M3|queue.go|I1|s/added, err = book\.Record\(rec\)/added, err = true, nil/
-M4|queue.go|I4|s/h\.buildRev = q\.nextRev\(\)/_ = q.nextRev()/
-M5|wakeplan.go|I1|s/if hasGood \&\& in\.Book\.Blocks\(/if false \&\& in.Book.Blocks(/
-M6|wakeplan.go|I1|s/plan\.Fallback = \&good/plan.Fallback = nil/'
-
# run LOG: one go test call against the scratch tree, verbose so that the
# test prints its --- PASS or --- FAIL line.
run() {
@@ -100,7 +131,6 @@ found() {
grep -oE 'rolloutsim: I[1-4] violated at seed' "$1" | awk '{print $2}' | sort -u | tr '\n' ' ' | sed 's/ $//'
}
-fail=0
blog="$SCRATCH/logs/baseline.log"
run "$blog"
broke=$(integrity "$blog")
@@ -158,4 +188,5 @@ while IFS='|' read -r name file inv sub; do
fail=1
fi
done <<<"$MUTANTS"
+complete=1
exit $fail