a73x

479b5c2e

test: e2e - a rebooted daemon's panes wear gone and the cut never moves

a73x   2026-09-01 10:50

Commit message
test: e2e - a rebooted daemon's panes wear gone and the cut never moves

Three sessions, a saved three-pane cut, and a daemon that comes back
holding only its own `0`. The wall paints the saved cut and stands: pane
0 binds, panes b and c dress `gone` where they were cut. Enter in pane b
creates session b anew in the same rect; `x` in pane c drops that tile on
the first press, and only that collapses anything.

Three sessions and not two because the fates have to differ per pane, and
a two-pane fixture could not tell "the cut stood" from "the cut happened
to be the default".

The claim is measured twice, because a capture is a stream and holds
every rail the run ever painted: the rail columns catch a re-cut that
MOVES a boundary, and the sidecar run 2 saves catches one that removes a
pane. Against the pre-gone-panes build (a6f3c66c, scratch worktree,
2026-09-01) the rails compared EQUAL — both of the original rails were
still in the capture from the opening paint — while the sidecar read one
leaf where three were saved. Both assertions are kept for that reason.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

test/e2e.sh
Old New
@@ -170,8 +170,8 @@ done
170 # one of those and adds a convergence point would be pinning a fact every 170 # one of those and adds a convergence point would be pinning a fact every
171 # leg above already establishes. 171 # leg above already establishes.
172 172
173 [ "$OK_COUNT" = "100" ] || { 173 [ "$OK_COUNT" = "101" ] || {
174 echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 100 —" 174 echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 101 —"
175 echo " a scenario was added (update the pin) or silently lost" 175 echo " a scenario was added (update the pin) or silently lost"
176 exit 1 176 exit 1
177 } 177 }
test/e2e_12_panes.sh
Old New
@@ -35,6 +35,8 @@ SOCK60="${TMPDIR:-/tmp}/muxd-e2e-lpheal-$$.sock"
35 defer_sock "$SOCK60" 35 defer_sock "$SOCK60"
36 SOCK61="${TMPDIR:-/tmp}/muxd-e2e-lpdegrade-$$.sock" 36 SOCK61="${TMPDIR:-/tmp}/muxd-e2e-lpdegrade-$$.sock"
37 defer_sock "$SOCK61" 37 defer_sock "$SOCK61"
38 SOCK62="${TMPDIR:-/tmp}/muxd-e2e-gonepanes-$$.sock"
39 defer_sock "$SOCK62"
38 40
39 # --- the cursor rests in the focused tile, whoever painted last ---------- 41 # --- the cursor rests in the focused tile, whoever painted last ----------
40 # The cursor-ownership leg: a daemon of its own, for the grid-size reason 42 # The cursor-ownership leg: a daemon of its own, for the grid-size reason
@@ -907,3 +909,190 @@ D62PID=""
907 rm -rf "$LPDSTATE" 909 rm -rf "$LPDSTATE"
908 ok "a corrupted sidecar degrades silently to the default layout" 910 ok "a corrupted sidecar degrades silently to the default layout"
909 911
912 # ---- gone panes: a rebooted daemon keeps the cut ----------------------
913 #
914 # The spec's scenario, end to end: three sessions, a saved three-pane cut,
915 # and a daemon that comes back EMPTY. The wall paints the saved cut and
916 # never moves it — panes b and c dress `gone` where they stood instead of
917 # collapsing out, which is what the pre-gone-panes build did on the first
918 # poll answer. Enter in pane b creates session b anew in the same rect; a
919 # later run `x`es pane c, and only THAT collapses it.
920 #
921 # Three sessions and not two: the fates have to differ per pane (one binds
922 # live, one revives, one is dismissed), and a two-pane fixture could not
923 # tell "the cut stood" from "the cut happened to be the default".
924 GPSTATE="${TMPDIR:-/tmp}/mux-e2e-gonepanes-state-$$"
925 defer_rm "$GPSTATE"
926 GPHOSTS="$GPSTATE/mux/hosts"
927 GPLAYOUT="$GPSTATE/mux/layout"
928 start_daemon "$SOCK62" "$OUT.gp.d" "gone-panes daemon never bound" --shell /bin/sh
929 D63PID=$DPID
930
931 pipe_mux "$OUT.gpa" "$OUT.gpa.err" env XDG_STATE_HOME="$GPSTATE" timeout 40 "$MUX" --sock "$SOCK62"
932 pipe_send 'printf "gp-%%s\\n" zero\n'
933 await_out "$OUT.gpa" "gp-zero" "gone-panes: the default session's marker never reached the client"
934 pipe_detach
935 wait_grid "$SOCK62" "gp-zero" "gone-panes: the default session's marker"
936 pipe_mux "$OUT.gpb" "$OUT.gpb.err" env XDG_STATE_HOME="$GPSTATE" timeout 40 "$MUX" --sock "$SOCK62" --session b
937 pipe_send 'printf "gp-%%s\\n" bee\n'
938 await_out "$OUT.gpb" "gp-bee" "gone-panes: session b's marker never reached the client"
939 pipe_detach
940 wait_grid "$SOCK62" "gp-bee" "gone-panes: session b's marker" b
941 pipe_mux "$OUT.gpc" "$OUT.gpc.err" env XDG_STATE_HOME="$GPSTATE" timeout 40 "$MUX" --sock "$SOCK62" --session c
942 pipe_send 'printf "gp-%%s\\n" sea\n'
943 await_out "$OUT.gpc" "gp-sea" "gone-panes: session c's marker never reached the client"
944 pipe_detach
945 wait_grid "$SOCK62" "gp-sea" "gone-panes: session c's marker" c
946
947 # One line, the daemon. Its three sessions are what the poll turns into
948 # three panes; the wall knows nothing about them until it asks.
949 mkdir -p "$GPSTATE/mux"
950 printf -- '--sock %s\n' "$SOCK62" > "$GPHOSTS"
951 # No saved tree yet, for the restore leg's reason: what this leg saves and
952 # reads back has to be the tree run 1 builds.
953 no_saved_tree "$GPSTATE"
954
955 set +e
956 # Run 1: hydrate the three-pane wall on a tty and detach. The detach saves
957 # the sidecar, which is the cut every later run has to be measured against.
958 XDG_STATE_HOME="$GPSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \
959 --out "$OUT.gpcap1" --err "$OUT.gpcap1.err" -- \
960 "$MUX" > "$OUT.gppc1" 2>&1 <<'EOF'
961 expect gp-zero 20000
962 settle 1200 25000
963 send \x1cd
964 waitexit 10000
965 EOF
966 RC=$?
967 set -e
968 [ "$RC" -eq 0 ] || {
969 echo "e2e FAIL: gone-panes: run 1's ptyclient exited $RC:"
970 cat "$OUT.gppc1" "$OUT.gpcap1.err"; exit 1; }
971 [ -f "$GPLAYOUT" ] || {
972 echo "e2e FAIL: gone-panes: sidecar not saved at $GPLAYOUT"
973 ls -la "$GPSTATE/mux/" 2>&1; exit 1; }
974 # Three leaves, and the ORDER matters below: a seeded pane takes the tile
975 # index of its leaf, so `Ctrl-\ 2` is leaf 1 and `Ctrl-\ 3` is leaf 2. Pin
976 # the mapping here rather than in a comment, or a change in the order the
977 # poll births tiles would silently retarget every chord this leg sends.
978 _gp_leaves=$(sed -n 's/^ *leaf [0-9][0-9]* .*#\(.*\)$/\1/p' "$GPLAYOUT" | tr '\n' ' ')
979 [ "$_gp_leaves" = "0 b c " ] || {
980 echo "e2e FAIL: gone-panes: sidecar leaves are '$_gp_leaves', want '0 b c ':"
981 cat "$GPLAYOUT"; exit 1; }
982 # The rails of the saved cut: a three-way `beside` at 80 columns has two.
983 # The glyph is matched WHOLE — CUP, reverse video, ONE space, reset — for
984 # the layout-restore leg's reason: a label bar opens with the same CUP+SGR
985 # at its pane's own left column, and a looser prefix counts bars as rails.
986 _gp_railre=$'\x1b\[[0-9]+;[0-9]+H\x1b\[7m \x1b\[0m'
987 _gp_rails1=$(grep -aoE "$_gp_railre" "$OUT.gpcap1" |
988 sed -E 's/.*;([0-9]+)H.*/\1/' | sort -n | uniq | tr '\n' ' ')
989 [ "$(printf '%s' "$_gp_rails1" | wc -w)" -eq 2 ] || {
990 echo "e2e FAIL: gone-panes: run 1 painted rails at '$_gp_rails1', want two columns"
991 echo " — the three-pane cut this leg measures never happened"
992 cat "$OUT.gppc1"; exit 1; }
993
994 # The reboot: the same socket, a fresh daemon, none of our sessions on it.
995 # A fresh daemon is born holding `0`, so pane 0 has a live session to bind
996 # and panes b and c have none — reachable host, missing sessions, which is
997 # exactly the `gone` case and not the `unreachable` one.
998 assert_stopped "$SOCK62" "$D63PID" "gone-panes" "$OUT.gpstop1"
999 start_daemon "$SOCK62" "$OUT.gp.d2" "gone-panes daemon 2 never bound" --shell /bin/sh
1000 D63PID=$DPID
1001
1002 set +e
1003 # Run 2: the saved cut must paint and STAY. No chord, no resize, nothing
1004 # but time — two and a half seconds of quiet is several poll answers, and
1005 # the first of them is what used to re-cut this wall. The marker at the end
1006 # proves the one pane that DID find its session is bound to a live shell.
1007 XDG_STATE_HOME="$GPSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \
1008 --out "$OUT.gpcap2" --err "$OUT.gpcap2.err" -- \
1009 "$MUX" > "$OUT.gppc2" 2>&1 <<'EOF'
1010 settle 2500 30000
1011 send printf 'gp-%s\n' watch\n
1012 expect gp-watch 20000
1013 settle 700 15000
1014 send \x1cd
1015 waitexit 10000
1016 EOF
1017 RC=$?
1018 set -e
1019 [ "$RC" -eq 0 ] || {
1020 echo "e2e FAIL: gone-panes: run 2's ptyclient exited $RC:"
1021 cat "$OUT.gppc2" "$OUT.gpcap2.err"; exit 1; }
1022 # The word reached the grid. A bar is byte-truncated from the LABEL end and
1023 # the state survives that cut (`labelText`), so `[gone]` is on screen for
1024 # any pane width this leg could produce.
1025 grep -qa '\[gone\]' "$OUT.gpcap2" || {
1026 echo "e2e FAIL: gone-panes: no pane wore [gone] after the reboot:"
1027 cat "$OUT.gppc2"; exit 1; }
1028 # The rails did not move. A capture is a stream and holds every rail the
1029 # run ever painted, so this catches a re-cut that MOVES a boundary; the
1030 # sidecar below is what catches one that removes a pane (measured against
1031 # the pre-gone-panes build 2026-09-01: it collapsed to a single leaf while
1032 # leaving both of the original rails in this capture).
1033 _gp_rails2=$(grep -aoE "$_gp_railre" "$OUT.gpcap2" |
1034 sed -E 's/.*;([0-9]+)H.*/\1/' | sort -n | uniq | tr '\n' ' ')
1035 [ "$_gp_rails1" = "$_gp_rails2" ] || {
1036 echo "e2e FAIL: gone-panes: rails moved across the reboot (run 1: $_gp_rails1 run 2: $_gp_rails2)"
1037 cat "$OUT.gppc2"; exit 1; }
1038 # ...and the structural half, which is the one the pre-gone-panes build
1039 # fails: run 2 detached from a wall of THREE panes, still named 0, b and c.
1040 # A build that collapses a disowned pane saves a sidecar of one leaf here.
1041 _gp_leaves2=$(sed -n 's/^ *leaf [0-9][0-9]* .*#\(.*\)$/\1/p' "$GPLAYOUT" | tr '\n' ' ')
1042 [ "$_gp_leaves2" = "0 b c " ] || {
1043 echo "e2e FAIL: gone-panes: after the reboot the saved cut is '$_gp_leaves2', want '0 b c ':"
1044 echo " — the panes whose sessions the daemon forgot were collapsed out"
1045 cat "$GPLAYOUT"; exit 1; }
1046
1047 set +e
1048 # Run 3: the two verbs a gone pane has. Enter in pane b (leaf 1) creates
1049 # session b anew in the same rect; `x` in pane c (leaf 2) drops that tile
1050 # on the KEYPRESS — one press, because a pane with no pump reads no ask and
1051 # `endKey` answers `.drop` rather than arming the two-step.
1052 XDG_STATE_HOME="$GPSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \
1053 --out "$OUT.gpcap3" --err "$OUT.gpcap3.err" -- \
1054 "$MUX" > "$OUT.gppc3" 2>&1 <<'EOF'
1055 settle 2500 30000
1056 send \x1c2
1057 settle 700 15000
1058 send \r
1059 settle 2000 25000
1060 send printf 'gp-%s\n' reborn\n
1061 expect gp-reborn 20000
1062 settle 700 15000
1063 send \x1c3
1064 settle 700 15000
1065 send \x1cx
1066 settle 1500 20000
1067 send \x1cd
1068 waitexit 10000
1069 EOF
1070 RC=$?
1071 set -e
1072 [ "$RC" -eq 0 ] || {
1073 echo "e2e FAIL: gone-panes: run 3's ptyclient exited $RC:"
1074 cat "$OUT.gppc3" "$OUT.gpcap3.err"; exit 1; }
1075 # The revived session is REAL and carries the name its pane remembered:
1076 # the daemon's own grid for `b` holds the marker, and the entry session
1077 # does not — a revive that created `0` again, or created nothing and typed
1078 # into the focused survivor, fails one of these two.
1079 wait_sessions "$SOCK62" 2 "gone-panes: the daemon should hold 0 and the revived b"
1080 timeout 20 "$MUX" a capture --sock "$SOCK62" --session b > "$OUT.gprb" 2>&1
1081 grep -q "gp-reborn" "$OUT.gprb" || {
1082 echo "e2e FAIL: gone-panes: the reborn marker is not in session b:"
1083 cat "$OUT.gprb"; exit 1; }
1084 timeout 20 "$MUX" a capture --sock "$SOCK62" > "$OUT.gpra" 2>&1
1085 ! grep -q "gp-reborn" "$OUT.gpra" || {
1086 echo "e2e FAIL: gone-panes: the reborn marker landed in session 0 — Enter did not"
1087 echo " revive the pane it was pressed in:"
1088 cat "$OUT.gpra"; exit 1; }
1089 # `x` dismissed pane c and nothing else: two leaves, and c is not one of
1090 # them. The sidecar is written on DETACH, so this reads run 3's save.
1091 _gp_leaves3=$(sed -n 's/^ *leaf [0-9][0-9]* .*#\(.*\)$/\1/p' "$GPLAYOUT" | tr '\n' ' ')
1092 [ "$_gp_leaves3" = "0 b " ] || {
1093 echo "e2e FAIL: gone-panes: after x the saved cut is '$_gp_leaves3', want '0 b ':"
1094 cat "$GPLAYOUT"; exit 1; }
1095 assert_stopped "$SOCK62" "$D63PID" "gone-panes" "$OUT.gpstop2"
1096 D63PID=""
1097 rm -rf "$GPSTATE"
1098 ok "a rebooted daemon's panes wear gone and the cut never moves; Enter revives, x dismisses"