9dca0bb1
test: the layout survives the round trip, heals, and degrades
a73x 2026-08-25 09:10
Commit message
test/e2e.sh
| Old | New | ||
|---|---|---|---|
| @@ -375,12 +375,18 @@ SOCK55="${TMPDIR:-/tmp}/muxd-e2e-resize-$$.sock" | |||
| 375 | SOCK56="${TMPDIR:-/tmp}/muxd-e2e-spanclear-$$.sock" | 375 | SOCK56="${TMPDIR:-/tmp}/muxd-e2e-spanclear-$$.sock" |
| 376 | SOCK57="${TMPDIR:-/tmp}/muxd-e2e-hjkl-$$.sock" | 376 | SOCK57="${TMPDIR:-/tmp}/muxd-e2e-hjkl-$$.sock" |
| 377 | SOCK58="${TMPDIR:-/tmp}/muxd-e2e-splitbirth-$$.sock" | 377 | SOCK58="${TMPDIR:-/tmp}/muxd-e2e-splitbirth-$$.sock" |
| 378 | SOCK59="${TMPDIR:-/tmp}/muxd-e2e-lprrestore-$$.sock" | ||
| 379 | SOCK60="${TMPDIR:-/tmp}/muxd-e2e-lpheal-$$.sock" | ||
| 380 | SOCK61="${TMPDIR:-/tmp}/muxd-e2e-lpdegrade-$$.sock" | ||
| 378 | D54PID="" | 381 | D54PID="" |
| 379 | D55PID="" | 382 | D55PID="" |
| 380 | D56PID="" | 383 | D56PID="" |
| 381 | D57PID="" | 384 | D57PID="" |
| 382 | D58PID="" | 385 | D58PID="" |
| 383 | D59PID="" | 386 | D59PID="" |
| 387 | D60PID="" | ||
| 388 | D61PID="" | ||
| 389 | D62PID="" | ||
| 384 | 390 | ||
| 385 | # The wall as attach HISTORY (phase 2). A daemon AND a state home of its | 391 | # The wall as attach HISTORY (phase 2). A daemon AND a state home of its |
| 386 | # own, for the dynamic-wall leg's reason turned up one notch: what these | 392 | # own, for the dynamic-wall leg's reason turned up one notch: what these |
| @@ -1284,6 +1290,9 @@ cleanup() { | |||
| 1284 | [ -n "${D57PID:-}" ] && kill "$D57PID" 2>/dev/null || true | 1290 | [ -n "${D57PID:-}" ] && kill "$D57PID" 2>/dev/null || true |
| 1285 | [ -n "${D58PID:-}" ] && kill "$D58PID" 2>/dev/null || true | 1291 | [ -n "${D58PID:-}" ] && kill "$D58PID" 2>/dev/null || true |
| 1286 | [ -n "${D59PID:-}" ] && kill "$D59PID" 2>/dev/null || true | 1292 | [ -n "${D59PID:-}" ] && kill "$D59PID" 2>/dev/null || true |
| 1293 | [ -n "${D60PID:-}" ] && kill "$D60PID" 2>/dev/null || true | ||
| 1294 | [ -n "${D61PID:-}" ] && kill "$D61PID" 2>/dev/null || true | ||
| 1295 | [ -n "${D62PID:-}" ] && kill "$D62PID" 2>/dev/null || true | ||
| 1287 | # The ssh-agents the forwarding legs start. Not mux processes and so not | 1296 | # The ssh-agents the forwarding legs start. Not mux processes and so not |
| 1288 | # the leak sweep's business, but they are daemons this file forked: left | 1297 | # the leak sweep's business, but they are daemons this file forked: left |
| 1289 | # alive they outlive the suite holding a private key, which is the one | 1298 | # alive they outlive the suite holding a private key, which is the one |
| @@ -1333,6 +1342,9 @@ cleanup() { | |||
| 1333 | [ -S "$SOCK56" ] && "$MUXD" stop --sock "$SOCK56" 2>/dev/null || true | 1342 | [ -S "$SOCK56" ] && "$MUXD" stop --sock "$SOCK56" 2>/dev/null || true |
| 1334 | [ -S "$SOCK57" ] && "$MUXD" stop --sock "$SOCK57" 2>/dev/null || true | 1343 | [ -S "$SOCK57" ] && "$MUXD" stop --sock "$SOCK57" 2>/dev/null || true |
| 1335 | [ -S "$SOCK58" ] && "$MUXD" stop --sock "$SOCK58" 2>/dev/null || true | 1344 | [ -S "$SOCK58" ] && "$MUXD" stop --sock "$SOCK58" 2>/dev/null || true |
| 1345 | [ -S "$SOCK59" ] && "$MUXD" stop --sock "$SOCK59" 2>/dev/null || true | ||
| 1346 | [ -S "$SOCK60" ] && "$MUXD" stop --sock "$SOCK60" 2>/dev/null || true | ||
| 1347 | [ -S "$SOCK61" ] && "$MUXD" stop --sock "$SOCK61" 2>/dev/null || true | ||
| 1336 | 1348 | ||
| 1337 | # ---- the leak sweep (hygiene kit, 6a) ---- | 1349 | # ---- the leak sweep (hygiene kit, 6a) ---- |
| 1338 | # Here rather than at the bottom of the file, which `set -e` reaches only | 1350 | # Here rather than at the bottom of the file, which `set -e` reaches only |
| @@ -1348,7 +1360,8 @@ cleanup() { | |||
| 1348 | "$D26PID" "$D27PID" "$D28PID" "$D29PID" "$D30PID" "$D31PID" \ | 1360 | "$D26PID" "$D27PID" "$D28PID" "$D29PID" "$D30PID" "$D31PID" \ |
| 1349 | "$D32PID" "$D33PID" "$D34PID" "$D35PID" "$D36PID" "$D37PID" \ | 1361 | "$D32PID" "$D33PID" "$D34PID" "$D35PID" "$D36PID" "$D37PID" \ |
| 1350 | "$D38PID" "$D39PID" "$D40PID" "$D41PID" "$D42PID" "$D43PID" "$D54PID" \ | 1362 | "$D38PID" "$D39PID" "$D40PID" "$D41PID" "$D42PID" "$D43PID" "$D54PID" \ |
| 1351 | "$D55PID" "$D56PID" "$D57PID" "$D58PID" "$D59PID" | 1363 | "$D55PID" "$D56PID" "$D57PID" "$D58PID" "$D59PID" \ |
| 1364 | "$D60PID" "$D61PID" "$D62PID" | ||
| 1352 | _leak=0 | 1365 | _leak=0 |
| 1353 | leak_sweep "$_rc" || _leak=1 | 1366 | leak_sweep "$_rc" || _leak=1 |
| 1354 | 1367 | ||
| @@ -1430,7 +1443,18 @@ cleanup() { | |||
| 1430 | "$OUT.hjk.d" "$OUT.hjkcap" "$OUT.hjkcap.err" "$OUT.hjkpc" \ | 1443 | "$OUT.hjk.d" "$OUT.hjkcap" "$OUT.hjkcap.err" "$OUT.hjkpc" \ |
| 1431 | "$OUT.hjkfa" "$OUT.hjkfb" "$OUT.hjkfc" "$OUT.hjkstop" "$SOCK57" \ | 1444 | "$OUT.hjkfa" "$OUT.hjkfb" "$OUT.hjkfc" "$OUT.hjkstop" "$SOCK57" \ |
| 1432 | "$OUT.spl.d" "$OUT.splcap" "$OUT.splcap.err" "$OUT.splpc" \ | 1445 | "$OUT.spl.d" "$OUT.splcap" "$OUT.splcap.err" "$OUT.splpc" \ |
| 1433 | "$OUT.splsta" "$OUT.splstop" "$SOCK58" | 1446 | "$OUT.splsta" "$OUT.splstop" "$SOCK58" \ |
| 1447 | "$OUT.lpr.d" "$OUT.lpra" "$OUT.lpra.err" "$OUT.lprb" "$OUT.lprb.err" \ | ||
| 1448 | "$OUT.lprcap1" "$OUT.lprcap1.err" "$OUT.lprpc1" \ | ||
| 1449 | "$OUT.lprcap2" "$OUT.lprcap2.err" "$OUT.lprpc2" \ | ||
| 1450 | "$OUT.lprfa" "$OUT.lprstop" "$SOCK59" \ | ||
| 1451 | "$OUT.lph.d" "$OUT.lpha" "$OUT.lpha.err" "$OUT.lphb" "$OUT.lphb.err" \ | ||
| 1452 | "$OUT.lphc" "$OUT.lphc.err" "$OUT.lphcap1" "$OUT.lphcap1.err" \ | ||
| 1453 | "$OUT.lphpc1" "$OUT.lphcap2" "$OUT.lphcap2.err" "$OUT.lphpc2" \ | ||
| 1454 | "$OUT.lphfa" "$OUT.lphstop" "$SOCK60" \ | ||
| 1455 | "$OUT.lpd.d" "$OUT.lpda" "$OUT.lpda.err" "$OUT.lpdb" "$OUT.lpdb.err" \ | ||
| 1456 | "$OUT.lpdcap" "$OUT.lpdcap.err" "$OUT.lpdpc" \ | ||
| 1457 | "$OUT.lpdfa" "$OUT.lpdstop" "$SOCK61" | ||
| 1434 | # ...and the non-tty capture that leg's session feeds. | 1458 | # ...and the non-tty capture that leg's session feeds. |
| 1435 | rm -f "$OUT.nogate" | 1459 | rm -f "$OUT.nogate" |
| 1436 | # ...and its other half: the paste capture and the file nvim wrote, which | 1460 | # ...and its other half: the paste capture and the file nvim wrote, which |
| @@ -7964,8 +7988,276 @@ assert_stopped "$SOCK58" "$D59PID" "split-birth" "$OUT.splstop" | |||
| 7964 | D59PID="" | 7988 | D59PID="" |
| 7965 | ok "Ctrl-\\ | births a session beside; its label bar sits right of the rail" | 7989 | ok "Ctrl-\\ | births a session beside; its label bar sits right of the rail" |
| 7966 | 7990 | ||
| 7967 | [ "$OK_COUNT" = "68" ] || { | 7991 | # ---- layout sidecar: restore round-trip ------------------------------ |
| 7968 | echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 68 —" | 7992 | # |
| 7993 | # A no-argv `mux wall` hydrates from the wall file, and on Ctrl-\ d the | ||
| 7994 | # resized tree is saved to the layout sidecar. Reattaching with the same | ||
| 7995 | # state home loads the sidecar verbatim — the resize survived the round | ||
| 7996 | # trip. The rail's CUP column in the second capture is >= 43 (three | ||
| 7997 | # right-resizes from a 40|40 split), and a marker typed into the restored | ||
| 7998 | # wall lands in the focused session. | ||
| 7999 | LPSTATE="${TMPDIR:-/tmp}/mux-e2e-lprrestore-state-$$" | ||
| 8000 | LPWALL="$LPSTATE/mux/wall" | ||
| 8001 | LPLAYOUT="$LPSTATE/mux/layout" | ||
| 8002 | "$MUXD" run --sock "$SOCK59" --shell /bin/sh > "$OUT.lpr.d" 2>&1 & | ||
| 8003 | D60PID=$! | ||
| 8004 | wait_sock "$SOCK59" "$OUT.lpr.d" "layout-restore daemon never bound" | ||
| 8005 | |||
| 8006 | pipe_mux "$OUT.lpra" "$OUT.lpra.err" timeout 40 "$MUX" --sock "$SOCK59" --session a | ||
| 8007 | pipe_send 'printf "lpr-%%s\\n" origin-a\n' | ||
| 8008 | await_out "$OUT.lpra" "lpr-origin-a" "layout-restore: session a marker never reached the client" | ||
| 8009 | pipe_detach | ||
| 8010 | wait_grid "$SOCK59" "lpr-origin-a" "layout-restore: session a marker" a | ||
| 8011 | pipe_mux "$OUT.lprb" "$OUT.lprb.err" timeout 40 "$MUX" --sock "$SOCK59" --session b | ||
| 8012 | pipe_send 'printf "lpr-%%s\\n" origin-b\n' | ||
| 8013 | await_out "$OUT.lprb" "lpr-origin-b" "layout-restore: session b marker never reached the client" | ||
| 8014 | pipe_detach | ||
| 8015 | wait_grid "$SOCK59" "lpr-origin-b" "layout-restore: session b marker" b | ||
| 8016 | |||
| 8017 | # Write the wall file by hand: two spellings, one per line. The no-argv | ||
| 8018 | # `mux wall` reads this on startup — the hydration path. | ||
| 8019 | mkdir -p "$LPSTATE/mux" | ||
| 8020 | printf -- '--sock %s#a\n--sock %s#b\n' "$SOCK59" "$SOCK59" > "$LPWALL" | ||
| 8021 | |||
| 8022 | set +e | ||
| 8023 | # First ptyclient: hydrate, resize three right, detach. The detach saves | ||
| 8024 | # the sidecar. | ||
| 8025 | XDG_STATE_HOME="$LPSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ | ||
| 8026 | --out "$OUT.lprcap1" --err "$OUT.lprcap1.err" -- \ | ||
| 8027 | "$MUX" wall > "$OUT.lprpc1" 2>&1 <<'EOF' | ||
| 8028 | expect lpr-origin-a 20000 | ||
| 8029 | settle 700 20000 | ||
| 8030 | send \x1cr | ||
| 8031 | settle 300 5000 | ||
| 8032 | send l | ||
| 8033 | settle 300 5000 | ||
| 8034 | send l | ||
| 8035 | settle 300 5000 | ||
| 8036 | send l | ||
| 8037 | settle 500 5000 | ||
| 8038 | send \x1b | ||
| 8039 | settle 500 5000 | ||
| 8040 | send \x1cd | ||
| 8041 | waitexit 10000 | ||
| 8042 | EOF | ||
| 8043 | RC=$? | ||
| 8044 | set -e | ||
| 8045 | [ "$RC" -eq 0 ] || { | ||
| 8046 | echo "e2e FAIL: layout-restore: first ptyclient exited $RC:" | ||
| 8047 | cat "$OUT.lprpc1" "$OUT.lprcap1.err"; exit 1; } | ||
| 8048 | |||
| 8049 | # The sidecar exists and its header is the format the spec mandates. | ||
| 8050 | [ -f "$LPLAYOUT" ] || { | ||
| 8051 | echo "e2e FAIL: layout-restore: sidecar not saved at $LPLAYOUT" | ||
| 8052 | ls -la "$LPSTATE/mux/" 2>&1; exit 1; } | ||
| 8053 | _head1=$(head -1 "$LPLAYOUT") | ||
| 8054 | _head2=$(sed -n '2p' "$LPLAYOUT") | ||
| 8055 | [ "$_head1" = "mux-layout 1" ] || { | ||
| 8056 | echo "e2e FAIL: layout-restore: sidecar header is '$_head1', want 'mux-layout 1'" | ||
| 8057 | cat "$LPLAYOUT"; exit 1; } | ||
| 8058 | # A 2-pane beside at 80 cols: the root is `beside 0`. | ||
| 8059 | case "$_head2" in | ||
| 8060 | "beside 0") ;; | ||
| 8061 | *) echo "e2e FAIL: layout-restore: sidecar root is '$_head2', want 'beside 0'" | ||
| 8062 | cat "$LPLAYOUT"; exit 1 ;; | ||
| 8063 | esac | ||
| 8064 | |||
| 8065 | set +e | ||
| 8066 | # Second ptyclient: reattach. The sidecar restores the resized tree — | ||
| 8067 | # the rail sits at column >= 43. Type a marker, then detach. | ||
| 8068 | XDG_STATE_HOME="$LPSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ | ||
| 8069 | --out "$OUT.lprcap2" --err "$OUT.lprcap2.err" -- \ | ||
| 8070 | "$MUX" wall > "$OUT.lprpc2" 2>&1 <<'EOF' | ||
| 8071 | expect lpr-origin-a 20000 | ||
| 8072 | settle 1000 20000 | ||
| 8073 | send printf 'lpr-rt-%s\n' marker\n | ||
| 8074 | expect lpr-rt-marker 10000 | ||
| 8075 | settle 500 15000 | ||
| 8076 | send \x1cd | ||
| 8077 | waitexit 10000 | ||
| 8078 | EOF | ||
| 8079 | RC=$? | ||
| 8080 | set -e | ||
| 8081 | [ "$RC" -eq 0 ] || { | ||
| 8082 | echo "e2e FAIL: layout-restore: second ptyclient exited $RC:" | ||
| 8083 | cat "$OUT.lprpc2" "$OUT.lprcap2.err"; exit 1; } | ||
| 8084 | # The rail's CUP column in the second capture must be >= 43 — three | ||
| 8085 | # right-resizes moved the boundary from 41 to 44. The _rail_re idiom | ||
| 8086 | # (from the split-birth leg) matches ESC[row;colH ESC[7m. | ||
| 8087 | _rail_re=$'\x1b\\[[0-9][0-9]*;[0-9][0-9]*H\x1b\\[7m' | ||
| 8088 | _max_rail=$(grep -ao "$_rail_re" "$OUT.lprcap2" | \ | ||
| 8089 | sed 's/.*\x1b\[[0-9]*;\([0-9]*\)H\x1b\[7m/\1/' | sort -n | tail -1) | ||
| 8090 | [ -n "$_max_rail" ] || { | ||
| 8091 | echo "e2e FAIL: layout-restore: no rail glyph in the second capture:" | ||
| 8092 | cat "$OUT.lprcap2"; exit 1; } | ||
| 8093 | [ "$_max_rail" -ge 43 ] || { | ||
| 8094 | echo "e2e FAIL: layout-restore: rail column $_max_rail < 43 (resize did not survive)" | ||
| 8095 | cat "$OUT.lprcap2"; exit 1; } | ||
| 8096 | # The marker typed into the restored wall landed in session a (tile 0, | ||
| 8097 | # the default focus). | ||
| 8098 | timeout 20 "$MUXA" capture --sock "$SOCK59" --session a > "$OUT.lprfa" 2>&1 | ||
| 8099 | grep -q "lpr-rt-marker" "$OUT.lprfa" || { | ||
| 8100 | echo "e2e FAIL: layout-restore: marker not in session a after round trip:" | ||
| 8101 | cat "$OUT.lprfa"; exit 1; } | ||
| 8102 | assert_stopped "$SOCK59" "$D60PID" "layout-restore" "$OUT.lprstop" | ||
| 8103 | D60PID="" | ||
| 8104 | rm -rf "$LPSTATE" | ||
| 8105 | ok "a resized layout survives a detach/reattach round trip via the sidecar" | ||
| 8106 | |||
| 8107 | # ---- layout sidecar: healing on wall-file drift ---------------------- | ||
| 8108 | # | ||
| 8109 | # After a 2-pane sidecar is saved, the wall file drifts: one original is | ||
| 8110 | # removed and a new spelling is added. On reattach, restoreLayout heals — | ||
| 8111 | # the survivor leaf keeps its session, the new tile inserts beside it, | ||
| 8112 | # and the unmatched saved leaf collapses out. Two tiles, not three, and | ||
| 8113 | # the wall did not refuse. | ||
| 8114 | LPHSTATE="${TMPDIR:-/tmp}/mux-e2e-lpheal-state-$$" | ||
| 8115 | LPHWALL="$LPHSTATE/mux/wall" | ||
| 8116 | LPHLAYOUT="$LPHSTATE/mux/layout" | ||
| 8117 | "$MUXD" run --sock "$SOCK60" --shell /bin/sh > "$OUT.lph.d" 2>&1 & | ||
| 8118 | D61PID=$! | ||
| 8119 | wait_sock "$SOCK60" "$OUT.lph.d" "layout-heal daemon never bound" | ||
| 8120 | |||
| 8121 | pipe_mux "$OUT.lpha" "$OUT.lpha.err" timeout 40 "$MUX" --sock "$SOCK60" --session a | ||
| 8122 | pipe_send 'printf "lph-%%s\\n" survivor-a\n' | ||
| 8123 | await_out "$OUT.lpha" "lph-survivor-a" "layout-heal: session a marker never reached the client" | ||
| 8124 | pipe_detach | ||
| 8125 | wait_grid "$SOCK60" "lph-survivor-a" "layout-heal: session a marker" a | ||
| 8126 | pipe_mux "$OUT.lphb" "$OUT.lphb.err" timeout 40 "$MUX" --sock "$SOCK60" --session b | ||
| 8127 | pipe_send 'printf "lph-%%s\\n" removed-b\n' | ||
| 8128 | await_out "$OUT.lphb" "lph-removed-b" "layout-heal: session b marker never reached the client" | ||
| 8129 | pipe_detach | ||
| 8130 | wait_grid "$SOCK60" "lph-removed-b" "layout-heal: session b marker" b | ||
| 8131 | pipe_mux "$OUT.lphc" "$OUT.lphc.err" timeout 40 "$MUX" --sock "$SOCK60" --session c | ||
| 8132 | pipe_send 'printf "lph-%%s\\n" newcomer-c\n' | ||
| 8133 | await_out "$OUT.lphc" "lph-newcomer-c" "layout-heal: session c marker never reached the client" | ||
| 8134 | pipe_detach | ||
| 8135 | wait_grid "$SOCK60" "lph-newcomer-c" "layout-heal: session c marker" c | ||
| 8136 | |||
| 8137 | # Wall file starts with a and b. The first ptyclient detaches and saves | ||
| 8138 | # a 2-pane sidecar for [a, b]. | ||
| 8139 | mkdir -p "$LPHSTATE/mux" | ||
| 8140 | printf -- '--sock %s#a\n--sock %s#b\n' "$SOCK60" "$SOCK60" > "$LPHWALL" | ||
| 8141 | |||
| 8142 | set +e | ||
| 8143 | XDG_STATE_HOME="$LPHSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ | ||
| 8144 | --out "$OUT.lphcap1" --err "$OUT.lphcap1.err" -- \ | ||
| 8145 | "$MUX" wall > "$OUT.lphpc1" 2>&1 <<'EOF' | ||
| 8146 | expect lph-survivor-a 20000 | ||
| 8147 | settle 700 20000 | ||
| 8148 | send \x1cd | ||
| 8149 | waitexit 10000 | ||
| 8150 | EOF | ||
| 8151 | RC=$? | ||
| 8152 | set -e | ||
| 8153 | [ "$RC" -eq 0 ] || { | ||
| 8154 | echo "e2e FAIL: layout-heal: first ptyclient exited $RC:" | ||
| 8155 | cat "$OUT.lphpc1" "$OUT.lphcap1.err"; exit 1; } | ||
| 8156 | [ -f "$LPHLAYOUT" ] || { | ||
| 8157 | echo "e2e FAIL: layout-heal: sidecar not saved after first detach" | ||
| 8158 | ls -la "$LPHSTATE/mux/" 2>&1; exit 1; } | ||
| 8159 | |||
| 8160 | # Drift: remove session a's line, add session c's line. The sidecar still | ||
| 8161 | # describes [a, b]; the wall file now says [b, c]. On reattach, b is the | ||
| 8162 | # survivor (matched), c inserts beside it, a collapses out (unmatched | ||
| 8163 | # saved leaf). | ||
| 8164 | printf -- '--sock %s#b\n--sock %s#c\n' "$SOCK60" "$SOCK60" > "$LPHWALL" | ||
| 8165 | |||
| 8166 | set +e | ||
| 8167 | XDG_STATE_HOME="$LPHSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ | ||
| 8168 | --out "$OUT.lphcap2" --err "$OUT.lphcap2.err" -- \ | ||
| 8169 | "$MUX" wall > "$OUT.lphpc2" 2>&1 <<'EOF' | ||
| 8170 | expect lph-removed-b 20000 | ||
| 8171 | settle 700 20000 | ||
| 8172 | send printf 'lph-heal-%s\n' marker\n | ||
| 8173 | expect lph-heal-marker 10000 | ||
| 8174 | settle 500 15000 | ||
| 8175 | send \x1cd | ||
| 8176 | waitexit 10000 | ||
| 8177 | EOF | ||
| 8178 | RC=$? | ||
| 8179 | set -e | ||
| 8180 | [ "$RC" -eq 0 ] || { | ||
| 8181 | echo "e2e FAIL: layout-heal: second ptyclient exited $RC:" | ||
| 8182 | cat "$OUT.lphpc2" "$OUT.lphcap2.err"; exit 1; } | ||
| 8183 | # The wall must not have refused: the ptyclient exited 0 (checked above), | ||
| 8184 | # and stderr mentions no layout error. The survivor (b) still reaches its | ||
| 8185 | # session — the marker typed after hydration landed in the focused tile, | ||
| 8186 | # which is tile 0 (session b, the first wall-file line). | ||
| 8187 | grep -qi "layout" "$OUT.lphcap2.err" && { | ||
| 8188 | echo "e2e FAIL: layout-heal: stderr mentions layout (heal was not silent):" | ||
| 8189 | cat "$OUT.lphcap2.err"; exit 1; } | ||
| 8190 | # Two tiles present: the survivor (b) and the newcomer (c). The daemon | ||
| 8191 | # has sessions a, b, and c; the wall shows b and c. muxa capture of b | ||
| 8192 | # finds the survivor's marker. | ||
| 8193 | timeout 20 "$MUXA" capture --sock "$SOCK60" --session b > "$OUT.lphfa" 2>&1 | ||
| 8194 | grep -q "lph-heal-marker" "$OUT.lphfa" || { | ||
| 8195 | echo "e2e FAIL: layout-heal: survivor's marker not in session b:" | ||
| 8196 | cat "$OUT.lphfa"; exit 1; } | ||
| 8197 | assert_stopped "$SOCK60" "$D61PID" "layout-heal" "$OUT.lphstop" | ||
| 8198 | D61PID="" | ||
| 8199 | rm -rf "$LPHSTATE" | ||
| 8200 | ok "the layout heals on wall-file drift: survivor keeps its session, newcomer joins" | ||
| 8201 | |||
| 8202 | # ---- layout sidecar: silent degrade on garbage ----------------------- | ||
| 8203 | # | ||
| 8204 | # A corrupted sidecar (`bogus 9`) is not a fatal error: restoreLayout | ||
| 8205 | # returns null, the default tree is built, and the wall comes up. The | ||
| 8206 | # assertion is that a marker typed into the wall reaches the focused | ||
| 8207 | # session (the wall works), and the client's stderr never mentions | ||
| 8208 | # "layout" (the degrade is silent). | ||
| 8209 | LPDSTATE="${TMPDIR:-/tmp}/mux-e2e-lpdegrade-state-$$" | ||
| 8210 | LPDWALL="$LPDSTATE/mux/wall" | ||
| 8211 | LPDLAYOUT="$LPDSTATE/mux/layout" | ||
| 8212 | "$MUXD" run --sock "$SOCK61" --shell /bin/sh > "$OUT.lpd.d" 2>&1 & | ||
| 8213 | D62PID=$! | ||
| 8214 | wait_sock "$SOCK61" "$OUT.lpd.d" "layout-degrade daemon never bound" | ||
| 8215 | |||
| 8216 | pipe_mux "$OUT.lpda" "$OUT.lpda.err" timeout 40 "$MUX" --sock "$SOCK61" --session a | ||
| 8217 | pipe_send 'printf "lpd-%%s\\n" origin\n' | ||
| 8218 | await_out "$OUT.lpda" "lpd-origin" "layout-degrade: session a marker never reached the client" | ||
| 8219 | pipe_detach | ||
| 8220 | wait_grid "$SOCK61" "lpd-origin" "layout-degrade: session a marker" a | ||
| 8221 | |||
| 8222 | mkdir -p "$LPDSTATE/mux" | ||
| 8223 | printf -- '--sock %s#a\n' "$SOCK61" > "$LPDWALL" | ||
| 8224 | # A garbage sidecar: wrong magic, wrong version. restoreLayout returns | ||
| 8225 | # null on the header check and the caller builds the default tree. | ||
| 8226 | printf 'bogus 9\n' > "$LPDLAYOUT" | ||
| 8227 | |||
| 8228 | set +e | ||
| 8229 | XDG_STATE_HOME="$LPDSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ | ||
| 8230 | --out "$OUT.lpdcap" --err "$OUT.lpdcap.err" -- \ | ||
| 8231 | "$MUX" wall > "$OUT.lpdpc" 2>&1 <<'EOF' | ||
| 8232 | expect lpd-origin 20000 | ||
| 8233 | settle 700 20000 | ||
| 8234 | send printf 'lpd-degrade-%s\n' marker\n | ||
| 8235 | expect lpd-degrade-marker 10000 | ||
| 8236 | settle 500 15000 | ||
| 8237 | send \x1cd | ||
| 8238 | waitexit 10000 | ||
| 8239 | EOF | ||
| 8240 | RC=$? | ||
| 8241 | set -e | ||
| 8242 | [ "$RC" -eq 0 ] || { | ||
| 8243 | echo "e2e FAIL: layout-degrade: ptyclient exited $RC:" | ||
| 8244 | cat "$OUT.lpdpc" "$OUT.lpdcap.err"; exit 1; } | ||
| 8245 | # The wall came up: the marker reached the focused session. | ||
| 8246 | timeout 20 "$MUXA" capture --sock "$SOCK61" --session a > "$OUT.lpdfa" 2>&1 | ||
| 8247 | grep -q "lpd-degrade-marker" "$OUT.lpdfa" || { | ||
| 8248 | echo "e2e FAIL: layout-degrade: marker not in session a (wall did not come up):" | ||
| 8249 | cat "$OUT.lpdfa"; exit 1; } | ||
| 8250 | # The degrade was silent: no mention of "layout" on the client's stderr. | ||
| 8251 | grep -qi "layout" "$OUT.lpdcap.err" && { | ||
| 8252 | echo "e2e FAIL: layout-degrade: stderr mentions layout (degrade was not silent):" | ||
| 8253 | cat "$OUT.lpdcap.err"; exit 1; } | ||
| 8254 | assert_stopped "$SOCK61" "$D62PID" "layout-degrade" "$OUT.lpdstop" | ||
| 8255 | D62PID="" | ||
| 8256 | rm -rf "$LPDSTATE" | ||
| 8257 | ok "a corrupted sidecar degrades silently to the default layout" | ||
| 8258 | |||
| 8259 | [ "$OK_COUNT" = "71" ] || { | ||
| 8260 | echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 71 —" | ||
| 7969 | echo " a scenario was added (update the pin) or silently lost" | 8261 | echo " a scenario was added (update the pin) or silently lost" |
| 7970 | exit 1 | 8262 | exit 1 |
| 7971 | } | 8263 | } |