carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1#!/bin/bash
2# Mutation checks for the swap invariants (zero-downtime plan 3, task 13).
3# Each mutant rewrites one marked line (// swapsim:Mn) in a scratch copy of
4# the tree, never in the checkout. Against that copy it then runs, as
5# separate go test calls, the simulation (TestSwapInvariants) and each of
6# the mutant's designated witnesses, so a witness that panics cannot keep
7# the sweep from running, and another witness's failure is not counted.
8#
9# internal/activator/testdata/swapsim/mutate.sh 300
10#
11# Names after the seed count run only those mutants (a fix loop's re-check):
12# internal/activator/testdata/swapsim/mutate.sh 300 M5 M7
13#
14# The script runs at nice 19 (it re-executes itself under nice if it has
15# to, and warns if it cannot) and one mutant at a time with GOMAXPROCS=2
16# (the CPU budget on shared boxes). No witness binds a socket, so TMPDIR
17# stays whatever it is; Go's own scratch goes under the script's.
18#
19# Each verdict line reports the sweep and every designated witness:
20# sweep: "violated at K seed(s)" (an I1-I3 violation; the sweep stops
21# after three failing seeds, so K is at most 3), "liveness only at K
22# seed(s)", "passed", "TIMEOUT", "PANIC", or "NOT RUN" (no PASS or FAIL
23# line for TestSwapInvariants).
24# witness: FAIL (an assertion failed), PASS, TIMEOUT, PANIC or NOT RUN.
25# Execution integrity is recorded apart from detection. Every run the
26# mutant needs (the sweep and each designated witness) must build, start,
27# and end in its own PASS or FAIL line. A run that panicked, timed out, never
28# ran, or did not build fails the mutant's gate even when another run caught
29# the mutation; that kill is still printed, as a diagnostic.
30# Verdicts:
31# CAUGHT every run executed intact, and a designated witness failed by
32# assertion or the sweep reported an I1-I3 violation
33# BROKEN a run panicked, timed out or never ran (the detail names it);
34# "caught" in the detail is the diagnostic kill, not a pass
35# LIVENESS not caught; the sweep's only failure is a Swap that never
36# returned
37# MISSED everything passed under the mutation
38# ERROR the marker is not on exactly one line, the substitution did
39# not apply, or the mutant did not build or set up
40# Exit 1 on anything but CAUGHT. Scratch: a fresh directory per run, made
41# with mktemp -d under $TMPDIR (named zdd3-mut.XXXXXX) and printed first.
42# Its tree copy is removed on exit; its logs stay. Remove that directory
43# after. The script never touches any other path under $TMPDIR.
44set -u
45if [ "$(nice)" -lt 19 ] && [ -z "${SWAPSIM_RENICED:-}" ]; then
46 SWAPSIM_RENICED=1 exec nice -n 19 "$0" "$@"
47fi
48if [ "$(nice)" -lt 19 ]; then
49 echo "warning: running at niceness $(nice), not 19" >&2
50fi
51SEEDS=${1:-300}
52shift $(($# > 0 ? 1 : 0))
53ONLY=" $* "
54export GOMAXPROCS=2
55ROOT=$(git -C "$(dirname "$0")" rev-parse --show-toplevel) || exit 2
56# This run's own scratch: never a fixed name, so two runs sharing TMPDIR
57# cannot delete each other's trees or logs.
58SCRATCH=$(mktemp -d "${TMPDIR:?}/zdd3-mut.XXXXXX") || exit 2
59echo "scratch: $SCRATCH"
60A=internal/activator
61# The default build cache is read-only inside a sandbox.
62if [ -z "${GOCACHE:-}" ] && [ ! -w "$(go env GOCACHE)" ]; then
63 export GOCACHE=$TMPDIR/gocache
64fi
65mkdir -p "$SCRATCH/tree" "$SCRATCH/logs" "$SCRATCH/gowork" || exit 2
66trap 'rm -rf "$SCRATCH/tree" "$SCRATCH/gowork"' EXIT
67export GOTMPDIR=$SCRATCH/gowork
68cp -a "$ROOT/go.mod" "$ROOT/go.sum" "$ROOT/internal" "$ROOT/launchctx" "$SCRATCH/tree/" || exit 2
69
70# name|file|marker|designated witnesses (comma-separated)|perl substitution
71# applied to the marked line only. The marker match is anchored (\b), so
72# swapsim:M1 never matches swapsim:M10. The substitution is the last field,
73# so it may contain "|".
74MUTANTS='M1|activator.go|swapsim:M1|TestSwapWitnessBarrierJoinsBeforeTheLease,TestSwapWitnessAgentRestartMidSwap|s/a\.joinOps\(e\)/true/
75M2|swap.go|swapsim:M2|TestSwapWitnessAgentRestartMidSwap|s/a\.addMemberLocked\(e, retiring\)/_ = retiring/
76M3|activator.go|swapsim:M3|TestSwapWitnessHoldExecsTheStagedBuild|s/e\.pin != nil/false/
77M4|opset.go|swapsim:M4|TestSwapWitnessBarrierJoinsBeforeTheLease,TestSwapWitnessAgentRestartMidSwap|s/proven := a\.stopMember\(e, m\)/proven := true; go a.stopMember(e, m)/
78M5|swapadmit.go|swapsim:M5|TestSwapWitnessNoAdmissionOntoAStoppingEntry|s/!waking \&\& e\.state != actAwake/false/
79M6|activator.go|swapsim:M6|TestSwapWitnessShutdownJoinsAWakingEntry,TestShutdownJoinsAWakingEntryWithSwapOff|s/waking = append\(waking, e\)/_ = waking/
80M7|opset.go|swapsim:M7|TestSwapWitnessMemberStopProvesTheGenerationChildEmpty|s/return a\.genTerminated\(rt, m\.gen, m\.proc\)/return true || a.genTerminated(rt, m.gen, m.proc)/
81M8|activator.go|swapsim:M8|TestSwapWitnessStopProvesTheCurrentGenerationEmpty|s/if a\.gens \&\& !a\.releasable\(e\)/if false \&\& !a.releasable(e)/
82M9|activator.go|swapsim:M9|TestSwapWitnessUnprovenWakeKeepsTheLease|s/w\.genUnproven = genStartUnproven\(err\)/w.genUnproven = false/'
83
84# runtest LOG TIMEOUT RUN [ENV...]: one go test call against the scratch
85# tree, verbose so that a test that ran prints its --- PASS or --- FAIL.
86runtest() {
87 local log=$1 timeout=$2 run=$3
88 shift 3
89 (cd "$SCRATCH/tree" && env "$@" go test -v -p 1 -count=1 -timeout "$timeout" -run "$run" ./$A/) >"$log" 2>&1
90}
91
92# crashed LOG: TIMEOUT or PANIC if the test binary died, else empty.
93crashed() {
94 if grep -q '^panic: test timed out' "$1"; then
95 echo TIMEOUT
96 elif grep -qE '^(panic: |fatal error: )' "$1"; then
97 echo PANIC
98 fi
99}
100
101fail=0
102while IFS='|' read -r name file marker witnesses sub; do
103 if [ "$ONLY" != " " ] && [[ "$ONLY" != *" $name "* ]]; then
104 continue
105 fi
106 src="$SCRATCH/tree/$A/$file"
107 # Each marker sits on exactly one line of the package's Go sources.
108 lines=$(cat "$ROOT/$A"/*.go | grep -cE "$marker\\b")
109 if [ "$lines" -ne 1 ]; then
110 echo "ERROR $name: $marker is on $lines lines of $A/*.go, not one"
111 fail=1
112 continue
113 fi
114 cp "$ROOT/$A/$file" "$src"
115 perl -pi -e "if (/\\Q$marker\\E\\b/) { $sub }" "$src"
116 if cmp -s "$ROOT/$A/$file" "$src"; then
117 echo "ERROR $name: the substitution did not apply to $file"
118 fail=1
119 continue
120 fi
121
122 # The sweep, on its own. Its integrity is judged apart from what it
123 # found: a crash or a missing result line breaks the run even when the
124 # log also reports a violation.
125 slog="$SCRATCH/logs/$name.sweep.log"
126 runtest "$slog" 20m '^TestSwapInvariants$' SWAP_SIM_SEEDS="$SEEDS"
127 built=1
128 grep -q '\[build failed\]\|\[setup failed\]' "$slog" && built=0
129 inv=$(grep -c 'invariant violated at seed' "$slog")
130 live=$(grep -c 'liveness failed at seed' "$slog")
131 broken=""
132 scrash=$(crashed "$slog")
133 if [ -n "$scrash" ]; then
134 broken="$broken sweep=$scrash"
135 elif ! grep -qE -- '--- (PASS|FAIL): TestSwapInvariants' "$slog"; then
136 broken="$broken sweep=NOT-RUN"
137 fi
138 if [ "$inv" -gt 0 ]; then
139 sweep="violated at $inv seed(s)"
140 elif [ -n "$scrash" ]; then
141 sweep=$scrash
142 elif ! grep -qE -- '--- (PASS|FAIL): TestSwapInvariants' "$slog"; then
143 sweep="NOT RUN"
144 elif [ "$live" -gt 0 ]; then
145 sweep="liveness only at $live seed(s)"
146 else
147 sweep=passed
148 fi
149
150 # Each designated witness, on its own. A witness that crashed or never
151 # reported is broken even if its log also holds a FAIL line.
152 wres=""
153 wcaught=0
154 for w in ${witnesses//,/ }; do
155 wlog="$SCRATCH/logs/$name.$w.log"
156 runtest "$wlog" 5m "^$w\$"
157 grep -q '\[build failed\]\|\[setup failed\]' "$wlog" && built=0
158 wcrash=$(crashed "$wlog")
159 if [ -n "$wcrash" ]; then
160 r=$wcrash
161 broken="$broken $w=$wcrash"
162 elif grep -q -- "--- FAIL: $w " "$wlog"; then
163 r=FAIL
164 wcaught=1
165 elif grep -q -- "--- PASS: $w " "$wlog"; then
166 r=PASS
167 else
168 r="NOT RUN"
169 broken="$broken $w=NOT-RUN"
170 fi
171 wres="$wres $w=$r"
172 done
173 cp "$ROOT/$A/$file" "$src"
174
175 caught=0
176 if [ $wcaught -eq 1 ] || [ "$inv" -gt 0 ]; then
177 caught=1
178 fi
179 detail="sweep $sweep; witnesses${wres}"
180 if [ $built -eq 0 ]; then
181 echo "ERROR $name: the mutant did not build or set up; see $SCRATCH/logs/$name.*"
182 fail=1
183 elif [ -n "$broken" ]; then
184 kill="not caught"
185 if [ $caught -eq 1 ]; then
186 kill="caught (diagnostic only)"
187 fi
188 printf 'BROKEN %s: execution integrity failed:%s; %s; %s; see %s\n' "$name" "$broken" "$kill" "$detail" "$SCRATCH/logs/$name.*"
189 fail=1
190 elif [ $caught -eq 1 ]; then
191 echo "CAUGHT $name: $detail"
192 elif [ "$live" -gt 0 ]; then
193 echo "LIVENESS $name: $detail"
194 fail=1
195 else
196 echo "MISSED $name: $detail"
197 fail=1
198 fi
199done <<<"$MUTANTS"
200exit $fail
201