a73x

483606dd

test: e2e gates the CLI wall — two sessions and a live delta, one pty

a73x   2026-08-19 17:17

Commit message
test: e2e gates the CLI wall — two sessions and a live delta, one pty

The 33rd scenario: a daemon with sessions a and b, `mux wall` under
ptyclient, and three witnessed facts — both markers painted (snapshot
path), a muxa injection into b painted WHILE attached (delta path), and
`q` exited 0. The expect order leans on that injection deliberately:
tile threads race, so b's snapshot bytes can land behind the expect
cursor; the t+5s injection repaints b's stripe late in the stream,
putting snapshot and live markers downstream of a's match by
construction. Passivity gets its negative twin: after the wall, muxa
status still answers cols:80 — a wall of 0x0 attaches claimed nothing.

test/e2e.sh
Old New
@@ -242,6 +242,11 @@ DWSTATE="${TMPDIR:-/tmp}/mux-e2e-dynwall-state-$$"
242 D22PID="" 242 D22PID=""
243 W4PID="" 243 W4PID=""
244 244
245 # The CLI wall (`mux wall`): its own daemon, so its two sessions can't be
246 # confused with any other block's.
247 SOCK26="${TMPDIR:-/tmp}/muxd-e2e-cliwall-$$.sock"
248 D23PID=""
249
245 # One counter out of a MUX_PREDICT_STATS line. The client prints exactly one 250 # One counter out of a MUX_PREDICT_STATS line. The client prints exactly one
246 # such line on exit; every field is a key=value pair, so a rename or reorder 251 # such line on exit; every field is a key=value pair, so a rename or reorder
247 # in the client shows up here as an empty read rather than a wrong number. 252 # in the client shows up here as an empty read rather than a wrong number.
@@ -761,6 +766,7 @@ cleanup() {
761 # one the run died under. 766 # one the run died under.
762 [ -n "$W4PID" ] && kill "$W4PID" 2>/dev/null || true 767 [ -n "$W4PID" ] && kill "$W4PID" 2>/dev/null || true
763 [ -n "$D22PID" ] && kill "$D22PID" 2>/dev/null || true 768 [ -n "$D22PID" ] && kill "$D22PID" 2>/dev/null || true
769 [ -n "$D23PID" ] && kill "$D23PID" 2>/dev/null || true
764 # The stops still precede the socket rm below, like SOCK14-17 above: 770 # The stops still precede the socket rm below, like SOCK14-17 above:
765 # unlinking a socket first would leave a live daemon nothing could reach 771 # unlinking a socket first would leave a live daemon nothing could reach
766 # by path. 772 # by path.
@@ -768,6 +774,7 @@ cleanup() {
768 [ -S "$SOCK19" ] && "$MUXD" stop --sock "$SOCK19" 2>/dev/null || true 774 [ -S "$SOCK19" ] && "$MUXD" stop --sock "$SOCK19" 2>/dev/null || true
769 [ -S "$SOCK20" ] && "$MUXD" stop --sock "$SOCK20" 2>/dev/null || true 775 [ -S "$SOCK20" ] && "$MUXD" stop --sock "$SOCK20" 2>/dev/null || true
770 [ -S "$SOCK25" ] && "$MUXD" stop --sock "$SOCK25" 2>/dev/null || true 776 [ -S "$SOCK25" ] && "$MUXD" stop --sock "$SOCK25" 2>/dev/null || true
777 [ -S "$SOCK26" ] && "$MUXD" stop --sock "$SOCK26" 2>/dev/null || true
771 778
772 # ---- the leak sweep (hygiene kit, 6a) ---- 779 # ---- the leak sweep (hygiene kit, 6a) ----
773 # Here rather than at the bottom of the file, which `set -e` reaches only 780 # Here rather than at the bottom of the file, which `set -e` reaches only
@@ -779,7 +786,7 @@ cleanup() {
779 "$D7PID" "$D9PID" "$D10PID" "$D12PID" "$D13PID" "$SPID" "$TPID" \ 786 "$D7PID" "$D9PID" "$D10PID" "$D12PID" "$D13PID" "$SPID" "$TPID" \
780 "$GPID" "$APID" "$PAPID" "$HAPID" "$HDPID" \ 787 "$GPID" "$APID" "$PAPID" "$HAPID" "$HDPID" \
781 "$D14PID" "$D15PID" "$D16PID" "$D17PID" "$D18PID" "$D19PID" \ 788 "$D14PID" "$D15PID" "$D16PID" "$D17PID" "$D18PID" "$D19PID" \
782 "$D20PID" "$D21PID" "$D22PID" 789 "$D20PID" "$D21PID" "$D22PID" "$D23PID"
783 _leak=0 790 _leak=0
784 leak_sweep "$_rc" || _leak=1 791 leak_sweep "$_rc" || _leak=1
785 792
@@ -4334,6 +4341,71 @@ assert_stopped "$SOCK25" "$D22PID" "dyn wall" "$OUT.dwstop"
4334 D22PID="" 4341 D22PID=""
4335 ok "the wall is runtime state: add, remove, reorder, restore, argv overrides" 4342 ok "the wall is runtime state: add, remove, reorder, restore, argv overrides"
4336 4343
4344 # ---- the CLI wall: the hub's multiattach, in one terminal --------------
4345 # One daemon, two named sessions, one `mux wall` on a real pty showing
4346 # both AT ONCE — the browser wall's claim, made by the CLI. Three facts,
4347 # each with its own witness: both sessions' markers painted (the snapshot
4348 # path), a line injected into b WHILE the wall is attached painted too
4349 # (the delta path — the wall is a live replica, not a capture), and `q`
4350 # ended the process with exit 0 (the wall left, not died). Markers are
4351 # shell-EXPANDED (`%s`+pin), the M18 trick: a hit is the shell's work,
4352 # never an echo of anything typed here.
4353 #
4354 # The expect ORDER leans on the injection, deliberately: tile threads
4355 # race, so b's snapshot bytes may land before a's and behind the expect
4356 # cursor. The injection at t+5s repaints b's whole stripe late in the
4357 # stream — snapshot marker and live marker in one ordered repaint — so
4358 # every needle below is downstream of the cwa-pin match by construction
4359 # (a's local-socket snapshot paints in well under 5s).
4360 "$MUXD" run --sock "$SOCK26" --shell /bin/sh > "$OUT.cwall.d" 2>&1 &
4361 D23PID=$!
4362 wait_sock "$SOCK26" "$OUT.cwall.d" "CLI wall daemon never bound"
4363
4364 { printf 'printf "cwa-%%s\\n" pin\n'; sleep 2; printf '\034'; } | \
4365 timeout 40 "$MUX" --sock "$SOCK26" --session a > "$OUT.cwa" 2> "$OUT.cwa.err"
4366 wait_grid "$SOCK26" "cwa-pin" "CLI wall: session a's marker" a
4367 { printf 'printf "cwb-%%s\\n" pin\n'; sleep 2; printf '\034'; } | \
4368 timeout 40 "$MUX" --sock "$SOCK26" --session b > "$OUT.cwb" 2> "$OUT.cwb.err"
4369 wait_grid "$SOCK26" "cwb-pin" "CLI wall: session b's marker" b
4370
4371 ( sleep 5; "$MUXA" send 'printf "cwlive-%s\n" pin\n' \
4372 --sock "$SOCK26" --session b > "$OUT.cwinj" 2>&1 ) &
4373 CWINJPID=$!
4374 set +e
4375 timeout 40 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.cwcap" --err "$OUT.cwcap.err" -- \
4376 "$MUX" wall "--sock $SOCK26#a" "--sock $SOCK26#b" > "$OUT.cwpc" 2>&1 <<'EOF'
4377 expect cwa-pin 15000
4378 expect cwb-pin 20000
4379 expect cwlive-pin 5000
4380 send q
4381 waitexit 10000
4382 EOF
4383 RC=$?
4384 set -e
4385 wait "$CWINJPID" 2>/dev/null || true
4386 [ "$RC" -eq 0 ] || {
4387 echo "e2e FAIL: CLI wall: ptyclient leg exited $RC:"
4388 cat "$OUT.cwpc"; cat "$OUT.cwinj"; exit 1; }
4389 # The multiattach fact itself: both sessions' bytes in ONE process's
4390 # capture. Redundant with the expects above only until someone edits the
4391 # script block; a grep each keeps the claim standing on its own.
4392 grep -q "cwa-pin" "$OUT.cwcap" || {
4393 echo "e2e FAIL: CLI wall: a's marker never painted"; exit 1; }
4394 grep -q "cwb-pin" "$OUT.cwcap" || {
4395 echo "e2e FAIL: CLI wall: b's marker never painted"; exit 1; }
4396 grep -q "cwlive-pin" "$OUT.cwcap" || {
4397 echo "e2e FAIL: CLI wall: the live delta never painted"; exit 1; }
4398 # Passivity, M-web's negative twin: a wall of 0x0 attaches must never
4399 # have claimed either grid — the daemon still answers with the size the
4400 # creating clients set, not a degenerate one.
4401 "$MUXA" status --sock "$SOCK26" --session a > "$OUT.cwst" 2>&1
4402 grep -q '"cols":80' "$OUT.cwst" || {
4403 echo "e2e FAIL: CLI wall: a 0x0 wall attach moved session a's grid:"
4404 cat "$OUT.cwst"; exit 1; }
4405 assert_stopped "$SOCK26" "$D23PID" "CLI wall" "$OUT.cwstop"
4406 D23PID=""
4407 ok "mux wall: two sessions and a live delta on one terminal, read-only"
4408
4337 # The long-lived daemon has served every scenario that wanted it; stop it 4409 # The long-lived daemon has served every scenario that wanted it; stop it
4338 # NOW so its allocator verdict is written while the suite is still running 4410 # NOW so its allocator verdict is written while the suite is still running
4339 # and can say so. SIGTERM runs the clean-shutdown path, so the defer chain 4411 # and can say so. SIGTERM runs the clean-shutdown path, so the defer chain
@@ -4349,7 +4421,7 @@ DPID=""
4349 4421
4350 # The pins. Literals, not variables set from counting something else — 4422 # The pins. Literals, not variables set from counting something else —
4351 # "assert the literal, never the constant the code under test reads" 4423 # "assert the literal, never the constant the code under test reads"
4352 # (decisions.md, M10). 32 scenario checkpoints; 35 convergence points. 4424 # (decisions.md, M10). 33 scenario checkpoints; 35 convergence points.
4353 # Anyone adding a scenario updates these by hand, on purpose. 4425 # Anyone adding a scenario updates these by hand, on purpose.
4354 # 4426 #
4355 # M18 added three checkpoints and no convergence points: its wall block 4427 # M18 added three checkpoints and no convergence points: its wall block
@@ -4370,9 +4442,11 @@ DPID=""
4370 # the dynamic wall, and no convergence point either: what it asserts on is 4442 # the dynamic wall, and no convergence point either: what it asserts on is
4371 # the hub's HTTP answers and the wall FILE — its one grid check rides on 4443 # the hub's HTTP answers and the wall FILE — its one grid check rides on
4372 # `expectgrid` inside the stand-in, which no more counts here than the M18 4444 # `expectgrid` inside the stand-in, which no more counts here than the M18
4373 # wall block's convergence does. 4445 # wall block's convergence does. The 33rd is the CLI wall, and no
4374 [ "$OK_COUNT" = "32" ] || { 4446 # convergence point for the M12 reason: it asserts on a ptyclient capture
4375 echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 32 —" 4447 # — bytes a wall painted to a terminal — which no grid dump can witness.
4448 [ "$OK_COUNT" = "33" ] || {
4449 echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 33 —"
4376 echo " a scenario was added (update the pin) or silently lost" 4450 echo " a scenario was added (update the pin) or silently lost"
4377 exit 1 4451 exit 1
4378 } 4452 }
@@ -4380,4 +4454,4 @@ DPID=""
4380 echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 35" 4454 echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 35"
4381 exit 1 4455 exit 1
4382 } 4456 }
4383 echo "e2e OK (32 scenarios, 35 convergence points)" 4457 echo "e2e OK (33 scenarios, 35 convergence points)"