a73x

a0915d2d

test: the non-tty scenarios wait for their output instead of sleeping for it

a73x   2026-08-22 10:09

Commit message
test: the non-tty scenarios wait for their output instead of sleeping for it

Every scenario driving mux over a pipe paced itself with `sleep 2` between
the command and the detach — 285s of literal sleep in a 329s suite, bought
for output that arrives in about 2ms. pipe_mux holds the client's stdin
open on a FIFO, so a scenario sends, waits for the needle it was going to
grep anyway, and detaches on it. 329s -> 183s, the same 60 scenarios and
the same assertions. pipe_detach asserts the client's exit status while it
is there, which a foreground `| "$MUX"` under set -e had only been doing
by accident.

Two waits carry the legs with no marker of their own to wait for.
await_repaint watches for the full repaint the client guarantees after a
resync (interact.zig's repaint_after_resync), which is how the transport
tear and the daemon restart know the resume happened rather than assuming
it from a clock. pipe_waitexit waits out a client whose session ended
under it, and takes the status that session meant to return.

What still sleeps, sleeps on purpose, and the distinction is worth keeping:
the prediction legs snapshot the capture a fixed time after a keystroke, so
the clock IS the assertion. So it is for the three muxa injections — a 0x0
wall tile paints nothing of its session's existing content until a live
delta wakes it, so an inject gated on seeing its own marker would be
waiting for the thing it exists to cause.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

test/e2e.sh
Old New
@@ -210,15 +210,13 @@ HQPORT=$(( 11000 + ($$ % 4000) ))
210 HDEADPORT=$(( 16000 + ($$ % 4000) )) 210 HDEADPORT=$(( 16000 + ($$ % 4000) ))
211 HAPID="" 211 HAPID=""
212 HDPID="" 212 HDPID=""
213 # The three handoff clients that run in the BACKGROUND. Each is already 213 # The pipe_mux client in flight, and its FIFO. Bounded by its own
214 # bounded by its own `timeout`, which stays the primary guarantee; these 214 # `timeout`, which stays the primary guarantee; this is the trap's backstop
215 # are the trap's backstop for the window where a scenario exits between 215 # for a scenario that exits between pipe_mux and pipe_detach, leaving a
216 # the launch and the wait, leaving a client the shell would otherwise 216 # client the shell would otherwise orphan. Declared here so the trap can
217 # orphan. Declared here so the trap can read them under `set -u`. 217 # read them under `set -u`.
218 H1PID="" 218 PIPE_PID=""
219 H3PID="" 219 PIPE_IN=""
220 H4PID=""
221 H5PID=""
222 # M-web. Three daemons (the passivity pin's, the hub-basic one, and the 220 # M-web. Three daemons (the passivity pin's, the hub-basic one, and the
223 # tear scenario's two incarnations share SOCK20), two hub processes, and 221 # tear scenario's two incarnations share SOCK20), two hub processes, and
224 # a port band of its own following the 5000-spacing convention — INSIDE 222 # a port band of its own following the 5000-spacing convention — INSIDE
@@ -605,6 +603,107 @@ wait_sock() {
605 } 603 }
606 } 604 }
607 605
606 # pipe_mux OUT ERR CMD... — a client on a plain pipe, and the pipe kept
607 # open. ERR is the stderr capture, "" to leave it on the suite's own.
608 # The suite's non-tty scenarios used to be `{ printf cmd; sleep 2; printf
609 # detach; } | "$MUX"`: two seconds bought for output that lands in
610 # milliseconds (measured: the needle is in the capture 2ms after the send),
611 # and 120s of the suite's runtime was exactly that purchase. Here the stdin
612 # is a FIFO held open on fd 9, so a scenario sends, then WAITS FOR THE NEEDLE
613 # with await_out, then detaches — the same assertion it was going to make
614 # anyway, moved to where it ends the wait. fd 9 is opened read-write so
615 # neither the open nor the client's EOF blocks on the other side; the client
616 # sees EOF only when pipe_detach closes it.
617 #
618 # Not for the scenarios that pace on purpose: the prediction legs snapshot
619 # the capture N ms after a keystroke, and a sleep there IS the test.
620 pipe_mux() {
621 PIPE_OUT="$1"; PIPE_ERR="$2"; shift 2
622 PIPE_IN="$PIPE_OUT.in"
623 rm -f "$PIPE_IN"
624 mkfifo "$PIPE_IN"
625 exec 9<>"$PIPE_IN"
626 if [ -n "$PIPE_ERR" ]; then
627 "$@" < "$PIPE_IN" > "$PIPE_OUT" 2> "$PIPE_ERR" &
628 else
629 "$@" < "$PIPE_IN" > "$PIPE_OUT" &
630 fi
631 PIPE_PID=$!
632 }
633
634 # pipe_send FMT [ARGS] — printf into the open client, same spelling the
635 # inline blocks used so a conversion moves the format string verbatim.
636 pipe_send() {
637 # shellcheck disable=SC2059
638 printf "$@" >&9
639 }
640
641 # await_out FILE NEEDLE LABEL — wait_for with the verdict attached: FAIL
642 # with the capture when NEEDLE never comes (5s, scaled). The same `grep -q`
643 # the scenario asserts with afterwards, so waiting on it costs no new claim.
644 await_out() {
645 wait_for "$1" "$2" 5 || {
646 echo "e2e FAIL: $3; never saw '$2' in $1, which holds:"
647 cat "$1"
648 exit 1
649 }
650 }
651
652 # repaints FILE — how many full repaints (ESC[2J) a capture holds. The
653 # first paint after a reconnect is always a full one (interact.zig
654 # `repaint_after_resync`), so a count that rose is the resume itself, seen
655 # from the terminal's side. Keystrokes typed into a tear are dropped, not
656 # held, so a scenario that tears the transport must see this before it
657 # types again.
658 repaints() {
659 _clr=$(printf '\033[2J')
660 grep -a -F -o "$_clr" "$1" 2>/dev/null | wc -l
661 }
662
663 # await_repaint FILE BASELINE LABEL — wait until repaints FILE exceeds
664 # BASELINE (5s, scaled), FAIL with the capture otherwise.
665 await_repaint() {
666 _i=0
667 while [ "$(repaints "$1")" -le "$2" ] && [ "$_i" -lt $(( 50 * TIME_SCALE )) ]; do
668 sleep 0.1; _i=$((_i+1))
669 done
670 [ "$(repaints "$1")" -gt "$2" ] || {
671 echo "e2e FAIL: $3; no repaint after the tear in $1, which holds:"
672 cat "$1"
673 exit 1
674 }
675 }
676
677 # pipe_detach [LABEL] — the Ctrl-\ chord, then EOF, then the client's own
678 # exit, which must be 0: a foreground `| "$MUX"` under set -e asserted that
679 # by accident, and this asserts it on purpose. LABEL names the client in
680 # the verdict; PIPE_RC is left for a scenario with more to say.
681 pipe_detach() {
682 printf '\034\034' >&9
683 pipe_waitexit "$@"
684 }
685
686 # pipe_waitexit [LABEL [WANT]] — the client leaves on its own (its session
687 # ended); wait for that, then release the FIFO. Stdin stays open until the
688 # exit so the client never sees an EOF it was not sent. WANT is the exit
689 # status the scenario means (default 0): the exit-semantics legs want the
690 # shell's own.
691 pipe_waitexit() {
692 set +e
693 wait "$PIPE_PID"
694 PIPE_RC=$?
695 set -e
696 exec 9>&-
697 PIPE_PID=""
698 rm -f "$PIPE_IN"
699 [ "$PIPE_RC" -eq "${2:-0}" ] || {
700 echo "e2e FAIL: ${1:-piped client} exited $PIPE_RC (want ${2:-0}; 124 means it hung)"
701 cat "$PIPE_OUT"
702 if [ -n "$PIPE_ERR" ]; then cat "$PIPE_ERR"; fi
703 exit 1
704 }
705 }
706
608 # dump_session SOCK [NAME] — `muxd dump` against one session (M18). An 707 # dump_session SOCK [NAME] — `muxd dump` against one session (M18). An
609 # EMPTY or absent NAME passes no --session flag AT ALL rather than an empty 708 # EMPTY or absent NAME passes no --session flag AT ALL rather than an empty
610 # one, and that is the load-bearing part: no tail is the wire's own 709 # one, and that is the load-bearing part: no tail is the wire's own
@@ -1126,10 +1225,10 @@ cleanup() {
1126 # The backgrounded clients, by tracked pid. Their `timeout` bounds them 1225 # The backgrounded clients, by tracked pid. Their `timeout` bounds them
1127 # anyway; this only shortens the wait on a run that failed between a 1226 # anyway; this only shortens the wait on a run that failed between a
1128 # launch and its wait. 1227 # launch and its wait.
1129 [ -n "$H1PID" ] && kill "$H1PID" 2>/dev/null || true 1228 # Whatever pipe_mux client a failing scenario left mid-flight, and the
1130 [ -n "$H3PID" ] && kill "$H3PID" 2>/dev/null || true 1229 # FIFO that was its stdin.
1131 [ -n "$H4PID" ] && kill "$H4PID" 2>/dev/null || true 1230 [ -n "$PIPE_PID" ] && kill "$PIPE_PID" 2>/dev/null || true
1132 [ -n "$H5PID" ] && kill "$H5PID" 2>/dev/null || true 1231 [ -n "$PIPE_IN" ] && rm -f "$PIPE_IN" || true
1133 [ -n "${MUXD:-}" ] && "$MUXD" stop --sock "$SOCK16" >/dev/null 2>&1 || true 1232 [ -n "${MUXD:-}" ] && "$MUXD" stop --sock "$SOCK16" >/dev/null 2>&1 || true
1134 [ -n "${MUXD:-}" ] && "$MUXD" stop --sock "$SOCK17" >/dev/null 2>&1 || true 1233 [ -n "${MUXD:-}" ] && "$MUXD" stop --sock "$SOCK17" >/dev/null 2>&1 || true
1135 # M-web: hubs and daemons by tracked pid, then stop-by-socket for any 1234 # M-web: hubs and daemons by tracked pid, then stop-by-socket for any
@@ -1516,15 +1615,13 @@ DPID=$!
1516 1615
1517 wait_sock "$SOCK" "$OUT.d1.d" "socket never appeared" 1616 wait_sock "$SOCK" "$OUT.d1.d" "socket never appeared"
1518 1617
1519 # Client with piped stdio: types a command, waits, detaches with the 1618 # Client with piped stdio: types a command, waits for its output, detaches
1520 # Ctrl-\ chord (\034\034 — prefix, then prefix again for detach). 1619 # with the Ctrl-\ chord (prefix, then prefix again for detach).
1521 { printf 'printf "e2e-%%s\\n" works\n'; sleep 2; printf '\034\034'; } | \ 1620 pipe_mux "$OUT" "" timeout 30 "$MUX" --sock "$SOCK"
1522 timeout 30 "$MUX" --sock "$SOCK" > "$OUT" 1621 pipe_send 'printf "e2e-%%s\\n" works\n'
1523
1524 # 1. The client's rendered output must contain the command's result. 1622 # 1. The client's rendered output must contain the command's result.
1525 grep -q "e2e-works" "$OUT" || { 1623 await_out "$OUT" "e2e-works" "client render missing output"
1526 echo "e2e FAIL: client render missing output; got:"; cat "$OUT"; exit 1; 1624 pipe_detach
1527 }
1528 1625
1529 # 2. The daemon kept the session; its grid must match. 1626 # 2. The daemon kept the session; its grid must match.
1530 "$MUXD" dump --sock "$SOCK" | grep -q "e2e-works" || { 1627 "$MUXD" dump --sock "$SOCK" | grep -q "e2e-works" || {
@@ -1561,12 +1658,12 @@ ok "convergence control fires on a doctored stream"
1561 # The escapes are doubled because they are written by the SESSION's shell, 1658 # The escapes are doubled because they are written by the SESSION's shell,
1562 # not by this one: what goes down the pipe is the literal text 1659 # not by this one: what goes down the pipe is the literal text
1563 # `printf "\033[1;31mstyled-%s\033[0m\n" red bold`. 1660 # `printf "\033[1;31mstyled-%s\033[0m\n" red bold`.
1564 { printf 'printf "\\033[1;31mstyled-%%s\\033[0m\\n" red bold\n'; sleep 2; printf '\034\034'; } | \ 1661 pipe_mux "$OUT.st" "" timeout 30 "$MUX" --sock "$SOCK"
1565 timeout 30 "$MUX" --sock "$SOCK" > "$OUT.st" 1662 pipe_send 'printf "\\033[1;31mstyled-%%s\\033[0m\\n" red bold\n'
1663 await_out "$OUT.st" "styled-bold" "the second styled row never reached the client"
1664 pipe_detach
1566 grep -q "styled-red" "$OUT.st" || { 1665 grep -q "styled-red" "$OUT.st" || {
1567 echo "e2e FAIL: styled output never reached the client"; cat "$OUT.st"; exit 1; } 1666 echo "e2e FAIL: styled output never reached the client"; cat "$OUT.st"; exit 1; }
1568 grep -q "styled-bold" "$OUT.st" || {
1569 echo "e2e FAIL: the second styled row never reached the client"; cat "$OUT.st"; exit 1; }
1570 # The plain leg would pass on a bled attribute — same glyphs, wrong colours. 1667 # The plain leg would pass on a bled attribute — same glyphs, wrong colours.
1571 # The --vt leg inside assert_converged is the one that speaks here. 1668 # The --vt leg inside assert_converged is the one that speaks here.
1572 assert_converged "$OUT.st" "$SOCK" "styled content" 1669 assert_converged "$OUT.st" "$SOCK" "styled content"
@@ -1624,13 +1721,11 @@ rm_swept "$OUT.a" "$OUT.b"
1624 # XDG_RUNTIME_DIR is pointed at nothing so the test cannot pass by environment 1721 # XDG_RUNTIME_DIR is pointed at nothing so the test cannot pass by environment
1625 # luck: if --via ever silently fell back to the default socket path, that path 1722 # luck: if --via ever silently fell back to the default socket path, that path
1626 # would resolve to a directory that does not exist and the session would fail 1723 # would resolve to a directory that does not exist and the session would fail
1627 # instead of quietly attaching over the local socket. `|| true` keeps set -e 1724 # instead of quietly attaching over the local socket.
1628 # from aborting before the labelled diagnostic below runs. 1725 pipe_mux "$OUT.via" "" env XDG_RUNTIME_DIR=/nonexistent-mux-e2e "$MUX" --via "$MUXD proxy --sock $SOCK"
1629 { sleep 0.5; printf 'printf "m6-%%s\\n" via-pipe\n'; sleep 2; printf '\034\034'; } | \ 1726 pipe_send 'printf "m6-%%s\\n" via-pipe\n'
1630 XDG_RUNTIME_DIR=/nonexistent-mux-e2e "$MUX" --via "$MUXD proxy --sock $SOCK" > "$OUT.via" || true 1727 await_out "$OUT.via" "m6-via-pipe" "--via transport"
1631 grep -q "m6-via-pipe" "$OUT.via" || { 1728 pipe_detach
1632 echo "e2e FAIL: --via transport"; cat "$OUT.via"; exit 1;
1633 }
1634 kill -0 "$DPID" || { echo "e2e FAIL: daemon died in --via scenario"; exit 1; } 1729 kill -0 "$DPID" || { echo "e2e FAIL: daemon died in --via scenario"; exit 1; }
1635 assert_converged "$OUT.via" "$SOCK" "via transport" 1730 assert_converged "$OUT.via" "$SOCK" "via transport"
1636 rm_swept "$OUT.via" 1731 rm_swept "$OUT.via"
@@ -1743,21 +1838,18 @@ rm_swept "$OUT.abort" "$SOCK2"
1743 # resume renders "m7-after" perfectly well, so only `snapshots` holding 1838 # resume renders "m7-after" perfectly well, so only `snapshots` holding
1744 # still across the tear proves the resume was a delta — which is the whole 1839 # still across the tear proves the resume was a delta — which is the whole
1745 # milestone claim. 1840 # milestone claim.
1746 set +e 1841 pipe_mux "$OUT.m7" "$OUT.m7.err" env XDG_RUNTIME_DIR=/nonexistent-mux-e2e timeout 40 \
1747 { sleep 0.5; printf 'printf "m7-%%s\\n" before\n'; sleep 8; \ 1842 "$MUX" --via "$MUXD proxy --sock $SOCK"
1748 printf 'printf "m7-%%s\\n" after\n'; sleep 2.5; printf '\034\034'; } | \ 1843 pipe_send 'printf "m7-%%s\\n" before\n'
1749 XDG_RUNTIME_DIR=/nonexistent-mux-e2e timeout 40 \
1750 "$MUX" --via "$MUXD proxy --sock $SOCK" > "$OUT.m7" 2> "$OUT.m7.err" &
1751 M7PID=$!
1752 set -e
1753 1844
1754 # Tear only once the session is provably live and idle: the marker is the 1845 # Tear only once the session is provably live and idle: the marker is the
1755 # shell's own output, so seeing it means attached, drained, mid-sleep. 1846 # shell's own output, so seeing it means attached and drained.
1756 wait_for "$OUT.m7" "m7-before" 20 || { 1847 wait_for "$OUT.m7" "m7-before" 20 || {
1757 echo "e2e FAIL: m7 pre-tear output never arrived" 1848 echo "e2e FAIL: m7 pre-tear output never arrived"
1758 cat "$OUT.m7" "$OUT.m7.err" 2>/dev/null; exit 1; 1849 cat "$OUT.m7" "$OUT.m7.err" 2>/dev/null; exit 1;
1759 } 1850 }
1760 SNAPS_BEFORE=$("$MUXD" stats --sock "$SOCK" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') 1851 SNAPS_BEFORE=$("$MUXD" stats --sock "$SOCK" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p')
1852 PAINTS_BEFORE=$(repaints "$OUT.m7")
1761 PPID_PROXY=$(proxy_pid "$SOCK") 1853 PPID_PROXY=$(proxy_pid "$SOCK")
1762 [ -n "$PPID_PROXY" ] || { echo "e2e FAIL: could not find the proxy to kill"; exit 1; } 1854 [ -n "$PPID_PROXY" ] || { echo "e2e FAIL: could not find the proxy to kill"; exit 1; }
1763 kill -9 "$PPID_PROXY" 1855 kill -9 "$PPID_PROXY"
@@ -1765,19 +1857,10 @@ kill -9 "$PPID_PROXY"
1765 # proving survives, so say so out loud rather than inferring it later. 1857 # proving survives, so say so out loud rather than inferring it later.
1766 kill -0 "$DPID" || { echo "e2e FAIL: the tear killed the daemon, not the proxy"; exit 1; } 1858 kill -0 "$DPID" || { echo "e2e FAIL: the tear killed the daemon, not the proxy"; exit 1; }
1767 1859
1768 set +e 1860 await_repaint "$OUT.m7" "$PAINTS_BEFORE" "m7 client never resumed after the transport was killed"
1769 wait "$M7PID" 1861 pipe_send 'printf "m7-%%s\\n" after\n'
1770 RC=$? 1862 await_out "$OUT.m7" "m7-after" "m7 client did not resume after the transport was killed"
1771 set -e 1863 pipe_detach "m7 client"
1772 [ "$RC" -eq 0 ] || {
1773 echo "e2e FAIL: m7 client exited $RC after a transport tear (want 0)"
1774 cat "$OUT.m7" "$OUT.m7.err" 2>/dev/null; exit 1;
1775 }
1776 grep -q "m7-before" "$OUT.m7" || { echo "e2e FAIL: m7 pre-tear output missing"; exit 1; }
1777 grep -q "m7-after" "$OUT.m7" || {
1778 echo "e2e FAIL: m7 client did not resume after the transport was killed"
1779 cat "$OUT.m7" "$OUT.m7.err" 2>/dev/null; exit 1;
1780 }
1781 SNAPS_AFTER=$("$MUXD" stats --sock "$SOCK" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') 1864 SNAPS_AFTER=$("$MUXD" stats --sock "$SOCK" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p')
1782 [ -n "$SNAPS_BEFORE" ] && [ -n "$SNAPS_AFTER" ] || { 1865 [ -n "$SNAPS_BEFORE" ] && [ -n "$SNAPS_AFTER" ] || {
1783 echo "e2e FAIL: could not read the snapshots counter"; exit 1; 1866 echo "e2e FAIL: could not read the snapshots counter"; exit 1;
@@ -1797,12 +1880,8 @@ rm_swept "$OUT.m7" "$OUT.m7.err"
1797 D3PID=$! 1880 D3PID=$!
1798 wait_sock "$SOCK3" "$OUT.d3a.d" "restart-scenario socket never appeared" 1881 wait_sock "$SOCK3" "$OUT.d3a.d" "restart-scenario socket never appeared"
1799 1882
1800 set +e 1883 pipe_mux "$OUT.m7b" "$OUT.m7b.err" timeout 40 "$MUX" --sock "$SOCK3"
1801 { sleep 0.5; printf 'printf "m7b-%%s\\n" pre-restart\n'; sleep 8; \ 1884 pipe_send 'printf "m7b-%%s\\n" pre-restart\n'
1802 printf 'printf "m7b-%%s\\n" post-restart\n'; sleep 3; printf '\034\034'; } | \
1803 timeout 40 "$MUX" --sock "$SOCK3" > "$OUT.m7b" 2> "$OUT.m7b.err" &
1804 M7BPID=$!
1805 set -e
1806 wait_for "$OUT.m7b" "m7b-pre-restart" 20 || { 1885 wait_for "$OUT.m7b" "m7b-pre-restart" 20 || {
1807 echo "e2e FAIL: m7b pre-restart output never arrived" 1886 echo "e2e FAIL: m7b pre-restart output never arrived"
1808 cat "$OUT.m7b" "$OUT.m7b.err" 2>/dev/null; exit 1; 1887 cat "$OUT.m7b" "$OUT.m7b.err" 2>/dev/null; exit 1;
@@ -1810,24 +1889,17 @@ wait_for "$OUT.m7b" "m7b-pre-restart" 20 || {
1810 1889
1811 # SIGKILL leaves the socket file behind; the new daemon's stale-socket 1890 # SIGKILL leaves the socket file behind; the new daemon's stale-socket
1812 # recovery (ECONNREFUSED + S_ISSOCK -> unlink) is what lets it rebind here. 1891 # recovery (ECONNREFUSED + S_ISSOCK -> unlink) is what lets it rebind here.
1892 PAINTS_BEFORE=$(repaints "$OUT.m7b")
1813 hardkill "$D3PID" 1893 hardkill "$D3PID"
1814 sleep 0.5 1894 sleep 0.5
1815 "$MUXD" run --sock "$SOCK3" --shell /bin/sh > "$OUT.d3b.d" 2>&1 & 1895 "$MUXD" run --sock "$SOCK3" --shell /bin/sh > "$OUT.d3b.d" 2>&1 &
1816 D3PID=$! 1896 D3PID=$!
1817 wait_sock "$SOCK3" "$OUT.d3b.d" "daemon did not rebind the stale socket" 1897 wait_sock "$SOCK3" "$OUT.d3b.d" "daemon did not rebind the stale socket"
1818 1898
1819 set +e 1899 await_repaint "$OUT.m7b" "$PAINTS_BEFORE" "m7b client never resumed into the restarted daemon"
1820 wait "$M7BPID" 1900 pipe_send 'printf "m7b-%%s\\n" post-restart\n'
1821 RC=$? 1901 await_out "$OUT.m7b" "m7b-post-restart" "m7b client did not resume into the restarted daemon"
1822 set -e 1902 pipe_detach "m7b client"
1823 [ "$RC" -eq 0 ] || {
1824 echo "e2e FAIL: m7b client exited $RC across a daemon restart (want 0)"
1825 cat "$OUT.m7b" "$OUT.m7b.err" 2>/dev/null; exit 1;
1826 }
1827 grep -q "m7b-post-restart" "$OUT.m7b" || {
1828 echo "e2e FAIL: m7b client did not resume into the restarted daemon"
1829 cat "$OUT.m7b" "$OUT.m7b.err" 2>/dev/null; exit 1;
1830 }
1831 # The fresh session really is fresh: a new daemon means a new shell, so the 1903 # The fresh session really is fresh: a new daemon means a new shell, so the
1832 # pre-restart marker cannot be in its grid. If it were, we would be looking 1904 # pre-restart marker cannot be in its grid. If it were, we would be looking
1833 # at a client that reconnected to something with the old session's history — 1905 # at a client that reconnected to something with the old session's history —
@@ -1968,8 +2040,10 @@ grep -q "already listening" "$OUT.q" || {
1968 2040
1969 # ...and the daemon is still an ordinary daemon: the session runs and the 2041 # ...and the daemon is still an ordinary daemon: the session runs and the
1970 # unix-socket path is unaffected by the listener sharing its poll loop. 2042 # unix-socket path is unaffected by the listener sharing its poll loop.
1971 { printf 'printf "quic-%%s\\n" flags-ok\n'; sleep 2; printf '\034\034'; } | \ 2043 pipe_mux "$OUT.q" "" "$MUX" --sock "$SOCK4"
1972 "$MUX" --sock "$SOCK4" > "$OUT.q" 2044 pipe_send 'printf "quic-%%s\\n" flags-ok\n'
2045 await_out "$OUT.q" "quic-flags-ok" "quic-flags-ok never reached the client"
2046 pipe_detach
1973 grep -q "quic-flags-ok" "$OUT.q" || { 2047 grep -q "quic-flags-ok" "$OUT.q" || {
1974 echo "e2e FAIL: --quic daemon did not serve an ordinary socket client"; cat "$OUT.q"; exit 1; 2048 echo "e2e FAIL: --quic daemon did not serve an ordinary socket client"; cat "$OUT.q"; exit 1;
1975 } 2049 }
@@ -1984,16 +2058,10 @@ assert_converged "$OUT.q" "$SOCK4" "quic daemon serves sockets"
1984 # 1. Attach, run something, detach. The whole point of the milestone in one 2058 # 1. Attach, run something, detach. The whole point of the milestone in one
1985 # scenario: the wire protocol did not change, so this must behave exactly 2059 # scenario: the wire protocol did not change, so this must behave exactly
1986 # as the socket client does. 2060 # as the socket client does.
1987 set +e 2061 pipe_mux "$OUT.qc" "$OUT.qc.err" timeout 30 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" --quic-idle-ms 15000
1988 { printf 'printf "quic-%%s\\n" attach-ok\n'; sleep 2; printf '\034\034'; } | \ 2062 pipe_send 'printf "quic-%%s\\n" attach-ok\n'
1989 timeout 30 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" \ 2063 await_out "$OUT.qc" "quic-attach-ok" "quic-attach-ok never reached the client"
1990 --quic-idle-ms 15000 > "$OUT.qc" 2> "$OUT.qc.err" 2064 pipe_detach "quic:// client"
1991 RC=$?
1992 set -e
1993 [ "$RC" -eq 0 ] || {
1994 echo "e2e FAIL: quic:// client exited $RC (want 0)"
1995 cat "$OUT.qc" "$OUT.qc.err" 2>/dev/null; exit 1;
1996 }
1997 grep -q "quic-attach-ok" "$OUT.qc" || { 2065 grep -q "quic-attach-ok" "$OUT.qc" || {
1998 echo "e2e FAIL: quic:// client render missing output" 2066 echo "e2e FAIL: quic:// client render missing output"
1999 cat "$OUT.qc" "$OUT.qc.err" 2>/dev/null; exit 1; 2067 cat "$OUT.qc" "$OUT.qc.err" 2>/dev/null; exit 1;
@@ -2008,16 +2076,10 @@ assert_converged "$OUT.qc" "$SOCK4" "quic attach"
2008 # because a client that detached deliberately holds nothing to resume 2076 # because a client that detached deliberately holds nothing to resume
2009 # from. The counter is what tells that apart from a delta. 2077 # from. The counter is what tells that apart from a delta.
2010 SNAPS_RA=$("$MUXD" stats --sock "$SOCK4" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') 2078 SNAPS_RA=$("$MUXD" stats --sock "$SOCK4" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p')
2011 set +e 2079 pipe_mux "$OUT.qc" "$OUT.qc.err" timeout 30 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" --quic-idle-ms 15000
2012 { printf 'printf "quic-%%s\\n" reattached\n'; sleep 2; printf '\034\034'; } | \ 2080 pipe_send 'printf "quic-%%s\\n" reattached\n'
2013 timeout 30 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" \ 2081 await_out "$OUT.qc" "quic-reattached" "quic-reattached never reached the client"
2014 --quic-idle-ms 15000 > "$OUT.qc" 2> "$OUT.qc.err" 2082 pipe_detach "quic:// reattach"
2015 RC=$?
2016 set -e
2017 [ "$RC" -eq 0 ] || {
2018 echo "e2e FAIL: quic:// reattach exited $RC (want 0)"
2019 cat "$OUT.qc" "$OUT.qc.err" 2>/dev/null; exit 1;
2020 }
2021 # The earlier marker is still on the grid this client was handed, which is 2083 # The earlier marker is still on the grid this client was handed, which is
2022 # the session having survived the detach rather than a new shell. 2084 # the session having survived the detach rather than a new shell.
2023 grep -q "quic-attach-ok" "$OUT.qc" || { 2085 grep -q "quic-attach-ok" "$OUT.qc" || {
@@ -2116,37 +2178,23 @@ QMS=$(( (QT1 - QT0) / 1000000 ))
2116 SNAPS_BEFORE=$("$MUXD" stats --sock "$SOCK4" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') 2178 SNAPS_BEFORE=$("$MUXD" stats --sock "$SOCK4" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p')
2117 [ -n "$SNAPS_BEFORE" ] || { echo "e2e FAIL: could not read snapshots before the tear"; exit 1; } 2179 [ -n "$SNAPS_BEFORE" ] || { echo "e2e FAIL: could not read snapshots before the tear"; exit 1; }
2118 2180
2119 set +e 2181 pipe_mux "$OUT.qr" "$OUT.qr.err" timeout 40 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" \
2120 { printf 'printf "quic-%%s\\n" pre-tear\n'; sleep 9; printf '\034\034'; } | \ 2182 --quic-idle-ms 1500
2121 timeout 40 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" \ 2183 pipe_send 'printf "quic-%%s\\n" pre-tear\n'
2122 --quic-idle-ms 1500 > "$OUT.qr" 2> "$OUT.qr.err" & 2184 await_out "$OUT.qr" "quic-pre-tear" "quic reconnect client never got its pre-tear marker"
2123 QRPID=$!
2124 set -e
2125 wait_for "$OUT.qr" "quic-pre-tear" 20 || {
2126 echo "e2e FAIL: quic reconnect client never got its pre-tear marker"
2127 cat "$OUT.qr" "$OUT.qr.err" 2>/dev/null; exit 1;
2128 }
2129 2185
2130 # Held well past the 1500ms idle timeout, so the client cannot mistake it
2131 # for a slow moment.
2132 kill -STOP "$D4PID"
2133 # Twice the 1500ms idle timeout: long enough that the client cannot mistake 2186 # Twice the 1500ms idle timeout: long enough that the client cannot mistake
2134 # it for a slow moment, short enough not to pad the suite. 2187 # it for a slow moment, short enough not to pad the suite.
2188 kill -STOP "$D4PID"
2135 sleep 3 2189 sleep 3
2136 kill -CONT "$D4PID" 2190 kill -CONT "$D4PID"
2137 2191
2138 set +e 2192 # The session is alive on the far side of the tear only if a NEW round trip
2139 wait "$QRPID" 2193 # completes: the pre-tear marker is already in the capture and proves
2140 RC=$? 2194 # nothing about the reconnect.
2141 set -e 2195 pipe_send 'printf "quic-%%s\\n" post-tear\n'
2142 [ "$RC" -eq 0 ] || { 2196 await_out "$OUT.qr" "quic-post-tear" "quic client lost its session across the tear"
2143 echo "e2e FAIL: quic client exited $RC across a tear (want 0)" 2197 pipe_detach "quic client"
2144 cat "$OUT.qr" "$OUT.qr.err" 2>/dev/null; exit 1;
2145 }
2146 grep -q "quic-pre-tear" "$OUT.qr" || {
2147 echo "e2e FAIL: quic client lost its session across the tear"
2148 cat "$OUT.qr" "$OUT.qr.err" 2>/dev/null; exit 1;
2149 }
2150 2198
2151 # The counter is the only witness to HOW the resume was served: a snapshot 2199 # The counter is the only witness to HOW the resume was served: a snapshot
2152 # renders identically to a delta, so markers cannot tell them apart. One 2200 # renders identically to a delta, so markers cannot tell them apart. One
@@ -2170,18 +2218,15 @@ assert_converged "$OUT.qr" "$SOCK4" "quic delta resume"
2170 # the restarted daemon must be able to rebind the UDP port at all — there 2218 # the restarted daemon must be able to rebind the UDP port at all — there
2171 # is no SO_REUSEADDR any more, so anything lingering from the killed 2219 # is no SO_REUSEADDR any more, so anything lingering from the killed
2172 # process would show up as a bind failure rather than as silent sharing. 2220 # process would show up as a bind failure rather than as silent sharing.
2173 set +e 2221 pipe_mux "$OUT.qk" "$OUT.qk.err" timeout 40 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" \
2174 { printf 'printf "quic-%%s\\n" pre-restart\n'; sleep 3; \ 2222 --quic-idle-ms 1500
2175 printf 'printf "quic-%%s\\n" post-restart\n'; sleep 3; printf '\034\034'; } | \ 2223 pipe_send 'printf "quic-%%s\\n" pre-restart\n'
2176 timeout 40 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" \
2177 --quic-idle-ms 1500 > "$OUT.qk" 2> "$OUT.qk.err" &
2178 QKPID=$!
2179 set -e
2180 wait_for "$OUT.qk" "quic-pre-restart" 20 || { 2224 wait_for "$OUT.qk" "quic-pre-restart" 20 || {
2181 echo "e2e FAIL: quic restart client never got its pre-restart marker" 2225 echo "e2e FAIL: quic restart client never got its pre-restart marker"
2182 cat "$OUT.qk" "$OUT.qk.err" 2>/dev/null; exit 1; 2226 cat "$OUT.qk" "$OUT.qk.err" 2>/dev/null; exit 1;
2183 } 2227 }
2184 2228
2229 PAINTS_BEFORE=$(repaints "$OUT.qk")
2185 hardkill "$D4PID" 2230 hardkill "$D4PID"
2186 D4PID="" 2231 D4PID=""
2187 sleep 0.5 2232 sleep 0.5
@@ -2226,18 +2271,10 @@ udp_local_bound "$QHEX" || {
2226 } 2271 }
2227 kill -0 "$D4PID" || { echo "e2e FAIL: restarted --quic daemon died"; cat "$OUT.q"; exit 1; } 2272 kill -0 "$D4PID" || { echo "e2e FAIL: restarted --quic daemon died"; cat "$OUT.q"; exit 1; }
2228 2273
2229 set +e 2274 await_repaint "$OUT.qk" "$PAINTS_BEFORE" "quic client never resumed into the restarted daemon"
2230 wait "$QKPID" 2275 pipe_send 'printf "quic-%%s\\n" post-restart\n'
2231 RC=$? 2276 await_out "$OUT.qk" "quic-post-restart" "quic client did not resume into the restarted daemon"
2232 set -e 2277 pipe_detach "quic client"
2233 [ "$RC" -eq 0 ] || {
2234 echo "e2e FAIL: quic client exited $RC across a daemon restart (want 0)"
2235 cat "$OUT.qk" "$OUT.qk.err" 2>/dev/null; exit 1;
2236 }
2237 grep -q "quic-post-restart" "$OUT.qk" || {
2238 echo "e2e FAIL: quic client did not resume into the restarted daemon"
2239 cat "$OUT.qk" "$OUT.qk.err" 2>/dev/null; exit 1;
2240 }
2241 # A new daemon means a new shell, so the old marker cannot be in its grid. 2278 # A new daemon means a new shell, so the old marker cannot be in its grid.
2242 # If it were, we would be looking at a client that resumed off a seq 2279 # If it were, we would be looking at a client that resumed off a seq
2243 # belonging to a session that no longer exists. 2280 # belonging to a session that no longer exists.
@@ -2270,16 +2307,10 @@ env MUX_KEY_FILE="$QKEY" "$MUXD" run --sock "$SOCK9" --shell /bin/sh \
2270 D9PID=$! 2307 D9PID=$!
2271 wait_sock "$SOCK9" "$OUT.d9.d" "MUX_KEY_FILE daemon never bound its session socket" 2308 wait_sock "$SOCK9" "$OUT.d9.d" "MUX_KEY_FILE daemon never bound its session socket"
2272 2309
2273 set +e 2310 pipe_mux "$OUT.env1" "$OUT.env1.err" timeout 30 "$MUX" "quic://127.0.0.1:$QPORT2" --key "$QKEY" --quic-idle-ms 15000
2274 { printf 'printf "envkey-%%s\\n" ok\n'; sleep 2; printf '\034\034'; } | \ 2311 pipe_send 'printf "envkey-%%s\\n" ok\n'
2275 timeout 30 "$MUX" "quic://127.0.0.1:$QPORT2" --key "$QKEY" \ 2312 await_out "$OUT.env1" "envkey-ok" "envkey-ok never reached the client"
2276 --quic-idle-ms 15000 > "$OUT.env1" 2> "$OUT.env1.err" 2313 pipe_detach "quic attach to a MUX_KEY_FILE daemon"
2277 RC=$?
2278 set -e
2279 [ "$RC" -eq 0 ] || {
2280 echo "e2e FAIL: quic attach to a MUX_KEY_FILE daemon exited $RC (want 0)"
2281 cat "$OUT.env1" "$OUT.env1.err" 2>/dev/null; exit 1;
2282 }
2283 grep -q "envkey-ok" "$OUT.env1" || { 2314 grep -q "envkey-ok" "$OUT.env1" || {
2284 echo "e2e FAIL: MUX_KEY_FILE daemon served no session" 2315 echo "e2e FAIL: MUX_KEY_FILE daemon served no session"
2285 cat "$OUT.env1" "$OUT.env1.err" 2>/dev/null; exit 1; } 2316 cat "$OUT.env1" "$OUT.env1.err" 2>/dev/null; exit 1; }
@@ -2297,16 +2328,10 @@ env MUX_KEY_FILE="$QKEY.wrong" "$MUXD" run --sock "$SOCK10" --shell /bin/sh \
2297 D10PID=$! 2328 D10PID=$!
2298 wait_sock "$SOCK10" "$OUT.d10.d" "--key-beats-env daemon never bound its session socket" 2329 wait_sock "$SOCK10" "$OUT.d10.d" "--key-beats-env daemon never bound its session socket"
2299 2330
2300 set +e 2331 pipe_mux "$OUT.env2" "$OUT.env2.err" timeout 30 "$MUX" "quic://127.0.0.1:$QPORT3" --key "$QKEY" --quic-idle-ms 15000
2301 { printf 'printf "flagwins-%%s\\n" ok\n'; sleep 2; printf '\034\034'; } | \ 2332 pipe_send 'printf "flagwins-%%s\\n" ok\n'
2302 timeout 30 "$MUX" "quic://127.0.0.1:$QPORT3" --key "$QKEY" \ 2333 await_out "$OUT.env2" "flagwins-ok" "flagwins-ok never reached the client"
2303 --quic-idle-ms 15000 > "$OUT.env2" 2> "$OUT.env2.err" 2334 pipe_detach "the --key attach (which must beat MUX_KEY_FILE)"
2304 RC=$?
2305 set -e
2306 [ "$RC" -eq 0 ] || {
2307 echo "e2e FAIL: --key did not beat MUX_KEY_FILE: attach exited $RC (want 0)"
2308 cat "$OUT.env2" "$OUT.env2.err" 2>/dev/null; exit 1;
2309 }
2310 grep -q "flagwins-ok" "$OUT.env2" || { 2335 grep -q "flagwins-ok" "$OUT.env2" || {
2311 echo "e2e FAIL: --key-beats-env daemon served no session" 2336 echo "e2e FAIL: --key-beats-env daemon served no session"
2312 cat "$OUT.env2" "$OUT.env2.err" 2>/dev/null; exit 1; } 2337 cat "$OUT.env2" "$OUT.env2.err" 2>/dev/null; exit 1; }
@@ -2342,8 +2367,10 @@ kill -0 "$SPID" || { echo "e2e FAIL: started daemon not alive"; exit 1; }
2342 echo "e2e FAIL: non-tty start not exactly two lines:"; cat "$OUT.start"; exit 1; } 2367 echo "e2e FAIL: non-tty start not exactly two lines:"; cat "$OUT.start"; exit 1; }
2343 2368
2344 # The daemon it started serves a session (marker in, marker in dump). 2369 # The daemon it started serves a session (marker in, marker in dump).
2345 { printf 'printf "start-%%s\\n" works\n'; sleep 2; printf '\034\034'; } | \ 2370 pipe_mux "$OUT.s8" "$OUT.s8.err" "$MUX" --sock "$SOCK8"
2346 "$MUX" --sock "$SOCK8" > "$OUT.s8" 2> "$OUT.s8.err" 2371 pipe_send 'printf "start-%%s\\n" works\n'
2372 await_out "$OUT.s8" "start-works" "start-works never reached the client"
2373 pipe_detach
2347 "$MUXD" dump --sock "$SOCK8" | grep -q "start-works" || { 2374 "$MUXD" dump --sock "$SOCK8" | grep -q "start-works" || {
2348 echo "e2e FAIL: auto-started daemon lost the marker" 2375 echo "e2e FAIL: auto-started daemon lost the marker"
2349 cat "$OUT.s8" "$OUT.s8.err" 2>/dev/null; exit 1; } 2376 cat "$OUT.s8" "$OUT.s8.err" 2>/dev/null; exit 1; }
@@ -2389,8 +2416,10 @@ wait "$RB"; RCB=$?
2389 set -e 2416 set -e
2390 { [ "$RCA" = "0" ] && [ "$RCB" = "0" ]; } || { 2417 { [ "$RCA" = "0" ] && [ "$RCB" = "0" ]; } || {
2391 echo "e2e FAIL: race: exits $RCA/$RCB"; cat "$OUT.ra" "$OUT.rb"; exit 1; } 2418 echo "e2e FAIL: race: exits $RCA/$RCB"; cat "$OUT.ra" "$OUT.rb"; exit 1; }
2392 { printf 'printf "race-%%s\\n" one\n'; sleep 2; printf '\034\034'; } | \ 2419 pipe_mux "$OUT.race" /dev/null "$MUX" --sock "$SOCK8"
2393 "$MUX" --sock "$SOCK8" > "$OUT.race" 2> /dev/null 2420 pipe_send 'printf "race-%%s\\n" one\n'
2421 await_out "$OUT.race" "race-one" "race-one never reached the client"
2422 pipe_detach
2394 "$MUXD" dump --sock "$SOCK8" | grep -q "race-one" || { 2423 "$MUXD" dump --sock "$SOCK8" | grep -q "race-one" || {
2395 echo "e2e FAIL: race: session unusable"; exit 1; } 2424 echo "e2e FAIL: race: session unusable"; exit 1; }
2396 SPID=$(cat "$OUT.ra" "$OUT.rb" | sed -n 's/.* pid=\([0-9]*\).*/\1/p' | while read -r p; do 2425 SPID=$(cat "$OUT.ra" "$OUT.rb" | sed -n 's/.* pid=\([0-9]*\).*/\1/p' | while read -r p; do
@@ -2461,8 +2490,10 @@ ok "a start whose daemon dies young says so, with the log path"
2461 "$MUXD" start --sock "$SOCK11" --quic "127.0.0.1:$QPORT4" 2> "$OUT.goal" 2490 "$MUXD" start --sock "$SOCK11" --quic "127.0.0.1:$QPORT4" 2> "$OUT.goal"
2462 GPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.goal") 2491 GPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.goal")
2463 [ -n "$GPID" ] || { echo "e2e FAIL: goal start reported no pid"; cat "$OUT.goal"; exit 1; } 2492 [ -n "$GPID" ] || { echo "e2e FAIL: goal start reported no pid"; cat "$OUT.goal"; exit 1; }
2464 { printf 'printf "goal-%%s\\n" quic\n'; sleep 2; printf '\034\034'; } | \ 2493 pipe_mux "$OUT.g9" "$OUT.g9.err" timeout 30 "$MUX" "quic://127.0.0.1:$QPORT4"
2465 timeout 30 "$MUX" "quic://127.0.0.1:$QPORT4" > "$OUT.g9" 2> "$OUT.g9.err" 2494 pipe_send 'printf "goal-%%s\\n" quic\n'
2495 await_out "$OUT.g9" "goal-quic" "goal-quic never reached the client"
2496 pipe_detach
2466 "$MUXD" dump --sock "$SOCK11" | grep -q "goal-quic" || { 2497 "$MUXD" dump --sock "$SOCK11" | grep -q "goal-quic" || {
2467 echo "e2e FAIL: no-key-flag QUIC attach did not reach the session" 2498 echo "e2e FAIL: no-key-flag QUIC attach did not reach the session"
2468 cat "$OUT.g9" "$OUT.g9.err" 2>/dev/null; exit 1; } 2499 cat "$OUT.g9" "$OUT.g9.err" 2>/dev/null; exit 1; }
@@ -3212,14 +3243,10 @@ ok "reconnect while scrolled: view restored, prediction resumed"
3212 # Nothing is serving SOCK14: the attach itself must produce the daemon. 3243 # Nothing is serving SOCK14: the attach itself must produce the daemon.
3213 # SHELL pinned because the auto-started daemon gets no --shell flag and 3244 # SHELL pinned because the auto-started daemon gets no --shell flag and
3214 # resolves $SHELL — the suite must not inherit the developer's. 3245 # resolves $SHELL — the suite must not inherit the developer's.
3215 # 3246 pipe_mux "$OUT.as" "$OUT.as.err" env SHELL=/bin/sh timeout 30 "$MUX" --via "$MUXD proxy --sock $SOCK14"
3216 # `|| true` for the same reason the M6 --via scenario has it: without it 3247 pipe_send 'printf "auto-%%s\\n" start\n'
3217 # `set -e` aborts the run on the client's exit status before the labelled 3248 await_out "$OUT.as" "auto-start" "proxy auto-start: marker never reached the client"
3218 # diagnostics below get to print the stderr that says why. `timeout` is 3249 pipe_detach
3219 # what keeps that from being a licence to hang.
3220 { printf 'printf "auto-%%s\\n" start\n'; sleep 2; printf '\034\034'; } | \
3221 SHELL=/bin/sh timeout 30 "$MUX" --via "$MUXD proxy --sock $SOCK14" \
3222 > "$OUT.as" 2> "$OUT.as.err" || true
3223 "$MUXD" dump --sock "$SOCK14" | grep -q "auto-start" || { 3250 "$MUXD" dump --sock "$SOCK14" | grep -q "auto-start" || {
3224 echo "e2e FAIL: proxy auto-start: attached daemon lost the marker" 3251 echo "e2e FAIL: proxy auto-start: attached daemon lost the marker"
3225 cat "$OUT.as" "$OUT.as.err" 2>/dev/null; exit 1; } 3252 cat "$OUT.as" "$OUT.as.err" 2>/dev/null; exit 1; }
@@ -3239,9 +3266,10 @@ kill -0 "$APID" || { echo "e2e FAIL: auto-started daemon not alive"; exit 1; }
3239 assert_converged "$OUT.as" "$SOCK14" "auto-start via proxy" 3266 assert_converged "$OUT.as" "$SOCK14" "auto-start via proxy"
3240 3267
3241 # Silence is the fast path: a warm attach must print NO spawn progress. 3268 # Silence is the fast path: a warm attach must print NO spawn progress.
3242 { printf 'printf "auto-%%s\\n" again\n'; sleep 2; printf '\034\034'; } | \ 3269 pipe_mux "$OUT.as2" "$OUT.as2.err" env SHELL=/bin/sh timeout 30 "$MUX" --via "$MUXD proxy --sock $SOCK14"
3243 SHELL=/bin/sh timeout 30 "$MUX" --via "$MUXD proxy --sock $SOCK14" \ 3270 pipe_send 'printf "auto-%%s\\n" again\n'
3244 > "$OUT.as2" 2> "$OUT.as2.err" || true 3271 await_out "$OUT.as2" "auto-again" "warm attach: marker never reached the client"
3272 pipe_detach
3245 grep -q 'starting' "$OUT.as2.err" && { 3273 grep -q 'starting' "$OUT.as2.err" && {
3246 echo "e2e FAIL: warm attach printed spawn progress" 3274 echo "e2e FAIL: warm attach printed spawn progress"
3247 cat "$OUT.as2.err"; exit 1; } 3275 cat "$OUT.as2.err"; exit 1; }
@@ -3802,11 +3830,9 @@ HCACHE="$XDG_CACHE_HOME/mux/hosts/$HHOST"
3802 # inference: the ssh the announce arrived on is killed by the client on 3830 # inference: the ssh the announce arrived on is killed by the client on
3803 # QUIC success, so once its pid is OBSERVED gone, a second marker still 3831 # QUIC success, so once its pid is OBSERVED gone, a second marker still
3804 # making the round trip can only be riding QUIC. The pipe's owner is dead. 3832 # making the round trip can only be riding QUIC. The pipe's owner is dead.
3805 { printf 'printf "cold-%%s\\n" one\n'; sleep 6; \ 3833 pipe_mux "$OUT.h1" "$OUT.h1.err" env SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN" PATH="$HPATH" timeout 60 \
3806 printf 'printf "cold-%%s\\n" two\n'; sleep 2.5; printf '\034\034'; } | \ 3834 "$MUX" "$HHOST"
3807 SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN" PATH="$HPATH" timeout 60 \ 3835 pipe_send 'printf "cold-%%s\\n" one\n'
3808 "$MUX" "$HHOST" > "$OUT.h1" 2> "$OUT.h1.err" &
3809 H1PID=$!
3810 # The bounded wait, with a message of its own, because of what it is FOR: 3836 # The bounded wait, with a message of its own, because of what it is FOR:
3811 # a client blocked on an announce that is never coming has no other 3837 # a client blocked on an announce that is never coming has no other
3812 # symptom. It cannot be left to the suite's timeout or the trap — those 3838 # symptom. It cannot be left to the suite's timeout or the trap — those
@@ -3823,49 +3849,19 @@ HSHIMS=$(wc -l < "$SSHIM_PIDLOG")
3823 cat "$SSHIM_PIDLOG"; exit 1; } 3849 cat "$SSHIM_PIDLOG"; exit 1; }
3824 HSHIMPID=$(head -1 "$SSHIM_PIDLOG") 3850 HSHIMPID=$(head -1 "$SSHIM_PIDLOG")
3825 wait_pid_gone "$HSHIMPID" "cold handoff: QUIC took over, so ssh must be gone" 3851 wait_pid_gone "$HSHIMPID" "cold handoff: QUIC took over, so ssh must be gone"
3826 # The BASELINE, and it comes AFTER the death rather than before it so that 3852 # The second marker is typed only now, after the death was OBSERVED, so its
3827 # what it establishes is exactly what the next check needs: at the instant 3853 # round trip is traffic that moved after the pipe's owner died — by
3828 # ssh was observed gone, the second marker had not arrived. Its later 3854 # construction, not by a clock. What guarantees the ordering on the product
3829 # arrival is therefore traffic that moved after the pipe's owner died. 3855 # side: openHandoff kills the ssh child the moment QUIC is ready, before
3830 # Taken before the death instead, it would pin absence at some earlier 3856 # Transport.open returns, so before the session can emit a single byte.
3831 # moment and lean on wait_pid_gone's 2s budget staying small against the 3857 # Anyone who moves that kill (to after the first frame, into a deferred
3832 # script's 6s — a coupling nothing states and nobody would maintain. 3858 # teardown) takes this scenario's premise with them.
3833 # 3859 pipe_send 'printf "cold-%%s\\n" two\n'
3834 # What guarantees the ordering in the first place is the PRODUCT, and
3835 # saying so is the other half of this comment's job: openHandoff kills the
3836 # ssh child the moment QUIC is ready, before Transport.open returns, so
3837 # before the session can emit a single byte — before cold-one, never mind
3838 # cold-two. Anyone who moves that kill (to after the first frame, into a
3839 # deferred teardown) takes this scenario's premise with them and should
3840 # expect to rewrite the block rather than nudge a sleep.
3841 #
3842 # The case it catches is real rather than theoretical. Both markers are
3843 # typed by a script on its own clock: if an attach ever outran the 6s
3844 # between them, both would be buffered before the session existed and
3845 # delivered together, and cold-two would already be on screen here. The
3846 # scenario could then no longer WITNESS post-death traffic — not because
3847 # the product misbehaved (the kill still preceded both) but because the
3848 # two events stopped being separable. That is what this reports.
3849 grep -q "cold-two" "$OUT.h1" && {
3850 echo "e2e FAIL: cold handoff: the second marker is already on screen at the"
3851 echo " baseline, so it cannot witness bytes moving after ssh died —"
3852 echo " both markers were delivered together, which is what happens"
3853 echo " when the attach outruns this scenario's 6s script (~300ms"
3854 echo " observed). A timing premise broke, not necessarily the product."
3855 exit 1; }
3856 # ...and the session did not go with it.
3857 wait_for "$OUT.h1" "cold-two" 20 || { 3860 wait_for "$OUT.h1" "cold-two" 20 || {
3858 echo "e2e FAIL: cold handoff: the session stopped converging once ssh was gone," 3861 echo "e2e FAIL: cold handoff: the session stopped converging once ssh was gone,"
3859 echo " so the bytes were riding the pipe rather than QUIC" 3862 echo " so the bytes were riding the pipe rather than QUIC"
3860 cat "$OUT.h1" "$OUT.h1.err" 2>/dev/null; exit 1; } 3863 cat "$OUT.h1" "$OUT.h1.err" 2>/dev/null; exit 1; }
3861 set +e 3864 pipe_detach "cold handoff client"
3862 wait "$H1PID"
3863 RC=$?
3864 set -e
3865 H1PID=""
3866 [ "$RC" -eq 0 ] || {
3867 echo "e2e FAIL: cold handoff client exited $RC (want 0; 124 means it hung)"
3868 cat "$OUT.h1" "$OUT.h1.err" 2>/dev/null; exit 1; }
3869 3865
3870 # The daemon `muxd endpoint` started, by the pid its own up-line reported — 3866 # The daemon `muxd endpoint` started, by the pid its own up-line reported —
3871 # the only handle this suite has on a process that is nobody's child. Same 3867 # the only handle this suite has on a process that is nobody's child. Same
@@ -3912,15 +3908,10 @@ ok "cold handoff: ssh fetches the coordinates, QUIC carries the session"
3912 # contract that a warm attach prints no spawn progress, extended to the 3908 # contract that a warm attach prints no spawn progress, extended to the
3913 # handoff's own line, which has nothing to report when nothing fell back. 3909 # handoff's own line, which has nothing to report when nothing fell back.
3914 HSHIMS_B=$(wc -l < "$SSHIM_PIDLOG") 3910 HSHIMS_B=$(wc -l < "$SSHIM_PIDLOG")
3915 set +e 3911 pipe_mux "$OUT.h2" "$OUT.h2.err" env SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN" PATH="$HPATH" timeout 40 "$MUX" "$HHOST"
3916 { printf 'printf "warm-%%s\\n" ok\n'; sleep 2.5; printf '\034\034'; } | \ 3912 pipe_send 'printf "warm-%%s\\n" ok\n'
3917 SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN" PATH="$HPATH" timeout 40 \ 3913 await_out "$OUT.h2" "warm-ok" "warm-ok never reached the client"
3918 "$MUX" "$HHOST" > "$OUT.h2" 2> "$OUT.h2.err" 3914 pipe_detach "warm handoff client"
3919 RC=$?
3920 set -e
3921 [ "$RC" -eq 0 ] || {
3922 echo "e2e FAIL: warm handoff client exited $RC (want 0)"
3923 cat "$OUT.h2" "$OUT.h2.err" 2>/dev/null; exit 1; }
3924 grep -q "warm-ok" "$OUT.h2" || { 3915 grep -q "warm-ok" "$OUT.h2" || {
3925 echo "e2e FAIL: warm handoff served no session" 3916 echo "e2e FAIL: warm handoff served no session"
3926 cat "$OUT.h2" "$OUT.h2.err" 2>/dev/null; exit 1; } 3917 cat "$OUT.h2" "$OUT.h2.err" 2>/dev/null; exit 1; }
@@ -3956,39 +3947,26 @@ ok "warm handoff: the cache dials QUIC, ssh never runs (pidlog still $HSHIMS_B2)
3956 # loopback round trip and the whole heal measures ~200ms. The bound below 3947 # loopback round trip and the whole heal measures ~200ms. The bound below
3957 # was a floor on that spent budget and is now a ceiling on its absence. 3948 # was a floor on that spent budget and is now a ceiling on its absence.
3958 # 3949 #
3959 # The detach byte stays six seconds out even so. The stdin script runs on 3950 # The detach is sent only after the marker has rendered, and that ordering
3960 # its own clock, in PARALLEL with the attach, and `waitReady` answers 3951 # is load-bearing: `waitReady` answers Ctrl-\ DURING a handshake (M8 pins
3961 # Ctrl-\ DURING a handshake (M8 pins exactly that). A detach landing 3952 # exactly that), so a detach landing inside the dial window aborts the
3962 # inside the dial window aborts the attach: no session, no error, exit 0 — 3953 # attach — no session, no error, exit 0 — and a scenario that paced the
3963 # a scenario that passes its exit check having tested nothing. The window 3954 # detach on a clock could pass its exit check having tested nothing.
3964 # it has to clear shrank from 2172ms to ~200ms; six seconds is now margin
3965 # over the ssh refetch and the session that follows it.
3966 HHEXKEY=$(sed -n 's/^endpoint [0-9][0-9]* \([0-9a-f]*\)$/\1/p' "$HCACHE") 3955 HHEXKEY=$(sed -n 's/^endpoint [0-9][0-9]* \([0-9a-f]*\)$/\1/p' "$HCACHE")
3967 [ -n "$HHEXKEY" ] || { echo "e2e FAIL: could not read the cached key"; exit 1; } 3956 [ -n "$HHEXKEY" ] || { echo "e2e FAIL: could not read the cached key"; exit 1; }
3968 printf 'endpoint %s %s\n' "$HDEADPORT" "$HHEXKEY" > "$HCACHE" 3957 printf 'endpoint %s %s\n' "$HDEADPORT" "$HHEXKEY" > "$HCACHE"
3969 chmod 600 "$HCACHE" 3958 chmod 600 "$HCACHE"
3970 HSHIMS_C=$(wc -l < "$SSHIM_PIDLOG") 3959 HSHIMS_C=$(wc -l < "$SSHIM_PIDLOG")
3971 HT4=$(date +%s%N) 3960 HT4=$(date +%s%N)
3972 { printf 'printf "heal-%%s\\n" ok\n'; sleep 6; printf '\034\034'; } | \ 3961 pipe_mux "$OUT.h3" "$OUT.h3.err" env SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN" PATH="$HPATH" timeout 40 \
3973 SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN" PATH="$HPATH" timeout 40 \ 3962 "$MUX" "$HHOST"
3974 "$MUX" "$HHOST" > "$OUT.h3" 2> "$OUT.h3.err" & 3963 pipe_send 'printf "heal-%%s\\n" ok\n'
3975 H3PID=$!
3976 wait_for "$OUT.h3" "heal-ok" 30 || { 3964 wait_for "$OUT.h3" "heal-ok" 30 || {
3977 echo "e2e FAIL: stale-cache handoff never served a session" 3965 echo "e2e FAIL: stale-cache handoff never served a session"
3978 cat "$OUT.h3" "$OUT.h3.err" 2>/dev/null; exit 1; } 3966 cat "$OUT.h3" "$OUT.h3.err" 2>/dev/null; exit 1; }
3979 HT5=$(date +%s%N) 3967 HT5=$(date +%s%N)
3980 HMS_C=$(( (HT5 - HT4) / 1000000 )) 3968 HMS_C=$(( (HT5 - HT4) / 1000000 ))
3981 set +e 3969 pipe_detach "stale-cache handoff client"
3982 wait "$H3PID"
3983 RC=$?
3984 set -e
3985 H3PID=""
3986 [ "$RC" -eq 0 ] || {
3987 echo "e2e FAIL: stale-cache handoff client exited $RC (want 0)"
3988 cat "$OUT.h3" "$OUT.h3.err" 2>/dev/null; exit 1; }
3989 grep -q "heal-ok" "$OUT.h3" || {
3990 echo "e2e FAIL: stale-cache handoff served no session"
3991 cat "$OUT.h3" "$OUT.h3.err" 2>/dev/null; exit 1; }
3992 # The absent-grep, and the control that proves it can fire is scenario (d) 3970 # The absent-grep, and the control that proves it can fire is scenario (d)
3993 # below: the same pattern, asserted PRESENT, against a build where the 3971 # below: the same pattern, asserted PRESENT, against a build where the
3994 # fallback really happened. 3972 # fallback really happened.
@@ -4060,10 +4038,9 @@ HDPID=$!
4060 wait_sock "$SOCK17" "$OUT.d17.d" "key-mismatch daemon never bound" 4038 wait_sock "$SOCK17" "$OUT.d17.d" "key-mismatch daemon never bound"
4061 HSHIMS_D=$(wc -l < "$SSHIM_PIDLOG") 4039 HSHIMS_D=$(wc -l < "$SSHIM_PIDLOG")
4062 HT0=$(date +%s%N) 4040 HT0=$(date +%s%N)
4063 { printf 'printf "fallback-%%s\\n" ok\n'; sleep 6; printf '\034\034'; } | \ 4041 pipe_mux "$OUT.h4" "$OUT.h4.err" env SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN2" PATH="$HPATH" timeout 40 \
4064 SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN2" PATH="$HPATH" timeout 40 \ 4042 "$MUX" "127.0.0.1"
4065 "$MUX" "127.0.0.1" > "$OUT.h4" 2> "$OUT.h4.err" & 4043 pipe_send 'printf "fallback-%%s\\n" ok\n'
4066 H4PID=$!
4067 # Timed to the LINE rather than to the client's exit, so no session sleeps 4044 # Timed to the LINE rather than to the client's exit, so no session sleeps
4068 # are folded in. The elapsed time is the dial's budget plus launch overhead 4045 # are folded in. The elapsed time is the dial's budget plus launch overhead
4069 # (measured 26-129ms), which only inflates it — the floor is safe. 4046 # (measured 26-129ms), which only inflates it — the floor is safe.
@@ -4097,13 +4074,8 @@ grep -q "^mux: quic://127.0.0.1:$HQPORT unreachable, attaching over ssh$" "$OUT.
4097 # misses, while making the single scenario that already pays 2s by design 4074 # misses, while making the single scenario that already pays 2s by design
4098 # the most load-sensitive check in the suite. 4075 # the most load-sensitive check in the suite.
4099 # 4076 #
4100 # And it is worth knowing what this bound can actually SEE, which is a 4077 # The wait_for above is the backstop beyond it: an overrun past 15s is
4101 # narrow band. Past about six seconds the script's own detach byte lands 4078 # reported there, with its own message, rather than here.
4102 # inside the dial, `waitReady` answers it as an abort, no fallback line is
4103 # ever printed, and the overrun is reported by the wait_for above — with
4104 # its own message — rather than here. So this check owns the range between
4105 # the budget and that: long enough to be a real overrun, short enough that
4106 # the line still gets printed. The wait_for is the backstop beyond it.
4107 [ "$HMS" -ge 1500 ] || { 4079 [ "$HMS" -ge 1500 ] || {
4108 echo "e2e FAIL: the fallback came after ${HMS}ms, too fast to have spent the" 4080 echo "e2e FAIL: the fallback came after ${HMS}ms, too fast to have spent the"
4109 echo " 2000ms QUIC budget — the dial cannot have happened" 4081 echo " 2000ms QUIC budget — the dial cannot have happened"
@@ -4111,19 +4083,10 @@ grep -q "^mux: quic://127.0.0.1:$HQPORT unreachable, attaching over ssh$" "$OUT.
4111 [ "$HMS" -lt 10000 ] || { 4083 [ "$HMS" -lt 10000 ] || {
4112 echo "e2e FAIL: the fallback took ${HMS}ms; the deadline is meant to bound it" 4084 echo "e2e FAIL: the fallback took ${HMS}ms; the deadline is meant to bound it"
4113 exit 1; } 4085 exit 1; }
4114 set +e
4115 wait "$H4PID"
4116 RC=$?
4117 set -e
4118 H4PID=""
4119 [ "$RC" -eq 0 ] || {
4120 echo "e2e FAIL: key-mismatch client exited $RC (want 0)"
4121 cat "$OUT.h4" "$OUT.h4.err" 2>/dev/null; exit 1; }
4122 # ...and the session it fell back to is a real one, over the pipe that 4086 # ...and the session it fell back to is a real one, over the pipe that
4123 # carried the announce. 4087 # carried the announce.
4124 grep -q "fallback-ok" "$OUT.h4" || { 4088 await_out "$OUT.h4" "fallback-ok" "key-mismatch attach printed the line but served no session"
4125 echo "e2e FAIL: key-mismatch attach printed the line but served no session" 4089 pipe_detach "key-mismatch client"
4126 cat "$OUT.h4" "$OUT.h4.err" 2>/dev/null; exit 1; }
4127 HSHIMS_D2=$(wc -l < "$SSHIM_PIDLOG") 4090 HSHIMS_D2=$(wc -l < "$SSHIM_PIDLOG")
4128 [ "$((HSHIMS_D2 - HSHIMS_D))" -eq 1 ] || { 4091 [ "$((HSHIMS_D2 - HSHIMS_D))" -eq 1 ] || {
4129 echo "e2e FAIL: the fallback attach ran $((HSHIMS_D2 - HSHIMS_D)) ssh invocations, want 1" 4092 echo "e2e FAIL: the fallback attach ran $((HSHIMS_D2 - HSHIMS_D)) ssh invocations, want 1"
@@ -4143,24 +4106,16 @@ ok "a key mismatch falls back to the ssh pipe: one deadline (${HMS}ms), one line
4143 : > "$HCFGBAD" 4106 : > "$HCFGBAD"
4144 HSHIMS_E=$(wc -l < "$SSHIM_PIDLOG") 4107 HSHIMS_E=$(wc -l < "$SSHIM_PIDLOG")
4145 HT2=$(date +%s%N) 4108 HT2=$(date +%s%N)
4146 { printf 'printf "none-%%s\\n" ok\n'; sleep 2.5; printf '\034\034'; } | \ 4109 pipe_mux "$OUT.h5" "$OUT.h5.err" env SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN" XDG_CONFIG_HOME="$HCFGBAD" \
4147 SHELL=/bin/sh XDG_RUNTIME_DIR="$HRUN" XDG_CONFIG_HOME="$HCFGBAD" \
4148 PATH="$HPATH" timeout 40 \ 4110 PATH="$HPATH" timeout 40 \
4149 "$MUX" "mux-e2e-none@127.0.0.1" > "$OUT.h5" 2> "$OUT.h5.err" & 4111 "$MUX" "mux-e2e-none@127.0.0.1"
4150 H5PID=$! 4112 pipe_send 'printf "none-%%s\\n" ok\n'
4151 wait_for "$OUT.h5" "none-ok" 20 || { 4113 wait_for "$OUT.h5" "none-ok" 20 || {
4152 echo "e2e FAIL: announce-none handoff never served a session" 4114 echo "e2e FAIL: announce-none handoff never served a session"
4153 cat "$OUT.h5" "$OUT.h5.err" 2>/dev/null; exit 1; } 4115 cat "$OUT.h5" "$OUT.h5.err" 2>/dev/null; exit 1; }
4154 HT3=$(date +%s%N) 4116 HT3=$(date +%s%N)
4155 HMS_E=$(( (HT3 - HT2) / 1000000 )) 4117 HMS_E=$(( (HT3 - HT2) / 1000000 ))
4156 set +e 4118 pipe_detach "announce-none client"
4157 wait "$H5PID"
4158 RC=$?
4159 set -e
4160 H5PID=""
4161 [ "$RC" -eq 0 ] || {
4162 echo "e2e FAIL: announce-none client exited $RC (want 0)"
4163 cat "$OUT.h5" "$OUT.h5.err" 2>/dev/null; exit 1; }
4164 # The remote said why, in one line, on the stderr ssh already carries. 4119 # The remote said why, in one line, on the stderr ssh already carries.
4165 grep -q '^muxd endpoint: .*staying on ssh' "$OUT.h5.err" || { 4120 grep -q '^muxd endpoint: .*staying on ssh' "$OUT.h5.err" || {
4166 echo "e2e FAIL: announce-none said nothing about why it stayed on ssh" 4121 echo "e2e FAIL: announce-none said nothing about why it stayed on ssh"
@@ -4249,12 +4204,16 @@ wait_sock "$SOCK21" "$OUT.m18.d" "M18 multi-session daemon never bound"
4249 # client leaves (decision 8) — so these greps run against two sessions 4204 # client leaves (decision 8) — so these greps run against two sessions
4250 # with nobody attached, which is also why none of their answers can be 4205 # with nobody attached, which is also why none of their answers can be
4251 # explained by a live client holding something open. 4206 # explained by a live client holding something open.
4252 { printf 'printf "m18a-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 4207 pipe_mux "$OUT.m18a" "$OUT.m18a.err" timeout 40 "$MUX" --sock "$SOCK21" --session a
4253 timeout 40 "$MUX" --sock "$SOCK21" --session a > "$OUT.m18a" 2> "$OUT.m18a.err" 4208 pipe_send 'printf "m18a-%%s\\n" pin\n'
4209 await_out "$OUT.m18a" "m18a-pin" "m18a-pin never reached the client"
4210 pipe_detach
4254 wait_grid "$SOCK21" "m18a-pin" "M18: session a's marker" a 4211 wait_grid "$SOCK21" "m18a-pin" "M18: session a's marker" a
4255 4212
4256 { printf 'printf "m18b-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 4213 pipe_mux "$OUT.m18b" "$OUT.m18b.err" timeout 40 "$MUX" --sock "$SOCK21" --session b
4257 timeout 40 "$MUX" --sock "$SOCK21" --session b > "$OUT.m18b" 2> "$OUT.m18b.err" 4214 pipe_send 'printf "m18b-%%s\\n" pin\n'
4215 await_out "$OUT.m18b" "m18b-pin" "m18b-pin never reached the client"
4216 pipe_detach
4258 wait_grid "$SOCK21" "m18b-pin" "M18: session b's marker" b 4217 wait_grid "$SOCK21" "m18b-pin" "M18: session b's marker" b
4259 4218
4260 # Each session's grid holds its own marker and NOT the other's. 4219 # Each session's grid holds its own marker and NOT the other's.
@@ -4333,14 +4292,9 @@ fi
4333 # every death but the last (decision 8). This client attaches to a session 4292 # every death but the last (decision 8). This client attaches to a session
4334 # that ALREADY EXISTS — attach-or-create's join arm, the other half of the 4293 # that ALREADY EXISTS — attach-or-create's join arm, the other half of the
4335 # two creations above — and types the exit that ends it. 4294 # two creations above — and types the exit that ends it.
4336 set +e 4295 pipe_mux "$OUT.m18ax" "$OUT.m18ax.err" timeout 40 "$MUX" --sock "$SOCK21" --session a
4337 { printf 'exit\n'; sleep 3; } | \ 4296 pipe_send 'exit\n'
4338 timeout 40 "$MUX" --sock "$SOCK21" --session a > "$OUT.m18ax" 2> "$OUT.m18ax.err" 4297 pipe_waitexit "M18: the client of an exiting session"
4339 RC=$?
4340 set -e
4341 [ "$RC" -eq 0 ] || {
4342 echo "e2e FAIL: M18: the client of an exiting session exited $RC, want 0"
4343 cat "$OUT.m18ax.err"; exit 1; }
4344 4298
4345 wait_sessions "$SOCK21" 2 "M18: session a's shell exited" 4299 wait_sessions "$SOCK21" 2 "M18: session a's shell exited"
4346 4300
@@ -4375,11 +4329,15 @@ wait_for "$OUT.m18wh" "serving" 10 || {
4375 4329
4376 # Content for each session, planted by a CLI client and left behind: the 4330 # Content for each session, planted by a CLI client and left behind: the
4377 # tiles are passive 0x0 wall tiles and can never type anything themselves. 4331 # tiles are passive 0x0 wall tiles and can never type anything themselves.
4378 { printf 'printf "wall-a-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 4332 pipe_mux "$OUT.m18wa" "$OUT.m18wa.err" timeout 40 "$MUX" --sock "$SOCK22" --session a
4379 timeout 40 "$MUX" --sock "$SOCK22" --session a > "$OUT.m18wa" 2> "$OUT.m18wa.err" 4333 pipe_send 'printf "wall-a-%%s\\n" pin\n'
4334 await_out "$OUT.m18wa" "wall-a-pin" "M18 wall: session a's marker never reached the client"
4335 pipe_detach
4380 wait_grid "$SOCK22" "wall-a-pin" "M18 wall: session a's marker" a 4336 wait_grid "$SOCK22" "wall-a-pin" "M18 wall: session a's marker" a
4381 { printf 'printf "wall-b-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 4337 pipe_mux "$OUT.m18wb" "$OUT.m18wb.err" timeout 40 "$MUX" --sock "$SOCK22" --session b
4382 timeout 40 "$MUX" --sock "$SOCK22" --session b > "$OUT.m18wb" 2> "$OUT.m18wb.err" 4338 pipe_send 'printf "wall-b-%%s\\n" pin\n'
4339 await_out "$OUT.m18wb" "wall-b-pin" "M18 wall: session b's marker never reached the client"
4340 pipe_detach
4383 wait_grid "$SOCK22" "wall-b-pin" "M18 wall: session b's marker" b 4341 wait_grid "$SOCK22" "wall-b-pin" "M18 wall: session b's marker" b
4384 4342
4385 # Tile 0 is session a's; tile 1 is session b's. The stand-in spells the 4343 # Tile 0 is session a's; tile 1 is session b's. The stand-in spells the
@@ -4476,26 +4434,16 @@ while : ; do
4476 QPORT5=$((QPORT5 + 137)) 4434 QPORT5=$((QPORT5 + 137))
4477 done 4435 done
4478 4436
4479 set +e 4437 pipe_mux "$OUT.m18qa" "$OUT.m18qa.err" timeout 40 "$MUX" "quic://127.0.0.1:$QPORT5" --key "$M18KEY" --quic-idle-ms 15000 --session a
4480 { printf 'printf "q18a-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 4438 pipe_send 'printf "q18a-%%s\\n" pin\n'
4481 timeout 40 "$MUX" "quic://127.0.0.1:$QPORT5" --key "$M18KEY" \ 4439 await_out "$OUT.m18qa" "q18a-pin" "q18a-pin never reached the client"
4482 --quic-idle-ms 15000 --session a > "$OUT.m18qa" 2> "$OUT.m18qa.err" 4440 pipe_detach "M18 quic: the --session a dial"
4483 RC=$?
4484 set -e
4485 [ "$RC" -eq 0 ] || {
4486 echo "e2e FAIL: M18 quic: the --session a dial exited $RC"
4487 cat "$OUT.m18qa" "$OUT.m18qa.err" 2>/dev/null; exit 1; }
4488 wait_grid "$SOCK23" "q18a-pin" "M18 quic: session a's marker" a 4441 wait_grid "$SOCK23" "q18a-pin" "M18 quic: session a's marker" a
4489 4442
4490 set +e 4443 pipe_mux "$OUT.m18qb" "$OUT.m18qb.err" timeout 40 "$MUX" "quic://127.0.0.1:$QPORT5" --key "$M18KEY" --quic-idle-ms 15000 --session b
4491 { printf 'printf "q18b-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 4444 pipe_send 'printf "q18b-%%s\\n" pin\n'
4492 timeout 40 "$MUX" "quic://127.0.0.1:$QPORT5" --key "$M18KEY" \ 4445 await_out "$OUT.m18qb" "q18b-pin" "q18b-pin never reached the client"
4493 --quic-idle-ms 15000 --session b > "$OUT.m18qb" 2> "$OUT.m18qb.err" 4446 pipe_detach "M18 quic: the --session b dial"
4494 RC=$?
4495 set -e
4496 [ "$RC" -eq 0 ] || {
4497 echo "e2e FAIL: M18 quic: the --session b dial exited $RC"
4498 cat "$OUT.m18qb" "$OUT.m18qb.err" 2>/dev/null; exit 1; }
4499 wait_grid "$SOCK23" "q18b-pin" "M18 quic: session b's marker" b 4447 wait_grid "$SOCK23" "q18b-pin" "M18 quic: session b's marker" b
4500 4448
4501 if dump_session "$SOCK23" a | grep -q "q18b-pin"; then 4449 if dump_session "$SOCK23" a | grep -q "q18b-pin"; then
@@ -4512,14 +4460,10 @@ fi
4512 4460
4513 # The cross-transport join. Same name, different door: this must land in 4461 # The cross-transport join. Same name, different door: this must land in
4514 # the shell the QUIC client left behind, not spawn a second one. 4462 # the shell the QUIC client left behind, not spawn a second one.
4515 set +e 4463 pipe_mux "$OUT.m18qx" "$OUT.m18qx.err" timeout 40 "$MUX" --sock "$SOCK23" --session a
4516 { printf 'printf "x18a-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 4464 pipe_send 'printf "x18a-%%s\\n" pin\n'
4517 timeout 40 "$MUX" --sock "$SOCK23" --session a > "$OUT.m18qx" 2> "$OUT.m18qx.err" 4465 await_out "$OUT.m18qx" "x18a-pin" "x18a-pin never reached the client"
4518 RC=$? 4466 pipe_detach "M18 quic: the socket client of session a"
4519 set -e
4520 [ "$RC" -eq 0 ] || {
4521 echo "e2e FAIL: M18 quic: the socket client of session a exited $RC"
4522 cat "$OUT.m18qx" "$OUT.m18qx.err" 2>/dev/null; exit 1; }
4523 wait_grid "$SOCK23" "x18a-pin" "M18 quic: the socket client's marker in a" a 4467 wait_grid "$SOCK23" "x18a-pin" "M18 quic: the socket client's marker in a" a
4524 4468
4525 # Both markers on one grid is the whole assertion: the QUIC client's 4469 # Both markers on one grid is the whole assertion: the QUIC client's
@@ -4556,22 +4500,22 @@ ok "quic: two dials are two sessions, and one name is one session on either tran
4556 D14PID=$! 4500 D14PID=$!
4557 wait_sock "$SOCK18" "$OUT.weba.d" "tiny attach daemon never bound" 4501 wait_sock "$SOCK18" "$OUT.weba.d" "tiny attach daemon never bound"
4558 4502
4559 # The 80x24 client: types the first marker, holds the session open long 4503 # The 80x24 client: types the first marker, holds the session open while
4560 # enough for the 1x1 attacher to come and go, types the second marker 4504 # the 1x1 attacher comes and goes, types the second marker (the geometry
4561 # (the geometry probe), detaches. 4505 # probe) once the attacher is provably on, detaches.
4562 { printf 'printf "wall-%%s\\n" pin\n'; sleep 4; printf 'printf "zz-%%s\\n" web\n'; sleep 2; printf '\034\034'; } | \ 4506 pipe_mux "$OUT.weba" "$OUT.weba.err" timeout 40 "$MUX" --sock "$SOCK18"
4563 timeout 40 "$MUX" --sock "$SOCK18" > "$OUT.weba" 2> "$OUT.weba.err" & 4507 pipe_send 'printf "wall-%%s\\n" pin\n'
4564 WCLIPID=$!
4565 wait_grid "$SOCK18" "wall-pin" "tiny attach: the first marker" 4508 wait_grid "$SOCK18" "wall-pin" "tiny attach: the first marker"
4566 4509
4567 # The 1x1 attacher, on a real 1x1 pty. Its attach is answered with a 4510 # The 1x1 attacher, on a real 1x1 pty. Its attach is answered with a
4568 # unicast snapshot (the alt-screen enter proves the first frame came), 4511 # unicast snapshot (the alt-screen enter proves the first frame came),
4569 # its keystroke is the claim attempt, and the 'z' it waits for is the 4512 # its keystroke is the claim attempt, and the 'z' it waits for is the
4570 # second marker echoing — proof it was STILL ATTACHED while the marker 4513 # second marker echoing — proof it was STILL ATTACHED while the marker
4571 # landed, so the grid it could have wrecked was live the whole time. 4514 # landed, so the grid it could have wrecked was live the whole time. The
4572 set +e 4515 # marker is typed only after the fixture reports its claim sent (`done 2`),
4516 # so the ordering is witnessed rather than paced.
4573 timeout 40 "$PTYCLIENT" --cols 1 --rows 1 --out "$OUT.web1x1" --err "$OUT.web1x1.err" \ 4517 timeout 40 "$PTYCLIENT" --cols 1 --rows 1 --out "$OUT.web1x1" --err "$OUT.web1x1.err" \
4574 -- "$MUX" --sock "$SOCK18" > "$OUT.web1x1.log" 2>&1 <<'EOF' 4518 -- "$MUX" --sock "$SOCK18" > "$OUT.web1x1.log" 2>&1 <<'EOF' &
4575 expect \x1b[?1049h 15000 4519 expect \x1b[?1049h 15000
4576 send \x20 4520 send \x20
4577 expect z 20000 4521 expect z 20000
@@ -4579,6 +4523,13 @@ settle 300 10000
4579 send \x1c\x1c 4523 send \x1c\x1c
4580 waitexit 10000 4524 waitexit 10000
4581 EOF 4525 EOF
4526 W1X1PID=$!
4527 wait_for "$OUT.web1x1.log" "ptyclient: done 2" 20 || {
4528 echo "e2e FAIL: tiny attach: the 1x1 client never got as far as its claim"
4529 cat "$OUT.web1x1.log"; cat -v "$OUT.web1x1.err" 2>/dev/null; exit 1; }
4530 pipe_send 'printf "zz-%%s\\n" web\n'
4531 set +e
4532 wait "$W1X1PID"
4582 RC=$? 4533 RC=$?
4583 set -e 4534 set -e
4584 [ "$RC" -eq 0 ] || { 4535 [ "$RC" -eq 0 ] || {
@@ -4594,12 +4545,8 @@ set -e
4594 echo "e2e FAIL: tiny attach: the 1x1 attacher moved the grid (marker not contiguous):" 4545 echo "e2e FAIL: tiny attach: the 1x1 attacher moved the grid (marker not contiguous):"
4595 "$MUXD" dump --sock "$SOCK18"; exit 1; } 4546 "$MUXD" dump --sock "$SOCK18"; exit 1; }
4596 4547
4597 set +e 4548 await_out "$OUT.weba" "zz-web" "tiny attach: the second marker never reached the 80x24 client"
4598 wait "$WCLIPID" 4549 pipe_detach "tiny attach: 80x24 client"
4599 RC=$?
4600 set -e
4601 WCLIPID=""
4602 [ "$RC" -eq 0 ] || { echo "e2e FAIL: tiny attach: 80x24 client exited $RC"; cat "$OUT.weba.err"; exit 1; }
4603 assert_converged "$OUT.weba" "$SOCK18" "tiny attach: the 80x24 client never glitched" 4550 assert_converged "$OUT.weba" "$SOCK18" "tiny attach: the 80x24 client never glitched"
4604 4551
4605 assert_stopped "$SOCK18" "$D14PID" "tiny attach" "$OUT.webstop" 4552 assert_stopped "$SOCK18" "$D14PID" "tiny attach" "$OUT.webstop"
@@ -4615,9 +4562,10 @@ W1PID=$!
4615 wait_for "$OUT.webh" "serving" 10 || { 4562 wait_for "$OUT.webh" "serving" 10 || {
4616 echo "e2e FAIL: hub never reported serving"; cat "$OUT.webh"; exit 1; } 4563 echo "e2e FAIL: hub never reported serving"; cat "$OUT.webh"; exit 1; }
4617 4564
4618 { printf 'printf "web-%%s\\n" b1\n'; sleep 4; printf '\034\034'; } | \ 4565 # The typing client stays attached until after the browser leg below, so
4619 timeout 40 "$MUX" --sock "$SOCK19" > "$OUT.webb" 2> "$OUT.webb.err" & 4566 # the hub pumps a session that has a live CLI client on it too.
4620 WCLIPID=$! 4567 pipe_mux "$OUT.webb" "$OUT.webb.err" timeout 40 "$MUX" --sock "$SOCK19"
4568 pipe_send 'printf "web-%%s\\n" b1\n'
4621 wait_grid "$SOCK19" "web-b1" "web hub: the typing client's marker" 4569 wait_grid "$SOCK19" "web-b1" "web hub: the typing client's marker"
4622 4570
4623 # The browser stand-in: a passive 0x0 wall tile through the hub. Its 4571 # The browser stand-in: a passive 0x0 wall tile through the hub. Its
@@ -4657,12 +4605,7 @@ CLIENTS_AFTER=$("$MUXD" stats --sock "$SOCK19" | sed -n 's/.*clients=\([0-9]*\).
4657 [ "$CLIENTS_BEFORE" = "$CLIENTS_AFTER" ] || { 4605 [ "$CLIENTS_BEFORE" = "$CLIENTS_AFTER" ] || {
4658 echo "e2e FAIL: web hub: evil origin reached the daemon (clients $CLIENTS_BEFORE -> $CLIENTS_AFTER)"; exit 1; } 4606 echo "e2e FAIL: web hub: evil origin reached the daemon (clients $CLIENTS_BEFORE -> $CLIENTS_AFTER)"; exit 1; }
4659 4607
4660 set +e 4608 pipe_detach "web hub: typing client"
4661 wait "$WCLIPID"
4662 RC=$?
4663 set -e
4664 WCLIPID=""
4665 [ "$RC" -eq 0 ] || { echo "e2e FAIL: web hub: typing client exited $RC"; cat "$OUT.webb.err"; exit 1; }
4666 assert_converged "$OUT.webb" "$SOCK19" "web hub: the typing client" 4609 assert_converged "$OUT.webb" "$SOCK19" "web hub: the typing client"
4667 4610
4668 kill "$W1PID" 2>/dev/null || true 4611 kill "$W1PID" 2>/dev/null || true
@@ -4686,8 +4629,10 @@ W2PID=$!
4686 wait_for "$OUT.webh2" "serving" 10 || { 4629 wait_for "$OUT.webh2" "serving" 10 || {
4687 echo "e2e FAIL: tear hub never reported serving"; cat "$OUT.webh2"; exit 1; } 4630 echo "e2e FAIL: tear hub never reported serving"; cat "$OUT.webh2"; exit 1; }
4688 4631
4689 { printf 'printf "web-%%s\\n" c1\n'; sleep 1.5; printf '\034\034'; } | \ 4632 pipe_mux "$OUT.webc" "$OUT.webc.err" timeout 30 "$MUX" --sock "$SOCK20"
4690 timeout 30 "$MUX" --sock "$SOCK20" > "$OUT.webc" 2> "$OUT.webc.err" 4633 pipe_send 'printf "web-%%s\\n" c1\n'
4634 await_out "$OUT.webc" "web-c1" "web-c1 never reached the client"
4635 pipe_detach
4691 wait_grid "$SOCK20" "web-c1" "web tear: the first session's marker" 4636 wait_grid "$SOCK20" "web-c1" "web tear: the first session's marker"
4692 4637
4693 timeout 90 "$WSCLIENT" --port "$WPORT2" --tile 0 --out "$OUT.webws2" --err "$OUT.webws2.err" <<'EOF' & 4638 timeout 90 "$WSCLIENT" --port "$WPORT2" --tile 0 --out "$OUT.webws2" --err "$OUT.webws2.err" <<'EOF' &
@@ -4713,8 +4658,10 @@ D16PID=""
4713 D17PID=$! 4658 D17PID=$!
4714 wait_sock "$SOCK20" "$OUT.webc2.d" "web tear restart never bound" 4659 wait_sock "$SOCK20" "$OUT.webc2.d" "web tear restart never bound"
4715 4660
4716 { printf 'printf "web-%%s\\n" c2\n'; sleep 1.5; printf '\034\034'; } | \ 4661 pipe_mux "$OUT.webc2" "$OUT.webc2.err" timeout 30 "$MUX" --sock "$SOCK20"
4717 timeout 30 "$MUX" --sock "$SOCK20" > "$OUT.webc2" 2> "$OUT.webc2.err" 4662 pipe_send 'printf "web-%%s\\n" c2\n'
4663 await_out "$OUT.webc2" "web-c2" "web-c2 never reached the client"
4664 pipe_detach
4718 4665
4719 set +e 4666 set +e
4720 wait "$WCLIPID" 4667 wait "$WCLIPID"
@@ -4799,13 +4746,10 @@ RC=$(curl -s -o /dev/null -w '%{http_code}' -H "Origin: $DWORIG" -X POST \
4799 # passivity contract is in play in passing — a tile that claimed no size 4746 # passivity contract is in play in passing — a tile that claimed no size
4800 # never moves the grid, and the 80-wide marker is still one contiguous 4747 # never moves the grid, and the 80-wide marker is still one contiguous
4801 # string to expect. 4748 # string to expect.
4802 set +e 4749 pipe_mux "$OUT.dwcli" "$OUT.dwcli.err" timeout 40 "$MUX" --sock "$SOCK25"
4803 { printf 'printf "dyn-%%s\\n" w1\n'; sleep 4; printf '\034\034'; } | \ 4750 pipe_send 'printf "dyn-%%s\\n" w1\n'
4804 timeout 40 "$MUX" --sock "$SOCK25" > "$OUT.dwcli" 2> "$OUT.dwcli.err" 4751 await_out "$OUT.dwcli" "dyn-w1" "dyn-w1 never reached the client"
4805 RC=$? 4752 pipe_detach "dyn wall: CLI client"
4806 set -e
4807 [ "$RC" -eq 0 ] || {
4808 echo "e2e FAIL: dyn wall: CLI client exited $RC"; cat "$OUT.dwcli.err"; exit 1; }
4809 wait_grid "$SOCK25" "dyn-w1" "dyn wall: CLI marker" 4753 wait_grid "$SOCK25" "dyn-w1" "dyn wall: CLI marker"
4810 set +e 4754 set +e
4811 timeout 40 "$WSCLIENT" --port "$WPORT4" --tile 0 --out "$OUT.dwws" --err "$OUT.dwws.err" <<'EOF' 4755 timeout 40 "$WSCLIENT" --port "$WPORT4" --tile 0 --out "$OUT.dwws" --err "$OUT.dwws.err" <<'EOF'
@@ -4956,11 +4900,15 @@ ok "the wall is runtime state: add, remove, reorder, restore, argv adds"
4956 D23PID=$! 4900 D23PID=$!
4957 wait_sock "$SOCK26" "$OUT.cwall.d" "CLI wall daemon never bound" 4901 wait_sock "$SOCK26" "$OUT.cwall.d" "CLI wall daemon never bound"
4958 4902
4959 { printf 'printf "cwa-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 4903 pipe_mux "$OUT.cwa" "$OUT.cwa.err" timeout 40 "$MUX" --sock "$SOCK26" --session a
4960 timeout 40 "$MUX" --sock "$SOCK26" --session a > "$OUT.cwa" 2> "$OUT.cwa.err" 4904 pipe_send 'printf "cwa-%%s\\n" pin\n'
4905 await_out "$OUT.cwa" "cwa-pin" "cwa-pin never reached the client"
4906 pipe_detach
4961 wait_grid "$SOCK26" "cwa-pin" "CLI wall: session a's marker" a 4907 wait_grid "$SOCK26" "cwa-pin" "CLI wall: session a's marker" a
4962 { printf 'printf "cwb-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 4908 pipe_mux "$OUT.cwb" "$OUT.cwb.err" timeout 40 "$MUX" --sock "$SOCK26" --session b
4963 timeout 40 "$MUX" --sock "$SOCK26" --session b > "$OUT.cwb" 2> "$OUT.cwb.err" 4909 pipe_send 'printf "cwb-%%s\\n" pin\n'
4910 await_out "$OUT.cwb" "cwb-pin" "cwb-pin never reached the client"
4911 pipe_detach
4964 wait_grid "$SOCK26" "cwb-pin" "CLI wall: session b's marker" b 4912 wait_grid "$SOCK26" "cwb-pin" "CLI wall: session b's marker" b
4965 4913
4966 ( sleep 5; "$MUXA" send 'printf "cwlive-%s\n" pin\n' \ 4914 ( sleep 5; "$MUXA" send 'printf "cwlive-%s\n" pin\n' \
@@ -5552,11 +5500,15 @@ wait_sock "$SOCK32" "$OUT.wz.d" "wall-zoom daemon never bound"
5552 # Two sessions, each with its own marker, created exactly as scenario 33 5500 # Two sessions, each with its own marker, created exactly as scenario 33
5553 # creates them: no tty on either client, so both grids are the default 80 5501 # creates them: no tty on either client, so both grids are the default 80
5554 # wide — the number the passivity check reads back later. 5502 # wide — the number the passivity check reads back later.
5555 { printf 'printf "wza-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 5503 pipe_mux "$OUT.wza" "$OUT.wza.err" timeout 40 "$MUX" --sock "$SOCK32" --session a
5556 timeout 40 "$MUX" --sock "$SOCK32" --session a > "$OUT.wza" 2> "$OUT.wza.err" 5504 pipe_send 'printf "wza-%%s\\n" pin\n'
5505 await_out "$OUT.wza" "wza-pin" "wza-pin never reached the client"
5506 pipe_detach
5557 wait_grid "$SOCK32" "wza-pin" "wall zoom: session a's marker" a 5507 wait_grid "$SOCK32" "wza-pin" "wall zoom: session a's marker" a
5558 { printf 'printf "wzb-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 5508 pipe_mux "$OUT.wzb" "$OUT.wzb.err" timeout 40 "$MUX" --sock "$SOCK32" --session b
5559 timeout 40 "$MUX" --sock "$SOCK32" --session b > "$OUT.wzb" 2> "$OUT.wzb.err" 5509 pipe_send 'printf "wzb-%%s\\n" pin\n'
5510 await_out "$OUT.wzb" "wzb-pin" "wzb-pin never reached the client"
5511 pipe_detach
5560 wait_grid "$SOCK32" "wzb-pin" "wall zoom: session b's marker" b 5512 wait_grid "$SOCK32" "wzb-pin" "wall zoom: session b's marker" b
5561 5513
5562 # Brackets the wall and nothing else: every `muxa` and `wait_grid` call in 5514 # Brackets the wall and nothing else: every `muxa` and `wait_grid` call in
@@ -5928,11 +5880,15 @@ wait_sock "$SOCK37" "$OUT.zs.d" "zoom-skip daemon never bound"
5928 5880
5929 # Both sessions exist before the wall does, with a marker each so the wall 5881 # Both sessions exist before the wall does, with a marker each so the wall
5930 # has something to paint and this leg has an anchor to enter on. 5882 # has something to paint and this leg has an anchor to enter on.
5931 { printf 'printf "zsa-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 5883 pipe_mux "$OUT.zsa" "$OUT.zsa.err" timeout 40 "$MUX" --sock "$SOCK37" --session a
5932 timeout 40 "$MUX" --sock "$SOCK37" --session a > "$OUT.zsa" 2> "$OUT.zsa.err" 5884 pipe_send 'printf "zsa-%%s\\n" pin\n'
5885 await_out "$OUT.zsa" "zsa-pin" "zsa-pin never reached the client"
5886 pipe_detach
5933 wait_grid "$SOCK37" "zsa-pin" "zoom skip: session a's marker" a 5887 wait_grid "$SOCK37" "zsa-pin" "zoom skip: session a's marker" a
5934 { printf 'printf "zsb-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 5888 pipe_mux "$OUT.zsb" "$OUT.zsb.err" timeout 40 "$MUX" --sock "$SOCK37" --session b
5935 timeout 40 "$MUX" --sock "$SOCK37" --session b > "$OUT.zsb" 2> "$OUT.zsb.err" 5889 pipe_send 'printf "zsb-%%s\\n" pin\n'
5890 await_out "$OUT.zsb" "zsb-pin" "zsb-pin never reached the client"
5891 pipe_detach
5936 wait_grid "$SOCK37" "zsb-pin" "zoom skip: session b's marker" b 5892 wait_grid "$SOCK37" "zsb-pin" "zoom skip: session b's marker" b
5937 5893
5938 ZSATT_BEFORE=$(attaches_now "$SOCK37") 5894 ZSATT_BEFORE=$(attaches_now "$SOCK37")
@@ -6084,11 +6040,15 @@ ok "the zoom skips between tiles with Ctrl-\\ n / Ctrl-\\ l, and the daemon sees
6084 "$MUXD" run --sock "$SOCK38" --shell /bin/sh > "$OUT.zm.d" 2>&1 & 6040 "$MUXD" run --sock "$SOCK38" --shell /bin/sh > "$OUT.zm.d" 2>&1 &
6085 D35PID=$! 6041 D35PID=$!
6086 wait_sock "$SOCK38" "$OUT.zm.d" "zoom-mute daemon never bound" 6042 wait_sock "$SOCK38" "$OUT.zm.d" "zoom-mute daemon never bound"
6087 { printf 'printf "zma-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 6043 pipe_mux "$OUT.zma" "$OUT.zma.err" timeout 40 "$MUX" --sock "$SOCK38" --session a
6088 timeout 40 "$MUX" --sock "$SOCK38" --session a > "$OUT.zma" 2> "$OUT.zma.err" 6044 pipe_send 'printf "zma-%%s\\n" pin\n'
6045 await_out "$OUT.zma" "zma-pin" "zma-pin never reached the client"
6046 pipe_detach
6089 wait_grid "$SOCK38" "zma-pin" "zoom mute: session a's marker" a 6047 wait_grid "$SOCK38" "zma-pin" "zoom mute: session a's marker" a
6090 { printf 'printf "zmb-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 6048 pipe_mux "$OUT.zmb" "$OUT.zmb.err" timeout 40 "$MUX" --sock "$SOCK38" --session b
6091 timeout 40 "$MUX" --sock "$SOCK38" --session b > "$OUT.zmb" 2> "$OUT.zmb.err" 6049 pipe_send 'printf "zmb-%%s\\n" pin\n'
6050 await_out "$OUT.zmb" "zmb-pin" "zmb-pin never reached the client"
6051 pipe_detach
6092 wait_grid "$SOCK38" "zmb-pin" "zoom mute: session b's marker" b 6052 wait_grid "$SOCK38" "zmb-pin" "zoom mute: session b's marker" b
6093 6053
6094 set +e 6054 set +e
@@ -6486,8 +6446,10 @@ ok "a tile the zoom is not on paints nothing at all, however loud its session ge
6486 "$MUXD" run --sock "$SOCK39" --shell /bin/sh > "$OUT.zd.d" 2>&1 & 6446 "$MUXD" run --sock "$SOCK39" --shell /bin/sh > "$OUT.zd.d" 2>&1 &
6487 D36PID=$! 6447 D36PID=$!
6488 wait_sock "$SOCK39" "$OUT.zd.d" "zoom-dead daemon never bound" 6448 wait_sock "$SOCK39" "$OUT.zd.d" "zoom-dead daemon never bound"
6489 { printf 'printf "zdlive-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 6449 pipe_mux "$OUT.zda" "$OUT.zda.err" timeout 40 "$MUX" --sock "$SOCK39" --session a
6490 timeout 40 "$MUX" --sock "$SOCK39" --session a > "$OUT.zda" 2> "$OUT.zda.err" 6450 pipe_send 'printf "zdlive-%%s\\n" pin\n'
6451 await_out "$OUT.zda" "zdlive-pin" "zdlive-pin never reached the client"
6452 pipe_detach
6491 wait_grid "$SOCK39" "zdlive-pin" "zoom dead: the live session's marker" a 6453 wait_grid "$SOCK39" "zdlive-pin" "zoom dead: the live session's marker" a
6492 6454
6493 set +e 6455 set +e
@@ -6559,9 +6521,10 @@ ok "a tile whose pump has died narrates its zoom and gets its stripe back"
6559 D37PID=$! 6521 D37PID=$!
6560 wait_sock "$SOCK40" "$OUT.wh.d" "attach-history daemon never bound" 6522 wait_sock "$SOCK40" "$OUT.wh.d" "attach-history daemon never bound"
6561 6523
6562 { printf 'printf "wh1-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 6524 pipe_mux "$OUT.wh1" "$OUT.wh1.err" env XDG_STATE_HOME="$WHSTATE" timeout 40 "$MUX" --sock "$SOCK40"
6563 XDG_STATE_HOME="$WHSTATE" timeout 40 "$MUX" --sock "$SOCK40" \ 6525 pipe_send 'printf "wh1-%%s\\n" pin\n'
6564 > "$OUT.wh1" 2> "$OUT.wh1.err" 6526 await_out "$OUT.wh1" "wh1-pin" "wh1-pin never reached the client"
6527 pipe_detach
6565 wait_grid "$SOCK40" "wh1-pin" "attach history: the default session's marker" 6528 wait_grid "$SOCK40" "wh1-pin" "attach history: the default session's marker"
6566 grep -qx -- "--sock $SOCK40#0" "$WHWALL" || { 6529 grep -qx -- "--sock $SOCK40#0" "$WHWALL" || {
6567 echo "e2e FAIL: attach history: the attach wrote no tile (or not '#0'):" 6530 echo "e2e FAIL: attach history: the attach wrote no tile (or not '#0'):"
@@ -6571,9 +6534,10 @@ _wh_n=$(wc -l < "$WHWALL")
6571 echo "e2e FAIL: attach history: one attach wrote $_wh_n lines:" 6534 echo "e2e FAIL: attach history: one attach wrote $_wh_n lines:"
6572 cat "$WHWALL"; exit 1; } 6535 cat "$WHWALL"; exit 1; }
6573 6536
6574 { printf 'printf "wh2-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 6537 pipe_mux "$OUT.wh2" "$OUT.wh2.err" env XDG_STATE_HOME="$WHSTATE" timeout 40 "$MUX" --sock "$SOCK40"
6575 XDG_STATE_HOME="$WHSTATE" timeout 40 "$MUX" --sock "$SOCK40" \ 6538 pipe_send 'printf "wh2-%%s\\n" pin\n'
6576 > "$OUT.wh2" 2> "$OUT.wh2.err" 6539 await_out "$OUT.wh2" "wh2-pin" "wh2-pin never reached the client"
6540 pipe_detach
6577 wait_grid "$SOCK40" "wh2-pin" "attach history: the second attach's marker" 6541 wait_grid "$SOCK40" "wh2-pin" "attach history: the second attach's marker"
6578 _wh_n=$(wc -l < "$WHWALL") 6542 _wh_n=$(wc -l < "$WHWALL")
6579 [ "$_wh_n" -eq 1 ] || { 6543 [ "$_wh_n" -eq 1 ] || {
@@ -6609,9 +6573,10 @@ grep -qx -- "--sock $SOCK40#1" "$WHWALL" || {
6609 # and an attach that happened anyway — best effort means the attach is the 6573 # and an attach that happened anyway — best effort means the attach is the
6610 # act and the tile is only the record. 6574 # act and the tile is only the record.
6611 mkdir -p "$WHBAD/mux/wall" 6575 mkdir -p "$WHBAD/mux/wall"
6612 { printf 'printf "whro-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 6576 pipe_mux "$OUT.whro" "$OUT.whro.err" env XDG_STATE_HOME="$WHBAD" timeout 40 "$MUX" --sock "$SOCK40"
6613 XDG_STATE_HOME="$WHBAD" timeout 40 "$MUX" --sock "$SOCK40" \ 6577 pipe_send 'printf "whro-%%s\\n" pin\n'
6614 > "$OUT.whro" 2> "$OUT.whro.err" 6578 await_out "$OUT.whro" "whro-pin" "whro-pin never reached the client"
6579 pipe_detach
6615 grep -q "wall not updated" "$OUT.whro.err" || { 6580 grep -q "wall not updated" "$OUT.whro.err" || {
6616 echo "e2e FAIL: attach history: an unwritable wall file said nothing:" 6581 echo "e2e FAIL: attach history: an unwritable wall file said nothing:"
6617 cat "$OUT.whro.err"; exit 1; } 6582 cat "$OUT.whro.err"; exit 1; }
@@ -6688,13 +6653,15 @@ ok "the wall is attach history: mux adds, muxa never does, add/rm edit it"
6688 # Two ptyclient runs rather than one, because the file is the artifact: 6653 # Two ptyclient runs rather than one, because the file is the artifact:
6689 # what a run did is only readable after it has exited, so the one-tile 6654 # what a run did is only readable after it has exited, so the one-tile
6690 # state between them is the second run's input. 6655 # state between them is the second run's input.
6691 { printf 'printf "whxa-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 6656 pipe_mux "$OUT.whxa" "$OUT.whxa.err" env XDG_STATE_HOME="$WHXSTATE" timeout 40 "$MUX" --sock "$SOCK40" --session xa
6692 XDG_STATE_HOME="$WHXSTATE" timeout 40 "$MUX" --sock "$SOCK40" --session xa \ 6657 pipe_send 'printf "whxa-%%s\\n" pin\n'
6693 > "$OUT.whxa" 2> "$OUT.whxa.err" 6658 await_out "$OUT.whxa" "whxa-pin" "whxa-pin never reached the client"
6659 pipe_detach
6694 wait_grid "$SOCK40" "whxa-pin" "x forgets: session xa's marker" xa 6660 wait_grid "$SOCK40" "whxa-pin" "x forgets: session xa's marker" xa
6695 { printf 'printf "whxb-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 6661 pipe_mux "$OUT.whxb" "$OUT.whxb.err" env XDG_STATE_HOME="$WHXSTATE" timeout 40 "$MUX" --sock "$SOCK40" --session xb
6696 XDG_STATE_HOME="$WHXSTATE" timeout 40 "$MUX" --sock "$SOCK40" --session xb \ 6662 pipe_send 'printf "whxb-%%s\\n" pin\n'
6697 > "$OUT.whxb" 2> "$OUT.whxb.err" 6663 await_out "$OUT.whxb" "whxb-pin" "whxb-pin never reached the client"
6664 pipe_detach
6698 wait_grid "$SOCK40" "whxb-pin" "x forgets: session xb's marker" xb 6665 wait_grid "$SOCK40" "whxb-pin" "x forgets: session xb's marker" xb
6699 _whx_n=$(wc -l < "$WHXSTATE/mux/wall") 6666 _whx_n=$(wc -l < "$WHXSTATE/mux/wall")
6700 [ "$_whx_n" -eq 2 ] || { 6667 [ "$_whx_n" -eq 2 ] || {
@@ -6836,9 +6803,10 @@ wait_sock "$SOCK45" "$OUT.cv.d" "convergence daemon never bound"
6836 # The tile that is already on the wall before this leg's client starts. A 6803 # The tile that is already on the wall before this leg's client starts. A
6837 # piped `mux`, so its attach writes the line and then goes: what the leg 6804 # piped `mux`, so its attach writes the line and then goes: what the leg
6838 # asserts on is the FILE it left behind, not a process still holding it. 6805 # asserts on is the FILE it left behind, not a process still holding it.
6839 { printf 'printf "cvside-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 6806 pipe_mux "$OUT.cvside" "$OUT.cvside.err" env XDG_STATE_HOME="$CVSTATE" timeout 40 "$MUX" --sock "$SOCK45" --session side
6840 XDG_STATE_HOME="$CVSTATE" timeout 40 "$MUX" --sock "$SOCK45" --session side \ 6807 pipe_send 'printf "cvside-%%s\\n" pin\n'
6841 > "$OUT.cvside" 2> "$OUT.cvside.err" 6808 await_out "$OUT.cvside" "cvside-pin" "cvside-pin never reached the client"
6809 pipe_detach
6842 wait_grid "$SOCK45" "cvside-pin" "convergence: the saved tile's marker" side 6810 wait_grid "$SOCK45" "cvside-pin" "convergence: the saved tile's marker" side
6843 grep -qx -- "--sock $SOCK45#side" "$CVWALL" || { 6811 grep -qx -- "--sock $SOCK45#side" "$CVWALL" || {
6844 echo "e2e FAIL: convergence: the setup attach wrote no tile to unzoom onto:" 6812 echo "e2e FAIL: convergence: the setup attach wrote no tile to unzoom onto:"
@@ -6940,9 +6908,10 @@ wait_sock "$SOCK46" "$OUT.rg.d" "ring-grow daemon never bound"
6940 # `two` exists on the DAEMON but not in this leg's wall: a separate state 6908 # `two` exists on the DAEMON but not in this leg's wall: a separate state
6941 # home is what makes the setup client's own tile invisible here, so the 6909 # home is what makes the setup client's own tile invisible here, so the
6942 # first `n` meets a sibling with no tile — which is the case under test. 6910 # first `n` meets a sibling with no tile — which is the case under test.
6943 { printf 'printf "rgtwo-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 6911 pipe_mux "$OUT.rgtwo" "$OUT.rgtwo.err" env XDG_STATE_HOME="$RGOTHER" timeout 40 "$MUX" --sock "$SOCK46" --session two
6944 XDG_STATE_HOME="$RGOTHER" timeout 40 "$MUX" --sock "$SOCK46" --session two \ 6912 pipe_send 'printf "rgtwo-%%s\\n" pin\n'
6945 > "$OUT.rgtwo" 2> "$OUT.rgtwo.err" 6913 await_out "$OUT.rgtwo" "rgtwo-pin" "rgtwo-pin never reached the client"
6914 pipe_detach
6946 wait_grid "$SOCK46" "rgtwo-pin" "ring grow: the untiled sibling's marker" two 6915 wait_grid "$SOCK46" "rgtwo-pin" "ring grow: the untiled sibling's marker" two
6947 grep -qx -- "--sock $SOCK46#two" "$RGSTATE/mux/wall" 2>/dev/null && { 6916 grep -qx -- "--sock $SOCK46#two" "$RGSTATE/mux/wall" 2>/dev/null && {
6948 echo "e2e FAIL: ring grow: the sibling already had a tile, so the ADD is vacuous:" 6917 echo "e2e FAIL: ring grow: the sibling already had a tile, so the ADD is vacuous:"
@@ -7035,16 +7004,12 @@ ok "Ctrl-\\ n grows the wall for a sibling with no tile, and is free for one tha
7035 D47PID=$! 7004 D47PID=$!
7036 wait_sock "$SOCK47" "$OUT.xe.d" "exit-semantics daemon never bound" 7005 wait_sock "$SOCK47" "$OUT.xe.d" "exit-semantics daemon never bound"
7037 7006
7038 set +e 7007 # A wall it dropped to instead would read as 124: it never leaves, because
7039 { printf 'exit 7\n'; sleep 3; } | XDG_STATE_HOME="$XESTATE" timeout 40 \ 7008 # nothing closes its stdin under it.
7040 "$MUX" --sock "$SOCK47" > "$OUT.xe1" 2> "$OUT.xe1.err" 7009 pipe_mux "$OUT.xe1" "$OUT.xe1.err" env XDG_STATE_HOME="$XESTATE" timeout 40 \
7041 RC=$? 7010 "$MUX" --sock "$SOCK47"
7042 set -e 7011 pipe_send 'exit 7\n'
7043 [ "$RC" -eq 7 ] || { 7012 pipe_waitexit "exit semantics: the only tile's shell exited 7, and mux" 7
7044 echo "e2e FAIL: exit semantics: the only tile's shell exited 7 and mux exited $RC"
7045 echo " — a wall it dropped to instead reads as 0 once this script's"
7046 echo " stdin closes under it, or 124 if it never gets that far:"
7047 cat "$OUT.xe1.err"; exit 1; }
7048 7013
7049 set +e 7014 set +e
7050 XDG_STATE_HOME="$XESTATE" timeout 90 "$PTYCLIENT" --cols 100 --rows 30 \ 7015 XDG_STATE_HOME="$XESTATE" timeout 90 "$PTYCLIENT" --cols 100 --rows 30 \