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