a73x

9b404327

test: e2e walks the ring three sessions wide, both ways

a73x   2026-08-19 17:17

Commit message
test: e2e walks the ring three sessions wide, both ways

Two sessions would have proved nothing: next and previous land on the
same place, so the leg would pass with the directions swapped. Three
sessions, each holding its own marker, make a wrong direction land on a
marker the expect is not waiting for.

The step's answer is read off the screen, not off a name. A switch is an
attach and an attach repaints a snapshot, so the marker that arrives
says which grid the daemon just sent — and ptyclient's expect cursor
consumes matches, so the marker waited for at the top cannot satisfy the
one waited for after the wrap.

test/e2e.sh
Old New
@@ -258,6 +258,13 @@ D24PID=""
258 SOCK28="${TMPDIR:-/tmp}/muxd-e2e-newsess-$$.sock" 258 SOCK28="${TMPDIR:-/tmp}/muxd-e2e-newsess-$$.sock"
259 D25PID="" 259 D25PID=""
260 260
261 # Ctrl-\ n / Ctrl-\ p (step around the ring): its own daemon for the same
262 # reason as SOCK28, and more sharply — the ring IS the list of sessions on
263 # one daemon, so a stray session from another block would change where a
264 # step lands.
265 SOCK29="${TMPDIR:-/tmp}/muxd-e2e-ring-$$.sock"
266 D26PID=""
267
261 # One counter out of a MUX_PREDICT_STATS line. The client prints exactly one 268 # One counter out of a MUX_PREDICT_STATS line. The client prints exactly one
262 # such line on exit; every field is a key=value pair, so a rename or reorder 269 # such line on exit; every field is a key=value pair, so a rename or reorder
263 # in the client shows up here as an empty read rather than a wrong number. 270 # in the client shows up here as an empty read rather than a wrong number.
@@ -780,6 +787,7 @@ cleanup() {
780 [ -n "$D23PID" ] && kill "$D23PID" 2>/dev/null || true 787 [ -n "$D23PID" ] && kill "$D23PID" 2>/dev/null || true
781 [ -n "$D24PID" ] && kill "$D24PID" 2>/dev/null || true 788 [ -n "$D24PID" ] && kill "$D24PID" 2>/dev/null || true
782 [ -n "$D25PID" ] && kill "$D25PID" 2>/dev/null || true 789 [ -n "$D25PID" ] && kill "$D25PID" 2>/dev/null || true
790 [ -n "$D26PID" ] && kill "$D26PID" 2>/dev/null || true
783 # The stops still precede the socket rm below, like SOCK14-17 above: 791 # The stops still precede the socket rm below, like SOCK14-17 above:
784 # unlinking a socket first would leave a live daemon nothing could reach 792 # unlinking a socket first would leave a live daemon nothing could reach
785 # by path. 793 # by path.
@@ -790,6 +798,7 @@ cleanup() {
790 [ -S "$SOCK26" ] && "$MUXD" stop --sock "$SOCK26" 2>/dev/null || true 798 [ -S "$SOCK26" ] && "$MUXD" stop --sock "$SOCK26" 2>/dev/null || true
791 [ -S "$SOCK27" ] && "$MUXD" stop --sock "$SOCK27" 2>/dev/null || true 799 [ -S "$SOCK27" ] && "$MUXD" stop --sock "$SOCK27" 2>/dev/null || true
792 [ -S "$SOCK28" ] && "$MUXD" stop --sock "$SOCK28" 2>/dev/null || true 800 [ -S "$SOCK28" ] && "$MUXD" stop --sock "$SOCK28" 2>/dev/null || true
801 [ -S "$SOCK29" ] && "$MUXD" stop --sock "$SOCK29" 2>/dev/null || true
793 802
794 # ---- the leak sweep (hygiene kit, 6a) ---- 803 # ---- the leak sweep (hygiene kit, 6a) ----
795 # Here rather than at the bottom of the file, which `set -e` reaches only 804 # Here rather than at the bottom of the file, which `set -e` reaches only
@@ -801,7 +810,8 @@ cleanup() {
801 "$D7PID" "$D9PID" "$D10PID" "$D12PID" "$D13PID" "$SPID" "$TPID" \ 810 "$D7PID" "$D9PID" "$D10PID" "$D12PID" "$D13PID" "$SPID" "$TPID" \
802 "$GPID" "$APID" "$PAPID" "$HAPID" "$HDPID" \ 811 "$GPID" "$APID" "$PAPID" "$HAPID" "$HDPID" \
803 "$D14PID" "$D15PID" "$D16PID" "$D17PID" "$D18PID" "$D19PID" \ 812 "$D14PID" "$D15PID" "$D16PID" "$D17PID" "$D18PID" "$D19PID" \
804 "$D20PID" "$D21PID" "$D22PID" "$D23PID" "$D24PID" "$D25PID" 813 "$D20PID" "$D21PID" "$D22PID" "$D23PID" "$D24PID" "$D25PID" \
814 "$D26PID"
805 _leak=0 815 _leak=0
806 leak_sweep "$_rc" || _leak=1 816 leak_sweep "$_rc" || _leak=1
807 817
@@ -4582,6 +4592,86 @@ assert_stopped "$SOCK28" "$D25PID" "new session" "$OUT.nswstop"
4582 D25PID="" 4592 D25PID=""
4583 ok "Ctrl-\\ c: a new session is created and switched to, the old one intact; a full daemon refuses and the client stays where it was" 4593 ok "Ctrl-\\ c: a new session is created and switched to, the old one intact; a full daemon refuses and the client stays where it was"
4584 4594
4595 # ---- M3: Ctrl-\ n / Ctrl-\ p step around the session ring ----------------
4596 #
4597 # THREE sessions, not two, and that is the whole point: with two, next and
4598 # previous land on the same place and the leg would pass with the two
4599 # directions swapped. Three makes a wrong direction land on a different
4600 # marker.
4601 #
4602 # Each session is given its own marker before the stepping starts, so the
4603 # ring's answer is read off the SCREEN rather than off a name: after a
4604 # step, the marker that arrives says which grid the daemon just sent.
4605 #
4606 # * `\x1cn` from the last slot must wrap to the first — m3a-pin, the
4607 # default session's marker. A step in the other direction would bring
4608 # m3b-pin, and the expect would time out on it.
4609 # * `\x1cp` from the first slot must wrap back to the last — m3c-pin.
4610 #
4611 # The markers arrive a second time because a switch is an attach and an
4612 # attach repaints a snapshot; ptyclient's expect cursor consumes matches,
4613 # so the m3a-pin the leg waited for at the top cannot satisfy the one it
4614 # waits for after the wrap.
4615 #
4616 # The captures afterwards pin which marker lives where — three sessions
4617 # that each kept their own line, so the stepping moved the CLIENT and left
4618 # the sessions where they were.
4619 "$MUXD" run --sock "$SOCK29" --shell /bin/sh > "$OUT.ring.d" 2>&1 &
4620 D26PID=$!
4621 wait_sock "$SOCK29" "$OUT.ring.d" "session-ring daemon never bound"
4622
4623 set +e
4624 timeout 60 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.ring" --err "$OUT.ring.err" \
4625 -- "$MUX" --sock "$SOCK29" > "$OUT.ring.log" 2>&1 <<'EOF'
4626 expect \x1b[?1049h 15000
4627 settle 400 15000
4628 send printf 'm3a-%s\\n' pin\n
4629 expect m3a-pin 15000
4630 settle 400 15000
4631 send \x1cc
4632 expect \x1b[?1049h 15000
4633 settle 400 15000
4634 send printf 'm3b-%s\\n' pin\n
4635 expect m3b-pin 15000
4636 settle 400 15000
4637 send \x1cc
4638 expect \x1b[?1049h 15000
4639 settle 400 15000
4640 send printf 'm3c-%s\\n' pin\n
4641 expect m3c-pin 15000
4642 settle 400 15000
4643 send \x1cn
4644 expect \x1b[?1049h 15000
4645 expect m3a-pin 15000
4646 settle 400 15000
4647 send \x1cp
4648 expect \x1b[?1049h 15000
4649 expect m3c-pin 15000
4650 settle 400 15000
4651 send \x1c\x1c
4652 waitexit 10000
4653 EOF
4654 RC=$?
4655 set -e
4656 [ "$RC" -eq 0 ] || {
4657 echo "e2e FAIL: session ring: ptyclient leg exited $RC (did \\x1cn wrap?):"
4658 cat "$OUT.ring.log"; exit 1; }
4659 for s in 0:m3a 1:m3b 2:m3c; do
4660 _sess="${s%%:*}"; _mark="${s##*:}-pin"
4661 timeout 20 "$MUXA" capture --sock "$SOCK29" --session "$_sess" > "$OUT.ringcap" 2>&1
4662 grep -q "$_mark" "$OUT.ringcap" || {
4663 echo "e2e FAIL: session ring: session $_sess does not hold $_mark:"
4664 cat "$OUT.ringcap"; exit 1; }
4665 # Its neighbours' markers are not in it: the client moved between
4666 # sessions, it did not drag one session's shell along behind it.
4667 grep -v "$_mark" "$OUT.ringcap" | grep -qE 'm3[abc]-pin' && {
4668 echo "e2e FAIL: session ring: session $_sess holds another session's marker:"
4669 cat "$OUT.ringcap"; exit 1; }
4670 done
4671 assert_stopped "$SOCK29" "$D26PID" "session ring" "$OUT.ringstop"
4672 D26PID=""
4673 ok "Ctrl-\\ n / Ctrl-\\ p: the ring steps both ways and wraps at both ends"
4674
4585 # The long-lived daemon has served every scenario that wanted it; stop it 4675 # The long-lived daemon has served every scenario that wanted it; stop it
4586 # NOW so its allocator verdict is written while the suite is still running 4676 # NOW so its allocator verdict is written while the suite is still running
4587 # and can say so. SIGTERM runs the clean-shutdown path, so the defer chain 4677 # and can say so. SIGTERM runs the clean-shutdown path, so the defer chain
@@ -4597,7 +4687,7 @@ DPID=""
4597 4687
4598 # The pins. Literals, not variables set from counting something else — 4688 # The pins. Literals, not variables set from counting something else —
4599 # "assert the literal, never the constant the code under test reads" 4689 # "assert the literal, never the constant the code under test reads"
4600 # (decisions.md, M10). 35 scenario checkpoints; 35 convergence points. 4690 # (decisions.md, M10). 36 scenario checkpoints; 35 convergence points.
4601 # Anyone adding a scenario updates these by hand, on purpose. 4691 # Anyone adding a scenario updates these by hand, on purpose.
4602 # 4692 #
4603 # M18 added three checkpoints and no convergence points: its wall block 4693 # M18 added three checkpoints and no convergence points: its wall block
@@ -4626,9 +4716,11 @@ DPID=""
4626 # that never saw the bytes cannot testify to either way. The 35th is 4716 # that never saw the bytes cannot testify to either way. The 35th is
4627 # Ctrl-\ c, and no convergence point either: what it compares is TWO 4717 # Ctrl-\ c, and no convergence point either: what it compares is TWO
4628 # sessions' grids against each other — one holding a marker, one refusing 4718 # sessions' grids against each other — one holding a marker, one refusing
4629 # to — and assert_converged speaks about one session at a time. 4719 # to — and assert_converged speaks about one session at a time. The 36th is
4630 [ "$OK_COUNT" = "35" ] || { 4720 # the session ring, and no convergence point for that same reason a second
4631 echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 35 —" 4721 # time: it compares THREE sessions' grids against each other.
4722 [ "$OK_COUNT" = "36" ] || {
4723 echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 36 —"
4632 echo " a scenario was added (update the pin) or silently lost" 4724 echo " a scenario was added (update the pin) or silently lost"
4633 exit 1 4725 exit 1
4634 } 4726 }
@@ -4636,4 +4728,4 @@ DPID=""
4636 echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 35" 4728 echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 35"
4637 exit 1 4729 exit 1
4638 } 4730 }
4639 echo "e2e OK (35 scenarios, 35 convergence points)" 4731 echo "e2e OK (36 scenarios, 35 convergence points)"