2a5aa4d9
test: pin scenario and convergence counts to literals
a73x 2026-08-09 17:46
Commit message
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)" | ||