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 engines model (fleet-internal engines spec §8;
3# #335 plan 1). Adapted from internal/sim/hostlife/mutate.sh: the same
4# build-once, run-per-rule shape and the same verdicts.
5#
6# Build the test binary once (this is the only step that writes the Go
7# build cache), then run the sweep. The sweep never copies or edits the
8# source: each rule runs the same binary with ENGINESIM_MUTATE set.
9#
10# go test -c -o "$TMPDIR/enginesim/engines.test" ./internal/sim/engines/
11# nice -n 19 internal/sim/engines/mutate.sh 3000 # every rule
12# nice -n 19 internal/sim/engines/mutate.sh 3000 @all # the same
13# nice -n 19 internal/sim/engines/mutate.sh 3000 rule-a rule-b # named rules
14# nice -n 19 internal/sim/engines/mutate.sh 3000 rule-a,rule-b # a combination, dropped together
15# ENGINESIM_LIST=1 internal/sim/engines/mutate.sh 0 # print the rules a run would check, and run nothing
16#
17# Each rule gets two runs under its mutation: the random sweep (TestEngines
18# over the seeds) and the rule's deterministic witness scenarios
19# (TestWitnesses, witness_test.go). The verdict line reports both,
20# separately:
21#
22# CAUGHT rule: sweep N of S (first seed n); witness violated|held|none
23#
24# Classification:
25#
26# CAUGHT the sweep completed and reported at least one seed that
27# violated an invariant, with the seed number parsed from its
28# output (so it can be replayed with ENGINESIM_SEED), OR a
29# witness of the rule violated an invariant under the mutation;
30# MISSED the sweep completed with every seed passing, and every
31# witness held (or the rule has none);
32# ERROR anything else: an unknown rule, a stale binary, a panic, a
33# timeout, output the harness cannot parse, or a witness run
34# that failed (a witness that must violate under this mutation
35# and held: the witness is broken). An error is never a
36# detection.
37#
38# Expected outcomes live in expected-outcomes.txt beside this script: a
39# rule listed there as `missed` (caught only in a stated combination: its
40# witness holds alone) is reported as EXPECTED-MISS. A rule listed as
41# `rare <N>` is caught by the random sweep only at N seeds or more: below
42# N a miss is EXPECTED-MISS and a catch is CAUGHT; at N or more a miss is
43# MISSED. The sweep exits nonzero on any ERROR, on any unexpected MISSED,
44# and on any rule listed as `missed` that is CAUGHT (STALE).
45#
46# The machine is shared: run the sweep once per revision, serially, under
47# nice -n 19. ENGINESIM_PAR (default 1) runs that many rules at once.
48#
49# Scratch (the binary, every run's log, the verdicts) lives under
50# ${ENGINESIM_SCRATCH:-$TMPDIR/enginesim}; remove that one directory, by
51# its exact path, once, after the whole sweep.
52set -u
53SEEDS=${1:?seeds}
54shift
55ROOT=$(git -C "$(dirname "$0")" rev-parse --show-toplevel)
56PKG="$ROOT/internal/sim/engines"
57SCRATCH=${ENGINESIM_SCRATCH:-${TMPDIR:?}/enginesim}
58BIN=${ENGINESIM_BIN:-$SCRATCH/engines.test}
59RULES="$*"
60if [ -z "$RULES" ] || [ "$RULES" = "@all" ]; then
61 RULES=$(sed -n '/^var allRules/,/^}/p' "$PKG/rules_test.go" | grep -o '^\s*"[a-z0-9-]*"' | tr -d ' "\t')
62fi
63if [ -n "${ENGINESIM_LIST:-}" ]; then
64 printf '%s\n' $RULES
65 exit 0
66fi
67mkdir -p "$SCRATCH/logs" "$SCRATCH/verdicts" || { echo "cannot create $SCRATCH" >&2; exit 2; }
68EXPECT="$PKG/expected-outcomes.txt"
69if [ ! -x "$BIN" ]; then
70 echo "no test binary at $BIN; build it with: go test -c -o $BIN ./internal/sim/engines/" >&2
71 exit 2
72fi
73if [ -n "$(find "$PKG" -name '*.go' -newer "$BIN")" ]; then
74 echo "the test binary $BIN is older than the source; rebuild it" >&2
75 exit 2
76fi
77# the rules that have witnesses (the witnesses table in witness_test.go): a
78# run of one of them with no witness output is an ERROR, never "none"
79WRULES=$(sed -n '/^var witnesses/,/^}/p' "$PKG/witness_test.go" | grep -o '^\s*{"[a-z0-9-]*",' | tr -d ' \t{",' | sort -u)
80# the verdicts of this run only: remove the files inside, by exact name
81for f in "$SCRATCH"/verdicts/*; do
82 [ -e "$f" ] && { rm -f -- "$f" || exit 2; }
83done
84
85# the unmutated sweep first, at the same seed count: if the model violates
86# with every rule in place, no catch below means anything (ENGINESIM_MUTATE
87# set empty here, so a value in the caller's environment cannot leak in)
88blog="$SCRATCH/logs/unmutated.log"
89ENGINESIM_MUTATE= ENGINESIM_SEEDS=$SEEDS "$BIN" -test.run '^TestEngines$' -test.count=1 -test.v -test.timeout 180m >"$blog" 2>&1
90brc=$?
91if [ $brc -ne 0 ] || [ "$(grep -c "^ sim_test.go:[0-9]*: $SEEDS seeds, 0 failed, 0 residuals; mutated=\"\"" "$blog")" -ne 1 ]; then
92 echo "BASELINE unmutated: FAILED (rc=$brc); see $blog" >&2
93 exit 1
94fi
95echo "BASELINE unmutated: sweep 0 of $SEEDS"
96
97one() {
98 r=$1
99 tag=$(echo "$r" | tr ',' '+')
100 log="$SCRATCH/logs/$tag.log"
101 verdict="$SCRATCH/verdicts/$tag"
102 for x in $(echo "$r" | tr ',' ' '); do
103 if ! sed -n '/^var allRules/,/^}/p' "$PKG/rules_test.go" | grep -q "^\s*\"$x\","; then
104 echo "ERROR $r: no rule named $x" | tee "$verdict"
105 return
106 fi
107 done
108 # the random sweep
109 ENGINESIM_MUTATE=$r ENGINESIM_SEEDS=$SEEDS "$BIN" -test.run '^TestEngines$' -test.count=1 -test.v -test.timeout 180m >"$log" 2>&1
110 rc=$?
111 expect=$(awk -v r="$r" '$1==r {print $2}' "$EXPECT" 2>/dev/null)
112 depth=$(awk -v r="$r" '$1==r {print $3}' "$EXPECT" 2>/dev/null)
113 nviol=$(grep -o "[0-9]* of $SEEDS seeds violated an invariant" "$log" | head -1 | awk '{print $1}')
114 seed=$(grep -o "first seed [0-9]*" "$log" | head -1 | awk '{print $3}')
115 clean=$(grep -c "^ sim_test.go:[0-9]*: $SEEDS seeds, 0 failed.*mutated=\"$r\"" "$log")
116 sweep=""
117 if [ $rc -ne 0 ] && [ -n "$nviol" ] && [ -n "$seed" ] && ! grep -q "^panic:\|test timed out" "$log"; then
118 sweep="sweep $nviol of $SEEDS (first seed $seed)"
119 elif [ $rc -eq 0 ] && [ "$clean" -eq 1 ]; then
120 sweep="sweep 0 of $SEEDS"
121 fi
122 # the witnesses: the rule's own hand-scheduled scenarios, run under the
123 # same mutation; "violated" if any of them violates an invariant, "held"
124 # if they all hold, "none" if the rule has no witness. A witness that
125 # must violate under this mutation and holds fails the test, which is an
126 # ERROR here: the witness is broken, not the rule
127 wlog="$SCRATCH/logs/$tag.witness.log"
128 ENGINESIM_MUTATE=$r "$BIN" -test.run '^TestWitnesses$' -test.count=1 -test.v -test.timeout 10m >"$wlog" 2>&1
129 wrc=$?
130 wit=none
131 if grep -q "^ witness_test.go:[0-9]*: witness .*: violated" "$wlog"; then
132 wit=violated
133 elif grep -q "^ witness_test.go:[0-9]*: witness .*: held" "$wlog"; then
134 wit=held
135 fi
136 if [ $wrc -ne 0 ] || grep -q "^panic:\|test timed out" "$wlog"; then
137 echo "ERROR $r: witness run rc=$wrc; see $wlog" | tee "$verdict"
138 return
139 fi
140 if [ "$wit" = none ]; then
141 for x in $(echo "$r" | tr ',' ' '); do
142 if echo "$WRULES" | grep -qx "$x"; then
143 echo "ERROR $r: $x has witnesses but the run produced no witness output; see $wlog" | tee "$verdict"
144 return
145 fi
146 done
147 fi
148 if [ -z "$sweep" ]; then
149 echo "ERROR $r: rc=$rc, no parsable sweep verdict; see $log" | tee "$verdict"
150 return
151 fi
152 caught=0
153 if [ "$sweep" != "sweep 0 of $SEEDS" ] || [ "$wit" = violated ]; then
154 caught=1
155 fi
156 if [ $caught -eq 1 ]; then
157 if [ "$expect" = "missed" ]; then
158 echo "STALE $r: $sweep; witness $wit; but expected-outcomes.txt says missed" | tee "$verdict"
159 else
160 echo "CAUGHT $r: $sweep; witness $wit" | tee "$verdict"
161 fi
162 elif [ "$expect" = "missed" ]; then
163 echo "EXPECTED-MISS $r: $sweep; witness $wit" | tee "$verdict"
164 elif [ "$expect" = "rare" ] && [ -n "$depth" ] && [ "$SEEDS" -lt "$depth" ]; then
165 echo "EXPECTED-MISS $r: $sweep; witness $wit (rare: caught at $depth seeds)" | tee "$verdict"
166 else
167 echo "MISSED $r: $sweep; witness $wit" | tee "$verdict"
168 fi
169}
170export -f one
171export SEEDS PKG SCRATCH BIN WRULES EXPECT
172printf '%s\n' $RULES | xargs -P "${ENGINESIM_PAR:-1}" -I{} bash -c 'one "$@"' _ {}
173xrc=$?
174if [ $xrc -ne 0 ]; then
175 echo "sweep: orchestration failed (xargs rc=$xrc)" >&2
176 exit 2
177fi
178# exactly one well-formed verdict per requested mutation
179want=$(printf '%s\n' $RULES | wc -l)
180have=$(find "$SCRATCH/verdicts" -type f | wc -l)
181if [ "$have" -ne "$want" ]; then
182 echo "sweep: $want mutation(s) requested, $have verdict(s) written" >&2
183 exit 2
184fi
185for r in $RULES; do
186 v="$SCRATCH/verdicts/$(echo "$r" | tr ',' '+')"
187 if ! grep -qE '^(CAUGHT|MISSED|EXPECTED-MISS|STALE|ERROR) ' "$v" 2>/dev/null; then
188 echo "sweep: no well-formed verdict for $r" >&2
189 exit 2
190 fi
191done
192bad=$(cat "$SCRATCH"/verdicts/* | grep -c "^ERROR\|^MISSED\|^STALE")
193if [ "$bad" -gt 0 ]; then
194 echo "sweep: $bad rule(s) ERROR, MISSED or STALE" >&2
195 exit 1
196fi
197