789fd74e
test: the wall's column era gets its legs
a73x 2026-08-24 18:06
Commit message
test/e2e.sh
| Old | New | ||
|---|---|---|---|
| @@ -372,9 +372,15 @@ ZMOUSESH="${TMPDIR:-/tmp}/mux-e2e-wallappmouse-$$.sh" | |||
| 372 | SOCK53="${TMPDIR:-/tmp}/muxd-e2e-sbrect-$$.sock" | 372 | SOCK53="${TMPDIR:-/tmp}/muxd-e2e-sbrect-$$.sock" |
| 373 | SOCK54="${TMPDIR:-/tmp}/muxd-e2e-fullscreen-$$.sock" | 373 | SOCK54="${TMPDIR:-/tmp}/muxd-e2e-fullscreen-$$.sock" |
| 374 | SOCK55="${TMPDIR:-/tmp}/muxd-e2e-resize-$$.sock" | 374 | SOCK55="${TMPDIR:-/tmp}/muxd-e2e-resize-$$.sock" |
| 375 | SOCK56="${TMPDIR:-/tmp}/muxd-e2e-spanclear-$$.sock" | ||
| 376 | SOCK57="${TMPDIR:-/tmp}/muxd-e2e-hjkl-$$.sock" | ||
| 377 | SOCK58="${TMPDIR:-/tmp}/muxd-e2e-splitbirth-$$.sock" | ||
| 375 | D54PID="" | 378 | D54PID="" |
| 376 | D55PID="" | 379 | D55PID="" |
| 377 | D56PID="" | 380 | D56PID="" |
| 381 | D57PID="" | ||
| 382 | D58PID="" | ||
| 383 | D59PID="" | ||
| 378 | 384 | ||
| 379 | # The wall as attach HISTORY (phase 2). A daemon AND a state home of its | 385 | # The wall as attach HISTORY (phase 2). A daemon AND a state home of its |
| 380 | # own, for the dynamic-wall leg's reason turned up one notch: what these | 386 | # own, for the dynamic-wall leg's reason turned up one notch: what these |
| @@ -1275,6 +1281,9 @@ cleanup() { | |||
| 1275 | [ -n "${D51PID:-}" ] && kill "$D51PID" 2>/dev/null || true | 1281 | [ -n "${D51PID:-}" ] && kill "$D51PID" 2>/dev/null || true |
| 1276 | [ -n "${D52PID:-}" ] && kill "$D52PID" 2>/dev/null || true | 1282 | [ -n "${D52PID:-}" ] && kill "$D52PID" 2>/dev/null || true |
| 1277 | [ -n "${D55PID:-}" ] && kill "$D55PID" 2>/dev/null || true | 1283 | [ -n "${D55PID:-}" ] && kill "$D55PID" 2>/dev/null || true |
| 1284 | [ -n "${D57PID:-}" ] && kill "$D57PID" 2>/dev/null || true | ||
| 1285 | [ -n "${D58PID:-}" ] && kill "$D58PID" 2>/dev/null || true | ||
| 1286 | [ -n "${D59PID:-}" ] && kill "$D59PID" 2>/dev/null || true | ||
| 1278 | # The ssh-agents the forwarding legs start. Not mux processes and so not | 1287 | # The ssh-agents the forwarding legs start. Not mux processes and so not |
| 1279 | # the leak sweep's business, but they are daemons this file forked: left | 1288 | # the leak sweep's business, but they are daemons this file forked: left |
| 1280 | # alive they outlive the suite holding a private key, which is the one | 1289 | # alive they outlive the suite holding a private key, which is the one |
| @@ -1321,6 +1330,9 @@ cleanup() { | |||
| 1321 | [ -S "$SOCK53" ] && "$MUXD" stop --sock "$SOCK53" 2>/dev/null || true | 1330 | [ -S "$SOCK53" ] && "$MUXD" stop --sock "$SOCK53" 2>/dev/null || true |
| 1322 | [ -S "$SOCK54" ] && "$MUXD" stop --sock "$SOCK54" 2>/dev/null || true | 1331 | [ -S "$SOCK54" ] && "$MUXD" stop --sock "$SOCK54" 2>/dev/null || true |
| 1323 | [ -S "$SOCK55" ] && "$MUXD" stop --sock "$SOCK55" 2>/dev/null || true | 1332 | [ -S "$SOCK55" ] && "$MUXD" stop --sock "$SOCK55" 2>/dev/null || true |
| 1333 | [ -S "$SOCK56" ] && "$MUXD" stop --sock "$SOCK56" 2>/dev/null || true | ||
| 1334 | [ -S "$SOCK57" ] && "$MUXD" stop --sock "$SOCK57" 2>/dev/null || true | ||
| 1335 | [ -S "$SOCK58" ] && "$MUXD" stop --sock "$SOCK58" 2>/dev/null || true | ||
| 1324 | 1336 | ||
| 1325 | # ---- the leak sweep (hygiene kit, 6a) ---- | 1337 | # ---- the leak sweep (hygiene kit, 6a) ---- |
| 1326 | # Here rather than at the bottom of the file, which `set -e` reaches only | 1338 | # Here rather than at the bottom of the file, which `set -e` reaches only |
| @@ -1336,7 +1348,7 @@ cleanup() { | |||
| 1336 | "$D26PID" "$D27PID" "$D28PID" "$D29PID" "$D30PID" "$D31PID" \ | 1348 | "$D26PID" "$D27PID" "$D28PID" "$D29PID" "$D30PID" "$D31PID" \ |
| 1337 | "$D32PID" "$D33PID" "$D34PID" "$D35PID" "$D36PID" "$D37PID" \ | 1349 | "$D32PID" "$D33PID" "$D34PID" "$D35PID" "$D36PID" "$D37PID" \ |
| 1338 | "$D38PID" "$D39PID" "$D40PID" "$D41PID" "$D42PID" "$D43PID" "$D54PID" \ | 1350 | "$D38PID" "$D39PID" "$D40PID" "$D41PID" "$D42PID" "$D43PID" "$D54PID" \ |
| 1339 | "$D55PID" "$D56PID" | 1351 | "$D55PID" "$D56PID" "$D57PID" "$D58PID" "$D59PID" |
| 1340 | _leak=0 | 1352 | _leak=0 |
| 1341 | leak_sweep "$_rc" || _leak=1 | 1353 | leak_sweep "$_rc" || _leak=1 |
| 1342 | 1354 | ||
| @@ -1411,7 +1423,14 @@ cleanup() { | |||
| 1411 | "$OUT.fsstop" "$SOCK54" \ | 1423 | "$OUT.fsstop" "$SOCK54" \ |
| 1412 | "$OUT.rsz.d" "$OUT.rszcap" "$OUT.rszcap.err" "$OUT.rszpc" \ | 1424 | "$OUT.rsz.d" "$OUT.rszcap" "$OUT.rszcap.err" "$OUT.rszpc" \ |
| 1413 | "$OUT.rsza" "$OUT.rsza.err" "$OUT.rszb" "$OUT.rszb.err" \ | 1425 | "$OUT.rsza" "$OUT.rsza.err" "$OUT.rszb" "$OUT.rszb.err" \ |
| 1414 | "$OUT.rszsta" "$OUT.rszstb" "$OUT.rszstop" "$SOCK55" | 1426 | "$OUT.rszsta" "$OUT.rszstb" "$OUT.rszstop" "$SOCK55" \ |
| 1427 | "$OUT.spc.d" "$OUT.spca" "$OUT.spca.err" "$OUT.spcb" "$OUT.spcb.err" \ | ||
| 1428 | "$OUT.spccap" "$OUT.spccap.err" "$OUT.spcpc" "$OUT.spcgrid" \ | ||
| 1429 | "$OUT.spcstop" "$SOCK56" \ | ||
| 1430 | "$OUT.hjk.d" "$OUT.hjkcap" "$OUT.hjkcap.err" "$OUT.hjkpc" \ | ||
| 1431 | "$OUT.hjkfa" "$OUT.hjkfb" "$OUT.hjkfc" "$OUT.hjkstop" "$SOCK57" \ | ||
| 1432 | "$OUT.spl.d" "$OUT.splcap" "$OUT.splcap.err" "$OUT.splpc" \ | ||
| 1433 | "$OUT.splsta" "$OUT.splstop" "$SOCK58" | ||
| 1415 | # ...and the non-tty capture that leg's session feeds. | 1434 | # ...and the non-tty capture that leg's session feeds. |
| 1416 | rm -f "$OUT.nogate" | 1435 | rm -f "$OUT.nogate" |
| 1417 | # ...and its other half: the paste capture and the file nvim wrote, which | 1436 | # ...and its other half: the paste capture and the file nvim wrote, which |
| @@ -7755,8 +7774,198 @@ assert_stopped "$SOCK55" "$D56PID" "resize" "$OUT.rszstop" | |||
| 7755 | D56PID="" | 7774 | D56PID="" |
| 7756 | ok "Ctrl-\\ r trades cells between panes; Esc returns to prose" | 7775 | ok "Ctrl-\\ r trades cells between panes; Esc returns to prose" |
| 7757 | 7776 | ||
| 7758 | [ "$OK_COUNT" = "65" ] || { | 7777 | # ---- span-clear survival: the engine oracle ------------------------- |
| 7759 | echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 65 —" | 7778 | # |
| 7779 | # Two beside panes at 80x24 (aspect rule: cols >= 2*rows → beside). A | ||
| 7780 | # marker in the RIGHT pane's session, then a flood in the LEFT pane's | ||
| 7781 | # session (seq 1 200). The regression this catches forever is a whole-line | ||
| 7782 | # clear (\x1b[2K) blanking the neighbor: a painter that emits too much | ||
| 7783 | # still passes a byte grep, so the assertion replays the wall client's | ||
| 7784 | # FULL captured stdout through the render fixture (an engine, not a grep) | ||
| 7785 | # and checks the right pane's marker text survives on the replayed grid. | ||
| 7786 | "$MUXD" run --sock "$SOCK56" --shell /bin/sh > "$OUT.spc.d" 2>&1 & | ||
| 7787 | D57PID=$! | ||
| 7788 | wait_sock "$SOCK56" "$OUT.spc.d" "span-clear daemon never bound" | ||
| 7789 | |||
| 7790 | pipe_mux "$OUT.spca" "$OUT.spca.err" timeout 40 "$MUX" --sock "$SOCK56" --session a | ||
| 7791 | pipe_send 'printf "spc-%%s\\n" left-flood\n' | ||
| 7792 | await_out "$OUT.spca" "spc-left-flood" "span-clear: left session's marker never reached the client" | ||
| 7793 | pipe_detach | ||
| 7794 | wait_grid "$SOCK56" "spc-left-flood" "span-clear: left session's marker" a | ||
| 7795 | pipe_mux "$OUT.spcb" "$OUT.spcb.err" timeout 40 "$MUX" --sock "$SOCK56" --session b | ||
| 7796 | pipe_send 'printf "spc-%%s\\n" right-survivor\n' | ||
| 7797 | await_out "$OUT.spcb" "spc-right-survivor" "span-clear: right session's marker never reached the client" | ||
| 7798 | pipe_detach | ||
| 7799 | wait_grid "$SOCK56" "spc-right-survivor" "span-clear: right session's marker" b | ||
| 7800 | |||
| 7801 | set +e | ||
| 7802 | # 80x24: aspect rule → beside. The wall client is a tty session: its | ||
| 7803 | # stdout is the terminal's full byte stream, captured by ptyclient. | ||
| 7804 | timeout 90 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.spccap" --err "$OUT.spccap.err" -- \ | ||
| 7805 | "$MUX" wall "--sock $SOCK56#a" "--sock $SOCK56#b" > "$OUT.spcpc" 2>&1 <<'EOF' | ||
| 7806 | expect spc-right-survivor 20000 | ||
| 7807 | settle 700 20000 | ||
| 7808 | send \x1ch | ||
| 7809 | settle 500 15000 | ||
| 7810 | send seq 1 200\n | ||
| 7811 | expect 200 20000 | ||
| 7812 | settle 700 20000 | ||
| 7813 | send \x1cd | ||
| 7814 | waitexit 10000 | ||
| 7815 | EOF | ||
| 7816 | RC=$? | ||
| 7817 | set -e | ||
| 7818 | [ "$RC" -eq 0 ] || { | ||
| 7819 | echo "e2e FAIL: span-clear: ptyclient leg exited $RC:" | ||
| 7820 | cat "$OUT.spcpc" "$OUT.spccap.err"; exit 1; } | ||
| 7821 | # The oracle: replay the captured stream through the render fixture at the | ||
| 7822 | # same dimensions, producing the final grid text. The right pane's marker | ||
| 7823 | # must still be on that grid — a span-clear regression blanks it. | ||
| 7824 | "$RENDER" --cols 80 --rows 24 < "$OUT.spccap" > "$OUT.spcgrid" || { | ||
| 7825 | echo "e2e FAIL: span-clear: render fixture failed on the capture:" | ||
| 7826 | cat "$OUT.spcgrid"; exit 1; } | ||
| 7827 | grep -q "spc-right-survivor" "$OUT.spcgrid" || { | ||
| 7828 | echo "e2e FAIL: span-clear: right pane's marker lost from the replayed grid:" | ||
| 7829 | echo " a span-clear regression blanked the neighbor:" | ||
| 7830 | cat "$OUT.spcgrid"; exit 1; } | ||
| 7831 | assert_stopped "$SOCK56" "$D57PID" "span-clear" "$OUT.spcstop" | ||
| 7832 | D57PID="" | ||
| 7833 | ok "a span-clear flood in the left pane cannot blank the right pane's grid" | ||
| 7834 | |||
| 7835 | # ---- hjkl focus walk ------------------------------------------------- | ||
| 7836 | # | ||
| 7837 | # One tile, split right (births a second session), split below (births a | ||
| 7838 | # third). Then walk focus with hjkl and type a distinct marker after each | ||
| 7839 | # move; muxa capture names which session received it. Covers at least a | ||
| 7840 | # left move, a right move, and one vertical move. | ||
| 7841 | # | ||
| 7842 | # No convergence point: the assertion is per-session muxa capture, not a | ||
| 7843 | # daemon-grid diff. | ||
| 7844 | "$MUXD" run --sock "$SOCK57" --shell /bin/sh > "$OUT.hjk.d" 2>&1 & | ||
| 7845 | D58PID=$! | ||
| 7846 | wait_sock "$SOCK57" "$OUT.hjk.d" "hjkl daemon never bound" | ||
| 7847 | |||
| 7848 | pipe_mux "$OUT.hjkcap" "$OUT.hjkcap.err" timeout 40 "$MUX" --sock "$SOCK57" --session a | ||
| 7849 | pipe_send 'printf "hjk-%%s\\n" origin\n' | ||
| 7850 | await_out "$OUT.hjkcap" "hjk-origin" "hjkl: origin marker never reached the client" | ||
| 7851 | pipe_detach | ||
| 7852 | wait_grid "$SOCK57" "hjk-origin" "hjkl: origin marker" a | ||
| 7853 | |||
| 7854 | set +e | ||
| 7855 | # 80x24: aspect rule → beside for the first split; the second split goes | ||
| 7856 | # below, producing a 1 | (2 over 3) tree. | ||
| 7857 | timeout 90 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.hjkcap" --err "$OUT.hjkcap.err" -- \ | ||
| 7858 | "$MUX" wall "--sock $SOCK57#a" > "$OUT.hjkpc" 2>&1 <<'EOF' | ||
| 7859 | expect hjk-origin 20000 | ||
| 7860 | settle 700 20000 | ||
| 7861 | send \x1c| | ||
| 7862 | settle 1000 20000 | ||
| 7863 | send \x1c- | ||
| 7864 | settle 1000 20000 | ||
| 7865 | send \x1ch | ||
| 7866 | settle 500 15000 | ||
| 7867 | send printf 'hjk-%s\n' went-left\n | ||
| 7868 | expect hjk-went-left 10000 | ||
| 7869 | settle 500 15000 | ||
| 7870 | send \x1cl | ||
| 7871 | settle 500 15000 | ||
| 7872 | send printf 'hjk-%s\n' went-right\n | ||
| 7873 | expect hjk-went-right 10000 | ||
| 7874 | settle 500 15000 | ||
| 7875 | send \x1cj | ||
| 7876 | settle 500 15000 | ||
| 7877 | send printf 'hjk-%s\n' went-down\n | ||
| 7878 | expect hjk-went-down 10000 | ||
| 7879 | settle 500 15000 | ||
| 7880 | send \x1cd | ||
| 7881 | waitexit 10000 | ||
| 7882 | EOF | ||
| 7883 | RC=$? | ||
| 7884 | set -e | ||
| 7885 | [ "$RC" -eq 0 ] || { | ||
| 7886 | echo "e2e FAIL: hjkl: ptyclient leg exited $RC:" | ||
| 7887 | cat "$OUT.hjkpc" "$OUT.hjkcap.err"; exit 1; } | ||
| 7888 | # Three sessions exist now: a (the origin), and two birthed by splits. | ||
| 7889 | # The daemon assigns numeric names to the new sessions; capture all three. | ||
| 7890 | timeout 20 "$MUXA" capture --sock "$SOCK57" --session a > "$OUT.hjkfa" 2>&1 | ||
| 7891 | timeout 20 "$MUXA" capture --sock "$SOCK57" --session 1 > "$OUT.hjkfb" 2>&1 | ||
| 7892 | timeout 20 "$MUXA" capture --sock "$SOCK57" --session 2 > "$OUT.hjkfc" 2>&1 | ||
| 7893 | # went-left: typed after \x1ch from the right pane → lands in session a. | ||
| 7894 | grep -q "hjk-went-left" "$OUT.hjkfa" || { | ||
| 7895 | echo "e2e FAIL: hjkl: hjk-went-left not in session a (left focus move):" | ||
| 7896 | cat "$OUT.hjkfa"; exit 1; } | ||
| 7897 | # went-right: typed after \x1cl from session a → lands in session 1. | ||
| 7898 | grep -q "hjk-went-right" "$OUT.hjkfb" || { | ||
| 7899 | echo "e2e FAIL: hjkl: hjk-went-right not in session 1 (right focus move):" | ||
| 7900 | cat "$OUT.hjkfb"; exit 1; } | ||
| 7901 | # went-down: typed after \x1cj from session 1 → lands in session 2. | ||
| 7902 | grep -q "hjk-went-down" "$OUT.hjkfc" || { | ||
| 7903 | echo "e2e FAIL: hjkl: hjk-went-down not in session 2 (down focus move):" | ||
| 7904 | cat "$OUT.hjkfc"; exit 1; } | ||
| 7905 | assert_stopped "$SOCK57" "$D58PID" "hjkl" "$OUT.hjkstop" | ||
| 7906 | D58PID="" | ||
| 7907 | ok "hjkl walks focus across panes in both axes" | ||
| 7908 | |||
| 7909 | # ---- split births a session beside ----------------------------------- | ||
| 7910 | # | ||
| 7911 | # From one pane, Ctrl-\ | splits right and births a new session. The | ||
| 7912 | # daemon's session count rises to two, and the new pane's label bar sits | ||
| 7913 | # at a column to the right of the rail (the rail paints as ESC[r;cH | ||
| 7914 | # ESC[7m — the same idiom the resize leg uses). | ||
| 7915 | "$MUXD" run --sock "$SOCK58" --shell /bin/sh > "$OUT.spl.d" 2>&1 & | ||
| 7916 | D59PID=$! | ||
| 7917 | wait_sock "$SOCK58" "$OUT.spl.d" "split-birth daemon never bound" | ||
| 7918 | |||
| 7919 | pipe_mux "$OUT.splcap" "$OUT.splcap.err" timeout 40 "$MUX" --sock "$SOCK58" --session a | ||
| 7920 | pipe_send 'printf "spl-%%s\\n" origin\n' | ||
| 7921 | await_out "$OUT.splcap" "spl-origin" "split-birth: origin marker never reached the client" | ||
| 7922 | pipe_detach | ||
| 7923 | wait_grid "$SOCK58" "spl-origin" "split-birth: origin marker" a | ||
| 7924 | |||
| 7925 | set +e | ||
| 7926 | # 80x24: aspect rule → beside. One tile starts, then Ctrl-\ | births a | ||
| 7927 | # second session to the right. | ||
| 7928 | timeout 90 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.splcap" --err "$OUT.splcap.err" -- \ | ||
| 7929 | "$MUX" wall "--sock $SOCK58#a" > "$OUT.splpc" 2>&1 <<'EOF' | ||
| 7930 | expect spl-origin 20000 | ||
| 7931 | settle 700 20000 | ||
| 7932 | send \x1c| | ||
| 7933 | settle 1500 20000 | ||
| 7934 | send \x1cd | ||
| 7935 | waitexit 10000 | ||
| 7936 | EOF | ||
| 7937 | RC=$? | ||
| 7938 | set -e | ||
| 7939 | [ "$RC" -eq 0 ] || { | ||
| 7940 | echo "e2e FAIL: split-birth: ptyclient leg exited $RC:" | ||
| 7941 | cat "$OUT.splpc" "$OUT.splcap.err"; exit 1; } | ||
| 7942 | # The daemon held sessions 0 (default) and a; the split birthed one | ||
| 7943 | # more, so three live sessions proves the chord created a new session. | ||
| 7944 | wait_sessions "$SOCK58" 3 "split-birth: split did not birth a new session" | ||
| 7945 | # The rail paints as ESC[row;colH ESC[7m (reverse video). The new pane's | ||
| 7946 | # label bar is at a column greater than the rail's column — it sits to | ||
| 7947 | # the right of the separator, which is the whole point of beside. | ||
| 7948 | _rail_re=$'\x1b\\[[0-9][0-9]*;[0-9][0-9]*H\x1b\\[7m' | ||
| 7949 | _rail_col=$(grep -ao "$_rail_re" "$OUT.splcap" | \ | ||
| 7950 | sed 's/.*\x1b\[[0-9]*;\([0-9]*\)H\x1b\[7m/\1/' | sort -n | head -1) | ||
| 7951 | [ -n "$_rail_col" ] || { | ||
| 7952 | echo "e2e FAIL: split-birth: no rail glyph found in the capture:" | ||
| 7953 | cat "$OUT.splcap"; exit 1; } | ||
| 7954 | # The rail sits at the boundary; the new pane's label bar is beyond it. | ||
| 7955 | # A label bar is a CUP followed by text, not reverse-video. Find a CUP | ||
| 7956 | # whose column exceeds the rail's — that is the right pane's bar start. | ||
| 7957 | _bar_col=$(grep -ao $'\x1b\\[[0-9]*;[0-9]*H' "$OUT.splcap" | \ | ||
| 7958 | sed 's/\x1b\[[0-9]*;\([0-9]*\)H/\1/' | sort -n | \ | ||
| 7959 | awk -v r="$_rail_col" '$1 > r { print; exit }') | ||
| 7960 | [ -n "$_bar_col" ] || { | ||
| 7961 | echo "e2e FAIL: split-birth: no label bar found right of the rail (col $_rail_col):" | ||
| 7962 | cat "$OUT.splcap"; exit 1; } | ||
| 7963 | assert_stopped "$SOCK58" "$D59PID" "split-birth" "$OUT.splstop" | ||
| 7964 | D59PID="" | ||
| 7965 | ok "Ctrl-\\ | births a session beside; its label bar sits right of the rail" | ||
| 7966 | |||
| 7967 | [ "$OK_COUNT" = "68" ] || { | ||
| 7968 | echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 68 —" | ||
| 7760 | echo " a scenario was added (update the pin) or silently lost" | 7969 | echo " a scenario was added (update the pin) or silently lost" |
| 7761 | exit 1 | 7970 | exit 1 |
| 7762 | } | 7971 | } |
| @@ -7764,4 +7973,4 @@ ok "Ctrl-\\ r trades cells between panes; Esc returns to prose" | |||
| 7764 | echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 37" | 7973 | echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 37" |
| 7765 | exit 1 | 7974 | exit 1 |
| 7766 | } | 7975 | } |
| 7767 | echo "e2e OK (64 scenarios, 37 convergence points)" | 7976 | echo "e2e OK ($OK_COUNT scenarios, $CONV_COUNT convergence points)" |