a73x

fb51449b

test: the harness detaches with the chord, and a scenario pins the layer

a73x   2026-08-19 17:17

Commit message
test: the harness detaches with the chord, and a scenario pins the layer

Every live-session detach in the harness sent a single 0x1c, which is now
only half a chord — those legs would wait for a command key that never
comes. Doubled to \034\034 (prefix, then prefix again). The two sends
left bare are the ones aborting a client that is still dialling or
reconnecting: there is no session to command there, and those are the
regression tests for the paths this feature deliberately did not touch.

Scenario 34 pins both halves on a real pty: an unknown chord (\x1cx) is
swallowed whole — proved by a marker command that only runs if neither
byte reached the shell — and \x1cd detaches a session that still answers
afterwards.

test/bench.sh
Old New
@@ -22,7 +22,7 @@ while [ ! -S "$SOCK" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done
22 j=0 22 j=0
23 while [ "$j" -lt 120 ]; do printf 'x'; sleep 0.05; j=$((j+1)); done 23 while [ "$j" -lt 120 ]; do printf 'x'; sleep 0.05; j=$((j+1)); done
24 sleep 0.5 24 sleep 0.5
25 printf '\034' 25 printf '\034\034'
26 } | "$MUX" --sock "$SOCK" > /dev/null 26 } | "$MUX" --sock "$SOCK" > /dev/null
27 27
28 STATS="$("$MUXD" stats --sock "$SOCK")" 28 STATS="$("$MUXD" stats --sock "$SOCK")"
test/e2e.sh
Old New
@@ -247,6 +247,11 @@ W4PID=""
247 SOCK26="${TMPDIR:-/tmp}/muxd-e2e-cliwall-$$.sock" 247 SOCK26="${TMPDIR:-/tmp}/muxd-e2e-cliwall-$$.sock"
248 D23PID="" 248 D23PID=""
249 249
250 # The prefix chord (Ctrl-\ as a command key): its own daemon, so a chord
251 # that fails to detach cannot strand another block's session.
252 SOCK27="${TMPDIR:-/tmp}/muxd-e2e-prefix-$$.sock"
253 D24PID=""
254
250 # One counter out of a MUX_PREDICT_STATS line. The client prints exactly one 255 # One counter out of a MUX_PREDICT_STATS line. The client prints exactly one
251 # such line on exit; every field is a key=value pair, so a rename or reorder 256 # such line on exit; every field is a key=value pair, so a rename or reorder
252 # in the client shows up here as an empty read rather than a wrong number. 257 # in the client shows up here as an empty read rather than a wrong number.
@@ -767,6 +772,7 @@ cleanup() {
767 [ -n "$W4PID" ] && kill "$W4PID" 2>/dev/null || true 772 [ -n "$W4PID" ] && kill "$W4PID" 2>/dev/null || true
768 [ -n "$D22PID" ] && kill "$D22PID" 2>/dev/null || true 773 [ -n "$D22PID" ] && kill "$D22PID" 2>/dev/null || true
769 [ -n "$D23PID" ] && kill "$D23PID" 2>/dev/null || true 774 [ -n "$D23PID" ] && kill "$D23PID" 2>/dev/null || true
775 [ -n "$D24PID" ] && kill "$D24PID" 2>/dev/null || true
770 # The stops still precede the socket rm below, like SOCK14-17 above: 776 # The stops still precede the socket rm below, like SOCK14-17 above:
771 # unlinking a socket first would leave a live daemon nothing could reach 777 # unlinking a socket first would leave a live daemon nothing could reach
772 # by path. 778 # by path.
@@ -775,6 +781,7 @@ cleanup() {
775 [ -S "$SOCK20" ] && "$MUXD" stop --sock "$SOCK20" 2>/dev/null || true 781 [ -S "$SOCK20" ] && "$MUXD" stop --sock "$SOCK20" 2>/dev/null || true
776 [ -S "$SOCK25" ] && "$MUXD" stop --sock "$SOCK25" 2>/dev/null || true 782 [ -S "$SOCK25" ] && "$MUXD" stop --sock "$SOCK25" 2>/dev/null || true
777 [ -S "$SOCK26" ] && "$MUXD" stop --sock "$SOCK26" 2>/dev/null || true 783 [ -S "$SOCK26" ] && "$MUXD" stop --sock "$SOCK26" 2>/dev/null || true
784 [ -S "$SOCK27" ] && "$MUXD" stop --sock "$SOCK27" 2>/dev/null || true
778 785
779 # ---- the leak sweep (hygiene kit, 6a) ---- 786 # ---- the leak sweep (hygiene kit, 6a) ----
780 # Here rather than at the bottom of the file, which `set -e` reaches only 787 # Here rather than at the bottom of the file, which `set -e` reaches only
@@ -786,7 +793,7 @@ cleanup() {
786 "$D7PID" "$D9PID" "$D10PID" "$D12PID" "$D13PID" "$SPID" "$TPID" \ 793 "$D7PID" "$D9PID" "$D10PID" "$D12PID" "$D13PID" "$SPID" "$TPID" \
787 "$GPID" "$APID" "$PAPID" "$HAPID" "$HDPID" \ 794 "$GPID" "$APID" "$PAPID" "$HAPID" "$HDPID" \
788 "$D14PID" "$D15PID" "$D16PID" "$D17PID" "$D18PID" "$D19PID" \ 795 "$D14PID" "$D15PID" "$D16PID" "$D17PID" "$D18PID" "$D19PID" \
789 "$D20PID" "$D21PID" "$D22PID" "$D23PID" 796 "$D20PID" "$D21PID" "$D22PID" "$D23PID" "$D24PID"
790 _leak=0 797 _leak=0
791 leak_sweep "$_rc" || _leak=1 798 leak_sweep "$_rc" || _leak=1
792 799
@@ -950,8 +957,9 @@ DPID=$!
950 957
951 wait_sock "$SOCK" "$OUT.d1.d" "socket never appeared" 958 wait_sock "$SOCK" "$OUT.d1.d" "socket never appeared"
952 959
953 # Client with piped stdio: types a command, waits, detaches with Ctrl-\ (034). 960 # Client with piped stdio: types a command, waits, detaches with the
954 { printf 'printf "e2e-%%s\\n" works\n'; sleep 2; printf '\034'; } | \ 961 # Ctrl-\ chord (\034\034 — prefix, then prefix again for detach).
962 { printf 'printf "e2e-%%s\\n" works\n'; sleep 2; printf '\034\034'; } | \
955 timeout 30 "$MUX" --sock "$SOCK" > "$OUT" 963 timeout 30 "$MUX" --sock "$SOCK" > "$OUT"
956 964
957 # 1. The client's rendered output must contain the command's result. 965 # 1. The client's rendered output must contain the command's result.
@@ -994,7 +1002,7 @@ ok "convergence control fires on a doctored stream"
994 # The escapes are doubled because they are written by the SESSION's shell, 1002 # The escapes are doubled because they are written by the SESSION's shell,
995 # not by this one: what goes down the pipe is the literal text 1003 # not by this one: what goes down the pipe is the literal text
996 # `printf "\033[1;31mstyled-%s\033[0m\n" red bold`. 1004 # `printf "\033[1;31mstyled-%s\033[0m\n" red bold`.
997 { printf 'printf "\\033[1;31mstyled-%%s\\033[0m\\n" red bold\n'; sleep 2; printf '\034'; } | \ 1005 { printf 'printf "\\033[1;31mstyled-%%s\\033[0m\\n" red bold\n'; sleep 2; printf '\034\034'; } | \
998 timeout 30 "$MUX" --sock "$SOCK" > "$OUT.st" 1006 timeout 30 "$MUX" --sock "$SOCK" > "$OUT.st"
999 grep -q "styled-red" "$OUT.st" || { 1007 grep -q "styled-red" "$OUT.st" || {
1000 echo "e2e FAIL: styled output never reached the client"; cat "$OUT.st"; exit 1; } 1008 echo "e2e FAIL: styled output never reached the client"; cat "$OUT.st"; exit 1; }
@@ -1016,7 +1024,7 @@ kill -9 "$CPID" 2>/dev/null || true
1016 sleep 1 1024 sleep 1
1017 kill -0 "$DPID" || { echo "e2e FAIL: daemon died after client kill -9"; exit 1; } 1025 kill -0 "$DPID" || { echo "e2e FAIL: daemon died after client kill -9"; exit 1; }
1018 1026
1019 { sleep 1; printf '\034'; } | timeout 30 "$MUX" --sock "$SOCK" > "$OUT.re" 1027 { sleep 1; printf '\034\034'; } | timeout 30 "$MUX" --sock "$SOCK" > "$OUT.re"
1020 grep -q "60" "$OUT.re" || { 1028 grep -q "60" "$OUT.re" || {
1021 echo "e2e FAIL: reattach after kill missing state"; cat "$OUT.re"; exit 1; 1029 echo "e2e FAIL: reattach after kill missing state"; cat "$OUT.re"; exit 1;
1022 } 1030 }
@@ -1026,10 +1034,10 @@ rm_swept "$OUT.kill" "$OUT.re"
1026 # --- M5: two clients on one session. Output typed in A reaches both; then A 1034 # --- M5: two clients on one session. Output typed in A reaches both; then A
1027 # detaches and B must still have a live input path (its own marker echoes back 1035 # detaches and B must still have a live input path (its own marker echoes back
1028 # after A is gone), so one client's detach does not disturb the other. 1036 # after A is gone), so one client's detach does not disturb the other.
1029 { sleep 0.5; printf 'printf "m5-%%s\\n" both\n'; sleep 2.5; printf '\034'; } | \ 1037 { sleep 0.5; printf 'printf "m5-%%s\\n" both\n'; sleep 2.5; printf '\034\034'; } | \
1030 "$MUX" --sock "$SOCK" > "$OUT.a" & 1038 "$MUX" --sock "$SOCK" > "$OUT.a" &
1031 APID=$! 1039 APID=$!
1032 { sleep 4; printf 'printf "m5-%%s\\n" after-a-left\n'; sleep 2.5; printf '\034'; } | \ 1040 { sleep 4; printf 'printf "m5-%%s\\n" after-a-left\n'; sleep 2.5; printf '\034\034'; } | \
1033 "$MUX" --sock "$SOCK" > "$OUT.b" & 1041 "$MUX" --sock "$SOCK" > "$OUT.b" &
1034 BPID=$! 1042 BPID=$!
1035 wait "$APID" "$BPID" 1043 wait "$APID" "$BPID"
@@ -1059,7 +1067,7 @@ rm_swept "$OUT.a" "$OUT.b"
1059 # would resolve to a directory that does not exist and the session would fail 1067 # would resolve to a directory that does not exist and the session would fail
1060 # instead of quietly attaching over the local socket. `|| true` keeps set -e 1068 # instead of quietly attaching over the local socket. `|| true` keeps set -e
1061 # from aborting before the labelled diagnostic below runs. 1069 # from aborting before the labelled diagnostic below runs.
1062 { sleep 0.5; printf 'printf "m6-%%s\\n" via-pipe\n'; sleep 2; printf '\034'; } | \ 1070 { sleep 0.5; printf 'printf "m6-%%s\\n" via-pipe\n'; sleep 2; printf '\034\034'; } | \
1063 XDG_RUNTIME_DIR=/nonexistent-mux-e2e "$MUX" --via "$MUXD proxy --sock $SOCK" > "$OUT.via" || true 1071 XDG_RUNTIME_DIR=/nonexistent-mux-e2e "$MUX" --via "$MUXD proxy --sock $SOCK" > "$OUT.via" || true
1064 grep -q "m6-via-pipe" "$OUT.via" || { 1072 grep -q "m6-via-pipe" "$OUT.via" || {
1065 echo "e2e FAIL: --via transport"; cat "$OUT.via"; exit 1; 1073 echo "e2e FAIL: --via transport"; cat "$OUT.via"; exit 1;
@@ -1178,7 +1186,7 @@ rm_swept "$OUT.abort" "$SOCK2"
1178 # milestone claim. 1186 # milestone claim.
1179 set +e 1187 set +e
1180 { sleep 0.5; printf 'printf "m7-%%s\\n" before\n'; sleep 8; \ 1188 { sleep 0.5; printf 'printf "m7-%%s\\n" before\n'; sleep 8; \
1181 printf 'printf "m7-%%s\\n" after\n'; sleep 2.5; printf '\034'; } | \ 1189 printf 'printf "m7-%%s\\n" after\n'; sleep 2.5; printf '\034\034'; } | \
1182 XDG_RUNTIME_DIR=/nonexistent-mux-e2e timeout 40 \ 1190 XDG_RUNTIME_DIR=/nonexistent-mux-e2e timeout 40 \
1183 "$MUX" --via "$MUXD proxy --sock $SOCK" > "$OUT.m7" 2> "$OUT.m7.err" & 1191 "$MUX" --via "$MUXD proxy --sock $SOCK" > "$OUT.m7" 2> "$OUT.m7.err" &
1184 M7PID=$! 1192 M7PID=$!
@@ -1232,7 +1240,7 @@ wait_sock "$SOCK3" "$OUT.d3a.d" "restart-scenario socket never appeared"
1232 1240
1233 set +e 1241 set +e
1234 { sleep 0.5; printf 'printf "m7b-%%s\\n" pre-restart\n'; sleep 8; \ 1242 { sleep 0.5; printf 'printf "m7b-%%s\\n" pre-restart\n'; sleep 8; \
1235 printf 'printf "m7b-%%s\\n" post-restart\n'; sleep 3; printf '\034'; } | \ 1243 printf 'printf "m7b-%%s\\n" post-restart\n'; sleep 3; printf '\034\034'; } | \
1236 timeout 40 "$MUX" --sock "$SOCK3" > "$OUT.m7b" 2> "$OUT.m7b.err" & 1244 timeout 40 "$MUX" --sock "$SOCK3" > "$OUT.m7b" 2> "$OUT.m7b.err" &
1237 M7BPID=$! 1245 M7BPID=$!
1238 set -e 1246 set -e
@@ -1401,7 +1409,7 @@ grep -q "already listening" "$OUT.q" || {
1401 1409
1402 # ...and the daemon is still an ordinary daemon: the session runs and the 1410 # ...and the daemon is still an ordinary daemon: the session runs and the
1403 # unix-socket path is unaffected by the listener sharing its poll loop. 1411 # unix-socket path is unaffected by the listener sharing its poll loop.
1404 { printf 'printf "quic-%%s\\n" flags-ok\n'; sleep 2; printf '\034'; } | \ 1412 { printf 'printf "quic-%%s\\n" flags-ok\n'; sleep 2; printf '\034\034'; } | \
1405 "$MUX" --sock "$SOCK4" > "$OUT.q" 1413 "$MUX" --sock "$SOCK4" > "$OUT.q"
1406 grep -q "quic-flags-ok" "$OUT.q" || { 1414 grep -q "quic-flags-ok" "$OUT.q" || {
1407 echo "e2e FAIL: --quic daemon did not serve an ordinary socket client"; cat "$OUT.q"; exit 1; 1415 echo "e2e FAIL: --quic daemon did not serve an ordinary socket client"; cat "$OUT.q"; exit 1;
@@ -1418,7 +1426,7 @@ assert_converged "$OUT.q" "$SOCK4" "quic daemon serves sockets"
1418 # scenario: the wire protocol did not change, so this must behave exactly 1426 # scenario: the wire protocol did not change, so this must behave exactly
1419 # as the socket client does. 1427 # as the socket client does.
1420 set +e 1428 set +e
1421 { printf 'printf "quic-%%s\\n" attach-ok\n'; sleep 2; printf '\034'; } | \ 1429 { printf 'printf "quic-%%s\\n" attach-ok\n'; sleep 2; printf '\034\034'; } | \
1422 timeout 30 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" \ 1430 timeout 30 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" \
1423 --quic-idle-ms 15000 > "$OUT.qc" 2> "$OUT.qc.err" 1431 --quic-idle-ms 15000 > "$OUT.qc" 2> "$OUT.qc.err"
1424 RC=$? 1432 RC=$?
@@ -1442,7 +1450,7 @@ assert_converged "$OUT.qc" "$SOCK4" "quic attach"
1442 # from. The counter is what tells that apart from a delta. 1450 # from. The counter is what tells that apart from a delta.
1443 SNAPS_RA=$("$MUXD" stats --sock "$SOCK4" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') 1451 SNAPS_RA=$("$MUXD" stats --sock "$SOCK4" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p')
1444 set +e 1452 set +e
1445 { printf 'printf "quic-%%s\\n" reattached\n'; sleep 2; printf '\034'; } | \ 1453 { printf 'printf "quic-%%s\\n" reattached\n'; sleep 2; printf '\034\034'; } | \
1446 timeout 30 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" \ 1454 timeout 30 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" \
1447 --quic-idle-ms 15000 > "$OUT.qc" 2> "$OUT.qc.err" 1455 --quic-idle-ms 15000 > "$OUT.qc" 2> "$OUT.qc.err"
1448 RC=$? 1456 RC=$?
@@ -1550,7 +1558,7 @@ SNAPS_BEFORE=$("$MUXD" stats --sock "$SOCK4" | sed -n 's/.*snapshots=\([0-9]*\).
1550 [ -n "$SNAPS_BEFORE" ] || { echo "e2e FAIL: could not read snapshots before the tear"; exit 1; } 1558 [ -n "$SNAPS_BEFORE" ] || { echo "e2e FAIL: could not read snapshots before the tear"; exit 1; }
1551 1559
1552 set +e 1560 set +e
1553 { printf 'printf "quic-%%s\\n" pre-tear\n'; sleep 9; printf '\034'; } | \ 1561 { printf 'printf "quic-%%s\\n" pre-tear\n'; sleep 9; printf '\034\034'; } | \
1554 timeout 40 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" \ 1562 timeout 40 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" \
1555 --quic-idle-ms 1500 > "$OUT.qr" 2> "$OUT.qr.err" & 1563 --quic-idle-ms 1500 > "$OUT.qr" 2> "$OUT.qr.err" &
1556 QRPID=$! 1564 QRPID=$!
@@ -1605,7 +1613,7 @@ assert_converged "$OUT.qr" "$SOCK4" "quic delta resume"
1605 # process would show up as a bind failure rather than as silent sharing. 1613 # process would show up as a bind failure rather than as silent sharing.
1606 set +e 1614 set +e
1607 { printf 'printf "quic-%%s\\n" pre-restart\n'; sleep 3; \ 1615 { printf 'printf "quic-%%s\\n" pre-restart\n'; sleep 3; \
1608 printf 'printf "quic-%%s\\n" post-restart\n'; sleep 3; printf '\034'; } | \ 1616 printf 'printf "quic-%%s\\n" post-restart\n'; sleep 3; printf '\034\034'; } | \
1609 timeout 40 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" \ 1617 timeout 40 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" \
1610 --quic-idle-ms 1500 > "$OUT.qk" 2> "$OUT.qk.err" & 1618 --quic-idle-ms 1500 > "$OUT.qk" 2> "$OUT.qk.err" &
1611 QKPID=$! 1619 QKPID=$!
@@ -1704,7 +1712,7 @@ D9PID=$!
1704 wait_sock "$SOCK9" "$OUT.d9.d" "MUX_KEY_FILE daemon never bound its session socket" 1712 wait_sock "$SOCK9" "$OUT.d9.d" "MUX_KEY_FILE daemon never bound its session socket"
1705 1713
1706 set +e 1714 set +e
1707 { printf 'printf "envkey-%%s\\n" ok\n'; sleep 2; printf '\034'; } | \ 1715 { printf 'printf "envkey-%%s\\n" ok\n'; sleep 2; printf '\034\034'; } | \
1708 timeout 30 "$MUX" "quic://127.0.0.1:$QPORT2" --key "$QKEY" \ 1716 timeout 30 "$MUX" "quic://127.0.0.1:$QPORT2" --key "$QKEY" \
1709 --quic-idle-ms 15000 > "$OUT.env1" 2> "$OUT.env1.err" 1717 --quic-idle-ms 15000 > "$OUT.env1" 2> "$OUT.env1.err"
1710 RC=$? 1718 RC=$?
@@ -1731,7 +1739,7 @@ D10PID=$!
1731 wait_sock "$SOCK10" "$OUT.d10.d" "--key-beats-env daemon never bound its session socket" 1739 wait_sock "$SOCK10" "$OUT.d10.d" "--key-beats-env daemon never bound its session socket"
1732 1740
1733 set +e 1741 set +e
1734 { printf 'printf "flagwins-%%s\\n" ok\n'; sleep 2; printf '\034'; } | \ 1742 { printf 'printf "flagwins-%%s\\n" ok\n'; sleep 2; printf '\034\034'; } | \
1735 timeout 30 "$MUX" "quic://127.0.0.1:$QPORT3" --key "$QKEY" \ 1743 timeout 30 "$MUX" "quic://127.0.0.1:$QPORT3" --key "$QKEY" \
1736 --quic-idle-ms 15000 > "$OUT.env2" 2> "$OUT.env2.err" 1744 --quic-idle-ms 15000 > "$OUT.env2" 2> "$OUT.env2.err"
1737 RC=$? 1745 RC=$?
@@ -1775,7 +1783,7 @@ kill -0 "$SPID" || { echo "e2e FAIL: started daemon not alive"; exit 1; }
1775 echo "e2e FAIL: non-tty start not exactly two lines:"; cat "$OUT.start"; exit 1; } 1783 echo "e2e FAIL: non-tty start not exactly two lines:"; cat "$OUT.start"; exit 1; }
1776 1784
1777 # The daemon it started serves a session (marker in, marker in dump). 1785 # The daemon it started serves a session (marker in, marker in dump).
1778 { printf 'printf "start-%%s\\n" works\n'; sleep 2; printf '\034'; } | \ 1786 { printf 'printf "start-%%s\\n" works\n'; sleep 2; printf '\034\034'; } | \
1779 "$MUX" --sock "$SOCK8" > "$OUT.s8" 2> "$OUT.s8.err" 1787 "$MUX" --sock "$SOCK8" > "$OUT.s8" 2> "$OUT.s8.err"
1780 "$MUXD" dump --sock "$SOCK8" | grep -q "start-works" || { 1788 "$MUXD" dump --sock "$SOCK8" | grep -q "start-works" || {
1781 echo "e2e FAIL: auto-started daemon lost the marker" 1789 echo "e2e FAIL: auto-started daemon lost the marker"
@@ -1822,7 +1830,7 @@ wait "$RB"; RCB=$?
1822 set -e 1830 set -e
1823 { [ "$RCA" = "0" ] && [ "$RCB" = "0" ]; } || { 1831 { [ "$RCA" = "0" ] && [ "$RCB" = "0" ]; } || {
1824 echo "e2e FAIL: race: exits $RCA/$RCB"; cat "$OUT.ra" "$OUT.rb"; exit 1; } 1832 echo "e2e FAIL: race: exits $RCA/$RCB"; cat "$OUT.ra" "$OUT.rb"; exit 1; }
1825 { printf 'printf "race-%%s\\n" one\n'; sleep 2; printf '\034'; } | \ 1833 { printf 'printf "race-%%s\\n" one\n'; sleep 2; printf '\034\034'; } | \
1826 "$MUX" --sock "$SOCK8" > "$OUT.race" 2> /dev/null 1834 "$MUX" --sock "$SOCK8" > "$OUT.race" 2> /dev/null
1827 "$MUXD" dump --sock "$SOCK8" | grep -q "race-one" || { 1835 "$MUXD" dump --sock "$SOCK8" | grep -q "race-one" || {
1828 echo "e2e FAIL: race: session unusable"; exit 1; } 1836 echo "e2e FAIL: race: session unusable"; exit 1; }
@@ -1894,7 +1902,7 @@ ok "a start whose daemon dies young says so, with the log path"
1894 "$MUXD" start --sock "$SOCK11" --quic "127.0.0.1:$QPORT4" 2> "$OUT.goal" 1902 "$MUXD" start --sock "$SOCK11" --quic "127.0.0.1:$QPORT4" 2> "$OUT.goal"
1895 GPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.goal") 1903 GPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.goal")
1896 [ -n "$GPID" ] || { echo "e2e FAIL: goal start reported no pid"; cat "$OUT.goal"; exit 1; } 1904 [ -n "$GPID" ] || { echo "e2e FAIL: goal start reported no pid"; cat "$OUT.goal"; exit 1; }
1897 { printf 'printf "goal-%%s\\n" quic\n'; sleep 2; printf '\034'; } | \ 1905 { printf 'printf "goal-%%s\\n" quic\n'; sleep 2; printf '\034\034'; } | \
1898 timeout 30 "$MUX" "quic://127.0.0.1:$QPORT4" > "$OUT.g9" 2> "$OUT.g9.err" 1906 timeout 30 "$MUX" "quic://127.0.0.1:$QPORT4" > "$OUT.g9" 2> "$OUT.g9.err"
1899 "$MUXD" dump --sock "$SOCK11" | grep -q "goal-quic" || { 1907 "$MUXD" dump --sock "$SOCK11" | grep -q "goal-quic" || {
1900 echo "e2e FAIL: no-key-flag QUIC attach did not reach the session" 1908 echo "e2e FAIL: no-key-flag QUIC attach did not reach the session"
@@ -1946,7 +1954,7 @@ wait_sock "$SOCK5" "$OUT.p1.d" "prediction daemon never bound"
1946 # 1. Line mode: the glyph is on screen before the round trip could have 1954 # 1. Line mode: the glyph is on screen before the round trip could have
1947 # delivered it. 1955 # delivered it.
1948 set +e 1956 set +e
1949 { sleep 2; printf 'z'; sleep 4; printf '\034'; } | \ 1957 { sleep 2; printf 'z'; sleep 4; printf '\034\034'; } | \
1950 DELAY_MS="$PDELAY" MUX_PREDICT_STATS=1 timeout 40 "$MUX" \ 1958 DELAY_MS="$PDELAY" MUX_PREDICT_STATS=1 timeout 40 "$MUX" \
1951 --via "$DELAYPIPE | $MUXD proxy --sock $SOCK5 | $DELAYPIPE" \ 1959 --via "$DELAYPIPE | $MUXD proxy --sock $SOCK5 | $DELAYPIPE" \
1952 > "$OUT.p1" 2> "$OUT.p1.err" & 1960 > "$OUT.p1" 2> "$OUT.p1.err" &
@@ -1998,7 +2006,7 @@ assert_converged "$OUT.p1" "$SOCK5" "line-mode prediction"
1998 # three-way judgment, this scenario is what says so. 2006 # three-way judgment, this scenario is what says so.
1999 set +e 2007 set +e
2000 { sleep 2; for c in b u r s t; do printf '%s' "$c"; sleep 0.25; done; \ 2008 { sleep 2; for c in b u r s t; do printf '%s' "$c"; sleep 0.25; done; \
2001 sleep 4; printf '\034'; } | \ 2009 sleep 4; printf '\034\034'; } | \
2002 DELAY_MS="$PDELAY" MUX_PREDICT_STATS=1 timeout 40 "$MUX" \ 2010 DELAY_MS="$PDELAY" MUX_PREDICT_STATS=1 timeout 40 "$MUX" \
2003 --via "$DELAYPIPE | $MUXD proxy --sock $SOCK5 | $DELAYPIPE" \ 2011 --via "$DELAYPIPE | $MUXD proxy --sock $SOCK5 | $DELAYPIPE" \
2004 > "$OUT.pb" 2> "$OUT.pb.err" 2012 > "$OUT.pb" 2> "$OUT.pb.err"
@@ -2080,7 +2088,7 @@ wait_sock "$SOCK6" "$OUT.pw.d" "password daemon never bound"
2080 # scenario until the typing was split up. 2088 # scenario until the typing was split up.
2081 set +e 2089 set +e
2082 { sleep 2; for c in h u n t e r 2; do printf '%s' "$c"; sleep 0.2; done; \ 2090 { sleep 2; for c in h u n t e r 2; do printf '%s' "$c"; sleep 0.2; done; \
2083 printf '\n'; sleep 2; printf '\034'; } | \ 2091 printf '\n'; sleep 2; printf '\034\034'; } | \
2084 MUX_PREDICT_STATS=1 timeout 40 "$MUX" --sock "$SOCK6" > "$OUT.pw" 2> "$OUT.pw.err" 2092 MUX_PREDICT_STATS=1 timeout 40 "$MUX" --sock "$SOCK6" > "$OUT.pw" 2> "$OUT.pw.err"
2085 RC=$? 2093 RC=$?
2086 set -e 2094 set -e
@@ -2121,7 +2129,7 @@ wait_sock "$SOCK7" "$OUT.rw.d" "rawmode daemon never bound"
2121 2129
2122 set +e 2130 set +e
2123 { sleep 2; for c in a b c; do printf '%s' "$c"; sleep 0.5; done; \ 2131 { sleep 2; for c in a b c; do printf '%s' "$c"; sleep 0.5; done; \
2124 printf '\000'; sleep 0.5; printf 'j'; sleep 2.5; printf '\034'; } | \ 2132 printf '\000'; sleep 0.5; printf 'j'; sleep 2.5; printf '\034\034'; } | \
2125 MUX_PREDICT_STATS=1 timeout 40 "$MUX" --sock "$SOCK7" > "$OUT.rw" 2> "$OUT.rw.err" 2133 MUX_PREDICT_STATS=1 timeout 40 "$MUX" --sock "$SOCK7" > "$OUT.rw" 2> "$OUT.rw.err"
2126 RC=$? 2134 RC=$?
2127 set -e 2135 set -e
@@ -2169,7 +2177,7 @@ D5PID=$!
2169 wait_sock "$SOCK5" "$OUT.pr.d" "reconnect daemon never bound" 2177 wait_sock "$SOCK5" "$OUT.pr.d" "reconnect daemon never bound"
2170 2178
2171 set +e 2179 set +e
2172 { sleep 2; printf 'p'; sleep 5; printf 'q'; sleep 4; printf '\034'; } | \ 2180 { sleep 2; printf 'p'; sleep 5; printf 'q'; sleep 4; printf '\034\034'; } | \
2173 DELAY_MS="$PDELAY" MUX_PREDICT_STATS=1 timeout 60 "$MUX" \ 2181 DELAY_MS="$PDELAY" MUX_PREDICT_STATS=1 timeout 60 "$MUX" \
2174 --via "$DELAYPIPE | $MUXD proxy --sock $SOCK5 | $DELAYPIPE" \ 2182 --via "$DELAYPIPE | $MUXD proxy --sock $SOCK5 | $DELAYPIPE" \
2175 > "$OUT.pr" 2> "$OUT.pr.err" & 2183 > "$OUT.pr" 2> "$OUT.pr.err" &
@@ -2354,7 +2362,7 @@ send printf 'tp2-%s\\n' claim\n
2354 expect tp2-claim 10000 2362 expect tp2-claim 10000
2355 expect tp2rdy@ 10000 2363 expect tp2rdy@ 10000
2356 settle 500 15000 2364 settle 500 15000
2357 send \x1c 2365 send \x1c\x1c
2358 waitexit 10000 2366 waitexit 10000
2359 EOF 2367 EOF
2360 RC=$? 2368 RC=$?
@@ -2432,7 +2440,7 @@ expect 00007 10000
2432 resize 100 30 2440 resize 100 30
2433 expect 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 10000 2441 expect 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 10000
2434 settle 500 15000 2442 settle 500 15000
2435 send \x1c 2443 send \x1c\x1c
2436 waitexit 10000 2444 waitexit 10000
2437 EOF 2445 EOF
2438 RC=$? 2446 RC=$?
@@ -2526,7 +2534,7 @@ expect 100 20000
2526 send t 2534 send t
2527 expect \x1b[2K\x1b[0mt 20000 2535 expect \x1b[2K\x1b[0mt 20000
2528 settle 500 15000 2536 settle 500 15000
2529 send \x1c 2537 send \x1c\x1c
2530 waitexit 15000 2538 waitexit 15000
2531 EOF 2539 EOF
2532 TP1PID=$! 2540 TP1PID=$!
@@ -2645,7 +2653,7 @@ ok "reconnect while scrolled: view restored, prediction resumed"
2645 # `set -e` aborts the run on the client's exit status before the labelled 2653 # `set -e` aborts the run on the client's exit status before the labelled
2646 # diagnostics below get to print the stderr that says why. `timeout` is 2654 # diagnostics below get to print the stderr that says why. `timeout` is
2647 # what keeps that from being a licence to hang. 2655 # what keeps that from being a licence to hang.
2648 { printf 'printf "auto-%%s\\n" start\n'; sleep 2; printf '\034'; } | \ 2656 { printf 'printf "auto-%%s\\n" start\n'; sleep 2; printf '\034\034'; } | \
2649 SHELL=/bin/sh timeout 30 "$MUX" --via "$MUXD proxy --sock $SOCK14" \ 2657 SHELL=/bin/sh timeout 30 "$MUX" --via "$MUXD proxy --sock $SOCK14" \
2650 > "$OUT.as" 2> "$OUT.as.err" || true 2658 > "$OUT.as" 2> "$OUT.as.err" || true
2651 "$MUXD" dump --sock "$SOCK14" | grep -q "auto-start" || { 2659 "$MUXD" dump --sock "$SOCK14" | grep -q "auto-start" || {
@@ -2667,7 +2675,7 @@ kill -0 "$APID" || { echo "e2e FAIL: auto-started daemon not alive"; exit 1; }
2667 assert_converged "$OUT.as" "$SOCK14" "auto-start via proxy" 2675 assert_converged "$OUT.as" "$SOCK14" "auto-start via proxy"
2668 2676
2669 # Silence is the fast path: a warm attach must print NO spawn progress. 2677 # Silence is the fast path: a warm attach must print NO spawn progress.
2670 { printf 'printf "auto-%%s\\n" again\n'; sleep 2; printf '\034'; } | \ 2678 { printf 'printf "auto-%%s\\n" again\n'; sleep 2; printf '\034\034'; } | \
2671 SHELL=/bin/sh timeout 30 "$MUX" --via "$MUXD proxy --sock $SOCK14" \ 2679 SHELL=/bin/sh timeout 30 "$MUX" --via "$MUXD proxy --sock $SOCK14" \
2672 > "$OUT.as2" 2> "$OUT.as2.err" || true 2680 > "$OUT.as2" 2> "$OUT.as2.err" || true
2673 grep -q 'starting' "$OUT.as2.err" && { 2681 grep -q 'starting' "$OUT.as2.err" && {
@@ -2741,7 +2749,7 @@ expect \x1b[?1049h 15000
2741 send printf 'pty-%s\\n' auto\n 2749 send printf 'pty-%s\\n' auto\n
2742 expect pty-auto 15000 2750 expect pty-auto 15000
2743 settle 500 15000 2751 settle 500 15000
2744 send \x1c 2752 send \x1c\x1c
2745 waitexit 10000 2753 waitexit 10000
2746 EOF 2754 EOF
2747 RC=$? 2755 RC=$?
@@ -2811,7 +2819,7 @@ settle 400 15000
2811 send sh $OUT.clip.sh\n 2819 send sh $OUT.clip.sh\n
2812 expect CLIPDONE 15000 2820 expect CLIPDONE 15000
2813 settle 400 15000 2821 settle 400 15000
2814 send \x1c 2822 send \x1c\x1c
2815 waitexit 10000 2823 waitexit 10000
2816 EOF 2824 EOF
2817 RC=$? 2825 RC=$?
@@ -2867,7 +2875,7 @@ settle 400 15000
2867 send sh $OUT.title.sh\n 2875 send sh $OUT.title.sh\n
2868 expect TITLEDONE 15000 2876 expect TITLEDONE 15000
2869 settle 400 15000 2877 settle 400 15000
2870 send \x1c 2878 send \x1c\x1c
2871 waitexit 10000 2879 waitexit 10000
2872 EOF 2880 EOF
2873 RC=$? 2881 RC=$?
@@ -3056,7 +3064,7 @@ send :wq\r
3056 # 800 is slack on top of a guarantee, not a measurement of how long nvim 3064 # 800 is slack on top of a guarantee, not a measurement of how long nvim
3057 # takes. 3065 # takes.
3058 settle 800 20000 3066 settle 800 20000
3059 send \x1c 3067 send \x1c\x1c
3060 waitexit 10000 3068 waitexit 10000
3061 EOF 3069 EOF
3062 RC=$? 3070 RC=$?
@@ -3136,7 +3144,7 @@ settle 400 15000
3136 send sh $OUT.bell.sh\n 3144 send sh $OUT.bell.sh\n
3137 expect BELLDONE 15000 3145 expect BELLDONE 15000
3138 settle 400 15000 3146 settle 400 15000
3139 send \x1c 3147 send \x1c\x1c
3140 waitexit 10000 3148 waitexit 10000
3141 EOF 3149 EOF
3142 RC=$? 3150 RC=$?
@@ -3231,7 +3239,7 @@ HCACHE="$XDG_CACHE_HOME/mux/hosts/$HHOST"
3231 # QUIC success, so once its pid is OBSERVED gone, a second marker still 3239 # QUIC success, so once its pid is OBSERVED gone, a second marker still
3232 # making the round trip can only be riding QUIC. The pipe's owner is dead. 3240 # making the round trip can only be riding QUIC. The pipe's owner is dead.
3233 { printf 'printf "cold-%%s\\n" one\n'; sleep 6; \ 3241 { printf 'printf "cold-%%s\\n" one\n'; sleep 6; \
3234 printf 'printf "cold-%%s\\n" two\n'; sleep 2.5; printf '\034'; } | \ 3242 printf 'printf "cold-%%s\\n" two\n'; sleep 2.5; printf '\034\034'; } | \
3235 SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN" PATH="$HPATH" timeout 60 \ 3243 SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN" PATH="$HPATH" timeout 60 \
3236 "$MUX" "$HHOST" > "$OUT.h1" 2> "$OUT.h1.err" & 3244 "$MUX" "$HHOST" > "$OUT.h1" 2> "$OUT.h1.err" &
3237 H1PID=$! 3245 H1PID=$!
@@ -3341,7 +3349,7 @@ ok "cold handoff: ssh fetches the coordinates, QUIC carries the session"
3341 # handoff's own line, which has nothing to report when nothing fell back. 3349 # handoff's own line, which has nothing to report when nothing fell back.
3342 HSHIMS_B=$(wc -l < "$SSHIM_PIDLOG") 3350 HSHIMS_B=$(wc -l < "$SSHIM_PIDLOG")
3343 set +e 3351 set +e
3344 { printf 'printf "warm-%%s\\n" ok\n'; sleep 2.5; printf '\034'; } | \ 3352 { printf 'printf "warm-%%s\\n" ok\n'; sleep 2.5; printf '\034\034'; } | \
3345 SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN" PATH="$HPATH" timeout 40 \ 3353 SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN" PATH="$HPATH" timeout 40 \
3346 "$MUX" "$HHOST" > "$OUT.h2" 2> "$OUT.h2.err" 3354 "$MUX" "$HHOST" > "$OUT.h2" 2> "$OUT.h2.err"
3347 RC=$? 3355 RC=$?
@@ -3397,7 +3405,7 @@ printf 'endpoint %s %s\n' "$HDEADPORT" "$HHEXKEY" > "$HCACHE"
3397 chmod 600 "$HCACHE" 3405 chmod 600 "$HCACHE"
3398 HSHIMS_C=$(wc -l < "$SSHIM_PIDLOG") 3406 HSHIMS_C=$(wc -l < "$SSHIM_PIDLOG")
3399 HT4=$(date +%s%N) 3407 HT4=$(date +%s%N)
3400 { printf 'printf "heal-%%s\\n" ok\n'; sleep 6; printf '\034'; } | \ 3408 { printf 'printf "heal-%%s\\n" ok\n'; sleep 6; printf '\034\034'; } | \
3401 SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN" PATH="$HPATH" timeout 40 \ 3409 SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN" PATH="$HPATH" timeout 40 \
3402 "$MUX" "$HHOST" > "$OUT.h3" 2> "$OUT.h3.err" & 3410 "$MUX" "$HHOST" > "$OUT.h3" 2> "$OUT.h3.err" &
3403 H3PID=$! 3411 H3PID=$!
@@ -3488,7 +3496,7 @@ HDPID=$!
3488 wait_sock "$SOCK17" "$OUT.d17.d" "key-mismatch daemon never bound" 3496 wait_sock "$SOCK17" "$OUT.d17.d" "key-mismatch daemon never bound"
3489 HSHIMS_D=$(wc -l < "$SSHIM_PIDLOG") 3497 HSHIMS_D=$(wc -l < "$SSHIM_PIDLOG")
3490 HT0=$(date +%s%N) 3498 HT0=$(date +%s%N)
3491 { printf 'printf "fallback-%%s\\n" ok\n'; sleep 6; printf '\034'; } | \ 3499 { printf 'printf "fallback-%%s\\n" ok\n'; sleep 6; printf '\034\034'; } | \
3492 SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN2" PATH="$HPATH" timeout 40 \ 3500 SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN2" PATH="$HPATH" timeout 40 \
3493 "$MUX" "127.0.0.1" > "$OUT.h4" 2> "$OUT.h4.err" & 3501 "$MUX" "127.0.0.1" > "$OUT.h4" 2> "$OUT.h4.err" &
3494 H4PID=$! 3502 H4PID=$!
@@ -3571,7 +3579,7 @@ ok "a key mismatch falls back to the ssh pipe: one deadline (${HMS}ms), one line
3571 : > "$HCFGBAD" 3579 : > "$HCFGBAD"
3572 HSHIMS_E=$(wc -l < "$SSHIM_PIDLOG") 3580 HSHIMS_E=$(wc -l < "$SSHIM_PIDLOG")
3573 HT2=$(date +%s%N) 3581 HT2=$(date +%s%N)
3574 { printf 'printf "none-%%s\\n" ok\n'; sleep 2.5; printf '\034'; } | \ 3582 { printf 'printf "none-%%s\\n" ok\n'; sleep 2.5; printf '\034\034'; } | \
3575 SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN" XDG_CONFIG_HOME="$HCFGBAD" \ 3583 SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN" XDG_CONFIG_HOME="$HCFGBAD" \
3576 PATH="$HPATH" timeout 40 \ 3584 PATH="$HPATH" timeout 40 \
3577 "$MUX" "mux-e2e-none@127.0.0.1" > "$OUT.h5" 2> "$OUT.h5.err" & 3585 "$MUX" "mux-e2e-none@127.0.0.1" > "$OUT.h5" 2> "$OUT.h5.err" &
@@ -3671,17 +3679,17 @@ wait_sock "$SOCK21" "$OUT.m18.d" "M18 multi-session daemon never bound"
3671 # the output reads `m18a-pin`, so a grep that hits is the shell's work and 3679 # the output reads `m18a-pin`, so a grep that hits is the shell's work and
3672 # not an echo of our keystrokes (the M-web passivity block's trick). 3680 # not an echo of our keystrokes (the M-web passivity block's trick).
3673 # 3681 #
3674 # Both run in the foreground and DETACH (\034, Ctrl-\) rather than staying 3682 # Both run in the foreground and DETACH (\034\034, the Ctrl-\ chord) rather than staying
3675 # up: the session has to outlive its client for every assertion below to 3683 # up: the session has to outlive its client for every assertion below to
3676 # mean anything. A session ends when its shell exits, not when its last 3684 # mean anything. A session ends when its shell exits, not when its last
3677 # client leaves (decision 8) — so these greps run against two sessions 3685 # client leaves (decision 8) — so these greps run against two sessions
3678 # with nobody attached, which is also why none of their answers can be 3686 # with nobody attached, which is also why none of their answers can be
3679 # explained by a live client holding something open. 3687 # explained by a live client holding something open.
3680 { printf 'printf "m18a-%%s\\n" pin\n'; sleep 2; printf '\034'; } | \ 3688 { printf 'printf "m18a-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \
3681 timeout 40 "$MUX" --sock "$SOCK21" --session a > "$OUT.m18a" 2> "$OUT.m18a.err" 3689 timeout 40 "$MUX" --sock "$SOCK21" --session a > "$OUT.m18a" 2> "$OUT.m18a.err"
3682 wait_grid "$SOCK21" "m18a-pin" "M18: session a's marker" a 3690 wait_grid "$SOCK21" "m18a-pin" "M18: session a's marker" a
3683 3691
3684 { printf 'printf "m18b-%%s\\n" pin\n'; sleep 2; printf '\034'; } | \ 3692 { printf 'printf "m18b-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \
3685 timeout 40 "$MUX" --sock "$SOCK21" --session b > "$OUT.m18b" 2> "$OUT.m18b.err" 3693 timeout 40 "$MUX" --sock "$SOCK21" --session b > "$OUT.m18b" 2> "$OUT.m18b.err"
3686 wait_grid "$SOCK21" "m18b-pin" "M18: session b's marker" b 3694 wait_grid "$SOCK21" "m18b-pin" "M18: session b's marker" b
3687 3695
@@ -3803,10 +3811,10 @@ wait_for "$OUT.m18wh" "serving" 10 || {
3803 3811
3804 # Content for each session, planted by a CLI client and left behind: the 3812 # Content for each session, planted by a CLI client and left behind: the
3805 # tiles are passive 1x1 wall tiles and can never type anything themselves. 3813 # tiles are passive 1x1 wall tiles and can never type anything themselves.
3806 { printf 'printf "wall-a-%%s\\n" pin\n'; sleep 2; printf '\034'; } | \ 3814 { printf 'printf "wall-a-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \
3807 timeout 40 "$MUX" --sock "$SOCK22" --session a > "$OUT.m18wa" 2> "$OUT.m18wa.err" 3815 timeout 40 "$MUX" --sock "$SOCK22" --session a > "$OUT.m18wa" 2> "$OUT.m18wa.err"
3808 wait_grid "$SOCK22" "wall-a-pin" "M18 wall: session a's marker" a 3816 wait_grid "$SOCK22" "wall-a-pin" "M18 wall: session a's marker" a
3809 { printf 'printf "wall-b-%%s\\n" pin\n'; sleep 2; printf '\034'; } | \ 3817 { printf 'printf "wall-b-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \
3810 timeout 40 "$MUX" --sock "$SOCK22" --session b > "$OUT.m18wb" 2> "$OUT.m18wb.err" 3818 timeout 40 "$MUX" --sock "$SOCK22" --session b > "$OUT.m18wb" 2> "$OUT.m18wb.err"
3811 wait_grid "$SOCK22" "wall-b-pin" "M18 wall: session b's marker" b 3819 wait_grid "$SOCK22" "wall-b-pin" "M18 wall: session b's marker" b
3812 3820
@@ -3905,7 +3913,7 @@ while : ; do
3905 done 3913 done
3906 3914
3907 set +e 3915 set +e
3908 { printf 'printf "q18a-%%s\\n" pin\n'; sleep 2; printf '\034'; } | \ 3916 { printf 'printf "q18a-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \
3909 timeout 40 "$MUX" "quic://127.0.0.1:$QPORT5" --key "$M18KEY" \ 3917 timeout 40 "$MUX" "quic://127.0.0.1:$QPORT5" --key "$M18KEY" \
3910 --quic-idle-ms 15000 --session a > "$OUT.m18qa" 2> "$OUT.m18qa.err" 3918 --quic-idle-ms 15000 --session a > "$OUT.m18qa" 2> "$OUT.m18qa.err"
3911 RC=$? 3919 RC=$?
@@ -3916,7 +3924,7 @@ set -e
3916 wait_grid "$SOCK23" "q18a-pin" "M18 quic: session a's marker" a 3924 wait_grid "$SOCK23" "q18a-pin" "M18 quic: session a's marker" a
3917 3925
3918 set +e 3926 set +e
3919 { printf 'printf "q18b-%%s\\n" pin\n'; sleep 2; printf '\034'; } | \ 3927 { printf 'printf "q18b-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \
3920 timeout 40 "$MUX" "quic://127.0.0.1:$QPORT5" --key "$M18KEY" \ 3928 timeout 40 "$MUX" "quic://127.0.0.1:$QPORT5" --key "$M18KEY" \
3921 --quic-idle-ms 15000 --session b > "$OUT.m18qb" 2> "$OUT.m18qb.err" 3929 --quic-idle-ms 15000 --session b > "$OUT.m18qb" 2> "$OUT.m18qb.err"
3922 RC=$? 3930 RC=$?
@@ -3941,7 +3949,7 @@ fi
3941 # The cross-transport join. Same name, different door: this must land in 3949 # The cross-transport join. Same name, different door: this must land in
3942 # the shell the QUIC client left behind, not spawn a second one. 3950 # the shell the QUIC client left behind, not spawn a second one.
3943 set +e 3951 set +e
3944 { printf 'printf "x18a-%%s\\n" pin\n'; sleep 2; printf '\034'; } | \ 3952 { printf 'printf "x18a-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \
3945 timeout 40 "$MUX" --sock "$SOCK23" --session a > "$OUT.m18qx" 2> "$OUT.m18qx.err" 3953 timeout 40 "$MUX" --sock "$SOCK23" --session a > "$OUT.m18qx" 2> "$OUT.m18qx.err"
3946 RC=$? 3954 RC=$?
3947 set -e 3955 set -e
@@ -3984,7 +3992,7 @@ wait_sock "$SOCK18" "$OUT.weba.d" "web passivity daemon never bound"
3984 # The 80x24 client: types the first marker, holds the session open long 3992 # The 80x24 client: types the first marker, holds the session open long
3985 # enough for the 1x1 attacher to come and go, types the second marker 3993 # enough for the 1x1 attacher to come and go, types the second marker
3986 # (the geometry probe), detaches. 3994 # (the geometry probe), detaches.
3987 { printf 'printf "wall-%%s\\n" pin\n'; sleep 4; printf 'printf "zz-%%s\\n" web\n'; sleep 2; printf '\034'; } | \ 3995 { printf 'printf "wall-%%s\\n" pin\n'; sleep 4; printf 'printf "zz-%%s\\n" web\n'; sleep 2; printf '\034\034'; } | \
3988 timeout 40 "$MUX" --sock "$SOCK18" > "$OUT.weba" 2> "$OUT.weba.err" & 3996 timeout 40 "$MUX" --sock "$SOCK18" > "$OUT.weba" 2> "$OUT.weba.err" &
3989 WCLIPID=$! 3997 WCLIPID=$!
3990 wait_grid "$SOCK18" "wall-pin" "web passivity: the first marker" 3998 wait_grid "$SOCK18" "wall-pin" "web passivity: the first marker"
@@ -4001,7 +4009,7 @@ expect \x1b[?1049h 15000
4001 send \x20 4009 send \x20
4002 expect z 20000 4010 expect z 20000
4003 settle 300 10000 4011 settle 300 10000
4004 send \x1c 4012 send \x1c\x1c
4005 waitexit 10000 4013 waitexit 10000
4006 EOF 4014 EOF
4007 RC=$? 4015 RC=$?
@@ -4040,7 +4048,7 @@ W1PID=$!
4040 wait_for "$OUT.webh" "serving" 10 || { 4048 wait_for "$OUT.webh" "serving" 10 || {
4041 echo "e2e FAIL: hub never reported serving"; cat "$OUT.webh"; exit 1; } 4049 echo "e2e FAIL: hub never reported serving"; cat "$OUT.webh"; exit 1; }
4042 4050
4043 { printf 'printf "web-%%s\\n" b1\n'; sleep 4; printf '\034'; } | \ 4051 { printf 'printf "web-%%s\\n" b1\n'; sleep 4; printf '\034\034'; } | \
4044 timeout 40 "$MUX" --sock "$SOCK19" > "$OUT.webb" 2> "$OUT.webb.err" & 4052 timeout 40 "$MUX" --sock "$SOCK19" > "$OUT.webb" 2> "$OUT.webb.err" &
4045 WCLIPID=$! 4053 WCLIPID=$!
4046 wait_grid "$SOCK19" "web-b1" "web hub: the typing client's marker" 4054 wait_grid "$SOCK19" "web-b1" "web hub: the typing client's marker"
@@ -4111,7 +4119,7 @@ W2PID=$!
4111 wait_for "$OUT.webh2" "serving" 10 || { 4119 wait_for "$OUT.webh2" "serving" 10 || {
4112 echo "e2e FAIL: tear hub never reported serving"; cat "$OUT.webh2"; exit 1; } 4120 echo "e2e FAIL: tear hub never reported serving"; cat "$OUT.webh2"; exit 1; }
4113 4121
4114 { printf 'printf "web-%%s\\n" c1\n'; sleep 1.5; printf '\034'; } | \ 4122 { printf 'printf "web-%%s\\n" c1\n'; sleep 1.5; printf '\034\034'; } | \
4115 timeout 30 "$MUX" --sock "$SOCK20" > "$OUT.webc" 2> "$OUT.webc.err" 4123 timeout 30 "$MUX" --sock "$SOCK20" > "$OUT.webc" 2> "$OUT.webc.err"
4116 wait_grid "$SOCK20" "web-c1" "web tear: the first session's marker" 4124 wait_grid "$SOCK20" "web-c1" "web tear: the first session's marker"
4117 4125
@@ -4138,7 +4146,7 @@ D16PID=""
4138 D17PID=$! 4146 D17PID=$!
4139 wait_sock "$SOCK20" "$OUT.webc2.d" "web tear restart never bound" 4147 wait_sock "$SOCK20" "$OUT.webc2.d" "web tear restart never bound"
4140 4148
4141 { printf 'printf "web-%%s\\n" c2\n'; sleep 1.5; printf '\034'; } | \ 4149 { printf 'printf "web-%%s\\n" c2\n'; sleep 1.5; printf '\034\034'; } | \
4142 timeout 30 "$MUX" --sock "$SOCK20" > "$OUT.webc2" 2> "$OUT.webc2.err" 4150 timeout 30 "$MUX" --sock "$SOCK20" > "$OUT.webc2" 2> "$OUT.webc2.err"
4143 4151
4144 set +e 4152 set +e
@@ -4224,7 +4232,7 @@ RC=$(curl -s -o /dev/null -w '%{http_code}' -H "Origin: $DWORIG" -X POST \
4224 # the M-web (a) contract in passing — a 1x1 attach never claims the grid, so 4232 # the M-web (a) contract in passing — a 1x1 attach never claims the grid, so
4225 # the 80-wide marker is still one contiguous string to expect. 4233 # the 80-wide marker is still one contiguous string to expect.
4226 set +e 4234 set +e
4227 { printf 'printf "dyn-%%s\\n" w1\n'; sleep 4; printf '\034'; } | \ 4235 { printf 'printf "dyn-%%s\\n" w1\n'; sleep 4; printf '\034\034'; } | \
4228 timeout 40 "$MUX" --sock "$SOCK25" > "$OUT.dwcli" 2> "$OUT.dwcli.err" 4236 timeout 40 "$MUX" --sock "$SOCK25" > "$OUT.dwcli" 2> "$OUT.dwcli.err"
4229 RC=$? 4237 RC=$?
4230 set -e 4238 set -e
@@ -4361,10 +4369,10 @@ ok "the wall is runtime state: add, remove, reorder, restore, argv overrides"
4361 D23PID=$! 4369 D23PID=$!
4362 wait_sock "$SOCK26" "$OUT.cwall.d" "CLI wall daemon never bound" 4370 wait_sock "$SOCK26" "$OUT.cwall.d" "CLI wall daemon never bound"
4363 4371
4364 { printf 'printf "cwa-%%s\\n" pin\n'; sleep 2; printf '\034'; } | \ 4372 { printf 'printf "cwa-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \
4365 timeout 40 "$MUX" --sock "$SOCK26" --session a > "$OUT.cwa" 2> "$OUT.cwa.err" 4373 timeout 40 "$MUX" --sock "$SOCK26" --session a > "$OUT.cwa" 2> "$OUT.cwa.err"
4366 wait_grid "$SOCK26" "cwa-pin" "CLI wall: session a's marker" a 4374 wait_grid "$SOCK26" "cwa-pin" "CLI wall: session a's marker" a
4367 { printf 'printf "cwb-%%s\\n" pin\n'; sleep 2; printf '\034'; } | \ 4375 { printf 'printf "cwb-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \
4368 timeout 40 "$MUX" --sock "$SOCK26" --session b > "$OUT.cwb" 2> "$OUT.cwb.err" 4376 timeout 40 "$MUX" --sock "$SOCK26" --session b > "$OUT.cwb" 2> "$OUT.cwb.err"
4369 wait_grid "$SOCK26" "cwb-pin" "CLI wall: session b's marker" b 4377 wait_grid "$SOCK26" "cwb-pin" "CLI wall: session b's marker" b
4370 4378
@@ -4406,6 +4414,58 @@ assert_stopped "$SOCK26" "$D23PID" "CLI wall" "$OUT.cwstop"
4406 D23PID="" 4414 D23PID=""
4407 ok "mux wall: two sessions and a live delta on one terminal, read-only" 4415 ok "mux wall: two sessions and a live delta on one terminal, read-only"
4408 4416
4417 # ---- M1: Ctrl-\ is a prefix, not an action ------------------------------
4418 #
4419 # Two claims in one client, because they are the same claim seen from both
4420 # sides: the chord layer eats exactly the two bytes of a chord and nothing
4421 # else.
4422 #
4423 # (a) an unknown command key is swallowed WITH its prefix. `\x1cx` is sent
4424 # at an empty prompt, then a marker command. If either byte had reached
4425 # the pty the shell would have read `xprintf ...` and the marker would
4426 # never print — so the marker is the assertion, and the explicit grep
4427 # for `xprintf` names what went wrong when it does.
4428 # (b) `\x1cd` detaches. The client exits 0 and the session it left behind
4429 # still answers, which is what separates a detach from a kill.
4430 #
4431 # A real pty (ptyclient) rather than a pipeline: the shell only echoes what
4432 # it was really given when there is a terminal to echo to, and the echo is
4433 # what carries the stray `x` if the layer leaks one.
4434 "$MUXD" run --sock "$SOCK27" --shell /bin/sh > "$OUT.pfx.d" 2>&1 &
4435 D24PID=$!
4436 wait_sock "$SOCK27" "$OUT.pfx.d" "prefix daemon never bound"
4437
4438 set +e
4439 timeout 40 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.pfx" --err "$OUT.pfx.err" \
4440 -- "$MUX" --sock "$SOCK27" > "$OUT.pfx.log" 2>&1 <<'EOF'
4441 expect \x1b[?1049h 15000
4442 settle 400 15000
4443 send \x1cx
4444 settle 400 15000
4445 send printf 'm1-%s\\n' pin\n
4446 expect m1-pin 15000
4447 settle 400 15000
4448 send \x1cd
4449 waitexit 10000
4450 EOF
4451 RC=$?
4452 set -e
4453 [ "$RC" -eq 0 ] || {
4454 echo "e2e FAIL: prefix: ptyclient leg exited $RC (did \\x1cd detach?):"
4455 cat "$OUT.pfx.log"; exit 1; }
4456 grep -q "m1-pin" "$OUT.pfx" || {
4457 echo "e2e FAIL: prefix: the marker never printed"; exit 1; }
4458 grep -q "xprintf" "$OUT.pfx" && {
4459 echo "e2e FAIL: prefix: the unknown command key reached the pty"
4460 cat "$OUT.pfx"; exit 1; }
4461 # Detached, not exited: the session outlives the client that left it.
4462 "$MUXA" status --sock "$SOCK27" > "$OUT.pfxst" 2>&1 || {
4463 echo "e2e FAIL: prefix: the session did not survive the detach:"
4464 cat "$OUT.pfxst"; exit 1; }
4465 assert_stopped "$SOCK27" "$D24PID" "prefix" "$OUT.pfxstop"
4466 D24PID=""
4467 ok "Ctrl-\\ prefix: an unknown chord is swallowed, Ctrl-\\ d detaches"
4468
4409 # The long-lived daemon has served every scenario that wanted it; stop it 4469 # The long-lived daemon has served every scenario that wanted it; stop it
4410 # NOW so its allocator verdict is written while the suite is still running 4470 # NOW so its allocator verdict is written while the suite is still running
4411 # and can say so. SIGTERM runs the clean-shutdown path, so the defer chain 4471 # and can say so. SIGTERM runs the clean-shutdown path, so the defer chain
@@ -4445,8 +4505,11 @@ DPID=""
4445 # wall block's convergence does. The 33rd is the CLI wall, and no 4505 # wall block's convergence does. The 33rd is the CLI wall, and no
4446 # convergence point for the M12 reason: it asserts on a ptyclient capture 4506 # convergence point for the M12 reason: it asserts on a ptyclient capture
4447 # — bytes a wall painted to a terminal — which no grid dump can witness. 4507 # — bytes a wall painted to a terminal — which no grid dump can witness.
4448 [ "$OK_COUNT" = "33" ] || { 4508 # The 34th is the Ctrl-\ prefix, and no convergence point for that same
4449 echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 33 —" 4509 # reason: its subject is what the client REFUSED to forward, which a grid
4510 # that never saw the bytes cannot testify to either way.
4511 [ "$OK_COUNT" = "34" ] || {
4512 echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 34 —"
4450 echo " a scenario was added (update the pin) or silently lost" 4513 echo " a scenario was added (update the pin) or silently lost"
4451 exit 1 4514 exit 1
4452 } 4515 }
@@ -4454,4 +4517,4 @@ DPID=""
4454 echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 35" 4517 echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 35"
4455 exit 1 4518 exit 1
4456 } 4519 }
4457 echo "e2e OK (33 scenarios, 35 convergence points)" 4520 echo "e2e OK (34 scenarios, 35 convergence points)"
test/soak.sh
Old New
@@ -85,7 +85,7 @@ else
85 ATTACH_FAILED=0 85 ATTACH_FAILED=0
86 c=1 86 c=1
87 while [ "$c" -le "$CYCLES" ]; do 87 while [ "$c" -le "$CYCLES" ]; do
88 { printf 'echo cycle-%s\n' "$c"; sleep 1; printf '\034'; } | \ 88 { printf 'echo cycle-%s\n' "$c"; sleep 1; printf '\034\034'; } | \
89 timeout 30 "$MUX" --sock "$PSOCK" > "$PCAP" 2>/dev/null 89 timeout 30 "$MUX" --sock "$PSOCK" > "$PCAP" 2>/dev/null
90 # Vacuous-green guard: a regression that makes the client fail 90 # Vacuous-green guard: a regression that makes the client fail
91 # instantly would leave fds/RSS trivially flat and this phase green 91 # instantly would leave fds/RSS trivially flat and this phase green
test/wan.sh
Old New
@@ -378,7 +378,7 @@ class Client:
378 378
379 def detach(self): 379 def detach(self):
380 try: 380 try:
381 self.send(b"\x1c") 381 self.send(b"\x1c\x1c")
382 self.p.wait(timeout=8) 382 self.p.wait(timeout=8)
383 except Exception: 383 except Exception:
384 self.kill() 384 self.kill()
@@ -1323,7 +1323,7 @@ for S in a b; do
1323 # A marker the REMOTE shell has to expand: the typed line carries a 1323 # A marker the REMOTE shell has to expand: the typed line carries a
1324 # literal %s, so the joined form can only exist if bash on the box ran 1324 # literal %s, so the joined form can only exist if bash on the box ran
1325 # printf. An echo of our own keystrokes cannot satisfy the greps below. 1325 # printf. An echo of our own keystrokes cannot satisfy the greps below.
1326 { printf 'printf "wansess-%s-%%s\\n" %s\n' "$S" "$M18_N"; sleep 3; printf '\034'; } | \ 1326 { printf 'printf "wansess-%s-%%s\\n" %s\n' "$S" "$M18_N"; sleep 3; printf '\034\034'; } | \
1327 timeout 90 "$MUX" --via "$VIA" --session "$S" > "$WORK/m18-$S.out" 2>&1 1327 timeout 90 "$MUX" --via "$VIA" --session "$S" > "$WORK/m18-$S.out" 2>&1
1328 rc=$? 1328 rc=$?
1329 if [ "$rc" -ne 0 ]; then 1329 if [ "$rc" -ne 0 ]; then
@@ -1361,7 +1361,7 @@ if [ "$M18_OK" -eq 1 ]; then
1361 # is both markers on one grid afterwards — a fresh shell would carry the 1361 # is both markers on one grid afterwards — a fresh shell would carry the
1362 # new marker alone, which is exactly the failure a name that resolved 1362 # new marker alone, which is exactly the failure a name that resolved
1363 # per-connection would produce. 1363 # per-connection would produce.
1364 { printf 'printf "wanre-%%s\\n" %s\n' "$M18_N"; sleep 3; printf '\034'; } | \ 1364 { printf 'printf "wanre-%%s\\n" %s\n' "$M18_N"; sleep 3; printf '\034\034'; } | \
1365 timeout 90 "$MUX" --via "$VIA" --session a > "$WORK/m18-re.out" 2>&1 1365 timeout 90 "$MUX" --via "$VIA" --session a > "$WORK/m18-re.out" 2>&1
1366 rc=$? 1366 rc=$?
1367 if [ "$rc" -ne 0 ]; then 1367 if [ "$rc" -ne 0 ]; then
test/xversion.sh
Old New
@@ -137,7 +137,7 @@ wait_sock() {
137 # hits is the shell's work rather than an echo of our keystrokes. 137 # hits is the shell's work rather than an echo of our keystrokes.
138 drive() { # drive BIN MARKER TRANSPORTARGS... 138 drive() { # drive BIN MARKER TRANSPORTARGS...
139 _bin="$1"; _marker="$2"; shift 2 139 _bin="$1"; _marker="$2"; shift 2
140 { printf 'printf "%s-%%s\\n" pin\n' "$_marker"; sleep 2; printf '\034'; } | \ 140 { printf 'printf "%s-%%s\\n" pin\n' "$_marker"; sleep 2; printf '\034\034'; } | \
141 timeout 40 "$_bin" "$@" > "$TMP/out" 2> "$TMP/err" 141 timeout 40 "$_bin" "$@" > "$TMP/out" 2> "$TMP/err"
142 } 142 }
143 143
@@ -421,7 +421,7 @@ settle 400 15000
421 send sh /run/mux/sc-$_sc_tag.sh\n 421 send sh /run/mux/sc-$_sc_tag.sh\n
422 expect sc$_sc_tag-pin 15000 422 expect sc$_sc_tag-pin 15000
423 settle 600 15000 423 settle 600 15000
424 send \x1c 424 send \x1c\x1c
425 waitexit 10000 425 waitexit 10000
426 SCRIPT 426 SCRIPT
427 } 427 }