a73x

9dca0bb1

test: the layout survives the round trip, heals, and degrades

a73x   2026-08-25 09:10

Commit message
test: the layout survives the round trip, heals, and degrades

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

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 }