a73x

2a5aa4d9

test: pin scenario and convergence counts to literals

a73x   2026-08-09 17:46

Commit message
test: pin scenario and convergence counts to literals

test/e2e.sh
Old New
@@ -174,6 +174,16 @@ assert_converged() {
174 } 174 }
175 } 175 }
176 176
177 # Scenario checkpoints. The suite's final line asserts the COUNT of these
178 # against a literal: adding or removing a scenario means updating that
179 # literal, and the friction is the feature — a scenario that silently
180 # stops running is the failure mode the pin exists for.
181 OK_COUNT=0
182 ok() {
183 OK_COUNT=$((OK_COUNT + 1))
184 echo "e2e OK: $1"
185 }
186
177 cleanup() { 187 cleanup() {
178 # `${DPID:-}` rather than `$DPID`: DPID is the one variable this trap 188 # `${DPID:-}` rather than `$DPID`: DPID is the one variable this trap
179 # reads that is assigned AFTER the trap is installed, and the scenarios 189 # reads that is assigned AFTER the trap is installed, and the scenarios
@@ -204,6 +214,7 @@ cleanup() {
204 "$SOCK5" "$SOCK6" "$SOCK7" "$PWSH" \ 214 "$SOCK5" "$SOCK6" "$SOCK7" "$PWSH" \
205 "$OUT.p1" "$OUT.p1.early" "$OUT.pb" "$OUT.pw" "$OUT.rw" "$OUT.pr" \ 215 "$OUT.p1" "$OUT.p1.early" "$OUT.pb" "$OUT.pw" "$OUT.rw" "$OUT.pr" \
206 "$OUT.p1.err" "$OUT.pb.err" "$OUT.pw.err" "$OUT.rw.err" "$OUT.pr.err" \ 216 "$OUT.p1.err" "$OUT.pb.err" "$OUT.pw.err" "$OUT.rw.err" "$OUT.pr.err" \
217 "$OUT.p1.d" "$OUT.pw.d" "$OUT.rw.d" "$OUT.pr.d" \
207 "$SOCK" "$SOCK2" "$SOCK3" "$SOCK4" "$SOCK4.second" "$QKEY" "$QKEY.bad" \ 218 "$SOCK" "$SOCK2" "$SOCK3" "$SOCK4" "$SOCK4.second" "$QKEY" "$QKEY.bad" \
208 "$OUT" "$OUT.kill" "$OUT.re" "$OUT.a" \ 219 "$OUT" "$OUT.kill" "$OUT.re" "$OUT.a" \
209 "$OUT.b" "$OUT.via" "$OUT.dead" "$OUT.abort" "$OUT.m7" "$OUT.m7b" \ 220 "$OUT.b" "$OUT.via" "$OUT.dead" "$OUT.abort" "$OUT.m7" "$OUT.m7b" \
@@ -228,7 +239,7 @@ trap cleanup EXIT INT TERM
228 # which is exactly the drift --version exists to rule out. 239 # which is exactly the drift --version exists to rule out.
229 [ "$("$MUXD" --version | cut -d' ' -f2)" = "$("$MUX" --version | cut -d' ' -f2)" ] || { 240 [ "$("$MUXD" --version | cut -d' ' -f2)" = "$("$MUX" --version | cut -d' ' -f2)" ] || {
230 echo "e2e FAIL: version drift between binaries"; exit 1; } 241 echo "e2e FAIL: version drift between binaries"; exit 1; }
231 echo "e2e OK: --version on both binaries" 242 ok "--version on both binaries"
232 243
233 # --- M10: keygen writes 0600, prints the path, refuses a second run. 244 # --- M10: keygen writes 0600, prints the path, refuses a second run.
234 # No convergence: keygen touches the filesystem, never a session. 245 # No convergence: keygen touches the filesystem, never a session.
@@ -245,7 +256,7 @@ if "$MUXD" keygen > /dev/null 2>&1; then
245 fi 256 fi
246 SUM2=$(sha256sum "$KEYOUT") 257 SUM2=$(sha256sum "$KEYOUT")
247 [ "$SUM1" = "$SUM2" ] || { echo "e2e FAIL: refused keygen still changed the key"; exit 1; } 258 [ "$SUM1" = "$SUM2" ] || { echo "e2e FAIL: refused keygen still changed the key"; exit 1; }
248 echo "e2e OK: keygen creates once, 0600 in a 0700 dir, refuses twice" 259 ok "keygen creates once, 0600 in a 0700 dir, refuses twice"
249 260
250 "$MUXD" run --sock "$SOCK" --shell /bin/sh & 261 "$MUXD" run --sock "$SOCK" --shell /bin/sh &
251 DPID=$! 262 DPID=$!
@@ -285,7 +296,7 @@ fi
285 rm -f "$OUT.doctored" "$OUT.doctored.render" "$OUT.doctored.dump" \ 296 rm -f "$OUT.doctored" "$OUT.doctored.render" "$OUT.doctored.dump" \
286 "$OUT.doctored.render.n" "$OUT.doctored.dump.n" "$OUT.doctored.diff" \ 297 "$OUT.doctored.render.n" "$OUT.doctored.dump.n" "$OUT.doctored.diff" \
287 "$OUT.doctored.rvt" "$OUT.doctored.dvt" 298 "$OUT.doctored.rvt" "$OUT.doctored.dvt"
288 echo "e2e OK: convergence control fires on a doctored stream" 299 ok "convergence control fires on a doctored stream"
289 300
290 # --- M3: kill a client mid-run; daemon survives; reattach lands correctly. 301 # --- M3: kill a client mid-run; daemon survives; reattach lands correctly.
291 # No convergence on $OUT.kill: kill -9 truncates the stream mid-paint, possibly 302 # No convergence on $OUT.kill: kill -9 truncates the stream mid-paint, possibly
@@ -362,7 +373,7 @@ grep -q "transport command failed before connecting" "$OUT.via" || {
362 grep -q "connection to muxd lost" "$OUT.via" && { 373 grep -q "connection to muxd lost" "$OUT.via" && {
363 echo "e2e FAIL: the old lie is still printed"; cat "$OUT.via"; exit 1; } 374 echo "e2e FAIL: the old lie is still printed"; cat "$OUT.via"; exit 1; }
364 rm -f "$OUT.via" 375 rm -f "$OUT.via"
365 echo "e2e OK: --via failure says what happened" 376 ok "--via failure says what happened"
366 377
367 # --- M7: a transport that dies before any session must exit, not retry. The 378 # --- M7: a transport that dies before any session must exit, not retry. The
368 # reconnect loop resumes sessions; it must not turn a bad --via command into 379 # reconnect loop resumes sessions; it must not turn a bad --via command into
@@ -612,7 +623,7 @@ env XDG_CONFIG_HOME="$NOKEY_CFG" "$MUXD" run --sock "$SOCK4.nokey" \
612 grep -q "muxd keygen" "$OUT.nokey" || { 623 grep -q "muxd keygen" "$OUT.nokey" || {
613 echo "e2e FAIL: key-missing message does not name keygen"; cat "$OUT.nokey"; exit 1; } 624 echo "e2e FAIL: key-missing message does not name keygen"; cat "$OUT.nokey"; exit 1; }
614 rm -rf "$NOKEY_CFG" 625 rm -rf "$NOKEY_CFG"
615 echo "e2e OK: no key anywhere is refused, and says how to make one" 626 ok "no key anywhere is refused, and says how to make one"
616 627
617 # The accepted case. 628 # The accepted case.
618 "$MUXD" run --sock "$SOCK4" --shell /bin/sh \ 629 "$MUXD" run --sock "$SOCK4" --shell /bin/sh \
@@ -972,7 +983,7 @@ grep -q "flagwins-ok" "$OUT.env2" || {
972 assert_converged "$OUT.env2" "$SOCK10" "flag beats env" 983 assert_converged "$OUT.env2" "$SOCK10" "flag beats env"
973 kill "$D10PID" 2>/dev/null || true 984 kill "$D10PID" 2>/dev/null || true
974 D10PID="" 985 D10PID=""
975 echo "e2e OK: daemon honours MUX_KEY_FILE, and --key beats it" 986 ok "daemon honours MUX_KEY_FILE, and --key beats it"
976 987
977 rm -f "$OUT.q" "$OUT.qc" "$OUT.qr" "$OUT.qa" "$OUT.qk" "$QKEY" "$QKEY.bad" "$QKEY.wrong" \ 988 rm -f "$OUT.q" "$OUT.qc" "$OUT.qr" "$OUT.qa" "$OUT.qk" "$QKEY" "$QKEY.bad" "$QKEY.wrong" \
978 "$OUT.qc.err" "$OUT.qr.err" "$OUT.qk.err" 989 "$OUT.qc.err" "$OUT.qr.err" "$OUT.qk.err"
@@ -1078,7 +1089,7 @@ assert_converged "$OUT.race" "$SOCK8" "start race"
1078 1089
1079 kill "$SPID" 2>/dev/null || true 1090 kill "$SPID" 2>/dev/null || true
1080 SPID="" 1091 SPID=""
1081 echo "e2e OK: muxd start — spawn, no-op rerun, log truncation, race" 1092 ok "muxd start — spawn, no-op rerun, log truncation, race"
1082 1093
1083 # --- M10: a start whose daemon dies young REPORTS it. This is the first-run 1094 # --- M10: a start whose daemon dies young REPORTS it. This is the first-run
1084 # mistake the failure line exists for — `ssh HOST 'muxd start --quic 0.0.0.0'` 1095 # mistake the failure line exists for — `ssh HOST 'muxd start --quic 0.0.0.0'`
@@ -1111,7 +1122,7 @@ grep -q "daemon did not answer" "$OUT.dead" || {
1111 [ "$(wc -l < "$OUT.dead")" = "2" ] || { 1122 [ "$(wc -l < "$OUT.dead")" = "2" ] || {
1112 echo "e2e FAIL: non-tty failure not exactly two lines:"; cat "$OUT.dead"; exit 1; } 1123 echo "e2e FAIL: non-tty failure not exactly two lines:"; cat "$OUT.dead"; exit 1; }
1113 rm -rf "$DEADCFG" 1124 rm -rf "$DEADCFG"
1114 echo "e2e OK: a start whose daemon dies young says so, with the log path" 1125 ok "a start whose daemon dies young says so, with the log path"
1115 1126
1116 # --- M10: the goal commands, minus ssh: keygen'd default key on both ends, 1127 # --- M10: the goal commands, minus ssh: keygen'd default key on both ends,
1117 # explicit loopback port (4433 on the suite machine is somebody's daemon). 1128 # explicit loopback port (4433 on the suite machine is somebody's daemon).
@@ -1128,7 +1139,7 @@ GPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.goal")
1128 assert_converged "$OUT.g9" "$SOCK11" "goal commands" 1139 assert_converged "$OUT.g9" "$SOCK11" "goal commands"
1129 kill "$GPID" 2>/dev/null || true 1140 kill "$GPID" 2>/dev/null || true
1130 GPID="" 1141 GPID=""
1131 echo "e2e OK: keygen + start --quic + mux quic:// with no --key anywhere" 1142 ok "keygen + start --quic + mux quic:// with no --key anywhere"
1132 1143
1133 # ---- M9: prediction --------------------------------------------------- 1144 # ---- M9: prediction ---------------------------------------------------
1134 # 1145 #
@@ -1164,11 +1175,12 @@ echo "e2e OK: keygen + start --quic + mux quic:// with no --key anywhere"
1164 # - Leaving margin on both sides, so neither is a race under load. 1175 # - Leaving margin on both sides, so neither is a race under load.
1165 PDELAY=300 1176 PDELAY=300
1166 1177
1167 "$MUXD" run --sock "$SOCK5" --shell /bin/cat > "$OUT.p1" 2>&1 & 1178 # Own file, not the client capture: the daemon's fd survives the client's truncation and would write into the replayed stream.
1179 "$MUXD" run --sock "$SOCK5" --shell /bin/cat > "$OUT.p1.d" 2>&1 &
1168 D5PID=$! 1180 D5PID=$!
1169 i=0 1181 i=0
1170 while [ ! -S "$SOCK5" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done 1182 while [ ! -S "$SOCK5" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done
1171 [ -S "$SOCK5" ] || { echo "e2e FAIL: prediction daemon never bound"; cat "$OUT.p1"; exit 1; } 1183 [ -S "$SOCK5" ] || { echo "e2e FAIL: prediction daemon never bound"; cat "$OUT.p1.d"; exit 1; }
1172 1184
1173 # 1. Line mode: the glyph is on screen before the round trip could have 1185 # 1. Line mode: the glyph is on screen before the round trip could have
1174 # delivered it. 1186 # delivered it.
@@ -1276,11 +1288,11 @@ read _hold
1276 PWEOF 1288 PWEOF
1277 chmod +x "$PWSH" 1289 chmod +x "$PWSH"
1278 1290
1279 "$MUXD" run --sock "$SOCK6" --shell "$PWSH" > "$OUT.pw" 2>&1 & 1291 "$MUXD" run --sock "$SOCK6" --shell "$PWSH" > "$OUT.pw.d" 2>&1 &
1280 D6PID=$! 1292 D6PID=$!
1281 i=0 1293 i=0
1282 while [ ! -S "$SOCK6" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done 1294 while [ ! -S "$SOCK6" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done
1283 [ -S "$SOCK6" ] || { echo "e2e FAIL: password daemon never bound"; cat "$OUT.pw"; exit 1; } 1295 [ -S "$SOCK6" ] || { echo "e2e FAIL: password daemon never bound"; cat "$OUT.pw.d"; exit 1; }
1284 1296
1285 # One character per write, and NOT as a single `printf hunter2`. A whole 1297 # One character per write, and NOT as a single `printf hunter2`. A whole
1286 # word in one write reaches the client as a multi-byte chunk, which is 1298 # word in one write reaches the client as a multi-byte chunk, which is
@@ -1325,11 +1337,11 @@ D6PID=""
1325 # 4. Raw mode: display is earned, then lost to a keystroke the application 1337 # 4. Raw mode: display is earned, then lost to a keystroke the application
1326 # swallows. rawmode echoes like an editor in insert mode until it is sent 1338 # swallows. rawmode echoes like an editor in insert mode until it is sent
1327 # 0x00, after which it consumes input and prints nothing. 1339 # 0x00, after which it consumes input and prints nothing.
1328 "$MUXD" run --sock "$SOCK7" --shell "$RAWMODE" > "$OUT.rw" 2>&1 & 1340 "$MUXD" run --sock "$SOCK7" --shell "$RAWMODE" > "$OUT.rw.d" 2>&1 &
1329 D7PID=$! 1341 D7PID=$!
1330 i=0 1342 i=0
1331 while [ ! -S "$SOCK7" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done 1343 while [ ! -S "$SOCK7" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done
1332 [ -S "$SOCK7" ] || { echo "e2e FAIL: rawmode daemon never bound"; cat "$OUT.rw"; exit 1; } 1344 [ -S "$SOCK7" ] || { echo "e2e FAIL: rawmode daemon never bound"; cat "$OUT.rw.d"; exit 1; }
1333 1345
1334 set +e 1346 set +e
1335 { sleep 2; for c in a b c; do printf '%s' "$c"; sleep 0.5; done; \ 1347 { sleep 2; for c in a b c; do printf '%s' "$c"; sleep 0.5; done; \
@@ -1376,11 +1388,11 @@ D7PID=""
1376 # 5. A transport torn down with predictions outstanding. The overlay must 1388 # 5. A transport torn down with predictions outstanding. The overlay must
1377 # come back empty: what was queued was predicted against a connection 1389 # come back empty: what was queued was predicted against a connection
1378 # that no longer exists. 1390 # that no longer exists.
1379 "$MUXD" run --sock "$SOCK5" --shell /bin/cat > "$OUT.pr" 2>&1 & 1391 "$MUXD" run --sock "$SOCK5" --shell /bin/cat > "$OUT.pr.d" 2>&1 &
1380 D5PID=$! 1392 D5PID=$!
1381 i=0 1393 i=0
1382 while [ ! -S "$SOCK5" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done 1394 while [ ! -S "$SOCK5" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done
1383 [ -S "$SOCK5" ] || { echo "e2e FAIL: reconnect daemon never bound"; cat "$OUT.pr"; exit 1; } 1395 [ -S "$SOCK5" ] || { echo "e2e FAIL: reconnect daemon never bound"; cat "$OUT.pr.d"; exit 1; }
1384 1396
1385 set +e 1397 set +e
1386 { sleep 2; printf 'p'; sleep 5; printf 'q'; sleep 4; printf '\034'; } | \ 1398 { sleep 2; printf 'p'; sleep 5; printf 'q'; sleep 4; printf '\034'; } | \
@@ -1443,4 +1455,17 @@ assert_converged "$OUT.pr" "$SOCK5" "reconnect flush"
1443 kill "$D5PID" 2>/dev/null || true 1455 kill "$D5PID" 2>/dev/null || true
1444 D5PID="" 1456 D5PID=""
1445 1457
1446 echo "e2e OK" 1458 # The pins. Literals, not variables set from counting something else —
1459 # "assert the literal, never the constant the code under test reads"
1460 # (decisions.md, M10). 9 scenario checkpoints; 21 convergence points.
1461 # Anyone adding a scenario updates these by hand, on purpose.
1462 [ "$OK_COUNT" = "9" ] || {
1463 echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 9 —"
1464 echo " a scenario was added (update the pin) or silently lost"
1465 exit 1
1466 }
1467 [ "$CONV_COUNT" = "21" ] || {
1468 echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 21"
1469 exit 1
1470 }
1471 echo "e2e OK (9 scenarios, 21 convergence points)"