0c07bcf8
test: e2e — a wall is a daemon, not a list of sessions; pin 81
a73x 2026-08-28 19:53
Commit message
test/e2e.sh
| Old | New | ||
|---|---|---|---|
| @@ -177,8 +177,8 @@ done | |||
| 177 | # one of those and adds a convergence point would be pinning a fact every | 177 | # one of those and adds a convergence point would be pinning a fact every |
| 178 | # leg above already establishes. | 178 | # leg above already establishes. |
| 179 | 179 | ||
| 180 | [ "$OK_COUNT" = "83" ] || { | 180 | [ "$OK_COUNT" = "81" ] || { |
| 181 | echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 83 —" | 181 | echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 81 —" |
| 182 | echo " a scenario was added (update the pin) or silently lost" | 182 | echo " a scenario was added (update the pin) or silently lost" |
| 183 | exit 1 | 183 | exit 1 |
| 184 | } | 184 | } |
test/e2e_07_wallcli.sh
| Old | New | ||
|---|---|---|---|
| @@ -2,25 +2,34 @@ | |||
| 2 | # e2e_07_wallcli.sh — sourced by test/e2e.sh after e2e_lib.sh. Scenarios run in | 2 | # e2e_07_wallcli.sh — sourced by test/e2e.sh after e2e_lib.sh. Scenarios run in |
| 3 | # the order they stand in; see the lib's header for what this file may | 3 | # the order they stand in; see the lib's header for what this file may |
| 4 | # assume and what it must register. | 4 | # assume and what it must register. |
| 5 | # The CLI wall (`mux wall`): its own daemon, so its two sessions can't be | 5 | # The CLI wall: its own daemon, so its sessions can't be confused with any |
| 6 | # confused with any other block's. | 6 | # other block's, and a state home of its own, because a wall is now the |
| 7 | # HOSTS FILE and the shared one is every daemon this suite has attached to. | ||
| 7 | SOCK26="${TMPDIR:-/tmp}/muxd-e2e-cliwall-$$.sock" | 8 | SOCK26="${TMPDIR:-/tmp}/muxd-e2e-cliwall-$$.sock" |
| 8 | defer_sock "$SOCK26" | 9 | defer_sock "$SOCK26" |
| 10 | CWSTATE="${TMPDIR:-/tmp}/mux-e2e-cliwall-state-$$" | ||
| 11 | defer_rm "$CWSTATE" | ||
| 9 | # The prefix chord (Ctrl-\ as a command key): its own daemon, so a chord | 12 | # The prefix chord (Ctrl-\ as a command key): its own daemon, so a chord |
| 10 | # that fails to detach cannot strand another block's session. | 13 | # that fails to detach cannot strand another block's session. |
| 11 | SOCK27="${TMPDIR:-/tmp}/muxd-e2e-prefix-$$.sock" | 14 | SOCK27="${TMPDIR:-/tmp}/muxd-e2e-prefix-$$.sock" |
| 12 | defer_sock "$SOCK27" | 15 | defer_sock "$SOCK27" |
| 16 | PFXSTATE="${TMPDIR:-/tmp}/mux-e2e-prefix-state-$$" | ||
| 17 | defer_rm "$PFXSTATE" | ||
| 13 | # Ctrl-\ c (create a session and switch to it): its own daemon, because the | 18 | # Ctrl-\ c (create a session and switch to it): its own daemon, because the |
| 14 | # assertion is about which sessions EXIST — another block's session on the | 19 | # assertion is about which sessions EXIST — another block's session on the |
| 15 | # same daemon would change the name the chord picks. | 20 | # same daemon would change the name the chord picks. |
| 16 | SOCK28="${TMPDIR:-/tmp}/muxd-e2e-newsess-$$.sock" | 21 | SOCK28="${TMPDIR:-/tmp}/muxd-e2e-newsess-$$.sock" |
| 17 | defer_sock "$SOCK28" | 22 | defer_sock "$SOCK28" |
| 23 | NSWSTATE="${TMPDIR:-/tmp}/mux-e2e-newsess-state-$$" | ||
| 24 | defer_rm "$NSWSTATE" | ||
| 18 | # Ctrl-\ n / Ctrl-\ p (step around the ring): its own daemon for the same | 25 | # Ctrl-\ n / Ctrl-\ p (step around the ring): its own daemon for the same |
| 19 | # reason as SOCK28, and more sharply — the ring IS the list of sessions on | 26 | # reason as SOCK28, and more sharply — the ring IS the list of sessions on |
| 20 | # one daemon, so a stray session from another block would change where a | 27 | # one daemon, so a stray session from another block would change where a |
| 21 | # step lands. | 28 | # step lands. |
| 22 | SOCK29="${TMPDIR:-/tmp}/muxd-e2e-ring-$$.sock" | 29 | SOCK29="${TMPDIR:-/tmp}/muxd-e2e-ring-$$.sock" |
| 23 | defer_sock "$SOCK29" | 30 | defer_sock "$SOCK29" |
| 31 | RINGSTATE="${TMPDIR:-/tmp}/mux-e2e-ring-state-$$" | ||
| 32 | defer_rm "$RINGSTATE" | ||
| 24 | # M5 (the self-attach refusal): its own daemon because the assertion reads | 33 | # M5 (the self-attach refusal): its own daemon because the assertion reads |
| 25 | # the SOCKET PATH out of a session shell's environment and compares it to | 34 | # the SOCKET PATH out of a session shell's environment and compares it to |
| 26 | # the one this block started — a shared daemon would make the comparison a | 35 | # the one this block started — a shared daemon would make the comparison a |
| @@ -29,13 +38,13 @@ SOCK31="${TMPDIR:-/tmp}/muxd-e2e-selfattach-$$.sock" | |||
| 29 | defer_sock "$SOCK31" | 38 | defer_sock "$SOCK31" |
| 30 | 39 | ||
| 31 | # ---- the CLI wall: the hub's multiattach, in one terminal -------------- | 40 | # ---- the CLI wall: the hub's multiattach, in one terminal -------------- |
| 32 | # One daemon, two named sessions, one `mux wall` on a real pty showing | 41 | # One daemon, two named sessions, one `mux` on a real pty showing both AT |
| 33 | # both AT ONCE — the browser wall's claim, made by the CLI. Three facts, | 42 | # ONCE — the browser wall's claim, made by the CLI. Three facts, each with |
| 34 | # each with its own witness: both sessions' markers painted (the snapshot | 43 | # its own witness: both sessions' markers painted (the snapshot path), a |
| 35 | # path), a line injected into b WHILE the wall is attached painted too | 44 | # line injected into b WHILE the wall is attached painted too (the delta |
| 36 | # (the delta path — the wall is a live replica, not a capture), and | 45 | # path — the wall is a live replica, not a capture), and `\x1cd` ended the |
| 37 | # `\x1cd` ended the process with exit 0 (the wall left, not died). Markers | 46 | # process with exit 0 (the wall left, not died). Markers are |
| 38 | # are shell-EXPANDED (`%s`+pin), the M18 trick: a hit is the shell's work, | 47 | # shell-EXPANDED (`%s`+pin), the M18 trick: a hit is the shell's work, |
| 39 | # never an echo of anything typed here. | 48 | # never an echo of anything typed here. |
| 40 | # | 49 | # |
| 41 | # The one expect anchors on the injected delta, deliberately: tile pumps | 50 | # The one expect anchors on the injected delta, deliberately: tile pumps |
| @@ -45,18 +54,19 @@ defer_sock "$SOCK31" | |||
| 45 | # replica that must already hold b's snapshot); the snapshot markers are | 54 | # replica that must already hold b's snapshot); the snapshot markers are |
| 46 | # asserted order-free by the greps on the capture below. | 55 | # asserted order-free by the greps on the capture below. |
| 47 | # | 56 | # |
| 48 | # The two tiles are spelled DIFFERENTLY on purpose: `--sock PATH` as two | 57 | # Nothing on the command line names a tile any more: the wall is the |
| 49 | # arguments (muxweb's dialect) and as one quoted spelling (the wall file's). | 58 | # DAEMON, and the tiles are whatever sessions it turns out to have. `a` is |
| 50 | # Both must reach the same tile, so both are pinned by this one leg. | 59 | # named only because the wall is entered zoomed on it; `b` and the |
| 60 | # daemon's own `0` arrive from the host's list, which is the claim. | ||
| 51 | start_daemon "$SOCK26" "$OUT.cwall.d" "CLI wall daemon never bound" --shell /bin/sh | 61 | start_daemon "$SOCK26" "$OUT.cwall.d" "CLI wall daemon never bound" --shell /bin/sh |
| 52 | D23PID=$DPID | 62 | D23PID=$DPID |
| 53 | 63 | ||
| 54 | pipe_mux "$OUT.cwa" "$OUT.cwa.err" timeout 40 "$MUX" --sock "$SOCK26" --session a | 64 | pipe_mux "$OUT.cwa" "$OUT.cwa.err" env XDG_STATE_HOME="$CWSTATE" timeout 40 "$MUX" --sock "$SOCK26" --session a |
| 55 | pipe_send 'printf "cwa-%%s\\n" pin\n' | 65 | pipe_send 'printf "cwa-%%s\\n" pin\n' |
| 56 | await_out "$OUT.cwa" "cwa-pin" "cwa-pin never reached the client" | 66 | await_out "$OUT.cwa" "cwa-pin" "cwa-pin never reached the client" |
| 57 | pipe_detach | 67 | pipe_detach |
| 58 | wait_grid "$SOCK26" "cwa-pin" "CLI wall: session a's marker" a | 68 | wait_grid "$SOCK26" "cwa-pin" "CLI wall: session a's marker" a |
| 59 | pipe_mux "$OUT.cwb" "$OUT.cwb.err" timeout 40 "$MUX" --sock "$SOCK26" --session b | 69 | pipe_mux "$OUT.cwb" "$OUT.cwb.err" env XDG_STATE_HOME="$CWSTATE" timeout 40 "$MUX" --sock "$SOCK26" --session b |
| 60 | pipe_send 'printf "cwb-%%s\\n" pin\n' | 70 | pipe_send 'printf "cwb-%%s\\n" pin\n' |
| 61 | await_out "$OUT.cwb" "cwb-pin" "cwb-pin never reached the client" | 71 | await_out "$OUT.cwb" "cwb-pin" "cwb-pin never reached the client" |
| 62 | pipe_detach | 72 | pipe_detach |
| @@ -67,8 +77,13 @@ wait_grid "$SOCK26" "cwb-pin" "CLI wall: session b's marker" b | |||
| 67 | CWINJPID=$! | 77 | CWINJPID=$! |
| 68 | defer_kill "$CWINJPID" | 78 | defer_kill "$CWINJPID" |
| 69 | set +e | 79 | set +e |
| 70 | timeout 40 "$PTYCLIENT" --cols 40 --rows 30 --out "$OUT.cwcap" --err "$OUT.cwcap.err" -- \ | 80 | # No saved tree: this leg asserts the DEFAULT cut, and every wall that |
| 71 | "$MUX" wall --sock "$SOCK26#a" "--sock $SOCK26#b" > "$OUT.cwpc" 2>&1 <<'EOF' | 81 | # came before it saved a sidecar on its way out. The aspect rule applies |
| 82 | # only when nothing is saved. | ||
| 83 | rm -f "$CWSTATE/mux/layout" | ||
| 84 | XDG_STATE_HOME="$CWSTATE" timeout 40 "$PTYCLIENT" --cols 40 --rows 30 \ | ||
| 85 | --out "$OUT.cwcap" --err "$OUT.cwcap.err" -- \ | ||
| 86 | "$MUX" --sock "$SOCK26" --session a > "$OUT.cwpc" 2>&1 <<'EOF' | ||
| 72 | expect cwlive-pin 20000 | 87 | expect cwlive-pin 20000 |
| 73 | send \x1cd | 88 | send \x1cd |
| 74 | waitexit 10000 | 89 | waitexit 10000 |
| @@ -93,7 +108,7 @@ grep -q "cwlive-pin" "$OUT.cwcap" || { | |||
| 93 | # assert — the wall writes the sessions it shows. | 108 | # assert — the wall writes the sessions it shows. |
| 94 | assert_stopped "$SOCK26" "$D23PID" "CLI wall" "$OUT.cwstop" | 109 | assert_stopped "$SOCK26" "$D23PID" "CLI wall" "$OUT.cwstop" |
| 95 | D23PID="" | 110 | D23PID="" |
| 96 | ok "mux wall: two sessions and a live delta on one terminal" | 111 | ok "mux --sock: every session the daemon has, and a live delta, on one terminal" |
| 97 | 112 | ||
| 98 | # ---- M1: Ctrl-\ is a prefix, not an action ------------------------------ | 113 | # ---- M1: Ctrl-\ is a prefix, not an action ------------------------------ |
| 99 | # | 114 | # |
| @@ -117,7 +132,12 @@ start_daemon "$SOCK27" "$OUT.pfx.d" "prefix daemon never bound" --shell /bin/sh | |||
| 117 | D24PID=$DPID | 132 | D24PID=$DPID |
| 118 | 133 | ||
| 119 | set +e | 134 | set +e |
| 120 | timeout 40 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.pfx" --err "$OUT.pfx.err" \ | 135 | # No saved tree: this leg asserts the DEFAULT cut, and every wall that |
| 136 | # came before it saved a sidecar on its way out. The aspect rule applies | ||
| 137 | # only when nothing is saved. | ||
| 138 | rm -f "$PFXSTATE/mux/layout" | ||
| 139 | XDG_STATE_HOME="$PFXSTATE" timeout 40 "$PTYCLIENT" --cols 80 --rows 24 \ | ||
| 140 | --out "$OUT.pfx" --err "$OUT.pfx.err" \ | ||
| 121 | -- "$MUX" --sock "$SOCK27" > "$OUT.pfx.log" 2>&1 <<'EOF' | 141 | -- "$MUX" --sock "$SOCK27" > "$OUT.pfx.log" 2>&1 <<'EOF' |
| 122 | expect \x1b[?1049h 15000 | 142 | expect \x1b[?1049h 15000 |
| 123 | settle 400 15000 | 143 | settle 400 15000 |
| @@ -147,7 +167,7 @@ assert_stopped "$SOCK27" "$D24PID" "prefix" "$OUT.pfxstop" | |||
| 147 | D24PID="" | 167 | D24PID="" |
| 148 | ok "Ctrl-\\ prefix: an unknown chord is swallowed, Ctrl-\\ d detaches" | 168 | ok "Ctrl-\\ prefix: an unknown chord is swallowed, Ctrl-\\ d detaches" |
| 149 | 169 | ||
| 150 | # ---- M2: Ctrl-\ c creates a session and switches to it ------------------- | 170 | # ---- M2: Ctrl-\ c creates a session and gives it a tile ------------------- |
| 151 | # | 171 | # |
| 152 | # The claim is a MOVE, which takes two witnesses: the client ends up | 172 | # The claim is a MOVE, which takes two witnesses: the client ends up |
| 153 | # somewhere new, and the place it left is still there and unchanged. | 173 | # somewhere new, and the place it left is still there and unchanged. |
| @@ -162,38 +182,32 @@ ok "Ctrl-\\ prefix: an unknown chord is swallowed, Ctrl-\\ d detaches" | |||
| 162 | # second. Without that pair the same log would be produced by a chord | 182 | # second. Without that pair the same log would be produced by a chord |
| 163 | # that did nothing at all. | 183 | # that did nothing at all. |
| 164 | # | 184 | # |
| 165 | # The leg then keeps pressing until the daemon runs out of sessions, because | 185 | # The arrival signal is the new tile's own LABEL BAR. `\x1cc` used to tear |
| 166 | # the refusal is the branch with the interesting answer: a refused attach is | 186 | # the terminal down and build it back up — a fresh `?1049h` per switch — |
| 167 | # the one failure the zoom recovers from instead of exiting, and nothing | 187 | # because each switch was a new client process; then it became a zoom, and |
| 168 | # else in the suite reaches it. See the block after the leg for the three | 188 | # the signal was `setFocus`'s `\x1b[2J`. Now the chord ADDS A TILE beside |
| 169 | # things "recovered" has to mean. | 189 | # the focus and leaves both on screen, so neither happens: what is new on |
| 190 | # the terminal is a second stripe whose marker carries the focus (`2> `, | ||
| 191 | # `paintLabel`). A one-tile wall draws no bar at all, so that byte sequence | ||
| 192 | # cannot be anything but the tile the chord just made. | ||
| 170 | # | 193 | # |
| 171 | # PHASE 3C evolved the WAIT, and only the wait. `\x1cc` used to tear the | 194 | # Then the refusal, which is the branch with the interesting answer: a |
| 172 | # terminal down and build it back up — a fresh `?1049h` per switch — because | 195 | # birth the WALL has no room for. The daemon is not full here and could not |
| 173 | # each switch was a new client process on a new transport. Now `mux` IS the | 196 | # usefully be — `max_tiles` and `max_sessions` are both 32, so a wall of one |
| 174 | # wall: the chord adds a tile and moves the ZOOM to it, and the alternate | 197 | # host runs out of stripes before its daemon runs out of slots, and the |
| 175 | # screen was entered once, at startup, and is never left. So the arrival | 198 | # terminal runs out of rows long before either. Eight sessions on a 24-row |
| 176 | # signal is the zoom's own: `setFocus` clears the screen (`\x1b[2J`) on the | 199 | # terminal is a saturated wall exactly (three rows a stripe: two for the |
| 177 | # thread that moves it, before the terminal changes hands. | 200 | # daemon's floor, one for the bar), and a ninth has nowhere to go. |
| 178 | # | ||
| 179 | # That needle is not merely "some paint": between the chord and the answer | ||
| 180 | # the old tile is idle and paints nothing, so the next `[2J` after the chord | ||
| 181 | # is the handover itself. Waiting for it MATTERS — keystrokes sent before it | ||
| 182 | # would go to the mailbox of the tile the user is leaving, which is the | ||
| 183 | # session they were typing in a moment ago and would silently swallow the | ||
| 184 | # marker. | ||
| 185 | start_daemon "$SOCK28" "$OUT.nsw.d" "new-session daemon never bound" --shell /bin/sh | 201 | start_daemon "$SOCK28" "$OUT.nsw.d" "new-session daemon never bound" --shell /bin/sh |
| 186 | D25PID=$DPID | 202 | D25PID=$DPID |
| 187 | # The refusal this leg's fourth chord asserts needs a FULL table. The | ||
| 188 | # chords make four (0, 1, 2, 3); the other 28 are filled here. `fillN` is | ||
| 189 | # not an integer, so nextFreeName still hands the chords 1, 2, 3 and | ||
| 190 | # reaches for "4" on the refused one — which is the name the post-check | ||
| 191 | # below asks about. | ||
| 192 | fill_sessions "$SOCK28" "$OUT.nswfill" fill 4 31 | ||
| 193 | 203 | ||
| 194 | set +e | 204 | set +e |
| 195 | # tall: Ctrl-\ c adds a second tile at 80x24 (80 >= 48) | 205 | # No saved tree: this leg asserts the DEFAULT cut, and every wall that |
| 196 | timeout 40 "$PTYCLIENT" --cols 40 --rows 24 --out "$OUT.nsw" --err "$OUT.nsw.err" \ | 206 | # came before it saved a sidecar on its way out. The aspect rule applies |
| 207 | # only when nothing is saved. | ||
| 208 | rm -f "$NSWSTATE/mux/layout" | ||
| 209 | XDG_STATE_HOME="$NSWSTATE" timeout 40 "$PTYCLIENT" --cols 40 --rows 24 \ | ||
| 210 | --out "$OUT.nsw" --err "$OUT.nsw.err" \ | ||
| 197 | -- "$MUX" --sock "$SOCK28" > "$OUT.nsw.log" 2>&1 <<'EOF' | 211 | -- "$MUX" --sock "$SOCK28" > "$OUT.nsw.log" 2>&1 <<'EOF' |
| 198 | expect \x1b[?1049h 15000 | 212 | expect \x1b[?1049h 15000 |
| 199 | settle 400 15000 | 213 | settle 400 15000 |
| @@ -201,33 +215,18 @@ send printf 'm2a-%s\\n' pin\n | |||
| 201 | expect m2a-pin 15000 | 215 | expect m2a-pin 15000 |
| 202 | settle 400 15000 | 216 | settle 400 15000 |
| 203 | send \x1cc | 217 | send \x1cc |
| 204 | expect \x1b[2J 15000 | 218 | expect 2> --sock 15000 |
| 205 | settle 400 15000 | 219 | settle 600 15000 |
| 206 | send printf 'm2b-%s\\n' pin\n | 220 | send printf 'm2b-%s\\n' pin\n |
| 207 | expect m2b-pin 15000 | 221 | expect m2b-pin 15000 |
| 208 | settle 400 15000 | 222 | settle 400 15000 |
| 209 | send \x1cc | ||
| 210 | expect \x1b[2J 15000 | ||
| 211 | settle 400 15000 | ||
| 212 | send \x1cc | ||
| 213 | expect \x1b[2J 15000 | ||
| 214 | settle 400 15000 | ||
| 215 | send printf 'm2c-%s\\n' pin\n | ||
| 216 | expect m2c-pin 15000 | ||
| 217 | settle 400 15000 | ||
| 218 | send \x1cc | ||
| 219 | expect m2c-pin 15000 | ||
| 220 | settle 400 15000 | ||
| 221 | send printf 'm2d-%s\\n' pin\n | ||
| 222 | expect m2d-pin 15000 | ||
| 223 | settle 400 15000 | ||
| 224 | send \x1c\x1c | 223 | send \x1c\x1c |
| 225 | waitexit 10000 | 224 | waitexit 10000 |
| 226 | EOF | 225 | EOF |
| 227 | RC=$? | 226 | RC=$? |
| 228 | set -e | 227 | set -e |
| 229 | [ "$RC" -eq 0 ] || { | 228 | [ "$RC" -eq 0 ] || { |
| 230 | echo "e2e FAIL: new session: ptyclient leg exited $RC (did \\x1cc switch?):" | 229 | echo "e2e FAIL: new session: ptyclient leg exited $RC (did \\x1cc make a tile?):" |
| 231 | cat "$OUT.nsw.log"; exit 1; } | 230 | cat "$OUT.nsw.log"; exit 1; } |
| 232 | "$MUXA" status --sock "$SOCK28" --session 1 > "$OUT.nswst" 2>&1 || { | 231 | "$MUXA" status --sock "$SOCK28" --session 1 > "$OUT.nswst" 2>&1 || { |
| 233 | echo "e2e FAIL: new session: the chord did not create session 1:" | 232 | echo "e2e FAIL: new session: the chord did not create session 1:" |
| @@ -241,44 +240,78 @@ grep -q "m2a-pin" "$OUT.nswcap" || { | |||
| 241 | grep -q "m2b-pin" "$OUT.nswcap" && { | 240 | grep -q "m2b-pin" "$OUT.nswcap" && { |
| 242 | echo "e2e FAIL: new session: the second marker ran in the OLD session" | 241 | echo "e2e FAIL: new session: the second marker ran in the OLD session" |
| 243 | cat "$OUT.nswcap"; exit 1; } | 242 | cat "$OUT.nswcap"; exit 1; } |
| 244 | # The fourth chord had nowhere to go: the fill above took 28 slots and the | 243 | "$MUXA" capture --sock "$SOCK28" --session 1 > "$OUT.nswcap1" 2>&1 |
| 245 | # chords the other four (0, 1, 2, 3), so the daemon answered the attach with an | 244 | grep -q "m2b-pin" "$OUT.nswcap1" || { |
| 246 | # exit_status before a single frame of state. That refusal is NOT fatal — | 245 | echo "e2e FAIL: new session: the chord's own session never got the marker:" |
| 247 | # the tile the chord created knows which tile it was born from — and the | 246 | cat "$OUT.nswcap1"; exit 1; } |
| 248 | # three claims below are what "fell back" means, none of which the log | 247 | |
| 249 | # shows on its own. | 248 | # Six more, so the wall the next client opens is eight tiles on 24 rows — |
| 249 | # saturated, with nothing left over. Exactly saturated on purpose: a wall | ||
| 250 | # with sessions it cannot place says `[+N not shown]` on every poll, and | ||
| 251 | # that notice would overwrite the one this leg is waiting to read. | ||
| 252 | fill_sessions "$SOCK28" "$OUT.nswfill" fill 2 7 | ||
| 253 | wait_sessions "$SOCK28" 8 "new session: the wall's terminal should be exactly full" | ||
| 254 | |||
| 255 | set +e | ||
| 256 | # No saved tree: this leg asserts the DEFAULT cut, and every wall that | ||
| 257 | # came before it saved a sidecar on its way out. The aspect rule applies | ||
| 258 | # only when nothing is saved. | ||
| 259 | rm -f "$NSWSTATE/mux/layout" | ||
| 260 | XDG_STATE_HOME="$NSWSTATE" timeout 60 "$PTYCLIENT" --cols 40 --rows 24 \ | ||
| 261 | --out "$OUT.nsw2" --err "$OUT.nsw2.err" \ | ||
| 262 | -- "$MUX" --sock "$SOCK28" > "$OUT.nsw2.log" 2>&1 <<'EOF' | ||
| 263 | expect m2a-pin 25000 | ||
| 264 | settle 2500 25000 | ||
| 265 | send \x1cc | ||
| 266 | settle 1500 15000 | ||
| 267 | send printf 'm2d-%s\\n' pin\n | ||
| 268 | expect m2d-pin 15000 | ||
| 269 | settle 400 15000 | ||
| 270 | # ...and then a focus move, which is what makes the refusal SPEAKABLE: the | ||
| 271 | # notice is left in `shared` for whichever Core claims the terminal next, | ||
| 272 | # so a refusal that moves no focus (the two sends above) paints nothing. | ||
| 273 | # `n` claims a neighbour, that claim takes the notice, and the banner is | ||
| 274 | # the sentence. `expect`, so a wall that went silent fails here rather than | ||
| 275 | # in a grep after the run. | ||
| 276 | send \x1cn | ||
| 277 | expect no room on the wall 15000 | ||
| 278 | send \x1c\x1c | ||
| 279 | waitexit 10000 | ||
| 280 | EOF | ||
| 281 | RC=$? | ||
| 282 | set -e | ||
| 283 | [ "$RC" -eq 0 ] || { | ||
| 284 | echo "e2e FAIL: new session: the saturated-wall leg exited $RC:" | ||
| 285 | cat "$OUT.nsw2.log"; exit 1; } | ||
| 286 | # The refusal is NOT fatal, and the three claims below are what "recovered" | ||
| 287 | # means — none of which the log shows on its own. | ||
| 250 | # | 288 | # |
| 251 | # It is also why the fourth chord's wait is `expect m2c-pin` and not the | 289 | # * the user was told. The sentence is read off the STREAM and not off a |
| 252 | # `[2J` the other three use: a refused tile's screen is cleared like any | 290 | # rendered grid: a banner is a transient overwrite that the next repaint |
| 253 | # other, so `[2J` cannot tell "the zoom arrived" from "the zoom came | 291 | # takes back, so the final screen is not where it lives. Nothing else in |
| 254 | # straight back". Session 3's own marker, repainted from a replica that was | 292 | # this suite spells this string, so a match is the client having said it. |
| 255 | # hot the whole time, can — and it is the same positive the capture | ||
| 256 | # assertion below leans on. | ||
| 257 | # | 293 | # |
| 258 | # * the client said so, once, in its own words. Its stderr is ptyclient's | 294 | # * the focus never left the tile the chord was typed in: the marker run |
| 259 | # --err file, not the pty, so the message is read there. | 295 | # AFTER the refusal is in the default session's grid, beside the one |
| 260 | grep -q "cannot create a new session" "$OUT.nsw.err" || { | 296 | # from before it. A capture holding only m2a-pin would equally describe |
| 261 | echo "e2e FAIL: new session: the refused switch said nothing:" | 297 | # a client that died there. |
| 262 | cat "$OUT.nsw.err"; exit 1; } | 298 | grep -qF -- "[no room on the wall for another tile]" "$OUT.nsw2" || { |
| 263 | # * it is back in session 3, the one it typed the chord in — proved | 299 | echo "e2e FAIL: new session: the refused chord said nothing — the notice" |
| 264 | # POSITIVELY, by a marker run AFTER the refusal landing in that | 300 | echo " never reached the terminal on the next claim:" |
| 265 | # session's grid beside the one from before it. A capture that held | 301 | cat "$OUT.nsw2.log"; exit 1; } |
| 266 | # only m2c-pin would equally describe a client that died there. | 302 | "$MUXA" capture --sock "$SOCK28" > "$OUT.nswcapd" 2>&1 |
| 267 | "$MUXA" capture --sock "$SOCK28" --session 3 > "$OUT.nswcap3" 2>&1 | 303 | grep -q "m2d-pin" "$OUT.nswcapd" || { |
| 268 | grep -q "m2c-pin" "$OUT.nswcap3" || { | 304 | echo "e2e FAIL: new session: the refused chord did not come back to the focused session:" |
| 269 | echo "e2e FAIL: new session: session 3 lost the marker from before the refusal:" | 305 | cat "$OUT.nswcapd"; exit 1; } |
| 270 | cat "$OUT.nswcap3"; exit 1; } | ||
| 271 | grep -q "m2d-pin" "$OUT.nswcap3" || { | ||
| 272 | echo "e2e FAIL: new session: the refused switch did not come back to session 3:" | ||
| 273 | cat "$OUT.nswcap3"; exit 1; } | ||
| 274 | # * and the name it was reaching for was never created. Without this the | 306 | # * and the name it was reaching for was never created. Without this the |
| 275 | # leg would pass on a daemon that quietly grew one more slot. | 307 | # leg would pass on a wall that refused the tile and made the session |
| 276 | "$MUXA" status --sock "$SOCK28" --session 4 > "$OUT.nswst4" 2>&1 && { | 308 | # anyway — a session nobody can see is the worse of the two failures. |
| 277 | echo "e2e FAIL: new session: the daemon created a 33rd session:" | 309 | "$MUXA" status --sock "$SOCK28" --session 2 > "$OUT.nswst2" 2>&1 && { |
| 278 | cat "$OUT.nswst4"; exit 1; } | 310 | echo "e2e FAIL: new session: a refused tile left a session behind:" |
| 311 | cat "$OUT.nswst2"; exit 1; } | ||
| 279 | assert_stopped "$SOCK28" "$D25PID" "new session" "$OUT.nswstop" | 312 | assert_stopped "$SOCK28" "$D25PID" "new session" "$OUT.nswstop" |
| 280 | D25PID="" | 313 | D25PID="" |
| 281 | ok "Ctrl-\\ c: a new session is created and switched to, the old one intact; a full daemon refuses and the client stays where it was" | 314 | ok "Ctrl-\\ c: a new session is created and given a tile, the old one intact; a full wall refuses and the client stays where it was" |
| 282 | 315 | ||
| 283 | # ---- M3: Ctrl-\ n / Ctrl-\ p step around the session ring ---------------- | 316 | # ---- M3: Ctrl-\ n / Ctrl-\ p step around the session ring ---------------- |
| 284 | # | 317 | # |
| @@ -318,7 +351,12 @@ D26PID=$DPID | |||
| 318 | 351 | ||
| 319 | set +e | 352 | set +e |
| 320 | # tall: Ctrl-\ c adds tiles at 80x24 (80 >= 48) | 353 | # tall: Ctrl-\ c adds tiles at 80x24 (80 >= 48) |
| 321 | timeout 60 "$PTYCLIENT" --cols 40 --rows 24 --out "$OUT.ring" --err "$OUT.ring.err" \ | 354 | # No saved tree: this leg asserts the DEFAULT cut, and every wall that |
| 355 | # came before it saved a sidecar on its way out. The aspect rule applies | ||
| 356 | # only when nothing is saved. | ||
| 357 | rm -f "$RINGSTATE/mux/layout" | ||
| 358 | XDG_STATE_HOME="$RINGSTATE" timeout 60 "$PTYCLIENT" --cols 40 --rows 24 \ | ||
| 359 | --out "$OUT.ring" --err "$OUT.ring.err" \ | ||
| 322 | -- "$MUX" --sock "$SOCK29" > "$OUT.ring.log" 2>&1 <<'EOF' | 360 | -- "$MUX" --sock "$SOCK29" > "$OUT.ring.log" 2>&1 <<'EOF' |
| 323 | expect \x1b[?1049h 15000 | 361 | expect \x1b[?1049h 15000 |
| 324 | settle 400 15000 | 362 | settle 400 15000 |
| @@ -385,8 +423,9 @@ ok "Ctrl-\\ n / Ctrl-\\ p: the ring steps both ways and wraps at both ends" | |||
| 385 | # this milestone that read true, because an inner client really had | 423 | # this milestone that read true, because an inner client really had |
| 386 | # taken the screen. A message grep alone would still pass if the client | 424 | # taken the screen. A message grep alone would still pass if the client |
| 387 | # printed the line and attached anyway. | 425 | # printed the line and attached anyway. |
| 388 | # 3. `mux wall` refuses a tile that is the session it is running in, by | 426 | # 3. a wall spelling is a DAEMON, so the session this shell is inside |
| 389 | # name. A wall that silently dropped it would be a wall that lies. | 427 | # cannot be named on the command line at all: `mux hosts add` refuses |
| 428 | # the `#` by name. | ||
| 390 | # | 429 | # |
| 391 | # Every marker is assembled by printf from pieces and never typed whole, so | 430 | # Every marker is assembled by printf from pieces and never typed whole, so |
| 392 | # the grid's echo of the command line can never satisfy the grep. `; echo` | 431 | # the grid's echo of the command line can never satisfy the grep. `; echo` |
| @@ -436,18 +475,21 @@ grep -q '"alt_screen":false' "$OUT.sast" || { | |||
| 436 | echo "e2e FAIL: self-attach: an inner client took the alternate screen — the loop ran:" | 475 | echo "e2e FAIL: self-attach: an inner client took the alternate screen — the loop ran:" |
| 437 | cat "$OUT.sast"; exit 1; } | 476 | cat "$OUT.sast"; exit 1; } |
| 438 | 477 | ||
| 439 | # The wall's spelling is ONE argument, quotes and all (the wall grammar, | 478 | # The wall's own half of the rule, and it is a SILENCE rather than a |
| 440 | # wall.zig) — the same string scenario 33 puts in the state file. | 479 | # refusal: `mux hosts add` names a DAEMON, so the session this shell is |
| 480 | # inside cannot be spelled at all — the wall simply never births a tile for | ||
| 481 | # it (`planHostDiff`'s `self_name`). A `#` spelling is refused by the | ||
| 482 | # grammar before any of that, which is scenario 09's leg. | ||
| 441 | timeout 20 "$MUXA" send \ | 483 | timeout 20 "$MUXA" send \ |
| 442 | "$MUXABS wall '--sock $SOCK31#0'; printf 'WALL%s-%s' rc \$?; echo\n" \ | 484 | "$MUXABS hosts add '--sock $SOCK31#0'; printf 'WALL%s-%s' rc \$?; echo\n" \ |
| 443 | --sock "$SOCK31" --session 0 > "$OUT.sawall" 2>&1 | 485 | --sock "$SOCK31" --session 0 > "$OUT.sawall" 2>&1 |
| 444 | sleep 2 | 486 | sleep 2 |
| 445 | timeout 20 "$MUXA" capture --sock "$SOCK31" --session 0 > "$OUT.sacap3" 2>&1 | 487 | timeout 20 "$MUXA" capture --sock "$SOCK31" --session 0 > "$OUT.sacap3" 2>&1 |
| 446 | grep -q -- "mux: wall target '--sock $SOCK31#0' is the session this shell is inside" "$OUT.sacap3" || { | 488 | grep -q -- "names a session after" "$OUT.sacap3" || { |
| 447 | echo "e2e FAIL: self-attach: mux wall struck its own tile without naming it:" | 489 | echo "e2e FAIL: self-attach: 'mux hosts add' took a session spelling:" |
| 448 | cat "$OUT.sawall"; cat "$OUT.sacap3"; exit 1; } | 490 | cat "$OUT.sawall"; cat "$OUT.sacap3"; exit 1; } |
| 449 | grep -q "WALLrc-2" "$OUT.sacap3" || { | 491 | grep -q "WALLrc-2" "$OUT.sacap3" || { |
| 450 | echo "e2e FAIL: self-attach: the wall refusal did not exit 2 (or did not exit):" | 492 | echo "e2e FAIL: self-attach: the hosts refusal did not exit 2 (or did not exit):" |
| 451 | cat "$OUT.sacap3"; exit 1; } | 493 | cat "$OUT.sacap3"; exit 1; } |
| 452 | assert_stopped "$SOCK31" "$D28PID" "self attach" "$OUT.sastop" | 494 | assert_stopped "$SOCK31" "$D28PID" "self attach" "$OUT.sastop" |
| 453 | D28PID="" | 495 | D28PID="" |
test/e2e_08_mouse.sh
| Old | New | ||
|---|---|---|---|
| @@ -33,11 +33,11 @@ defer_sock "$SOCK36" | |||
| 33 | # on this daemon — would make both numbers say nothing. | 33 | # on this daemon — would make both numbers say nothing. |
| 34 | SOCK37="${TMPDIR:-/tmp}/muxd-e2e-focusskip-$$.sock" | 34 | SOCK37="${TMPDIR:-/tmp}/muxd-e2e-focusskip-$$.sock" |
| 35 | defer_sock "$SOCK37" | 35 | defer_sock "$SOCK37" |
| 36 | # The dead tile. Its own daemon because the leg needs a session name that | 36 | # ...and a state home of its own for the same reason one step further out: |
| 37 | # does NOT exist — the daemon refuses the attach — and any other block's | 37 | # a wall is the HOSTS FILE now, and the shared state home is every daemon |
| 38 | # daemon might have one by that name. | 38 | # this suite has ever attached to, each of them a stripe. |
| 39 | SOCK39="${TMPDIR:-/tmp}/muxd-e2e-deadtile-$$.sock" | 39 | ZSSTATE="${TMPDIR:-/tmp}/mux-e2e-focusskip-state-$$" |
| 40 | defer_sock "$SOCK39" | 40 | defer_rm "$ZSSTATE" |
| 41 | # The wheel inside the focused tile. Two daemons for the wheel pair's own | 41 | # The wheel inside the focused tile. Two daemons for the wheel pair's own |
| 42 | # reason, turned up one notch: each needs a session whose SHELL is fixed for | 42 | # reason, turned up one notch: each needs a session whose SHELL is fixed for |
| 43 | # the session's whole life — one that scrolls and one that ASKS for the | 43 | # the session's whole life — one that scrolls and one that ASKS for the |
| @@ -46,10 +46,14 @@ defer_sock "$SOCK39" | |||
| 46 | # spelling is a bare `--sock PATH` and nothing has to create anything. | 46 | # spelling is a bare `--sock PATH` and nothing has to create anything. |
| 47 | SOCK41="${TMPDIR:-/tmp}/muxd-e2e-wallwheel-$$.sock" | 47 | SOCK41="${TMPDIR:-/tmp}/muxd-e2e-wallwheel-$$.sock" |
| 48 | defer_sock "$SOCK41" | 48 | defer_sock "$SOCK41" |
| 49 | ZWSTATE="${TMPDIR:-/tmp}/mux-e2e-wallwheel-state-$$" | ||
| 50 | defer_rm "$ZWSTATE" | ||
| 49 | ZWHEELSH="${TMPDIR:-/tmp}/mux-e2e-wallwheel-$$.sh" | 51 | ZWHEELSH="${TMPDIR:-/tmp}/mux-e2e-wallwheel-$$.sh" |
| 50 | defer_rm "$ZWHEELSH" | 52 | defer_rm "$ZWHEELSH" |
| 51 | SOCK42="${TMPDIR:-/tmp}/muxd-e2e-wallappmouse-$$.sock" | 53 | SOCK42="${TMPDIR:-/tmp}/muxd-e2e-wallappmouse-$$.sock" |
| 52 | defer_sock "$SOCK42" | 54 | defer_sock "$SOCK42" |
| 55 | ZM2STATE="${TMPDIR:-/tmp}/mux-e2e-wallappmouse-state-$$" | ||
| 56 | defer_rm "$ZM2STATE" | ||
| 53 | ZMOUSESH="${TMPDIR:-/tmp}/mux-e2e-wallappmouse-$$.sh" | 57 | ZMOUSESH="${TMPDIR:-/tmp}/mux-e2e-wallappmouse-$$.sh" |
| 54 | defer_rm "$ZMOUSESH" | 58 | defer_rm "$ZMOUSESH" |
| 55 | 59 | ||
| @@ -314,34 +318,40 @@ ok "a client with no terminal of its own forwards SGR-shaped bytes untouched" | |||
| 314 | # | 318 | # |
| 315 | # One ptyclient leg, two sessions' worth of typing on two tiles: | 319 | # One ptyclient leg, two sessions' worth of typing on two tiles: |
| 316 | # | 320 | # |
| 317 | # type mark a with zs-one (a is focused from the first byte) | 321 | # type mark 0 with zs-one (the entry tile is focused from the |
| 322 | # first byte) | ||
| 318 | # Ctrl-\ n move the focus to tile 2 (session b) | 323 | # Ctrl-\ n move the focus to tile 2 (session b) |
| 319 | # type mark b with zs-two | 324 | # type mark b with zs-two |
| 320 | # Ctrl-\ 1 focus tile 1 (session a) | 325 | # Ctrl-\ 1 focus tile 1 (the default session) |
| 321 | # type mark a again with zs-three | 326 | # type mark 0 again with zs-three |
| 322 | # Ctrl-\ d leave | 327 | # Ctrl-\ d leave |
| 323 | # | 328 | # |
| 324 | # Two claims: | 329 | # Two claims: |
| 325 | # | 330 | # |
| 326 | # * every marker landed in the session the focus was on, and in no | 331 | # * every marker landed in the session the focus was on, and in no |
| 327 | # other. zs-one and zs-three in a, zs-two in b, and each absent from | 332 | # other. zs-one and zs-three in `0`, zs-two in b, and each absent from |
| 328 | # the other session — the negatives are what prove the focus MOVED | 333 | # the other session — the negatives are what prove the focus MOVED |
| 329 | # rather than the wall typing on at one connection. | 334 | # rather than the wall typing on at one connection. |
| 330 | # * the daemon accepted exactly TWO attaches for the whole run — one per | 335 | # * the daemon accepted exactly TWO attaches for the whole run — one per |
| 331 | # tile, at wall startup. A `Ctrl-\ n` that re-dialled would be three or | 336 | # tile: the entry tile's, and the one the host's list grew for `b`. A |
| 332 | # more, and the cumulative counter says so however briefly the extra | 337 | # `Ctrl-\ n` that re-dialled would be three or more, and the cumulative |
| 333 | # connection lived. Focus is client-local and sends nothing on the wire. | 338 | # counter says so however briefly the extra connection lived. Focus is |
| 339 | # client-local and sends nothing on the wire. | ||
| 334 | start_daemon "$SOCK37" "$OUT.zs.d" "focus-skip daemon never bound" --shell /bin/sh | 340 | start_daemon "$SOCK37" "$OUT.zs.d" "focus-skip daemon never bound" --shell /bin/sh |
| 335 | D34PID=$DPID | 341 | D34PID=$DPID |
| 336 | 342 | ||
| 337 | # Both sessions exist before the wall does, each with a marker so the wall | 343 | # Both sessions exist before the wall does, each with a marker so the wall |
| 338 | # has something to paint and this leg has an anchor to enter on. | 344 | # has something to paint and this leg has an anchor to enter on. The first |
| 339 | pipe_mux "$OUT.zsa" "$OUT.zsa.err" timeout 40 "$MUX" --sock "$SOCK37" --session a | 345 | # of them is the daemon's OWN default session rather than a named one: a |
| 346 | # daemon is born holding `0`, so a wall over it always has that tile, and | ||
| 347 | # naming a second `a` would make this a three-tile leg pretending to be a | ||
| 348 | # two-tile one. | ||
| 349 | pipe_mux "$OUT.zsa" "$OUT.zsa.err" env XDG_STATE_HOME="$ZSSTATE" timeout 40 "$MUX" --sock "$SOCK37" | ||
| 340 | pipe_send 'printf "zsa-%%s\\n" pin\n' | 350 | pipe_send 'printf "zsa-%%s\\n" pin\n' |
| 341 | await_out "$OUT.zsa" "zsa-pin" "zsa-pin never reached the client" | 351 | await_out "$OUT.zsa" "zsa-pin" "zsa-pin never reached the client" |
| 342 | pipe_detach | 352 | pipe_detach |
| 343 | wait_grid "$SOCK37" "zsa-pin" "focus skip: session a's marker" a | 353 | wait_grid "$SOCK37" "zsa-pin" "focus skip: the default session's marker" |
| 344 | pipe_mux "$OUT.zsb" "$OUT.zsb.err" timeout 40 "$MUX" --sock "$SOCK37" --session b | 354 | pipe_mux "$OUT.zsb" "$OUT.zsb.err" env XDG_STATE_HOME="$ZSSTATE" timeout 40 "$MUX" --sock "$SOCK37" --session b |
| 345 | pipe_send 'printf "zsb-%%s\\n" pin\n' | 355 | pipe_send 'printf "zsb-%%s\\n" pin\n' |
| 346 | await_out "$OUT.zsb" "zsb-pin" "zsb-pin never reached the client" | 356 | await_out "$OUT.zsb" "zsb-pin" "zsb-pin never reached the client" |
| 347 | pipe_detach | 357 | pipe_detach |
| @@ -351,8 +361,13 @@ ZSATT_BEFORE=$(attaches_now "$SOCK37") | |||
| 351 | watch_clients "$SOCK37" "$OUT.zswatch" | 361 | watch_clients "$SOCK37" "$OUT.zswatch" |
| 352 | set +e | 362 | set +e |
| 353 | # tall: the aspect rule would cut columns at 92x30 (92 >= 60) | 363 | # tall: the aspect rule would cut columns at 92x30 (92 >= 60) |
| 354 | timeout 90 "$PTYCLIENT" --cols 40 --rows 30 --out "$OUT.zscap" --err "$OUT.zscap.err" -- \ | 364 | # No saved tree: this leg asserts the DEFAULT cut, and every wall that |
| 355 | "$MUX" wall "--sock $SOCK37#a" "--sock $SOCK37#b" > "$OUT.zspc" 2>&1 <<'EOF' | 365 | # came before it saved a sidecar on its way out. The aspect rule applies |
| 366 | # only when nothing is saved. | ||
| 367 | rm -f "$ZSSTATE/mux/layout" | ||
| 368 | XDG_STATE_HOME="$ZSSTATE" timeout 90 "$PTYCLIENT" --cols 40 --rows 30 \ | ||
| 369 | --out "$OUT.zscap" --err "$OUT.zscap.err" -- \ | ||
| 370 | "$MUX" --sock "$SOCK37" > "$OUT.zspc" 2>&1 <<'EOF' | ||
| 356 | expect zsb-pin 20000 | 371 | expect zsb-pin 20000 |
| 357 | settle 700 20000 | 372 | settle 700 20000 |
| 358 | send printf 'zs-%s\\n' one\n | 373 | send printf 'zs-%s\\n' one\n |
| @@ -380,7 +395,7 @@ ZSATT_AFTER=$(attaches_now "$SOCK37") | |||
| 380 | cat "$OUT.zspc"; exit 1; } | 395 | cat "$OUT.zspc"; exit 1; } |
| 381 | # Where each marker landed. `muxa capture` reads the session's own grid, so | 396 | # Where each marker landed. `muxa capture` reads the session's own grid, so |
| 382 | # none of this can be an echo of what this script typed at a terminal. | 397 | # none of this can be an echo of what this script typed at a terminal. |
| 383 | timeout 20 "$MUXA" capture --sock "$SOCK37" --session a > "$OUT.zsfa" 2>&1 | 398 | timeout 20 "$MUXA" capture --sock "$SOCK37" > "$OUT.zsfa" 2>&1 |
| 384 | timeout 20 "$MUXA" capture --sock "$SOCK37" --session b > "$OUT.zsfb" 2>&1 | 399 | timeout 20 "$MUXA" capture --sock "$SOCK37" --session b > "$OUT.zsfb" 2>&1 |
| 385 | # | 400 | # |
| 386 | # The markers are numbered rather than named after their sessions, and | 401 | # The markers are numbered rather than named after their sessions, and |
| @@ -390,7 +405,7 @@ timeout 20 "$MUXA" capture --sock "$SOCK37" --session b > "$OUT.zsfb" 2>&1 | |||
| 390 | # prefix, which is the property this needs. | 405 | # prefix, which is the property this needs. |
| 391 | for _m in zs-one zs-three; do | 406 | for _m in zs-one zs-three; do |
| 392 | grep -q "$_m" "$OUT.zsfa" || { | 407 | grep -q "$_m" "$OUT.zsfa" || { |
| 393 | echo "e2e FAIL: focus skip: session a never got $_m:" | 408 | echo "e2e FAIL: focus skip: the default session never got $_m:" |
| 394 | cat "$OUT.zsfa"; exit 1; } | 409 | cat "$OUT.zsfa"; exit 1; } |
| 395 | grep -q "$_m" "$OUT.zsfb" && { | 410 | grep -q "$_m" "$OUT.zsfb" && { |
| 396 | echo "e2e FAIL: focus skip: $_m reached session b, which the focus had left:" | 411 | echo "e2e FAIL: focus skip: $_m reached session b, which the focus had left:" |
| @@ -400,7 +415,7 @@ grep -q "zs-two" "$OUT.zsfb" || { | |||
| 400 | echo "e2e FAIL: focus skip: Ctrl-\\ n did not put the focus on session b:" | 415 | echo "e2e FAIL: focus skip: Ctrl-\\ n did not put the focus on session b:" |
| 401 | cat "$OUT.zsfb"; exit 1; } | 416 | cat "$OUT.zsfb"; exit 1; } |
| 402 | grep -q "zs-two" "$OUT.zsfa" && { | 417 | grep -q "zs-two" "$OUT.zsfa" && { |
| 403 | echo "e2e FAIL: focus skip: zs-two reached session a, so the focus never moved:" | 418 | echo "e2e FAIL: focus skip: zs-two reached the default session, so the focus never moved:" |
| 404 | cat "$OUT.zsfa"; exit 1; } | 419 | cat "$OUT.zsfa"; exit 1; } |
| 405 | # The whole point, said by the daemon: two tiles, two attaches, and never a | 420 | # The whole point, said by the daemon: two tiles, two attaches, and never a |
| 406 | # third across a focus move each way. This is the leg the counter matters | 421 | # third across a focus move each way. This is the leg the counter matters |
| @@ -408,12 +423,12 @@ grep -q "zs-two" "$OUT.zsfa" && { | |||
| 408 | # tell from a re-dial, since a connection that closes as another opens | 423 | # tell from a re-dial, since a connection that closes as another opens |
| 409 | # never shows two at once. | 424 | # never shows two at once. |
| 410 | assert_attach_delta "$ZSATT_BEFORE" "$ZSATT_AFTER" 2 "focus skip" | 425 | assert_attach_delta "$ZSATT_BEFORE" "$ZSATT_AFTER" 2 "focus skip" |
| 411 | assert_never_two_clients "$OUT.zswatch" a "focus skip" | 426 | assert_never_two_clients "$OUT.zswatch" 0 "focus skip" |
| 412 | assert_never_two_clients "$OUT.zswatch" b "focus skip" | 427 | assert_never_two_clients "$OUT.zswatch" b "focus skip" |
| 413 | # Both tiles claimed their rect: 40 cols each (this leg's own width) and 14 | 428 | # Both tiles claimed their rect: 40 cols each (this leg's own width) and 14 |
| 414 | # rows each (30 terminal rows cut into two stripes of 15, minus one label | 429 | # rows each (30 terminal rows cut into two stripes of 15, minus one label |
| 415 | # bar each). A focus move resizes nothing. | 430 | # bar each). A focus move resizes nothing. |
| 416 | for _s in a b; do | 431 | for _s in 0 b; do |
| 417 | timeout 20 "$MUXA" status --sock "$SOCK37" --session "$_s" > "$OUT.zsst$_s" 2>&1 | 432 | timeout 20 "$MUXA" status --sock "$SOCK37" --session "$_s" > "$OUT.zsst$_s" 2>&1 |
| 418 | grep -q '"cols":40' "$OUT.zsst$_s" || { | 433 | grep -q '"cols":40' "$OUT.zsst$_s" || { |
| 419 | echo "e2e FAIL: focus skip: session $_s is not at the width its tile claimed:" | 434 | echo "e2e FAIL: focus skip: session $_s is not at the width its tile claimed:" |
| @@ -462,8 +477,13 @@ until "$MUXD" dump --sock "$SOCK41" | grep -q "wln100"; do | |||
| 462 | sleep 0.1 | 477 | sleep 0.1 |
| 463 | done | 478 | done |
| 464 | set +e | 479 | set +e |
| 465 | timeout 90 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.zwcap" --err "$OUT.zwcap.err" -- \ | 480 | # No saved tree: this leg asserts the DEFAULT cut, and every wall that |
| 466 | "$MUX" wall "--sock $SOCK41" > "$OUT.zwpc" 2>&1 <<'EOF' | 481 | # came before it saved a sidecar on its way out. The aspect rule applies |
| 482 | # only when nothing is saved. | ||
| 483 | rm -f "$ZWSTATE/mux/layout" | ||
| 484 | XDG_STATE_HOME="$ZWSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ | ||
| 485 | --out "$OUT.zwcap" --err "$OUT.zwcap.err" -- \ | ||
| 486 | "$MUX" --sock "$SOCK41" > "$OUT.zwpc" 2>&1 <<'EOF' | ||
| 467 | expect wln100 20000 | 487 | expect wln100 20000 |
| 468 | settle 700 20000 | 488 | settle 700 20000 |
| 469 | send \x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M | 489 | send \x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M |
| @@ -534,8 +554,13 @@ done | |||
| 534 | # not. The tile claims the terminal on its first pass, so the modes are | 554 | # not. The tile claims the terminal on its first pass, so the modes are |
| 535 | # level-set before the wheel is sent. | 555 | # level-set before the wheel is sent. |
| 536 | set +e | 556 | set +e |
| 537 | timeout 90 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.zm2cap" --err "$OUT.zm2cap.err" -- \ | 557 | # No saved tree: this leg asserts the DEFAULT cut, and every wall that |
| 538 | "$MUX" wall "--sock $SOCK42" > "$OUT.zm2pc" 2>&1 <<'EOF' | 558 | # came before it saved a sidecar on its way out. The aspect rule applies |
| 559 | # only when nothing is saved. | ||
| 560 | rm -f "$ZM2STATE/mux/layout" | ||
| 561 | XDG_STATE_HOME="$ZM2STATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ | ||
| 562 | --out "$OUT.zm2cap" --err "$OUT.zm2cap.err" -- \ | ||
| 563 | "$MUX" --sock "$SOCK42" > "$OUT.zm2pc" 2>&1 <<'EOF' | ||
| 539 | expect mapp-holds-the-mouse 20000 | 564 | expect mapp-holds-the-mouse 20000 |
| 540 | settle 700 20000 | 565 | settle 700 20000 |
| 541 | send \x1b[<64;10;5M | 566 | send \x1b[<64;10;5M |
| @@ -569,99 +594,3 @@ assert_stopped "$SOCK42" "$D39PID" "app mouse" "$OUT.zm2stop" | |||
| 569 | D39PID="" | 594 | D39PID="" |
| 570 | ok "an application in the focused tile gets the wheel, and the tile does not" | 595 | ok "an application in the focused tile gets the wheel, and the tile does not" |
| 571 | 596 | ||
| 572 | # ---- a tile whose pump has died still says something ------------------- | ||
| 573 | # | ||
| 574 | # A dead tile keeps its rect and narrates on its label bar — the only | ||
| 575 | # paint a pump with nobody left to answer `repaint_gen` ever does. The | ||
| 576 | # keyboard is the only thread left, and it draws that bar so the wall | ||
| 577 | # never silently loses a row. | ||
| 578 | # | ||
| 579 | # The tile here is dead by the most ordinary route: a spelling naming a | ||
| 580 | # session that does not exist. The daemon refuses the attach and that pump | ||
| 581 | # ends — `[refused]`, the state the wall already had a word for. Its bar | ||
| 582 | # says so from the first paint, and the live tile beside it is unharmed: | ||
| 583 | # the wall is still a wall, and the focus stays on the tile that can be | ||
| 584 | # typed at. | ||
| 585 | # | ||
| 586 | # Deliberately NOT asserted: that a `[reconnecting]` tile gets the same | ||
| 587 | # treatment. It must not — it still has a thread that will repaint its hot | ||
| 588 | # replica within a poll timeout, and the keyboard drawing over that would | ||
| 589 | # replace something true with something stale. | ||
| 590 | start_daemon "$SOCK39" "$OUT.zd.d" "dead-tile daemon never bound" --shell /bin/sh | ||
| 591 | D36PID=$DPID | ||
| 592 | pipe_mux "$OUT.zda" "$OUT.zda.err" timeout 40 "$MUX" --sock "$SOCK39" --session a | ||
| 593 | pipe_send 'printf "zdlive-%%s\\n" pin\n' | ||
| 594 | await_out "$OUT.zda" "zdlive-pin" "zdlive-pin never reached the client" | ||
| 595 | pipe_detach | ||
| 596 | wait_grid "$SOCK39" "zdlive-pin" "dead tile: the live session's marker" a | ||
| 597 | |||
| 598 | set +e | ||
| 599 | # tall: two tiles at 90x24 trip the aspect rule (90 >= 48); 70x36 stays | ||
| 600 | # stacked (70 < 72) and fits the bar label the assertion greps for | ||
| 601 | timeout 90 "$PTYCLIENT" --cols 70 --rows 36 --out "$OUT.zdcap" --err "$OUT.zdcap.err" -- \ | ||
| 602 | "$MUX" wall "--sock $SOCK39#a" "--sock $SOCK39#ghost" > "$OUT.zdpc" 2>&1 <<'EOF' | ||
| 603 | expect zdlive-pin 20000 | ||
| 604 | settle 800 20000 | ||
| 605 | send printf 'zdagain-%s\\n' pin\n | ||
| 606 | expect zdagain-pin 15000 | ||
| 607 | settle 400 15000 | ||
| 608 | send \x1c2 | ||
| 609 | settle 400 15000 | ||
| 610 | send \x1c:abc | ||
| 611 | settle 400 15000 | ||
| 612 | send \x1b | ||
| 613 | settle 400 15000 | ||
| 614 | send \x1c:zz#bad name | ||
| 615 | settle 400 15000 | ||
| 616 | send \r | ||
| 617 | expect [bad target 10000 | ||
| 618 | settle 600 15000 | ||
| 619 | send \x1c1 | ||
| 620 | settle 400 15000 | ||
| 621 | send \x1cd | ||
| 622 | waitexit 10000 | ||
| 623 | EOF | ||
| 624 | RC=$? | ||
| 625 | set -e | ||
| 626 | [ "$RC" -eq 0 ] || { | ||
| 627 | echo "e2e FAIL: dead tile: ptyclient leg exited $RC:" | ||
| 628 | cat "$OUT.zdpc"; exit 1; } | ||
| 629 | # The dead tile narrated its own refusal: the state word the bar can only | ||
| 630 | # have if the pump reached the refusal path. Painted from the first | ||
| 631 | # Painted from the first relayout, and never cleared. | ||
| 632 | grep -q -- "--sock $SOCK39#ghost \[refused\]" "$OUT.zdcap" || { | ||
| 633 | echo "e2e FAIL: dead tile: the dead tile's bar never said [refused]"; exit 1; } | ||
| 634 | # The live tile is unharmed and answering — the wall is still a wall. | ||
| 635 | grep -q -- "--sock $SOCK39#a \[up\]" "$OUT.zdcap" || { | ||
| 636 | echo "e2e FAIL: dead tile: the live tile's bar never said [up]"; exit 1; } | ||
| 637 | # The focus stayed on the live tile: the marker typed after the wall came | ||
| 638 | # up landed in a, which is the tile that can be typed at. A focus that | ||
| 639 | # landed on the dead one would have dropped these bytes. | ||
| 640 | timeout 20 "$MUXA" capture --sock "$SOCK39" --session a > "$OUT.zdcapa" 2>&1 | ||
| 641 | grep -q "zdagain-pin" "$OUT.zdcapa" || { | ||
| 642 | echo "e2e FAIL: dead tile: the live session never got the focused marker:" | ||
| 643 | cat "$OUT.zdcapa"; exit 1; } | ||
| 644 | # `Ctrl-\ :` is the one chord that works with the focus on a dead tile — | ||
| 645 | # the splits go through the pump's `ask`, and a dead pump answers nothing — | ||
| 646 | # so it is the one chord that can leave a line on a row nothing repaints. | ||
| 647 | # The oracle, not the byte stream: a banner LEFT on the screen is in the | ||
| 648 | # capture either way, and only the render says what the human was still | ||
| 649 | # looking at. | ||
| 650 | "$RENDER" --cols 70 --rows 36 < "$OUT.zdcap" > "$OUT.zdgrid" || { | ||
| 651 | echo "e2e FAIL: dead tile: render oracle failed" | ||
| 652 | cat "$OUT.zdgrid"; exit 1; } | ||
| 653 | # The refusal reached the eyes that earned it. Nothing claims a dead tile, | ||
| 654 | # so a notice only the claim path can show would never have been seen. | ||
| 655 | grep -q "\[bad target" "$OUT.zdgrid" || { | ||
| 656 | echo "e2e FAIL: dead tile: a refused prompt never narrated on the dead tile:" | ||
| 657 | cat "$OUT.zdgrid"; exit 1; } | ||
| 658 | grep -q ": zz#bad" "$OUT.zdgrid" && { | ||
| 659 | echo "e2e FAIL: dead tile: the refused prompt is still on the dead tile's row:" | ||
| 660 | cat "$OUT.zdgrid"; exit 1; } | ||
| 661 | # And Esc leaves nothing behind either: same row, same missing claim. | ||
| 662 | grep -q ": abc" "$OUT.zdgrid" && { | ||
| 663 | echo "e2e FAIL: dead tile: the cancelled prompt is still on the dead tile's row:" | ||
| 664 | cat "$OUT.zdgrid"; exit 1; } | ||
| 665 | assert_stopped "$SOCK39" "$D36PID" "dead tile" "$OUT.zdstop" | ||
| 666 | D36PID="" | ||
| 667 | ok "a tile whose pump has died narrates on its bar and on its prompt, and the focus stays on the live one" | ||
test/e2e_11_select.sh
| Old | New | ||
|---|---|---|---|
| @@ -7,21 +7,37 @@ | |||
| 7 | # out, so there is nothing left to reuse. | 7 | # out, so there is nothing left to reuse. |
| 8 | SOCK50="${TMPDIR:-/tmp}/muxd-e2e-wallmouse-$$.sock" | 8 | SOCK50="${TMPDIR:-/tmp}/muxd-e2e-wallmouse-$$.sock" |
| 9 | defer_sock "$SOCK50" | 9 | defer_sock "$SOCK50" |
| 10 | WMSTATE="${TMPDIR:-/tmp}/mux-e2e-wallmouse-state-$$" | ||
| 11 | defer_rm "$WMSTATE" | ||
| 12 | # The selection legs need a wall of ONE tile — a terminal row has to BE a | ||
| 13 | # grid row for a drag to be spelled by coordinate, and `assert_converged` | ||
| 14 | # diffs this client's whole screen against one daemon grid. A wall shows | ||
| 15 | # every session its daemon has, so "one tile" is now a statement about the | ||
| 16 | # DAEMON: this one is left holding nothing but the session it was born | ||
| 17 | # with. | ||
| 18 | SOCKSEL="${TMPDIR:-/tmp}/muxd-e2e-select-$$.sock" | ||
| 19 | defer_sock "$SOCKSEL" | ||
| 20 | SELSTATE="${TMPDIR:-/tmp}/mux-e2e-select-state-$$" | ||
| 21 | defer_rm "$SELSTATE" | ||
| 10 | 22 | ||
| 11 | # ---- the SOCK50 cluster: a daemon, two named sessions ------------------ | 23 | # ---- the SOCK50 cluster: a daemon, two sessions ------------------------ |
| 12 | # | 24 | # |
| 13 | # A daemon and two named sessions (a, b) shared by the click leg below and | 25 | # A daemon and two sessions — its own default one and a named `b` — |
| 14 | # the drag legs after it. The wall-mouse filter this cluster used to open | 26 | # shared by the click legs below. A daemon is born holding `0`, so a wall |
| 15 | # with is gone — every click and drag routes to the focused tile's own | 27 | # over it always has that tile; naming a second would make these |
| 16 | # Core, hit-tested by rect — so what remains here is the setup: a daemon, | 28 | # two-tile legs three-tile ones. The wall-mouse filter this cluster used |
| 17 | # a marker per session, and the tiles a `mux wall` over them paints. | 29 | # to open with is gone — every click and drag routes to the focused tile's |
| 30 | # own Core, hit-tested by rect — so what remains here is the setup: a | ||
| 31 | # daemon, a marker per session, and the tiles `mux --sock` over it paints. | ||
| 18 | start_daemon "$SOCK50" "$OUT.wmse.d" "wall-cluster daemon never bound" --shell /bin/sh | 32 | start_daemon "$SOCK50" "$OUT.wmse.d" "wall-cluster daemon never bound" --shell /bin/sh |
| 19 | D48PID=$DPID | 33 | D48PID=$DPID |
| 34 | start_daemon "$SOCKSEL" "$OUT.sel.d" "selection daemon never bound" --shell /bin/sh | ||
| 35 | D49PID=$DPID | ||
| 20 | { printf 'printf "wma-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ | 36 | { printf 'printf "wma-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ |
| 21 | timeout 40 "$MUX" --sock "$SOCK50" --session a > "$OUT.wmsa" 2> "$OUT.wmsa.err" | 37 | env XDG_STATE_HOME="$WMSTATE" timeout 40 "$MUX" --sock "$SOCK50" > "$OUT.wmsa" 2> "$OUT.wmsa.err" |
| 22 | wait_grid "$SOCK50" "wma-pin" "wall cluster: session a's marker" a | 38 | wait_grid "$SOCK50" "wma-pin" "wall cluster: the default session's marker" |
| 23 | { printf 'printf "wmb-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ | 39 | { printf 'printf "wmb-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ |
| 24 | timeout 40 "$MUX" --sock "$SOCK50" --session b > "$OUT.wmsb" 2> "$OUT.wmsb.err" | 40 | env XDG_STATE_HOME="$WMSTATE" timeout 40 "$MUX" --sock "$SOCK50" --session b > "$OUT.wmsb" 2> "$OUT.wmsb.err" |
| 25 | wait_grid "$SOCK50" "wmb-pin" "wall cluster: session b's marker" b | 41 | wait_grid "$SOCK50" "wmb-pin" "wall cluster: session b's marker" b |
| 26 | 42 | ||
| 27 | # ...and a click in a tile's rect FOCUSES it. Two tiles over 30 rows is two | 43 | # ...and a click in a tile's rect FOCUSES it. Two tiles over 30 rows is two |
| @@ -40,8 +56,12 @@ wait_grid "$SOCK50" "wmb-pin" "wall cluster: session b's marker" b | |||
| 40 | # other way. A negative that cannot fail is not a test, so each carries the | 56 | # other way. A negative that cannot fail is not a test, so each carries the |
| 41 | # marker that MUST land in the focused session. | 57 | # marker that MUST land in the focused session. |
| 42 | set +e | 58 | set +e |
| 43 | timeout 60 "$PTYCLIENT" --cols 40 --rows 30 --out "$OUT.wmcap3" --err "$OUT.wmcap3.err" -- \ | 59 | # No saved tree: this leg asserts the DEFAULT cut, and every wall before |
| 44 | "$MUX" wall "--sock $SOCK50#a" "--sock $SOCK50#b" > "$OUT.wmpc3" 2>&1 <<'EOF' | 60 | # it saved a sidecar on its way out. |
| 61 | rm -f "$WMSTATE/mux/layout" | ||
| 62 | XDG_STATE_HOME="$WMSTATE" timeout 60 "$PTYCLIENT" --cols 40 --rows 30 \ | ||
| 63 | --out "$OUT.wmcap3" --err "$OUT.wmcap3.err" -- \ | ||
| 64 | "$MUX" --sock "$SOCK50" > "$OUT.wmpc3" 2>&1 <<'EOF' | ||
| 45 | expect wmb-pin 20000 | 65 | expect wmb-pin 20000 |
| 46 | settle 700 20000 | 66 | settle 700 20000 |
| 47 | send \x1b[<0;3;19M\x1b[<0;3;19m | 67 | send \x1b[<0;3;19M\x1b[<0;3;19m |
| @@ -57,7 +77,7 @@ set -e | |||
| 57 | [ "$RC" -eq 0 ] || { | 77 | [ "$RC" -eq 0 ] || { |
| 58 | echo "e2e FAIL: wall click: ptyclient leg exited $RC (did the click focus b?):" | 78 | echo "e2e FAIL: wall click: ptyclient leg exited $RC (did the click focus b?):" |
| 59 | cat "$OUT.wmpc3"; exit 1; } | 79 | cat "$OUT.wmpc3"; exit 1; } |
| 60 | timeout 20 "$MUXA" capture --sock "$SOCK50" --session a > "$OUT.wmfa" 2>&1 | 80 | timeout 20 "$MUXA" capture --sock "$SOCK50" > "$OUT.wmfa" 2>&1 |
| 61 | timeout 20 "$MUXA" capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1 | 81 | timeout 20 "$MUXA" capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1 |
| 62 | grep -q "wm-three" "$OUT.wmfb" || { | 82 | grep -q "wm-three" "$OUT.wmfb" || { |
| 63 | echo "e2e FAIL: wall click: a click in session b's stripe did not focus it:" | 83 | echo "e2e FAIL: wall click: a click in session b's stripe did not focus it:" |
| @@ -69,8 +89,12 @@ grep -q "wm-three" "$OUT.wmfa" && { | |||
| 69 | # The other direction: focus b explicitly, then click a's content and watch | 89 | # The other direction: focus b explicitly, then click a's content and watch |
| 70 | # the focus come back to a. | 90 | # the focus come back to a. |
| 71 | set +e | 91 | set +e |
| 72 | timeout 60 "$PTYCLIENT" --cols 40 --rows 30 --out "$OUT.wmcap4" --err "$OUT.wmcap4.err" -- \ | 92 | # No saved tree: this leg asserts the DEFAULT cut, and every wall before |
| 73 | "$MUX" wall "--sock $SOCK50#a" "--sock $SOCK50#b" > "$OUT.wmpc4" 2>&1 <<'EOF' | 93 | # it saved a sidecar on its way out. |
| 94 | rm -f "$WMSTATE/mux/layout" | ||
| 95 | XDG_STATE_HOME="$WMSTATE" timeout 60 "$PTYCLIENT" --cols 40 --rows 30 \ | ||
| 96 | --out "$OUT.wmcap4" --err "$OUT.wmcap4.err" -- \ | ||
| 97 | "$MUX" --sock "$SOCK50" > "$OUT.wmpc4" 2>&1 <<'EOF' | ||
| 74 | expect wmb-pin 20000 | 98 | expect wmb-pin 20000 |
| 75 | settle 700 20000 | 99 | settle 700 20000 |
| 76 | send \x1c2 | 100 | send \x1c2 |
| @@ -88,7 +112,7 @@ set -e | |||
| 88 | [ "$RC" -eq 0 ] || { | 112 | [ "$RC" -eq 0 ] || { |
| 89 | echo "e2e FAIL: wall click: label-bar leg exited $RC:" | 113 | echo "e2e FAIL: wall click: label-bar leg exited $RC:" |
| 90 | cat "$OUT.wmpc4"; exit 1; } | 114 | cat "$OUT.wmpc4"; exit 1; } |
| 91 | timeout 20 "$MUXA" capture --sock "$SOCK50" --session a > "$OUT.wmfa" 2>&1 | 115 | timeout 20 "$MUXA" capture --sock "$SOCK50" > "$OUT.wmfa" 2>&1 |
| 92 | timeout 20 "$MUXA" capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1 | 116 | timeout 20 "$MUXA" capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1 |
| 93 | grep -q "wm-four" "$OUT.wmfa" || { | 117 | grep -q "wm-four" "$OUT.wmfa" || { |
| 94 | echo "e2e FAIL: wall click: a click in session a's stripe did not focus it:" | 118 | echo "e2e FAIL: wall click: a click in session a's stripe did not focus it:" |
| @@ -104,8 +128,12 @@ ok "a click in a tile's rect focuses it, from wherever the focus was" | |||
| 104 | # test in rectHit earns its keep: focus starts on a (the left pane) and | 128 | # test in rectHit earns its keep: focus starts on a (the left pane) and |
| 105 | # the click must move it to b (the right pane). | 129 | # the click must move it to b (the right pane). |
| 106 | set +e | 130 | set +e |
| 107 | timeout 60 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.wmcap5" --err "$OUT.wmcap5.err" -- \ | 131 | # No saved tree: this leg asserts the DEFAULT cut, and every wall before |
| 108 | "$MUX" wall "--sock $SOCK50#a" "--sock $SOCK50#b" > "$OUT.wmpc5" 2>&1 <<'EOF' | 132 | # it saved a sidecar on its way out. |
| 133 | rm -f "$WMSTATE/mux/layout" | ||
| 134 | XDG_STATE_HOME="$WMSTATE" timeout 60 "$PTYCLIENT" --cols 100 --rows 30 \ | ||
| 135 | --out "$OUT.wmcap5" --err "$OUT.wmcap5.err" -- \ | ||
| 136 | "$MUX" --sock "$SOCK50" > "$OUT.wmpc5" 2>&1 <<'EOF' | ||
| 109 | expect wmb-pin 20000 | 137 | expect wmb-pin 20000 |
| 110 | settle 700 20000 | 138 | settle 700 20000 |
| 111 | send \x1b[<0;70;9M\x1b[<0;70;9m | 139 | send \x1b[<0;70;9M\x1b[<0;70;9m |
| @@ -121,7 +149,7 @@ set -e | |||
| 121 | [ "$RC" -eq 0 ] || { | 149 | [ "$RC" -eq 0 ] || { |
| 122 | echo "e2e FAIL: wall click: beside leg exited $RC (did the column click focus b?):" | 150 | echo "e2e FAIL: wall click: beside leg exited $RC (did the column click focus b?):" |
| 123 | cat "$OUT.wmpc5"; exit 1; } | 151 | cat "$OUT.wmpc5"; exit 1; } |
| 124 | timeout 20 "$MUXA" capture --sock "$SOCK50" --session a > "$OUT.wmfa" 2>&1 | 152 | timeout 20 "$MUXA" capture --sock "$SOCK50" > "$OUT.wmfa" 2>&1 |
| 125 | timeout 20 "$MUXA" capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1 | 153 | timeout 20 "$MUXA" capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1 |
| 126 | grep -q "wm-five" "$OUT.wmfb" || { | 154 | grep -q "wm-five" "$OUT.wmfb" || { |
| 127 | echo "e2e FAIL: wall click: a click in the right pane did not focus it:" | 155 | echo "e2e FAIL: wall click: a click in the right pane did not focus it:" |
| @@ -139,10 +167,13 @@ ok "a click in a beside pane finds it by column, not just row" | |||
| 139 | # it lands on the CLIENT's tty as OSC 52, which the fixture holding that | 167 | # it lands on the CLIENT's tty as OSC 52, which the fixture holding that |
| 140 | # tty can read, so the assertion is the copied text itself. | 168 | # tty can read, so the assertion is the copied text itself. |
| 141 | # | 169 | # |
| 142 | # A one-tile wall, because that is where the geometry is knowable: `mux | 170 | # A one-tile wall, because that is where the geometry is knowable: a wall |
| 143 | # TARGET` is a wall of one tile whose rect is the whole terminal (no label | 171 | # over a daemon holding ONE session is one tile whose rect is the whole |
| 144 | # bar), so a terminal row IS a grid row and the text can be put on one by | 172 | # terminal (no label bar), so a terminal row IS a grid row and the text can |
| 145 | # name. The session is given its content with an explicit cursor address | 173 | # be put on one by name. Its own daemon for exactly that: the cluster's |
| 174 | # has two sessions and would cut two stripes. | ||
| 175 | # | ||
| 176 | # The session is given its content with an explicit cursor address | ||
| 146 | # rather than by printing lines, so the row this drag crosses does not | 177 | # rather than by printing lines, so the row this drag crosses does not |
| 147 | # depend on where a prompt happened to leave the cursor. | 178 | # depend on where a prompt happened to leave the cursor. |
| 148 | # | 179 | # |
| @@ -152,8 +183,9 @@ ok "a click in a beside pane finds it by column, not just row" | |||
| 152 | # Columns 1 to 9 and no further: the prompt lands at column 10 on the same | 183 | # Columns 1 to 9 and no further: the prompt lands at column 10 on the same |
| 153 | # row, and a selection that reached it would copy the prompt too. | 184 | # row, and a selection that reached it would copy the prompt too. |
| 154 | set +e | 185 | set +e |
| 155 | timeout 60 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.selcap" --err "$OUT.selcap.err" -- \ | 186 | XDG_STATE_HOME="$SELSTATE" timeout 60 "$PTYCLIENT" --cols 100 --rows 30 \ |
| 156 | "$MUX" --sock "$SOCK50" --session s > "$OUT.selpc" 2>&1 <<'EOF' | 187 | --out "$OUT.selcap" --err "$OUT.selcap.err" -- \ |
| 188 | "$MUX" --sock "$SOCKSEL" > "$OUT.selpc" 2>&1 <<'EOF' | ||
| 157 | settle 900 20000 | 189 | settle 900 20000 |
| 158 | send printf '\\033[2J\\033[9;1HZZ-%s' COPYME\n | 190 | send printf '\\033[2J\\033[9;1HZZ-%s' COPYME\n |
| 159 | expect ZZ-COPYME 15000 | 191 | expect ZZ-COPYME 15000 |
| @@ -192,10 +224,9 @@ SELCOPIES=$(grep -ao "$(printf '\033]52;')" "$OUT.selcap" | wc -l) | |||
| 192 | # `w漢字x-WIDEMARK`, so column 3 is the spacer tail of 漢 and column 4 the | 224 | # `w漢字x-WIDEMARK`, so column 3 is the spacer tail of 漢 and column 4 the |
| 193 | # first half of 字 — the two ways a span can cut a wide cell in half. | 225 | # first half of 字 — the two ways a span can cut a wide cell in half. |
| 194 | # | 226 | # |
| 195 | # The DEFAULT session, unlike the copy leg above: `converged_quiet` dumps | 227 | # The same one-session daemon the copy leg used: `converged_quiet` dumps |
| 196 | # the daemon's default grid, so a leg that named a session would diff this | 228 | # the daemon's default grid, so this client's screen has to be that grid |
| 197 | # client's screen against a grid it never attached to and fail for a reason | 229 | # and nothing else — one tile, no bar, no neighbour. |
| 198 | # that has nothing to do with wide cells. | ||
| 199 | # | 230 | # |
| 200 | # Asserted to fail, per the wan.sh rule: with `snapWide` stubbed out to | 231 | # Asserted to fail, per the wan.sh rule: with `snapWide` stubbed out to |
| 201 | # return its arguments, this leg renders `w 漢字x-WIDEMARK` against the | 232 | # return its arguments, this leg renders `w 漢字x-WIDEMARK` against the |
| @@ -209,8 +240,9 @@ SELCOPIES=$(grep -ao "$(printf '\033]52;')" "$OUT.selcap" | wc -l) | |||
| 209 | # highlight leaves nothing behind when it goes — the overlay-never-becomes- | 240 | # highlight leaves nothing behind when it goes — the overlay-never-becomes- |
| 210 | # state rule, on the only overlay that paints through the replica. | 241 | # state rule, on the only overlay that paints through the replica. |
| 211 | set +e | 242 | set +e |
| 212 | timeout 60 "$PTYCLIENT" --cols 40 --rows 12 --out "$OUT.swcap" --err "$OUT.swcap.err" -- \ | 243 | XDG_STATE_HOME="$SELSTATE" timeout 60 "$PTYCLIENT" --cols 40 --rows 12 \ |
| 213 | "$MUX" --sock "$SOCK50" > "$OUT.swpc" 2>&1 <<'EOF' | 244 | --out "$OUT.swcap" --err "$OUT.swcap.err" -- \ |
| 245 | "$MUX" --sock "$SOCKSEL" > "$OUT.swpc" 2>&1 <<'EOF' | ||
| 214 | settle 900 20000 | 246 | settle 900 20000 |
| 215 | send printf '\\033[2J\\033[3;1Hw\\346\\274\\242\\345\\255\\227x-WIDEMARK'\n | 247 | send printf '\\033[2J\\033[3;1Hw\\346\\274\\242\\345\\255\\227x-WIDEMARK'\n |
| 216 | expect WIDEMARK 15000 | 248 | expect WIDEMARK 15000 |
| @@ -229,9 +261,11 @@ set -e | |||
| 229 | # The client's own stderr as well as the fixture's: the fixture can only | 261 | # The client's own stderr as well as the fixture's: the fixture can only |
| 230 | # report THAT the client closed the pty, and the reason is over here. | 262 | # report THAT the client closed the pty, and the reason is over here. |
| 231 | cat "$OUT.swpc" "$OUT.swcap.err"; exit 1; } | 263 | cat "$OUT.swpc" "$OUT.swcap.err"; exit 1; } |
| 232 | assert_converged "$OUT.swcap" "$SOCK50" "wide drag" 40 12 | 264 | assert_converged "$OUT.swcap" "$SOCKSEL" "wide drag" 40 12 |
| 233 | ok "a drag across wide cells leaves the client's screen converged" | 265 | ok "a drag across wide cells leaves the client's screen converged" |
| 234 | 266 | ||
| 267 | assert_stopped "$SOCKSEL" "$D49PID" "selection" "$OUT.selstop" | ||
| 268 | D49PID="" | ||
| 235 | assert_stopped "$SOCK50" "$D48PID" "wall mouse" "$OUT.wmstop" | 269 | assert_stopped "$SOCK50" "$D48PID" "wall mouse" "$OUT.wmstop" |
| 236 | D48PID="" | 270 | D48PID="" |
| 237 | ok "a drag copies on release, and a click copies nothing" | 271 | ok "a drag copies on release, and a click copies nothing" |
| @@ -254,10 +288,13 @@ defer_sock "$SOCK51" | |||
| 254 | # so the report — INPUT to the app — lands on the grid as text. Two reports | 288 | # so the report — INPUT to the app — lands on the grid as text. Two reports |
| 255 | # are asserted, because they are two code paths: the one the mode-set | 289 | # are asserted, because they are two code paths: the one the mode-set |
| 256 | # itself owes (the app sizes itself from it), and the one each resize owes. | 290 | # itself owes (the app sizes itself from it), and the one each resize owes. |
| 291 | IBSTATE="${TMPDIR:-/tmp}/mux-e2e-inband-state-$$" | ||
| 292 | defer_rm "$IBSTATE" | ||
| 257 | start_daemon "$SOCK51" "$OUT.inband.d" "in-band daemon never bound" --shell /bin/sh | 293 | start_daemon "$SOCK51" "$OUT.inband.d" "in-band daemon never bound" --shell /bin/sh |
| 258 | D51PID=$DPID | 294 | D51PID=$DPID |
| 259 | set +e | 295 | set +e |
| 260 | timeout 40 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.inband" --err "$OUT.inband.err" -- \ | 296 | XDG_STATE_HOME="$IBSTATE" timeout 40 "$PTYCLIENT" --cols 100 --rows 30 \ |
| 297 | --out "$OUT.inband" --err "$OUT.inband.err" -- \ | ||
| 261 | "$MUX" --sock "$SOCK51" > "$OUT.inband.log" 2>&1 <<'EOF' | 298 | "$MUX" --sock "$SOCK51" > "$OUT.inband.log" 2>&1 <<'EOF' |
| 262 | expect \x1b[?1049h 15000 | 299 | expect \x1b[?1049h 15000 |
| 263 | settle 400 15000 | 300 | settle 400 15000 |
| @@ -326,6 +363,8 @@ ok "no XDG_RUNTIME_DIR: the default path is refused by name, --version is not" | |||
| 326 | # channel for the client that would not), and the SECOND is refused in | 363 | # channel for the client that would not), and the SECOND is refused in |
| 327 | # milliseconds (the offer was taken away, so nobody routes to the mute | 364 | # milliseconds (the offer was taken away, so nobody routes to the mute |
| 328 | # client again and ssh pays nothing). | 365 | # client again and ssh pays nothing). |
| 366 | AGSTATE="${TMPDIR:-/tmp}/mux-e2e-agtmute-state-$$" | ||
| 367 | defer_rm "$AGSTATE" | ||
| 329 | start_daemon "$SOCK48" "$OUT.agtmute.d" "agent-mute daemon never bound" --shell /bin/sh | 368 | start_daemon "$SOCK48" "$OUT.agtmute.d" "agent-mute daemon never bound" --shell /bin/sh |
| 330 | D42PID=$DPID | 369 | D42PID=$DPID |
| 331 | ssh-agent -a "$AGENT48" > "$OUT.agtmute.env" 2>&1 | 370 | ssh-agent -a "$AGENT48" > "$OUT.agtmute.env" 2>&1 |
| @@ -336,7 +375,7 @@ defer_kill "$AGENT48PID" | |||
| 336 | cat "$OUT.agtmute.env"; exit 1; } | 375 | cat "$OUT.agtmute.env"; exit 1; } |
| 337 | kill -STOP "$AGENT48PID" | 376 | kill -STOP "$AGENT48PID" |
| 338 | set +e | 377 | set +e |
| 339 | SSH_AUTH_SOCK="$AGENT48" timeout 60 "$PTYCLIENT" --cols 100 --rows 30 \ | 378 | SSH_AUTH_SOCK="$AGENT48" XDG_STATE_HOME="$AGSTATE" timeout 60 "$PTYCLIENT" --cols 100 --rows 30 \ |
| 340 | --out "$OUT.agtmute" --err "$OUT.agtmute.err" \ | 379 | --out "$OUT.agtmute" --err "$OUT.agtmute.err" \ |
| 341 | -- "$MUX" -A --sock "$SOCK48" > "$OUT.agtmute.log" 2>&1 <<'EOF' | 380 | -- "$MUX" -A --sock "$SOCK48" > "$OUT.agtmute.log" 2>&1 <<'EOF' |
| 342 | expect \x1b[?1049h 15000 | 381 | expect \x1b[?1049h 15000 |
test/e2e_12_panes.sh
| Old | New | ||
|---|---|---|---|
| @@ -7,16 +7,28 @@ | |||
| 7 | # reason every wall leg has. | 7 | # reason every wall leg has. |
| 8 | SOCK53="${TMPDIR:-/tmp}/muxd-e2e-sbrect-$$.sock" | 8 | SOCK53="${TMPDIR:-/tmp}/muxd-e2e-sbrect-$$.sock" |
| 9 | defer_sock "$SOCK53" | 9 | defer_sock "$SOCK53" |
| 10 | SB2STATE="${TMPDIR:-/tmp}/mux-e2e-sbrect-state-$$" | ||
| 11 | defer_rm "$SB2STATE" | ||
| 10 | SOCK54="${TMPDIR:-/tmp}/muxd-e2e-fullscreen-$$.sock" | 12 | SOCK54="${TMPDIR:-/tmp}/muxd-e2e-fullscreen-$$.sock" |
| 11 | defer_sock "$SOCK54" | 13 | defer_sock "$SOCK54" |
| 14 | FSSTATE="${TMPDIR:-/tmp}/mux-e2e-fullscreen-state-$$" | ||
| 15 | defer_rm "$FSSTATE" | ||
| 12 | SOCK55="${TMPDIR:-/tmp}/muxd-e2e-resize-$$.sock" | 16 | SOCK55="${TMPDIR:-/tmp}/muxd-e2e-resize-$$.sock" |
| 13 | defer_sock "$SOCK55" | 17 | defer_sock "$SOCK55" |
| 18 | RSZSTATE="${TMPDIR:-/tmp}/mux-e2e-resize-state-$$" | ||
| 19 | defer_rm "$RSZSTATE" | ||
| 14 | SOCK56="${TMPDIR:-/tmp}/muxd-e2e-spanclear-$$.sock" | 20 | SOCK56="${TMPDIR:-/tmp}/muxd-e2e-spanclear-$$.sock" |
| 15 | defer_sock "$SOCK56" | 21 | defer_sock "$SOCK56" |
| 22 | SPCSTATE="${TMPDIR:-/tmp}/mux-e2e-spanclear-state-$$" | ||
| 23 | defer_rm "$SPCSTATE" | ||
| 16 | SOCK57="${TMPDIR:-/tmp}/muxd-e2e-hjkl-$$.sock" | 24 | SOCK57="${TMPDIR:-/tmp}/muxd-e2e-hjkl-$$.sock" |
| 17 | defer_sock "$SOCK57" | 25 | defer_sock "$SOCK57" |
| 26 | HJKSTATE="${TMPDIR:-/tmp}/mux-e2e-hjkl-state-$$" | ||
| 27 | defer_rm "$HJKSTATE" | ||
| 18 | SOCK58="${TMPDIR:-/tmp}/muxd-e2e-splitbirth-$$.sock" | 28 | SOCK58="${TMPDIR:-/tmp}/muxd-e2e-splitbirth-$$.sock" |
| 19 | defer_sock "$SOCK58" | 29 | defer_sock "$SOCK58" |
| 30 | SPLSTATE="${TMPDIR:-/tmp}/mux-e2e-splitbirth-state-$$" | ||
| 31 | defer_rm "$SPLSTATE" | ||
| 20 | SOCK59="${TMPDIR:-/tmp}/muxd-e2e-lprrestore-$$.sock" | 32 | SOCK59="${TMPDIR:-/tmp}/muxd-e2e-lprrestore-$$.sock" |
| 21 | defer_sock "$SOCK59" | 33 | defer_sock "$SOCK59" |
| 22 | SOCK60="${TMPDIR:-/tmp}/muxd-e2e-lpheal-$$.sock" | 34 | SOCK60="${TMPDIR:-/tmp}/muxd-e2e-lpheal-$$.sock" |
| @@ -30,6 +42,8 @@ defer_sock "$SOCK61" | |||
| 30 | # session another block created would change the tile it rests in. | 42 | # session another block created would change the tile it rests in. |
| 31 | SOCK52="${TMPDIR:-/tmp}/muxd-e2e-cursor-$$.sock" | 43 | SOCK52="${TMPDIR:-/tmp}/muxd-e2e-cursor-$$.sock" |
| 32 | defer_sock "$SOCK52" | 44 | defer_sock "$SOCK52" |
| 45 | CUSTATE="${TMPDIR:-/tmp}/mux-e2e-cursor-state-$$" | ||
| 46 | defer_rm "$CUSTATE" | ||
| 33 | # | 47 | # |
| 34 | # Every painter ends by showing the cursor where its own stripe sits, so | 48 | # Every painter ends by showing the cursor where its own stripe sits, so |
| 35 | # without an owner the visible cursor lands on whichever pump painted last: | 49 | # without an owner the visible cursor lands on whichever pump painted last: |
| @@ -40,33 +54,38 @@ defer_sock "$SOCK52" | |||
| 40 | # asserts the terminal's final cursor position is in tile 3's stripe — | 54 | # asserts the terminal's final cursor position is in tile 3's stripe — |
| 41 | # deterministic, because every paint now ends at the focused tile's cursor. | 55 | # deterministic, because every paint now ends at the focused tile's cursor. |
| 42 | # | 56 | # |
| 43 | # Three sessions on a daemon of its own, each marked by its shell before the | 57 | # Three sessions on a daemon of its own — the daemon's own `0` and two |
| 44 | # wall attaches (the shell-expanded marker trick: a hit is the shell's work, | 58 | # named ones, because a daemon is born holding `0` and a wall over it |
| 59 | # always has that tile — each marked by its shell before the wall attaches (the shell-expanded marker trick: a hit is the shell's work, | ||
| 45 | # never an echo of anything typed here). The ptyclient wall focuses tile 3, | 60 | # never an echo of anything typed here). The ptyclient wall focuses tile 3, |
| 46 | # settles, and detaches; the capture's last cursor-position escape is the | 61 | # settles, and detaches; the capture's last cursor-position escape is the |
| 47 | # witness. | 62 | # witness. |
| 48 | start_daemon "$SOCK52" "$OUT.cu.d" "cursor-ownership daemon never bound" --shell /bin/sh | 63 | start_daemon "$SOCK52" "$OUT.cu.d" "cursor-ownership daemon never bound" --shell /bin/sh |
| 49 | D52PID=$DPID | 64 | D52PID=$DPID |
| 50 | 65 | ||
| 51 | pipe_mux "$OUT.cua" "$OUT.cua.err" timeout 40 "$MUX" --sock "$SOCK52" --session a | 66 | pipe_mux "$OUT.cua" "$OUT.cua.err" env XDG_STATE_HOME="$CUSTATE" timeout 40 "$MUX" --sock "$SOCK52" |
| 52 | pipe_send 'printf "ma-%%s\\n" pin\n' | 67 | pipe_send 'printf "ma-%%s\\n" pin\n' |
| 53 | await_out "$OUT.cua" "ma-pin" "cursor: session a's marker never reached the client" | 68 | await_out "$OUT.cua" "ma-pin" "cursor: session a's marker never reached the client" |
| 54 | pipe_detach | 69 | pipe_detach |
| 55 | wait_grid "$SOCK52" "ma-pin" "cursor: session a's marker" a | 70 | wait_grid "$SOCK52" "ma-pin" "cursor: session a's marker" |
| 56 | pipe_mux "$OUT.cub" "$OUT.cub.err" timeout 40 "$MUX" --sock "$SOCK52" --session b | 71 | pipe_mux "$OUT.cub" "$OUT.cub.err" env XDG_STATE_HOME="$CUSTATE" timeout 40 "$MUX" --sock "$SOCK52" --session b |
| 57 | pipe_send 'printf "mb-%%s\\n" pin\n' | 72 | pipe_send 'printf "mb-%%s\\n" pin\n' |
| 58 | await_out "$OUT.cub" "mb-pin" "cursor: session b's marker never reached the client" | 73 | await_out "$OUT.cub" "mb-pin" "cursor: session b's marker never reached the client" |
| 59 | pipe_detach | 74 | pipe_detach |
| 60 | wait_grid "$SOCK52" "mb-pin" "cursor: session b's marker" b | 75 | wait_grid "$SOCK52" "mb-pin" "cursor: session b's marker" b |
| 61 | pipe_mux "$OUT.cuc" "$OUT.cuc.err" timeout 40 "$MUX" --sock "$SOCK52" --session c | 76 | pipe_mux "$OUT.cuc" "$OUT.cuc.err" env XDG_STATE_HOME="$CUSTATE" timeout 40 "$MUX" --sock "$SOCK52" --session c |
| 62 | pipe_send 'printf "mc-%%s\\n" pin\n' | 77 | pipe_send 'printf "mc-%%s\\n" pin\n' |
| 63 | await_out "$OUT.cuc" "mc-pin" "cursor: session c's marker never reached the client" | 78 | await_out "$OUT.cuc" "mc-pin" "cursor: session c's marker never reached the client" |
| 64 | pipe_detach | 79 | pipe_detach |
| 65 | wait_grid "$SOCK52" "mc-pin" "cursor: session c's marker" c | 80 | wait_grid "$SOCK52" "mc-pin" "cursor: session c's marker" c |
| 66 | 81 | ||
| 67 | set +e | 82 | set +e |
| 68 | timeout 40 "$PTYCLIENT" --cols 40 --rows 30 --out "$OUT.cucap" --err "$OUT.cucap.err" -- \ | 83 | # No saved tree: this leg asserts the DEFAULT cut, and the setup attaches |
| 69 | "$MUX" wall --sock "$SOCK52#a" "--sock $SOCK52#b" "--sock $SOCK52#c" > "$OUT.cupc" 2>&1 <<'EOF' | 84 | # above are walls too — each of them saved a one-leaf sidecar on its way |
| 85 | # out. The aspect rule applies only when nothing is saved. | ||
| 86 | rm -f "$CUSTATE/mux/layout" | ||
| 87 | XDG_STATE_HOME="$CUSTATE" timeout 40 "$PTYCLIENT" --cols 40 --rows 30 --out "$OUT.cucap" --err "$OUT.cucap.err" -- \ | ||
| 88 | "$MUX" --sock "$SOCK52" > "$OUT.cupc" 2>&1 <<'EOF' | ||
| 70 | expect mc-pin 20000 | 89 | expect mc-pin 20000 |
| 71 | settle 500 15000 | 90 | settle 500 15000 |
| 72 | send \x1c3 | 91 | send \x1c3 |
| @@ -79,7 +98,19 @@ set -e | |||
| 79 | [ "$RC" -eq 0 ] || { | 98 | [ "$RC" -eq 0 ] || { |
| 80 | echo "e2e FAIL: cursor-ownership: ptyclient leg exited $RC:" | 99 | echo "e2e FAIL: cursor-ownership: ptyclient leg exited $RC:" |
| 81 | cat "$OUT.cupc" "$OUT.cucap.err"; exit 1; } | 100 | cat "$OUT.cupc" "$OUT.cucap.err"; exit 1; } |
| 82 | # Rows 21-30 are tile 3's stripe (3 tiles on 30 rows: stripes at 1/11/21). | 101 | # WHICH rows tile 3's stripe owns is read off the capture rather than |
| 102 | # counted: a tile's digit is fixed for its life, but tiles arrive from a | ||
| 103 | # host's list and are inserted beside the focus, so the third DIGIT is not | ||
| 104 | # necessarily the third stripe DOWN. Its own label bar's CUP row is the top | ||
| 105 | # of its stripe, and the nine rows under that are its content (30 rows over | ||
| 106 | # three tiles, one of each ten spent on the bar). | ||
| 107 | _bar3=$(grep -ao $'\x1b\\[[0-9][0-9]*;1H\x1b\\[7m 3> ' "$OUT.cucap" | tail -1) | ||
| 108 | _bar_row=${_bar3#??} | ||
| 109 | _bar_row=${_bar_row%%;*} | ||
| 110 | [ -n "$_bar_row" ] || { | ||
| 111 | echo "e2e FAIL: cursor-ownership: tile 3's focused label bar never painted," | ||
| 112 | echo " so there is no stripe to hold the cursor to:" | ||
| 113 | cat "$OUT.cucap"; exit 1; } | ||
| 83 | # The focused tile's paint records its cursor; an unfocused paint's last act | 114 | # The focused tile's paint records its cursor; an unfocused paint's last act |
| 84 | # is a bare CUP putting it back there, with no show of its own. So the | 115 | # is a bare CUP putting it back there, with no show of its own. So the |
| 85 | # terminal's final cursor position — the last CUP in the capture, show or | 116 | # terminal's final cursor position — the last CUP in the capture, show or |
| @@ -91,8 +122,10 @@ _last_cup=$(grep -ao "$_cup_re" "$OUT.cucap" | tail -1) | |||
| 91 | # _last_cup is ESC[<row>;<col>H; peel the ESC[ prefix and the ;colH suffix. | 122 | # _last_cup is ESC[<row>;<col>H; peel the ESC[ prefix and the ;colH suffix. |
| 92 | _cur_row=${_last_cup#??} | 123 | _cur_row=${_last_cup#??} |
| 93 | _cur_row=${_cur_row%%;*} | 124 | _cur_row=${_cur_row%%;*} |
| 94 | [ -n "$_cur_row" ] && [ "$_cur_row" -ge 21 ] && [ "$_cur_row" -le 30 ] || { | 125 | [ -n "$_cur_row" ] && [ "$_cur_row" -gt "$_bar_row" ] && \ |
| 95 | echo "e2e FAIL: cursor-ownership: the last CUP was '$_last_cup' (row ${_cur_row:-none}), want tile 3's stripe (rows 21-30):" | 126 | [ "$_cur_row" -le $((_bar_row + 9)) ] || { |
| 127 | echo "e2e FAIL: cursor-ownership: the last CUP was '$_last_cup' (row ${_cur_row:-none})," | ||
| 128 | echo " want tile 3's stripe (rows $((_bar_row + 1))-$((_bar_row + 9))):" | ||
| 96 | cat "$OUT.cucap"; exit 1; } | 129 | cat "$OUT.cucap"; exit 1; } |
| 97 | assert_stopped "$SOCK52" "$D52PID" "cursor-ownership" "$OUT.custop" | 130 | assert_stopped "$SOCK52" "$D52PID" "cursor-ownership" "$OUT.custop" |
| 98 | D52PID="" | 131 | D52PID="" |
| @@ -121,24 +154,28 @@ D54PID=$DPID | |||
| 121 | # Session a: 100 lines of history, then cat. The wall attaches only once | 154 | # Session a: 100 lines of history, then cat. The wall attaches only once |
| 122 | # seq has finished, so the page the wheel fetches is content this tile was | 155 | # seq has finished, so the page the wheel fetches is content this tile was |
| 123 | # never sent live — the wheel leg's reason, verbatim. | 156 | # never sent live — the wheel leg's reason, verbatim. |
| 124 | pipe_mux "$OUT.sb2a" "$OUT.sb2a.err" timeout 40 "$MUX" --sock "$SOCK53" --session a | 157 | pipe_mux "$OUT.sb2a" "$OUT.sb2a.err" env XDG_STATE_HOME="$SB2STATE" timeout 40 "$MUX" --sock "$SOCK53" |
| 125 | pipe_send 'seq 1 100 | sed '\''s/^/wln/'\''; exec cat\n' | 158 | pipe_send 'seq 1 100 | sed '\''s/^/wln/'\''; exec cat\n' |
| 126 | await_out "$OUT.sb2a" "wln100" "scrollback-rect: session a never finished seq" | 159 | await_out "$OUT.sb2a" "wln100" "scrollback-rect: session a never finished seq" |
| 127 | pipe_detach | 160 | pipe_detach |
| 128 | wait_grid "$SOCK53" "wln100" "scrollback-rect: session a's tail" a | 161 | wait_grid "$SOCK53" "wln100" "scrollback-rect: session a's tail" |
| 129 | 162 | ||
| 130 | # Session b: a pin the capture can grep for, then cat. `printf "sb2%s\n" | 163 | # Session b: a pin the capture can grep for, then cat. `printf "sb2%s\n" |
| 131 | # nbr` so the pin is the output, not the echoed command — the focus-skip | 164 | # nbr` so the pin is the output, not the echoed command — the focus-skip |
| 132 | # leg's prefix trick. | 165 | # leg's prefix trick. |
| 133 | pipe_mux "$OUT.sb2b" "$OUT.sb2b.err" timeout 40 "$MUX" --sock "$SOCK53" --session b | 166 | pipe_mux "$OUT.sb2b" "$OUT.sb2b.err" env XDG_STATE_HOME="$SB2STATE" timeout 40 "$MUX" --sock "$SOCK53" --session b |
| 134 | pipe_send 'printf "sb2%%s\\n" nbr; exec cat\n' | 167 | pipe_send 'printf "sb2%%s\\n" nbr; exec cat\n' |
| 135 | await_out "$OUT.sb2b" "sb2nbr" "scrollback-rect: session b's pin never reached the client" | 168 | await_out "$OUT.sb2b" "sb2nbr" "scrollback-rect: session b's pin never reached the client" |
| 136 | pipe_detach | 169 | pipe_detach |
| 137 | wait_grid "$SOCK53" "sb2nbr" "scrollback-rect: session b's pin" b | 170 | wait_grid "$SOCK53" "sb2nbr" "scrollback-rect: session b's pin" b |
| 138 | 171 | ||
| 139 | set +e | 172 | set +e |
| 140 | timeout 90 "$PTYCLIENT" --cols 40 --rows 30 --out "$OUT.sb2cap" --err "$OUT.sb2cap.err" -- \ | 173 | # No saved tree: this leg asserts the DEFAULT cut, and the setup attaches |
| 141 | "$MUX" wall "--sock $SOCK53#a" "--sock $SOCK53#b" > "$OUT.sb2pc" 2>&1 <<'EOF' | 174 | # above are walls too — each of them saved a one-leaf sidecar on its way |
| 175 | # out. The aspect rule applies only when nothing is saved. | ||
| 176 | rm -f "$SB2STATE/mux/layout" | ||
| 177 | XDG_STATE_HOME="$SB2STATE" timeout 90 "$PTYCLIENT" --cols 40 --rows 30 --out "$OUT.sb2cap" --err "$OUT.sb2cap.err" -- \ | ||
| 178 | "$MUX" --sock "$SOCK53" > "$OUT.sb2pc" 2>&1 <<'EOF' | ||
| 142 | expect sb2nbr 20000 | 179 | expect sb2nbr 20000 |
| 143 | settle 700 20000 | 180 | settle 700 20000 |
| 144 | send \x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M | 181 | send \x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M |
| @@ -182,12 +219,12 @@ ok "a focused tile's scrollback page owns only its sub-rect, and the neighbour's | |||
| 182 | start_daemon "$SOCK54" "$OUT.fs.d" "fullscreen daemon never bound" --shell /bin/sh | 219 | start_daemon "$SOCK54" "$OUT.fs.d" "fullscreen daemon never bound" --shell /bin/sh |
| 183 | D55PID=$DPID | 220 | D55PID=$DPID |
| 184 | 221 | ||
| 185 | pipe_mux "$OUT.fsa" "$OUT.fsa.err" timeout 40 "$MUX" --sock "$SOCK54" --session a | 222 | pipe_mux "$OUT.fsa" "$OUT.fsa.err" env XDG_STATE_HOME="$FSSTATE" timeout 40 "$MUX" --sock "$SOCK54" |
| 186 | pipe_send 'printf "fs-%%s\\n" init-a\n' | 223 | pipe_send 'printf "fs-%%s\\n" init-a\n' |
| 187 | await_out "$OUT.fsa" "fs-init-a" "fullscreen: session a's marker never reached the client" | 224 | await_out "$OUT.fsa" "fs-init-a" "fullscreen: session a's marker never reached the client" |
| 188 | pipe_detach | 225 | pipe_detach |
| 189 | wait_grid "$SOCK54" "fs-init-a" "fullscreen: session a's marker" a | 226 | wait_grid "$SOCK54" "fs-init-a" "fullscreen: session a's marker" |
| 190 | pipe_mux "$OUT.fsb" "$OUT.fsb.err" timeout 40 "$MUX" --sock "$SOCK54" --session b | 227 | pipe_mux "$OUT.fsb" "$OUT.fsb.err" env XDG_STATE_HOME="$FSSTATE" timeout 40 "$MUX" --sock "$SOCK54" --session b |
| 191 | pipe_send 'printf "fs-%%s\\n" init-b\n' | 228 | pipe_send 'printf "fs-%%s\\n" init-b\n' |
| 192 | await_out "$OUT.fsb" "fs-init-b" "fullscreen: session b's marker never reached the client" | 229 | await_out "$OUT.fsb" "fs-init-b" "fullscreen: session b's marker never reached the client" |
| 193 | pipe_detach | 230 | pipe_detach |
| @@ -195,8 +232,12 @@ wait_grid "$SOCK54" "fs-init-b" "fullscreen: session b's marker" b | |||
| 195 | 232 | ||
| 196 | set +e | 233 | set +e |
| 197 | # wide: 80x24 trips the aspect rule (80 >= 48 → beside) | 234 | # wide: 80x24 trips the aspect rule (80 >= 48 → beside) |
| 198 | timeout 90 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.fscap" --err "$OUT.fscap.err" -- \ | 235 | # No saved tree: this leg asserts the DEFAULT cut, and the setup attaches |
| 199 | "$MUX" wall "--sock $SOCK54#a" "--sock $SOCK54#b" > "$OUT.fspc" 2>&1 <<'EOF' | 236 | # above are walls too — each of them saved a one-leaf sidecar on its way |
| 237 | # out. The aspect rule applies only when nothing is saved. | ||
| 238 | rm -f "$FSSTATE/mux/layout" | ||
| 239 | XDG_STATE_HOME="$FSSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.fscap" --err "$OUT.fscap.err" -- \ | ||
| 240 | "$MUX" --sock "$SOCK54" > "$OUT.fspc" 2>&1 <<'EOF' | ||
| 200 | expect fs-init-b 20000 | 241 | expect fs-init-b 20000 |
| 201 | settle 700 20000 | 242 | settle 700 20000 |
| 202 | send \x1cf | 243 | send \x1cf |
| @@ -221,7 +262,7 @@ set -e | |||
| 221 | cat "$OUT.fspc" "$OUT.fscap.err"; exit 1; } | 262 | cat "$OUT.fspc" "$OUT.fscap.err"; exit 1; } |
| 222 | # fs-one landed in session a (the tile that was fullscreened first); | 263 | # fs-one landed in session a (the tile that was fullscreened first); |
| 223 | # fs-two landed in session b (the tile Ctrl-\ 2 moved the full rect to). | 264 | # fs-two landed in session b (the tile Ctrl-\ 2 moved the full rect to). |
| 224 | timeout 20 "$MUXA" capture --sock "$SOCK54" --session a > "$OUT.fsfa" 2>&1 | 265 | timeout 20 "$MUXA" capture --sock "$SOCK54" > "$OUT.fsfa" 2>&1 |
| 225 | timeout 20 "$MUXA" capture --sock "$SOCK54" --session b > "$OUT.fsfb" 2>&1 | 266 | timeout 20 "$MUXA" capture --sock "$SOCK54" --session b > "$OUT.fsfb" 2>&1 |
| 226 | grep -q "fs-one" "$OUT.fsfa" || { | 267 | grep -q "fs-one" "$OUT.fsfa" || { |
| 227 | echo "e2e FAIL: fullscreen: session a never got fs-one:" | 268 | echo "e2e FAIL: fullscreen: session a never got fs-one:" |
| @@ -267,12 +308,12 @@ ok "fullscreen gives the focused tile the terminal; focus follows; f restores" | |||
| 267 | start_daemon "$SOCK55" "$OUT.rsz.d" "resize daemon never bound" --shell /bin/sh | 308 | start_daemon "$SOCK55" "$OUT.rsz.d" "resize daemon never bound" --shell /bin/sh |
| 268 | D56PID=$DPID | 309 | D56PID=$DPID |
| 269 | 310 | ||
| 270 | pipe_mux "$OUT.rsza" "$OUT.rsza.err" timeout 40 "$MUX" --sock "$SOCK55" --session a | 311 | pipe_mux "$OUT.rsza" "$OUT.rsza.err" env XDG_STATE_HOME="$RSZSTATE" timeout 40 "$MUX" --sock "$SOCK55" |
| 271 | pipe_send 'printf "rsz-%%s\\n" init-a\n' | 312 | pipe_send 'printf "rsz-%%s\\n" init-a\n' |
| 272 | await_out "$OUT.rsza" "rsz-init-a" "resize: session a's marker never reached the client" | 313 | await_out "$OUT.rsza" "rsz-init-a" "resize: session a's marker never reached the client" |
| 273 | pipe_detach | 314 | pipe_detach |
| 274 | wait_grid "$SOCK55" "rsz-init-a" "resize: session a's marker" a | 315 | wait_grid "$SOCK55" "rsz-init-a" "resize: session a's marker" |
| 275 | pipe_mux "$OUT.rszb" "$OUT.rszb.err" timeout 40 "$MUX" --sock "$SOCK55" --session b | 316 | pipe_mux "$OUT.rszb" "$OUT.rszb.err" env XDG_STATE_HOME="$RSZSTATE" timeout 40 "$MUX" --sock "$SOCK55" --session b |
| 276 | pipe_send 'printf "rsz-%%s\\n" init-b\n' | 317 | pipe_send 'printf "rsz-%%s\\n" init-b\n' |
| 277 | await_out "$OUT.rszb" "rsz-init-b" "resize: session b's marker never reached the client" | 318 | await_out "$OUT.rszb" "rsz-init-b" "resize: session b's marker never reached the client" |
| 278 | pipe_detach | 319 | pipe_detach |
| @@ -282,8 +323,12 @@ set +e | |||
| 282 | # 80x24: aspect rule makes beside; two panes split 39/40 with the rail | 323 | # 80x24: aspect rule makes beside; two panes split 39/40 with the rail |
| 283 | # at column 40 (1-indexed). Three l presses grow the left pane by 3, | 324 | # at column 40 (1-indexed). Three l presses grow the left pane by 3, |
| 284 | # moving the rail to column 43. | 325 | # moving the rail to column 43. |
| 285 | timeout 90 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.rszcap" --err "$OUT.rszcap.err" -- \ | 326 | # No saved tree: this leg asserts the DEFAULT cut, and the setup attaches |
| 286 | "$MUX" wall "--sock $SOCK55#a" "--sock $SOCK55#b" > "$OUT.rszpc" 2>&1 <<'EOF' | 327 | # above are walls too — each of them saved a one-leaf sidecar on its way |
| 328 | # out. The aspect rule applies only when nothing is saved. | ||
| 329 | rm -f "$RSZSTATE/mux/layout" | ||
| 330 | XDG_STATE_HOME="$RSZSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.rszcap" --err "$OUT.rszcap.err" -- \ | ||
| 331 | "$MUX" --sock "$SOCK55" > "$OUT.rszpc" 2>&1 <<'EOF' | ||
| 287 | expect rsz-init-b 20000 | 332 | expect rsz-init-b 20000 |
| 288 | settle 700 20000 | 333 | settle 700 20000 |
| 289 | send \x1cr | 334 | send \x1cr |
| @@ -324,7 +369,7 @@ _last_rail=$(echo "$_rail_cols" | tail -1) | |||
| 324 | echo "e2e FAIL: resize: rail column is $_last_rail, want >=43 (40 + 3 grows):" | 369 | echo "e2e FAIL: resize: rail column is $_last_rail, want >=43 (40 + 3 grows):" |
| 325 | cat "$OUT.rszcap"; exit 1; } | 370 | cat "$OUT.rszcap"; exit 1; } |
| 326 | # Esc ended resize mode and typed prose reached the focused session. | 371 | # Esc ended resize mode and typed prose reached the focused session. |
| 327 | timeout 20 "$MUXA" capture --sock "$SOCK55" --session a > "$OUT.rszsta" 2>&1 | 372 | timeout 20 "$MUXA" capture --sock "$SOCK55" > "$OUT.rszsta" 2>&1 |
| 328 | timeout 20 "$MUXA" capture --sock "$SOCK55" --session b > "$OUT.rszstb" 2>&1 | 373 | timeout 20 "$MUXA" capture --sock "$SOCK55" --session b > "$OUT.rszstb" 2>&1 |
| 329 | if ! grep -q "rsz-grown" "$OUT.rszsta" && ! grep -q "rsz-grown" "$OUT.rszstb"; then | 374 | if ! grep -q "rsz-grown" "$OUT.rszsta" && ! grep -q "rsz-grown" "$OUT.rszstb"; then |
| 330 | echo "e2e FAIL: resize: rsz-grown reached neither session after Esc:" | 375 | echo "e2e FAIL: resize: rsz-grown reached neither session after Esc:" |
| @@ -346,12 +391,12 @@ ok "Ctrl-\\ r trades cells between panes; Esc returns to prose" | |||
| 346 | start_daemon "$SOCK56" "$OUT.spc.d" "span-clear daemon never bound" --shell /bin/sh | 391 | start_daemon "$SOCK56" "$OUT.spc.d" "span-clear daemon never bound" --shell /bin/sh |
| 347 | D57PID=$DPID | 392 | D57PID=$DPID |
| 348 | 393 | ||
| 349 | pipe_mux "$OUT.spca" "$OUT.spca.err" timeout 40 "$MUX" --sock "$SOCK56" --session a | 394 | pipe_mux "$OUT.spca" "$OUT.spca.err" env XDG_STATE_HOME="$SPCSTATE" timeout 40 "$MUX" --sock "$SOCK56" |
| 350 | pipe_send 'printf "spc-%%s\\n" left-flood\n' | 395 | pipe_send 'printf "spc-%%s\\n" left-flood\n' |
| 351 | await_out "$OUT.spca" "spc-left-flood" "span-clear: left session's marker never reached the client" | 396 | await_out "$OUT.spca" "spc-left-flood" "span-clear: left session's marker never reached the client" |
| 352 | pipe_detach | 397 | pipe_detach |
| 353 | wait_grid "$SOCK56" "spc-left-flood" "span-clear: left session's marker" a | 398 | wait_grid "$SOCK56" "spc-left-flood" "span-clear: left session's marker" |
| 354 | pipe_mux "$OUT.spcb" "$OUT.spcb.err" timeout 40 "$MUX" --sock "$SOCK56" --session b | 399 | pipe_mux "$OUT.spcb" "$OUT.spcb.err" env XDG_STATE_HOME="$SPCSTATE" timeout 40 "$MUX" --sock "$SOCK56" --session b |
| 355 | pipe_send 'printf "spc-%%s\\n" right-survivor\n' | 400 | pipe_send 'printf "spc-%%s\\n" right-survivor\n' |
| 356 | await_out "$OUT.spcb" "spc-right-survivor" "span-clear: right session's marker never reached the client" | 401 | await_out "$OUT.spcb" "spc-right-survivor" "span-clear: right session's marker never reached the client" |
| 357 | pipe_detach | 402 | pipe_detach |
| @@ -360,8 +405,12 @@ wait_grid "$SOCK56" "spc-right-survivor" "span-clear: right session's marker" b | |||
| 360 | set +e | 405 | set +e |
| 361 | # 80x24: aspect rule → beside. The wall client is a tty session: its | 406 | # 80x24: aspect rule → beside. The wall client is a tty session: its |
| 362 | # stdout is the terminal's full byte stream, captured by ptyclient. | 407 | # stdout is the terminal's full byte stream, captured by ptyclient. |
| 363 | timeout 90 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.spccap" --err "$OUT.spccap.err" -- \ | 408 | # No saved tree: this leg asserts the DEFAULT cut, and the setup attaches |
| 364 | "$MUX" wall "--sock $SOCK56#a" "--sock $SOCK56#b" > "$OUT.spcpc" 2>&1 <<'EOF' | 409 | # above are walls too — each of them saved a one-leaf sidecar on its way |
| 410 | # out. The aspect rule applies only when nothing is saved. | ||
| 411 | rm -f "$SPCSTATE/mux/layout" | ||
| 412 | XDG_STATE_HOME="$SPCSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.spccap" --err "$OUT.spccap.err" -- \ | ||
| 413 | "$MUX" --sock "$SOCK56" > "$OUT.spcpc" 2>&1 <<'EOF' | ||
| 365 | expect spc-right-survivor 20000 | 414 | expect spc-right-survivor 20000 |
| 366 | settle 700 20000 | 415 | settle 700 20000 |
| 367 | send \x1ch | 416 | send \x1ch |
| @@ -403,17 +452,21 @@ ok "a span-clear flood in the left pane cannot blank the right pane's grid" | |||
| 403 | start_daemon "$SOCK57" "$OUT.hjk.d" "hjkl daemon never bound" --shell /bin/sh | 452 | start_daemon "$SOCK57" "$OUT.hjk.d" "hjkl daemon never bound" --shell /bin/sh |
| 404 | D58PID=$DPID | 453 | D58PID=$DPID |
| 405 | 454 | ||
| 406 | pipe_mux "$OUT.hjkcap" "$OUT.hjkcap.err" timeout 40 "$MUX" --sock "$SOCK57" --session a | 455 | pipe_mux "$OUT.hjkcap" "$OUT.hjkcap.err" env XDG_STATE_HOME="$HJKSTATE" timeout 40 "$MUX" --sock "$SOCK57" |
| 407 | pipe_send 'printf "hjk-%%s\\n" origin\n' | 456 | pipe_send 'printf "hjk-%%s\\n" origin\n' |
| 408 | await_out "$OUT.hjkcap" "hjk-origin" "hjkl: origin marker never reached the client" | 457 | await_out "$OUT.hjkcap" "hjk-origin" "hjkl: origin marker never reached the client" |
| 409 | pipe_detach | 458 | pipe_detach |
| 410 | wait_grid "$SOCK57" "hjk-origin" "hjkl: origin marker" a | 459 | wait_grid "$SOCK57" "hjk-origin" "hjkl: origin marker" |
| 411 | 460 | ||
| 412 | set +e | 461 | set +e |
| 413 | # 80x24: aspect rule → beside for the first split; the second split goes | 462 | # 80x24: aspect rule → beside for the first split; the second split goes |
| 414 | # below, producing a 1 | (2 over 3) tree. | 463 | # below, producing a 1 | (2 over 3) tree. |
| 415 | timeout 90 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.hjkcap" --err "$OUT.hjkcap.err" -- \ | 464 | # No saved tree: this leg asserts the DEFAULT cut, and the setup attaches |
| 416 | "$MUX" wall "--sock $SOCK57#a" > "$OUT.hjkpc" 2>&1 <<'EOF' | 465 | # above are walls too — each of them saved a one-leaf sidecar on its way |
| 466 | # out. The aspect rule applies only when nothing is saved. | ||
| 467 | rm -f "$HJKSTATE/mux/layout" | ||
| 468 | XDG_STATE_HOME="$HJKSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.hjkcap" --err "$OUT.hjkcap.err" -- \ | ||
| 469 | "$MUX" --sock "$SOCK57" > "$OUT.hjkpc" 2>&1 <<'EOF' | ||
| 417 | expect hjk-origin 20000 | 470 | expect hjk-origin 20000 |
| 418 | settle 700 20000 | 471 | settle 700 20000 |
| 419 | send \x1c| | 472 | send \x1c| |
| @@ -445,7 +498,7 @@ set -e | |||
| 445 | cat "$OUT.hjkpc" "$OUT.hjkcap.err"; exit 1; } | 498 | cat "$OUT.hjkpc" "$OUT.hjkcap.err"; exit 1; } |
| 446 | # Three sessions exist now: a (the origin), and two birthed by splits. | 499 | # Three sessions exist now: a (the origin), and two birthed by splits. |
| 447 | # The daemon assigns numeric names to the new sessions; capture all three. | 500 | # The daemon assigns numeric names to the new sessions; capture all three. |
| 448 | timeout 20 "$MUXA" capture --sock "$SOCK57" --session a > "$OUT.hjkfa" 2>&1 | 501 | timeout 20 "$MUXA" capture --sock "$SOCK57" > "$OUT.hjkfa" 2>&1 |
| 449 | timeout 20 "$MUXA" capture --sock "$SOCK57" --session 1 > "$OUT.hjkfb" 2>&1 | 502 | timeout 20 "$MUXA" capture --sock "$SOCK57" --session 1 > "$OUT.hjkfb" 2>&1 |
| 450 | timeout 20 "$MUXA" capture --sock "$SOCK57" --session 2 > "$OUT.hjkfc" 2>&1 | 503 | timeout 20 "$MUXA" capture --sock "$SOCK57" --session 2 > "$OUT.hjkfc" 2>&1 |
| 451 | # went-left: typed after \x1ch from the right pane → lands in session a. | 504 | # went-left: typed after \x1ch from the right pane → lands in session a. |
| @@ -473,17 +526,21 @@ ok "hjkl walks focus across panes in both axes" | |||
| 473 | start_daemon "$SOCK58" "$OUT.spl.d" "split-birth daemon never bound" --shell /bin/sh | 526 | start_daemon "$SOCK58" "$OUT.spl.d" "split-birth daemon never bound" --shell /bin/sh |
| 474 | D59PID=$DPID | 527 | D59PID=$DPID |
| 475 | 528 | ||
| 476 | pipe_mux "$OUT.splcap" "$OUT.splcap.err" timeout 40 "$MUX" --sock "$SOCK58" --session a | 529 | pipe_mux "$OUT.splcap" "$OUT.splcap.err" env XDG_STATE_HOME="$SPLSTATE" timeout 40 "$MUX" --sock "$SOCK58" |
| 477 | pipe_send 'printf "spl-%%s\\n" origin\n' | 530 | pipe_send 'printf "spl-%%s\\n" origin\n' |
| 478 | await_out "$OUT.splcap" "spl-origin" "split-birth: origin marker never reached the client" | 531 | await_out "$OUT.splcap" "spl-origin" "split-birth: origin marker never reached the client" |
| 479 | pipe_detach | 532 | pipe_detach |
| 480 | wait_grid "$SOCK58" "spl-origin" "split-birth: origin marker" a | 533 | wait_grid "$SOCK58" "spl-origin" "split-birth: origin marker" |
| 481 | 534 | ||
| 482 | set +e | 535 | set +e |
| 483 | # 80x24: aspect rule → beside. One tile starts, then Ctrl-\ | births a | 536 | # 80x24: aspect rule → beside. One tile starts, then Ctrl-\ | births a |
| 484 | # second session to the right. | 537 | # second session to the right. |
| 485 | timeout 90 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.splcap" --err "$OUT.splcap.err" -- \ | 538 | # No saved tree: this leg asserts the DEFAULT cut, and the setup attaches |
| 486 | "$MUX" wall "--sock $SOCK58#a" > "$OUT.splpc" 2>&1 <<'EOF' | 539 | # above are walls too — each of them saved a one-leaf sidecar on its way |
| 540 | # out. The aspect rule applies only when nothing is saved. | ||
| 541 | rm -f "$SPLSTATE/mux/layout" | ||
| 542 | XDG_STATE_HOME="$SPLSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.splcap" --err "$OUT.splcap.err" -- \ | ||
| 543 | "$MUX" --sock "$SOCK58" > "$OUT.splpc" 2>&1 <<'EOF' | ||
| 487 | expect spl-origin 20000 | 544 | expect spl-origin 20000 |
| 488 | settle 700 20000 | 545 | settle 700 20000 |
| 489 | send \x1c| | 546 | send \x1c| |
| @@ -496,9 +553,9 @@ set -e | |||
| 496 | [ "$RC" -eq 0 ] || { | 553 | [ "$RC" -eq 0 ] || { |
| 497 | echo "e2e FAIL: split-birth: ptyclient leg exited $RC:" | 554 | echo "e2e FAIL: split-birth: ptyclient leg exited $RC:" |
| 498 | cat "$OUT.splpc" "$OUT.splcap.err"; exit 1; } | 555 | cat "$OUT.splpc" "$OUT.splcap.err"; exit 1; } |
| 499 | # The daemon held sessions 0 (default) and a; the split birthed one | 556 | # The daemon held its own default session and nothing else; the split |
| 500 | # more, so three live sessions proves the chord created a new session. | 557 | # birthed one more, so two live sessions proves the chord created one. |
| 501 | wait_sessions "$SOCK58" 3 "split-birth: split did not birth a new session" | 558 | wait_sessions "$SOCK58" 2 "split-birth: split did not birth a new session" |
| 502 | # The rail paints as ESC[row;colH ESC[7m (reverse video). The new pane's | 559 | # The rail paints as ESC[row;colH ESC[7m (reverse video). The new pane's |
| 503 | # label bar is at a column greater than the rail's column — it sits to | 560 | # label bar is at a column greater than the rail's column — it sits to |
| 504 | # the right of the separator, which is the whole point of beside. | 561 | # the right of the separator, which is the whole point of beside. |
| @@ -523,42 +580,46 @@ ok "Ctrl-\\ | births a session beside; its label bar sits right of the rail" | |||
| 523 | 580 | ||
| 524 | # ---- layout sidecar: restore round-trip ------------------------------ | 581 | # ---- layout sidecar: restore round-trip ------------------------------ |
| 525 | # | 582 | # |
| 526 | # A no-argv `mux wall` hydrates from the wall file, and on Ctrl-\ d the | 583 | # A no-argv `mux` opens the wall of hosts, and on Ctrl-\ d the resized tree |
| 527 | # resized tree is saved to the layout sidecar. Reattaching with the same | 584 | # is saved to the layout sidecar. Reattaching with the same state home |
| 528 | # state home loads the sidecar verbatim — the resize survived the round | 585 | # loads the sidecar verbatim — the resize survived the round trip. The rail's CUP column in the second capture is >= 43 (three |
| 529 | # trip. The rail's CUP column in the second capture is >= 43 (three | ||
| 530 | # right-resizes from a 40|40 split), and a marker typed into the restored | 586 | # right-resizes from a 40|40 split), and a marker typed into the restored |
| 531 | # wall lands in the focused session. | 587 | # wall lands in the focused session. |
| 532 | LPSTATE="${TMPDIR:-/tmp}/mux-e2e-lprrestore-state-$$" | 588 | LPSTATE="${TMPDIR:-/tmp}/mux-e2e-lprrestore-state-$$" |
| 533 | defer_rm "$LPSTATE" | 589 | defer_rm "$LPSTATE" |
| 534 | LPWALL="$LPSTATE/mux/wall" | 590 | LPHOSTS="$LPSTATE/mux/hosts" |
| 535 | LPLAYOUT="$LPSTATE/mux/layout" | 591 | LPLAYOUT="$LPSTATE/mux/layout" |
| 536 | start_daemon "$SOCK59" "$OUT.lpr.d" "layout-restore daemon never bound" --shell /bin/sh | 592 | start_daemon "$SOCK59" "$OUT.lpr.d" "layout-restore daemon never bound" --shell /bin/sh |
| 537 | D60PID=$DPID | 593 | D60PID=$DPID |
| 538 | 594 | ||
| 539 | pipe_mux "$OUT.lpra" "$OUT.lpra.err" timeout 40 "$MUX" --sock "$SOCK59" --session a | 595 | pipe_mux "$OUT.lpra" "$OUT.lpra.err" env XDG_STATE_HOME="$LPSTATE" timeout 40 "$MUX" --sock "$SOCK59" |
| 540 | pipe_send 'printf "lpr-%%s\\n" origin-a\n' | 596 | pipe_send 'printf "lpr-%%s\\n" origin-0\n' |
| 541 | await_out "$OUT.lpra" "lpr-origin-a" "layout-restore: session a marker never reached the client" | 597 | await_out "$OUT.lpra" "lpr-origin-0" "layout-restore: the default session's marker never reached the client" |
| 542 | pipe_detach | 598 | pipe_detach |
| 543 | wait_grid "$SOCK59" "lpr-origin-a" "layout-restore: session a marker" a | 599 | wait_grid "$SOCK59" "lpr-origin-0" "layout-restore: the default session's marker" |
| 544 | pipe_mux "$OUT.lprb" "$OUT.lprb.err" timeout 40 "$MUX" --sock "$SOCK59" --session b | 600 | pipe_mux "$OUT.lprb" "$OUT.lprb.err" env XDG_STATE_HOME="$LPSTATE" timeout 40 "$MUX" --sock "$SOCK59" --session b |
| 545 | pipe_send 'printf "lpr-%%s\\n" origin-b\n' | 601 | pipe_send 'printf "lpr-%%s\\n" origin-b\n' |
| 546 | await_out "$OUT.lprb" "lpr-origin-b" "layout-restore: session b marker never reached the client" | 602 | await_out "$OUT.lprb" "lpr-origin-b" "layout-restore: session b marker never reached the client" |
| 547 | pipe_detach | 603 | pipe_detach |
| 548 | wait_grid "$SOCK59" "lpr-origin-b" "layout-restore: session b marker" b | 604 | wait_grid "$SOCK59" "lpr-origin-b" "layout-restore: session b marker" b |
| 549 | 605 | ||
| 550 | # Write the wall file by hand: two spellings, one per line. The no-argv | 606 | # Write the hosts file by hand: ONE line, the daemon. Its two sessions are |
| 551 | # `mux wall` reads this on startup — the hydration path. | 607 | # what the poll turns into two panes — the wall knows nothing about them |
| 608 | # until it asks. | ||
| 552 | mkdir -p "$LPSTATE/mux" | 609 | mkdir -p "$LPSTATE/mux" |
| 553 | printf -- '--sock %s#a\n--sock %s#b\n' "$SOCK59" "$SOCK59" > "$LPWALL" | 610 | printf -- '--sock %s\n' "$SOCK59" > "$LPHOSTS" |
| 611 | # ...and no saved tree yet: the setup attaches above are walls too, and | ||
| 612 | # each saved a one-leaf sidecar on its way out. What this leg saves and | ||
| 613 | # restores has to be the tree the FIRST ptyclient builds. | ||
| 614 | rm -f "$LPLAYOUT" | ||
| 554 | 615 | ||
| 555 | set +e | 616 | set +e |
| 556 | # First ptyclient: hydrate, resize three right, detach. The detach saves | 617 | # First ptyclient: hydrate, resize three right, detach. The detach saves |
| 557 | # the sidecar. | 618 | # the sidecar. |
| 558 | XDG_STATE_HOME="$LPSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ | 619 | XDG_STATE_HOME="$LPSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ |
| 559 | --out "$OUT.lprcap1" --err "$OUT.lprcap1.err" -- \ | 620 | --out "$OUT.lprcap1" --err "$OUT.lprcap1.err" -- \ |
| 560 | "$MUX" wall > "$OUT.lprpc1" 2>&1 <<'EOF' | 621 | "$MUX" > "$OUT.lprpc1" 2>&1 <<'EOF' |
| 561 | expect lpr-origin-a 20000 | 622 | expect lpr-origin-0 20000 |
| 562 | settle 700 20000 | 623 | settle 700 20000 |
| 563 | send \x1cr | 624 | send \x1cr |
| 564 | settle 300 5000 | 625 | settle 300 5000 |
| @@ -606,8 +667,8 @@ set +e | |||
| 606 | # the rail sits at column >= 43. Type a marker, then detach. | 667 | # the rail sits at column >= 43. Type a marker, then detach. |
| 607 | XDG_STATE_HOME="$LPSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ | 668 | XDG_STATE_HOME="$LPSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ |
| 608 | --out "$OUT.lprcap2" --err "$OUT.lprcap2.err" -- \ | 669 | --out "$OUT.lprcap2" --err "$OUT.lprcap2.err" -- \ |
| 609 | "$MUX" wall > "$OUT.lprpc2" 2>&1 <<'EOF' | 670 | "$MUX" > "$OUT.lprpc2" 2>&1 <<'EOF' |
| 610 | expect lpr-origin-a 20000 | 671 | expect lpr-origin-0 20000 |
| 611 | settle 1000 20000 | 672 | settle 1000 20000 |
| 612 | send printf 'lpr-rt-%s\n' marker\n | 673 | send printf 'lpr-rt-%s\n' marker\n |
| 613 | expect lpr-rt-marker 10000 | 674 | expect lpr-rt-marker 10000 |
| @@ -638,55 +699,53 @@ timeout 20 "$MUXA" capture --sock "$SOCK59" --session b > "$OUT.lprfb" 2>&1 | |||
| 638 | grep -q "lpr-rt-marker" "$OUT.lprfb" || { | 699 | grep -q "lpr-rt-marker" "$OUT.lprfb" || { |
| 639 | echo "e2e FAIL: layout-restore: marker not in session b (focus did not survive):" | 700 | echo "e2e FAIL: layout-restore: marker not in session b (focus did not survive):" |
| 640 | cat "$OUT.lprfb"; exit 1; } | 701 | cat "$OUT.lprfb"; exit 1; } |
| 641 | timeout 20 "$MUXA" capture --sock "$SOCK59" --session a > "$OUT.lprfa" 2>&1 | 702 | timeout 20 "$MUXA" capture --sock "$SOCK59" > "$OUT.lprfa" 2>&1 |
| 642 | ! grep -q "lpr-rt-marker" "$OUT.lprfa" || { | 703 | ! grep -q "lpr-rt-marker" "$OUT.lprfa" || { |
| 643 | echo "e2e FAIL: layout-restore: marker leaked into session a (focus was tile 0, not b):" | 704 | echo "e2e FAIL: layout-restore: marker leaked into the default session (focus was tile 0, not b):" |
| 644 | cat "$OUT.lprfa"; exit 1; } | 705 | cat "$OUT.lprfa"; exit 1; } |
| 645 | assert_stopped "$SOCK59" "$D60PID" "layout-restore" "$OUT.lprstop" | 706 | assert_stopped "$SOCK59" "$D60PID" "layout-restore" "$OUT.lprstop" |
| 646 | D60PID="" | 707 | D60PID="" |
| 647 | rm -rf "$LPSTATE" | 708 | rm -rf "$LPSTATE" |
| 648 | ok "a resized layout survives a detach/reattach round trip via the sidecar" | 709 | ok "a resized layout survives a detach/reattach round trip via the sidecar" |
| 649 | 710 | ||
| 650 | # ---- layout sidecar: healing on wall-file drift ---------------------- | 711 | # ---- layout sidecar: healing on LIVE drift --------------------------- |
| 651 | # | 712 | # |
| 652 | # After a 2-pane sidecar is saved, the wall file drifts: one original is | 713 | # The drift a wall of daemons can actually suffer, and it is no longer a |
| 653 | # removed and a new spelling is added. On reattach, restoreLayout heals — | 714 | # file being edited: a 2-pane sidecar is saved, and then one of those |
| 654 | # the survivor leaf keeps its session, the new tile inserts beside it, | 715 | # sessions ENDS while another is born. On reattach, restoreLayout heals — |
| 655 | # and the unmatched saved leaf collapses out. Two tiles, not three, and | 716 | # the survivor leaf keeps its session, the newcomer inserts beside it, and |
| 656 | # the wall did not refuse. | 717 | # the unmatched saved leaf collapses out. Two tiles, not three, and the |
| 718 | # wall did not refuse. | ||
| 657 | LPHSTATE="${TMPDIR:-/tmp}/mux-e2e-lpheal-state-$$" | 719 | LPHSTATE="${TMPDIR:-/tmp}/mux-e2e-lpheal-state-$$" |
| 658 | defer_rm "$LPHSTATE" | 720 | defer_rm "$LPHSTATE" |
| 659 | LPHWALL="$LPHSTATE/mux/wall" | 721 | LPHHOSTS="$LPHSTATE/mux/hosts" |
| 660 | LPHLAYOUT="$LPHSTATE/mux/layout" | 722 | LPHLAYOUT="$LPHSTATE/mux/layout" |
| 661 | start_daemon "$SOCK60" "$OUT.lph.d" "layout-heal daemon never bound" --shell /bin/sh | 723 | start_daemon "$SOCK60" "$OUT.lph.d" "layout-heal daemon never bound" --shell /bin/sh |
| 662 | D61PID=$DPID | 724 | D61PID=$DPID |
| 663 | 725 | ||
| 664 | pipe_mux "$OUT.lpha" "$OUT.lpha.err" timeout 40 "$MUX" --sock "$SOCK60" --session a | 726 | pipe_mux "$OUT.lpha" "$OUT.lpha.err" env XDG_STATE_HOME="$LPHSTATE" timeout 40 "$MUX" --sock "$SOCK60" |
| 665 | pipe_send 'printf "lph-%%s\\n" survivor-a\n' | 727 | pipe_send 'printf "lph-%%s\\n" survivor-0\n' |
| 666 | await_out "$OUT.lpha" "lph-survivor-a" "layout-heal: session a marker never reached the client" | 728 | await_out "$OUT.lpha" "lph-survivor-0" "layout-heal: the default session's marker never reached the client" |
| 667 | pipe_detach | ||
| 668 | wait_grid "$SOCK60" "lph-survivor-a" "layout-heal: session a marker" a | ||
| 669 | pipe_mux "$OUT.lphb" "$OUT.lphb.err" timeout 40 "$MUX" --sock "$SOCK60" --session b | ||
| 670 | pipe_send 'printf "lph-%%s\\n" removed-b\n' | ||
| 671 | await_out "$OUT.lphb" "lph-removed-b" "layout-heal: session b marker never reached the client" | ||
| 672 | pipe_detach | 729 | pipe_detach |
| 673 | wait_grid "$SOCK60" "lph-removed-b" "layout-heal: session b marker" b | 730 | wait_grid "$SOCK60" "lph-survivor-0" "layout-heal: the default session's marker" |
| 674 | pipe_mux "$OUT.lphc" "$OUT.lphc.err" timeout 40 "$MUX" --sock "$SOCK60" --session c | 731 | pipe_mux "$OUT.lphb" "$OUT.lphb.err" env XDG_STATE_HOME="$LPHSTATE" timeout 40 "$MUX" --sock "$SOCK60" --session b |
| 675 | pipe_send 'printf "lph-%%s\\n" newcomer-c\n' | 732 | pipe_send 'printf "lph-%%s\\n" doomed-b\n' |
| 676 | await_out "$OUT.lphc" "lph-newcomer-c" "layout-heal: session c marker never reached the client" | 733 | await_out "$OUT.lphb" "lph-doomed-b" "layout-heal: session b marker never reached the client" |
| 677 | pipe_detach | 734 | pipe_detach |
| 678 | wait_grid "$SOCK60" "lph-newcomer-c" "layout-heal: session c marker" c | 735 | wait_grid "$SOCK60" "lph-doomed-b" "layout-heal: session b marker" b |
| 679 | 736 | ||
| 680 | # Wall file starts with a and b. The first ptyclient detaches and saves | 737 | # One line, the daemon. Its two sessions are the two panes the first |
| 681 | # a 2-pane sidecar for [a, b]. | 738 | # ptyclient saves a sidecar for. |
| 682 | mkdir -p "$LPHSTATE/mux" | 739 | mkdir -p "$LPHSTATE/mux" |
| 683 | printf -- '--sock %s#a\n--sock %s#b\n' "$SOCK60" "$SOCK60" > "$LPHWALL" | 740 | printf -- '--sock %s\n' "$SOCK60" > "$LPHHOSTS" |
| 741 | # No saved tree yet, for the restore leg's reason. | ||
| 742 | rm -f "$LPHLAYOUT" | ||
| 684 | 743 | ||
| 685 | set +e | 744 | set +e |
| 686 | XDG_STATE_HOME="$LPHSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ | 745 | XDG_STATE_HOME="$LPHSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ |
| 687 | --out "$OUT.lphcap1" --err "$OUT.lphcap1.err" -- \ | 746 | --out "$OUT.lphcap1" --err "$OUT.lphcap1.err" -- \ |
| 688 | "$MUX" wall > "$OUT.lphpc1" 2>&1 <<'EOF' | 747 | "$MUX" > "$OUT.lphpc1" 2>&1 <<'EOF' |
| 689 | expect lph-survivor-a 20000 | 748 | expect lph-survivor-0 20000 |
| 690 | settle 700 20000 | 749 | settle 700 20000 |
| 691 | send \x1cd | 750 | send \x1cd |
| 692 | waitexit 10000 | 751 | waitexit 10000 |
| @@ -700,18 +759,29 @@ set -e | |||
| 700 | echo "e2e FAIL: layout-heal: sidecar not saved after first detach" | 759 | echo "e2e FAIL: layout-heal: sidecar not saved after first detach" |
| 701 | ls -la "$LPHSTATE/mux/" 2>&1; exit 1; } | 760 | ls -la "$LPHSTATE/mux/" 2>&1; exit 1; } |
| 702 | 761 | ||
| 703 | # Drift: remove session a's line, add session c's line. The sidecar still | 762 | # Drift, and the daemon is the one that does it: session b ENDS and |
| 704 | # describes [a, b]; the wall file now says [b, c]. On reattach, b is the | 763 | # session c is born. The sidecar still describes [0, b]; the wall the next |
| 705 | # survivor (matched), c inserts beside it, a collapses out (unmatched | 764 | # poll builds is [0, c]. On reattach, `0` is the survivor (matched), c |
| 706 | # saved leaf). | 765 | # inserts beside it, and b's saved leaf collapses out. Nothing was edited |
| 707 | printf -- '--sock %s#b\n--sock %s#c\n' "$SOCK60" "$SOCK60" > "$LPHWALL" | 766 | # — this is the only kind of drift a list of daemons can suffer. |
| 767 | pipe_mux "$OUT.lphb2" "$OUT.lphb2.err" env XDG_STATE_HOME="$LPHSTATE" timeout 40 "$MUX" --sock "$SOCK60" --session b | ||
| 768 | pipe_send 'exit 0\n' | ||
| 769 | pipe_waitexit "layout-heal: session b's shell" | ||
| 770 | pipe_mux "$OUT.lphc" "$OUT.lphc.err" env XDG_STATE_HOME="$LPHSTATE" timeout 40 "$MUX" --sock "$SOCK60" --session c | ||
| 771 | pipe_send 'printf "lph-%%s\\n" newcomer-c\n' | ||
| 772 | await_out "$OUT.lphc" "lph-newcomer-c" "layout-heal: session c marker never reached the client" | ||
| 773 | pipe_detach | ||
| 774 | wait_grid "$SOCK60" "lph-newcomer-c" "layout-heal: session c marker" c | ||
| 775 | wait_sessions "$SOCK60" 2 "layout-heal: the daemon should hold 0 and c" | ||
| 708 | 776 | ||
| 709 | set +e | 777 | set +e |
| 710 | XDG_STATE_HOME="$LPHSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ | 778 | XDG_STATE_HOME="$LPHSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ |
| 711 | --out "$OUT.lphcap2" --err "$OUT.lphcap2.err" -- \ | 779 | --out "$OUT.lphcap2" --err "$OUT.lphcap2.err" -- \ |
| 712 | "$MUX" wall > "$OUT.lphpc2" 2>&1 <<'EOF' | 780 | "$MUX" > "$OUT.lphpc2" 2>&1 <<'EOF' |
| 713 | expect lph-removed-b 20000 | 781 | expect lph-survivor-0 20000 |
| 714 | settle 700 20000 | 782 | settle 700 20000 |
| 783 | send \x1c1 | ||
| 784 | settle 500 15000 | ||
| 715 | send printf 'lph-heal-%s\n' marker\n | 785 | send printf 'lph-heal-%s\n' marker\n |
| 716 | expect lph-heal-marker 10000 | 786 | expect lph-heal-marker 10000 |
| 717 | settle 500 15000 | 787 | settle 500 15000 |
| @@ -724,23 +794,31 @@ set -e | |||
| 724 | echo "e2e FAIL: layout-heal: second ptyclient exited $RC:" | 794 | echo "e2e FAIL: layout-heal: second ptyclient exited $RC:" |
| 725 | cat "$OUT.lphpc2" "$OUT.lphcap2.err"; exit 1; } | 795 | cat "$OUT.lphpc2" "$OUT.lphcap2.err"; exit 1; } |
| 726 | # The wall must not have refused: the ptyclient exited 0 (checked above), | 796 | # The wall must not have refused: the ptyclient exited 0 (checked above), |
| 727 | # and stderr mentions no layout error. The survivor (b) still reaches its | 797 | # and stderr mentions no layout error. The survivor still reaches its |
| 728 | # session — the marker typed after hydration landed in the focused tile, | 798 | # session — tile 1 is the leaf the sidecar kept, and a marker typed there |
| 729 | # which is tile 0 (session b, the first wall-file line). | 799 | # lands in the session that leaf names. |
| 800 | # | ||
| 801 | # The focus is CHOSEN rather than inherited, and deliberately: where a | ||
| 802 | # heal leaves the focus is the restore leg's claim, and that leg makes it | ||
| 803 | # with no drift in the way. Here the drift is the subject, so the leg | ||
| 804 | # names the tile it means. | ||
| 730 | grep -qi "layout" "$OUT.lphcap2.err" && { | 805 | grep -qi "layout" "$OUT.lphcap2.err" && { |
| 731 | echo "e2e FAIL: layout-heal: stderr mentions layout (heal was not silent):" | 806 | echo "e2e FAIL: layout-heal: stderr mentions layout (heal was not silent):" |
| 732 | cat "$OUT.lphcap2.err"; exit 1; } | 807 | cat "$OUT.lphcap2.err"; exit 1; } |
| 733 | # Two tiles present: the survivor (b) and the newcomer (c). The daemon | 808 | # Two tiles: the survivor and the newcomer. The daemon has 0 and c, and |
| 734 | # has sessions a, b, and c; the wall shows b and c. muxa capture of b | 809 | # the tile the sidecar's surviving leaf carries is the one the marker |
| 735 | # finds the survivor's marker. | 810 | # landed in. |
| 736 | timeout 20 "$MUXA" capture --sock "$SOCK60" --session b > "$OUT.lphfa" 2>&1 | 811 | grep -q -- "--sock $SOCK60#c \[up\]" "$OUT.lphcap2" || { |
| 812 | echo "e2e FAIL: layout-heal: the newcomer never became a tile:" | ||
| 813 | cat "$OUT.lphpc2"; exit 1; } | ||
| 814 | timeout 20 "$MUXA" capture --sock "$SOCK60" > "$OUT.lphfa" 2>&1 | ||
| 737 | grep -q "lph-heal-marker" "$OUT.lphfa" || { | 815 | grep -q "lph-heal-marker" "$OUT.lphfa" || { |
| 738 | echo "e2e FAIL: layout-heal: survivor's marker not in session b:" | 816 | echo "e2e FAIL: layout-heal: the survivor's marker is not in the session its leaf kept:" |
| 739 | cat "$OUT.lphfa"; exit 1; } | 817 | cat "$OUT.lphfa"; exit 1; } |
| 740 | assert_stopped "$SOCK60" "$D61PID" "layout-heal" "$OUT.lphstop" | 818 | assert_stopped "$SOCK60" "$D61PID" "layout-heal" "$OUT.lphstop" |
| 741 | D61PID="" | 819 | D61PID="" |
| 742 | rm -rf "$LPHSTATE" | 820 | rm -rf "$LPHSTATE" |
| 743 | ok "the layout heals on wall-file drift: survivor keeps its session, newcomer joins" | 821 | ok "the layout heals on live drift: the survivor keeps its session, the newcomer joins" |
| 744 | 822 | ||
| 745 | # ---- layout sidecar: silent degrade on garbage ----------------------- | 823 | # ---- layout sidecar: silent degrade on garbage ----------------------- |
| 746 | # | 824 | # |
| @@ -751,19 +829,19 @@ ok "the layout heals on wall-file drift: survivor keeps its session, newcomer jo | |||
| 751 | # "layout" (the degrade is silent). | 829 | # "layout" (the degrade is silent). |
| 752 | LPDSTATE="${TMPDIR:-/tmp}/mux-e2e-lpdegrade-state-$$" | 830 | LPDSTATE="${TMPDIR:-/tmp}/mux-e2e-lpdegrade-state-$$" |
| 753 | defer_rm "$LPDSTATE" | 831 | defer_rm "$LPDSTATE" |
| 754 | LPDWALL="$LPDSTATE/mux/wall" | 832 | LPDHOSTS="$LPDSTATE/mux/hosts" |
| 755 | LPDLAYOUT="$LPDSTATE/mux/layout" | 833 | LPDLAYOUT="$LPDSTATE/mux/layout" |
| 756 | start_daemon "$SOCK61" "$OUT.lpd.d" "layout-degrade daemon never bound" --shell /bin/sh | 834 | start_daemon "$SOCK61" "$OUT.lpd.d" "layout-degrade daemon never bound" --shell /bin/sh |
| 757 | D62PID=$DPID | 835 | D62PID=$DPID |
| 758 | 836 | ||
| 759 | pipe_mux "$OUT.lpda" "$OUT.lpda.err" timeout 40 "$MUX" --sock "$SOCK61" --session a | 837 | pipe_mux "$OUT.lpda" "$OUT.lpda.err" env XDG_STATE_HOME="$LPDSTATE" timeout 40 "$MUX" --sock "$SOCK61" |
| 760 | pipe_send 'printf "lpd-%%s\\n" origin\n' | 838 | pipe_send 'printf "lpd-%%s\\n" origin\n' |
| 761 | await_out "$OUT.lpda" "lpd-origin" "layout-degrade: session a marker never reached the client" | 839 | await_out "$OUT.lpda" "lpd-origin" "layout-degrade: the default session's marker never reached the client" |
| 762 | pipe_detach | 840 | pipe_detach |
| 763 | wait_grid "$SOCK61" "lpd-origin" "layout-degrade: session a marker" a | 841 | wait_grid "$SOCK61" "lpd-origin" "layout-degrade: the default session's marker" |
| 764 | 842 | ||
| 765 | mkdir -p "$LPDSTATE/mux" | 843 | mkdir -p "$LPDSTATE/mux" |
| 766 | printf -- '--sock %s#a\n' "$SOCK61" > "$LPDWALL" | 844 | printf -- '--sock %s\n' "$SOCK61" > "$LPDHOSTS" |
| 767 | # A garbage sidecar: wrong magic, wrong version. restoreLayout returns | 845 | # A garbage sidecar: wrong magic, wrong version. restoreLayout returns |
| 768 | # null on the header check and the caller builds the default tree. | 846 | # null on the header check and the caller builds the default tree. |
| 769 | printf 'bogus 9\n' > "$LPDLAYOUT" | 847 | printf 'bogus 9\n' > "$LPDLAYOUT" |
| @@ -771,7 +849,7 @@ printf 'bogus 9\n' > "$LPDLAYOUT" | |||
| 771 | set +e | 849 | set +e |
| 772 | XDG_STATE_HOME="$LPDSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ | 850 | XDG_STATE_HOME="$LPDSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ |
| 773 | --out "$OUT.lpdcap" --err "$OUT.lpdcap.err" -- \ | 851 | --out "$OUT.lpdcap" --err "$OUT.lpdcap.err" -- \ |
| 774 | "$MUX" wall > "$OUT.lpdpc" 2>&1 <<'EOF' | 852 | "$MUX" > "$OUT.lpdpc" 2>&1 <<'EOF' |
| 775 | expect lpd-origin 20000 | 853 | expect lpd-origin 20000 |
| 776 | settle 700 20000 | 854 | settle 700 20000 |
| 777 | send printf 'lpd-degrade-%s\n' marker\n | 855 | send printf 'lpd-degrade-%s\n' marker\n |
| @@ -786,7 +864,7 @@ set -e | |||
| 786 | echo "e2e FAIL: layout-degrade: ptyclient exited $RC:" | 864 | echo "e2e FAIL: layout-degrade: ptyclient exited $RC:" |
| 787 | cat "$OUT.lpdpc" "$OUT.lpdcap.err"; exit 1; } | 865 | cat "$OUT.lpdpc" "$OUT.lpdcap.err"; exit 1; } |
| 788 | # The wall came up: the marker reached the focused session. | 866 | # The wall came up: the marker reached the focused session. |
| 789 | timeout 20 "$MUXA" capture --sock "$SOCK61" --session a > "$OUT.lpdfa" 2>&1 | 867 | timeout 20 "$MUXA" capture --sock "$SOCK61" > "$OUT.lpdfa" 2>&1 |
| 790 | grep -q "lpd-degrade-marker" "$OUT.lpdfa" || { | 868 | grep -q "lpd-degrade-marker" "$OUT.lpdfa" || { |
| 791 | echo "e2e FAIL: layout-degrade: marker not in session a (wall did not come up):" | 869 | echo "e2e FAIL: layout-degrade: marker not in session a (wall did not come up):" |
| 792 | cat "$OUT.lpdfa"; exit 1; } | 870 | cat "$OUT.lpdfa"; exit 1; } |
test/e2e_13_birth.sh
| Old | New | ||
|---|---|---|---|
| @@ -2,24 +2,6 @@ | |||
| 2 | # e2e_13_birth.sh — sourced by test/e2e.sh after e2e_lib.sh. Scenarios run in | 2 | # e2e_13_birth.sh — sourced by test/e2e.sh after e2e_lib.sh. Scenarios run in |
| 3 | # the order they stand in; see the lib's header for what this file may | 3 | # the order they stand in; see the lib's header for what this file may |
| 4 | # assume and what it must register. | 4 | # assume and what it must register. |
| 5 | # The hydrated-create leg. One daemon, and it serves QUIC as well as its | ||
| 6 | # socket: the leg's whole point is the LOCAL/remote split, so both halves | ||
| 7 | # have to be the same run of the same daemon or the comparison is between | ||
| 8 | # two rigs rather than between two kinds of line. Short socket name on | ||
| 9 | # purpose — the assertion reads a label bar back off a 70-column render. | ||
| 10 | SOCK64="${TMPDIR:-/tmp}/muxd-e2e-hyd-$$.sock" | ||
| 11 | defer_sock "$SOCK64" | ||
| 12 | # Base 6000, below the 5000-spaced band the other QUIC ports share: every | ||
| 13 | # base from 11000 up is already taken, and a DUPLICATED base passes only | ||
| 14 | # for as long as the other leg's daemon happens to be dead by the time | ||
| 15 | # this one binds. A collision reads as this daemon's failure, not the | ||
| 16 | # suite's, which is why the base has to be the leg's own. | ||
| 17 | HYPORT=$(( 6000 + ($$ % 4000) )) | ||
| 18 | HYKEY="${TMPDIR:-/tmp}/mux-e2e-hydkey-$$" | ||
| 19 | defer_rm "$HYKEY" | ||
| 20 | HYSTATE="${TMPDIR:-/tmp}/mux-e2e-hyd-state-$$" | ||
| 21 | defer_rm "$HYSTATE" | ||
| 22 | HYWALL="$HYSTATE/mux/wall" | ||
| 23 | # The refused-attach leg: its own daemon, because its assertions are about | 5 | # The refused-attach leg: its own daemon, because its assertions are about |
| 24 | # what a daemon says to a name it does NOT have, and a socket shared with | 6 | # what a daemon says to a name it does NOT have, and a socket shared with |
| 25 | # another leg would let that leg's sessions decide what "does not have" | 7 | # another leg would let that leg's sessions decide what "does not have" |
| @@ -57,21 +39,22 @@ SPDIR="${TMPDIR:-/tmp}/mux-e2e-sp-shim-$$" | |||
| 57 | defer_rm "$SPDIR" | 39 | defer_rm "$SPDIR" |
| 58 | SPINLOG="$SPDIR/dials" | 40 | SPINLOG="$SPDIR/dials" |
| 59 | 41 | ||
| 60 | # ---- Ctrl-\ : adds a tile by spelling --------------------------------- | 42 | # ---- Ctrl-\ : adds a HOST by spelling --------------------------------- |
| 61 | # | 43 | # |
| 62 | # Argv typed from inside. A spelling naming a session on ANOTHER daemon | 44 | # `mux hosts add` typed from inside. A spelling names a DAEMON: the line |
| 63 | # is born beside the focus, creates that session there, takes the focus, | 45 | # goes into the hosts file, a poller starts, and that daemon's sessions |
| 64 | # and is recorded into the wall file — mux TARGET's row of the birth | 46 | # become tiles — the prompt cannot conjure a session, only ask a machine |
| 65 | # table, without leaving the wall. A bad spelling is a notice and | 47 | # what it is running. A bad spelling is a notice and nothing else (the |
| 66 | # nothing else (the file does not grow). Esc eats the line: the shell | 48 | # file does not grow). Esc eats the line: the shell never sees it, and the |
| 67 | # never sees it, and the next keys reach the session again. | 49 | # next keys reach the session again. |
| 68 | # | 50 | # |
| 69 | # The prompt echoes what is typed, so the born tile's witness is not its | 51 | # The prompt echoes what is typed, so the added host's witness is not its |
| 70 | # label on the capture (the echo would match) but a marker typed AFTER | 52 | # label on the capture (the echo would match) but daemon B's OWN SHELL's |
| 71 | # the birth landing in daemon B's session b — a hit is B's shell's work. | 53 | # output painting on this wall — a marker B printed before this client |
| 54 | # existed, which only a real attach to B could put here. | ||
| 72 | PRSTATE="${TMPDIR:-/tmp}/mux-e2e-prompt-state-$$" | 55 | PRSTATE="${TMPDIR:-/tmp}/mux-e2e-prompt-state-$$" |
| 73 | defer_rm "$PRSTATE" | 56 | defer_rm "$PRSTATE" |
| 74 | PRWALL="$PRSTATE/mux/wall" | 57 | PRHOSTS="$PRSTATE/mux/hosts" |
| 75 | # Two daemons, because the assertion is that the birth landed on the one | 58 | # Two daemons, because the assertion is that the birth landed on the one |
| 76 | # the spelling NAMED: one socket could only ever say a session was created, | 59 | # the spelling NAMED: one socket could only ever say a session was created, |
| 77 | # not which daemon created it. | 60 | # not which daemon created it. |
| @@ -84,14 +67,25 @@ D63PID=$DPID | |||
| 84 | start_daemon "$SOCK63" "$OUT.prb.d" "prompt daemon B never bound" --shell /bin/sh | 67 | start_daemon "$SOCK63" "$OUT.prb.d" "prompt daemon B never bound" --shell /bin/sh |
| 85 | D64PID=$DPID | 68 | D64PID=$DPID |
| 86 | 69 | ||
| 87 | pipe_mux "$OUT.pra" "$OUT.pra.err" timeout 40 "$MUX" --sock "$SOCK62" --session a | 70 | pipe_mux "$OUT.pra" "$OUT.pra.err" env XDG_STATE_HOME="$PRSTATE" timeout 40 "$MUX" --sock "$SOCK62" |
| 88 | pipe_send 'printf "pr-%%s\\n" origin\n' | 71 | pipe_send 'printf "pr-%%s\\n" origin\n' |
| 89 | await_out "$OUT.pra" "pr-origin" "prompt: session a marker never reached the client" | 72 | await_out "$OUT.pra" "pr-origin" "prompt: daemon A's marker never reached the client" |
| 90 | pipe_detach | 73 | pipe_detach |
| 91 | wait_grid "$SOCK62" "pr-origin" "prompt: session a marker" a | 74 | wait_grid "$SOCK62" "pr-origin" "prompt: daemon A's marker" |
| 75 | # Daemon B's shell prints its own marker BEFORE this leg's wall exists. | ||
| 76 | # That is the witness: nothing on the wall can paint it unless the prompt | ||
| 77 | # really put B on the wall and B's session really became a tile. | ||
| 78 | pipe_mux "$OUT.prb" "$OUT.prb.err" env XDG_STATE_HOME="$PRSTATE" timeout 40 "$MUX" --sock "$SOCK63" | ||
| 79 | pipe_send 'printf "pr-%%s\\n" bmark\n' | ||
| 80 | await_out "$OUT.prb" "pr-bmark" "prompt: daemon B's marker never reached the client" | ||
| 81 | pipe_detach | ||
| 82 | wait_grid "$SOCK63" "pr-bmark" "prompt: daemon B's marker" | ||
| 92 | 83 | ||
| 93 | mkdir -p "$PRSTATE/mux" | 84 | mkdir -p "$PRSTATE/mux" |
| 94 | printf -- '--sock %s#a\n' "$SOCK62" > "$PRWALL" | 85 | printf -- '--sock %s\n' "$SOCK62" > "$PRHOSTS" |
| 86 | # No saved tree: the setup attaches are walls too, and each saved a | ||
| 87 | # one-leaf sidecar on its way out. | ||
| 88 | rm -f "$PRSTATE/mux/layout" | ||
| 95 | 89 | ||
| 96 | # The spelling goes in TWO sends with a settle between: the prompt paints | 90 | # The spelling goes in TWO sends with a settle between: the prompt paints |
| 97 | # its echo when a read ENDS with the prompt still open, so a spelling that | 91 | # its echo when a read ENDS with the prompt still open, so a spelling that |
| @@ -100,17 +94,16 @@ printf -- '--sock %s#a\n' "$SOCK62" > "$PRWALL" | |||
| 100 | set +e | 94 | set +e |
| 101 | XDG_STATE_HOME="$PRSTATE" timeout 90 "$PTYCLIENT" --cols 100 --rows 30 \ | 95 | XDG_STATE_HOME="$PRSTATE" timeout 90 "$PTYCLIENT" --cols 100 --rows 30 \ |
| 102 | --out "$OUT.prcap" --err "$OUT.prcap.err" -- \ | 96 | --out "$OUT.prcap" --err "$OUT.prcap.err" -- \ |
| 103 | "$MUX" wall > "$OUT.prpc" 2>&1 <<EOF | 97 | "$MUX" > "$OUT.prpc" 2>&1 <<EOF |
| 104 | expect pr-origin 20000 | 98 | expect pr-origin 20000 |
| 105 | settle 700 20000 | 99 | settle 700 20000 |
| 106 | send \x1c:--sock\x20 | 100 | send \x1c:--sock\x20 |
| 107 | settle 300 5000 | 101 | settle 300 5000 |
| 108 | send $SOCK63#b\r | 102 | send $SOCK63\r |
| 103 | expect pr-bmark 20000 | ||
| 109 | settle 1000 20000 | 104 | settle 1000 20000 |
| 110 | send printf 'pr-born-%s\n' marker\n | ||
| 111 | expect pr-born-marker 15000 | ||
| 112 | send \x1c:x#bad name\r | 105 | send \x1c:x#bad name\r |
| 113 | expect [bad target 10000 | 106 | expect [bad host 10000 |
| 114 | settle 500 15000 | 107 | settle 500 15000 |
| 115 | send \x1c:-A nosuchhost.invalid\r | 108 | send \x1c:-A nosuchhost.invalid\r |
| 116 | expect FlagLikeTarget 10000 | 109 | expect FlagLikeTarget 10000 |
| @@ -153,202 +146,43 @@ grep -q -- "-A nosuchhost.invalid \[" "$OUT.prgrid" && { | |||
| 153 | grep -aq ": --sock " "$OUT.prcap" || { | 146 | grep -aq ": --sock " "$OUT.prcap" || { |
| 154 | echo "e2e FAIL: prompt: the prompt line never painted its echo:" | 147 | echo "e2e FAIL: prompt: the prompt line never painted its echo:" |
| 155 | cat "$OUT.prpc"; exit 1; } | 148 | cat "$OUT.prpc"; exit 1; } |
| 156 | # Born on daemon B, focused: the marker typed after the birth is in B's | 149 | # Daemon B is really on this wall: its session got a stripe of its own, |
| 157 | # session b, and so are the keys typed after the Esc. | 150 | # naming B's socket, and the focus never left daemon A — the keys typed |
| 158 | timeout 20 "$MUXA" capture --sock "$SOCK63" --session b > "$OUT.prfb" 2>&1 | 151 | # after the Esc are in A's session and in nothing of B's. |
| 159 | grep -q "pr-born-marker" "$OUT.prfb" || { | 152 | # B's own shell's output, on THIS wall's terminal. Not the bar: two |
| 160 | echo "e2e FAIL: prompt: marker not in daemon B's session b (tile not born there, or not focused):" | 153 | # stripes over 100 columns cut a socket path in half, and `labelText` |
| 161 | cat "$OUT.prfb"; exit 1; } | 154 | # spends what is left on the state word. The marker cannot be an echo — |
| 162 | grep -q "pr-after-esc" "$OUT.prfb" || { | 155 | # B printed it before this process existed. |
| 163 | echo "e2e FAIL: prompt: keys after Esc never reached session b (prompt did not close):" | 156 | grep -qa "pr-bmark" "$OUT.prcap" || { |
| 164 | cat "$OUT.prfb"; exit 1; } | 157 | echo "e2e FAIL: prompt: the added host's session never painted on the wall:" |
| 165 | grep -q "zzz" "$OUT.prfb" && { | 158 | cat "$OUT.prpc"; exit 1; } |
| 159 | timeout 20 "$MUXA" capture --sock "$SOCK62" > "$OUT.prfa" 2>&1 | ||
| 160 | grep -q "pr-after-esc" "$OUT.prfa" || { | ||
| 161 | echo "e2e FAIL: prompt: keys after Esc never reached the focused session (prompt did not close):" | ||
| 162 | cat "$OUT.prfa"; exit 1; } | ||
| 163 | grep -q "zzz" "$OUT.prfa" && { | ||
| 166 | echo "e2e FAIL: prompt: Esc leaked the line into the shell:" | 164 | echo "e2e FAIL: prompt: Esc leaked the line into the shell:" |
| 165 | cat "$OUT.prfa"; exit 1; } | ||
| 166 | timeout 20 "$MUXA" capture --sock "$SOCK63" > "$OUT.prfb" 2>&1 | ||
| 167 | grep -q "pr-after-esc" "$OUT.prfb" && { | ||
| 168 | echo "e2e FAIL: prompt: adding a host moved the focus onto it:" | ||
| 167 | cat "$OUT.prfb"; exit 1; } | 169 | cat "$OUT.prfb"; exit 1; } |
| 168 | # Recorded, and only the accepted spelling: two lines, the born one among them. | 170 | # Recorded, and only the accepted spelling: two lines, the added one among |
| 169 | grep -qF -- "--sock $SOCK63#b" "$PRWALL" || { | 171 | # them. The file is the wall the NEXT `mux` opens, so a host the user is |
| 170 | echo "e2e FAIL: prompt: the born tile was not recorded:" | 172 | # looking at and a host they get back are the same host. |
| 171 | cat "$PRWALL"; exit 1; } | 173 | grep -qxF -- "--sock $SOCK63" "$PRHOSTS" || { |
| 172 | [ "$(grep -c . "$PRWALL")" = "2" ] || { | 174 | echo "e2e FAIL: prompt: the added host was not recorded:" |
| 173 | echo "e2e FAIL: prompt: wall file is not exactly two lines (a refused spelling recorded?):" | 175 | cat "$PRHOSTS"; exit 1; } |
| 174 | cat "$PRWALL"; exit 1; } | 176 | [ "$(grep -c . "$PRHOSTS")" = "2" ] || { |
| 177 | echo "e2e FAIL: prompt: hosts file is not exactly two lines (a refused spelling recorded?):" | ||
| 178 | cat "$PRHOSTS"; exit 1; } | ||
| 175 | assert_stopped "$SOCK62" "$D63PID" "prompt A" "$OUT.prastop" | 179 | assert_stopped "$SOCK62" "$D63PID" "prompt A" "$OUT.prastop" |
| 176 | D63PID="" | 180 | D63PID="" |
| 177 | assert_stopped "$SOCK63" "$D64PID" "prompt B" "$OUT.prbstop" | 181 | assert_stopped "$SOCK63" "$D64PID" "prompt B" "$OUT.prbstop" |
| 178 | D64PID="" | 182 | D64PID="" |
| 179 | rm -rf "$PRSTATE" | 183 | rm -rf "$PRSTATE" |
| 180 | ok "Ctrl-\\ : adds a tile by spelling: born on another daemon, recorded, refusals narrated, Esc eats the line" | 184 | ok "Ctrl-\\ : adds a host by spelling: its sessions become tiles, recorded, refusals narrated, Esc eats the line" |
| 181 | |||
| 182 | # ---- a saved local line comes back as a fresh session ------------------- | ||
| 183 | # | ||
| 184 | # The wall file is attach history, and the daemon that held those sessions | ||
| 185 | # dies on every reboot. Joining only meant the wall came back as a grid of | ||
| 186 | # [refused] tiles nobody could do anything with but forget them by hand. | ||
| 187 | # A restored line naming the LOCAL socket now attaches-or-creates. | ||
| 188 | # | ||
| 189 | # The remote half is the same wall, the same run and the same daemon, | ||
| 190 | # reached the other way: a `quic://` line for a session that does not | ||
| 191 | # exist stays [refused] and creates nothing. Recreating a shell on your | ||
| 192 | # own box is cheap and expected; spawning one on another host out of a | ||
| 193 | # saved file is not, and the split is the whole ruling. | ||
| 194 | # | ||
| 195 | # Daemon truth, not paint, answers "was it created": `muxa status` on a | ||
| 196 | # name the daemon does not have exits non-zero, which is the same probe | ||
| 197 | # the leg runs BEFORE the wall so it cannot pass vacuously. The render | ||
| 198 | # oracle answers the other question — what the human was looking at — | ||
| 199 | # because a bar left saying [refused] is invisible to a grep of the | ||
| 200 | # emitted bytes. | ||
| 201 | # | ||
| 202 | # Both roads a restored line can take are here, because they are two call | ||
| 203 | # sites of the one rule: the no-argv `mux wall` below runs the startup tile | ||
| 204 | # loop, and the `Ctrl-\ w` half at the end of this leg runs `hydrate()`. | ||
| 205 | head -c 32 /dev/urandom > "$HYKEY" | ||
| 206 | chmod 600 "$HYKEY" | ||
| 207 | start_daemon "$SOCK64" "$OUT.hyd.d" "hydrate-create daemon never bound" --shell /bin/sh \ | ||
| 208 | --quic "127.0.0.1:$HYPORT" --key "$HYKEY" --quic-idle-ms 15000 | ||
| 209 | D65PID=$DPID | ||
| 210 | |||
| 211 | # The wall file names three sessions this daemon does not have. Written by | ||
| 212 | # hand rather than earned by an attach, because an attach that earned the | ||
| 213 | # line would also have created the session — and then there would be | ||
| 214 | # nothing for the restore to create. | ||
| 215 | mkdir -p "$HYSTATE/mux" | ||
| 216 | printf -- '--sock %s#hya\n--sock %s#hyb\nquic://127.0.0.1:%s#hyghost\n' \ | ||
| 217 | "$SOCK64" "$SOCK64" "$HYPORT" > "$HYWALL" | ||
| 218 | |||
| 219 | # Vacuity guard: none of the three exists yet. A leg whose daemon already | ||
| 220 | # had them would pass without the restore creating anything. | ||
| 221 | for _hy in hya hyb hyghost; do | ||
| 222 | timeout 20 "$MUXA" status --sock "$SOCK64" --session "$_hy" > "$OUT.hydpre" 2>&1 && { | ||
| 223 | echo "e2e FAIL: hydrate-create: session $_hy existed before the wall ran:" | ||
| 224 | cat "$OUT.hydpre"; exit 1; } | ||
| 225 | done | ||
| 226 | |||
| 227 | # 70x36: under the aspect rule (70 < 2*36) the cut is stacked, so each of | ||
| 228 | # the three tiles gets the full width and its label bar survives the | ||
| 229 | # render intact — the socket path is in that label. | ||
| 230 | set +e | ||
| 231 | XDG_STATE_HOME="$HYSTATE" timeout 90 "$PTYCLIENT" --cols 70 --rows 36 \ | ||
| 232 | --out "$OUT.hydcap" --err "$OUT.hydcap.err" -- \ | ||
| 233 | "$MUX" wall --key "$HYKEY" > "$OUT.hydpc" 2>&1 <<'EOF' | ||
| 234 | expect [up] 25000 | ||
| 235 | settle 1000 25000 | ||
| 236 | send printf 'hyd-%s\n' marker\n | ||
| 237 | expect hyd-marker 15000 | ||
| 238 | settle 600 15000 | ||
| 239 | send \x1cd | ||
| 240 | waitexit 10000 | ||
| 241 | EOF | ||
| 242 | RC=$? | ||
| 243 | set -e | ||
| 244 | [ "$RC" -eq 0 ] || { | ||
| 245 | echo "e2e FAIL: hydrate-create: ptyclient exited $RC:" | ||
| 246 | cat "$OUT.hydpc" "$OUT.hydcap.err"; exit 1; } | ||
| 247 | |||
| 248 | # The local lines created their sessions, at the width of the stripe that | ||
| 249 | # created them. The width does not discriminate create from join on its own | ||
| 250 | # (a join's resize doorbell lands at 70 too, and the daemon refuses a create | ||
| 251 | # under min_session_cols) — the muxa exit code above is what says created. | ||
| 252 | # It is here to catch a session that came up at some OTHER client's size. | ||
| 253 | timeout 20 "$MUXA" status --sock "$SOCK64" --session hya > "$OUT.hydsta" 2>&1 || { | ||
| 254 | echo "e2e FAIL: hydrate-create: the saved line for hya did not create it:" | ||
| 255 | cat "$OUT.hydsta"; exit 1; } | ||
| 256 | grep -q '"cols":70' "$OUT.hydsta" || { | ||
| 257 | echo "e2e FAIL: hydrate-create: hya exists but not at the tile's width:" | ||
| 258 | cat "$OUT.hydsta"; exit 1; } | ||
| 259 | timeout 20 "$MUXA" status --sock "$SOCK64" --session hyb > "$OUT.hydstb" 2>&1 || { | ||
| 260 | echo "e2e FAIL: hydrate-create: the saved line for hyb did not create it:" | ||
| 261 | cat "$OUT.hydstb"; exit 1; } | ||
| 262 | |||
| 263 | # ...and the created session runs a real shell: the marker typed into the | ||
| 264 | # focused tile (tile 0, the first wall line) is the shell's own work. | ||
| 265 | timeout 20 "$MUXA" capture --sock "$SOCK64" --session hya > "$OUT.hydfa" 2>&1 | ||
| 266 | grep -q "hyd-marker" "$OUT.hydfa" || { | ||
| 267 | echo "e2e FAIL: hydrate-create: the marker never reached the created session:" | ||
| 268 | cat "$OUT.hydfa"; exit 1; } | ||
| 269 | |||
| 270 | # The remote line created NOTHING. Same daemon, same run, same file — only | ||
| 271 | # the spelling differs. | ||
| 272 | timeout 20 "$MUXA" status --sock "$SOCK64" --session hyghost > "$OUT.hydghost" 2>&1 && { | ||
| 273 | echo "e2e FAIL: hydrate-create: a quic:// wall line created a session:" | ||
| 274 | cat "$OUT.hydghost"; exit 1; } | ||
| 275 | |||
| 276 | # What the human saw: two tiles up, the remote one still refused. | ||
| 277 | "$RENDER" --cols 70 --rows 36 < "$OUT.hydcap" > "$OUT.hydgrid" || { | ||
| 278 | echo "e2e FAIL: hydrate-create: render oracle failed" | ||
| 279 | cat "$OUT.hydgrid"; exit 1; } | ||
| 280 | grep -q -- "#hya \[up\]" "$OUT.hydgrid" || { | ||
| 281 | echo "e2e FAIL: hydrate-create: the hya tile never said [up]:" | ||
| 282 | cat "$OUT.hydgrid"; exit 1; } | ||
| 283 | grep -q -- "#hyb \[up\]" "$OUT.hydgrid" || { | ||
| 284 | echo "e2e FAIL: hydrate-create: the hyb tile never said [up]:" | ||
| 285 | cat "$OUT.hydgrid"; exit 1; } | ||
| 286 | grep -q -- "#hyghost \[refused\]" "$OUT.hydgrid" || { | ||
| 287 | echo "e2e FAIL: hydrate-create: the remote tile did not say [refused]:" | ||
| 288 | cat "$OUT.hydgrid"; exit 1; } | ||
| 289 | # ...and each bar carries the chord that reaches it. Ctrl-\ 1-9 focuses by | ||
| 290 | # POSITION while the bar named the session, and the two series disagree the | ||
| 291 | # moment a tile is forgotten or a session dies. Tile 1 has the focus here, | ||
| 292 | # so its digit carries the `>` and the other two the same-width blank. | ||
| 293 | grep -q -- " 1> .*#hya \[up\]" "$OUT.hydgrid" || { | ||
| 294 | echo "e2e FAIL: hydrate-create: the focused bar does not read \` 1> \`:" | ||
| 295 | cat "$OUT.hydgrid"; exit 1; } | ||
| 296 | grep -q -- " 2 .*#hyb \[up\]" "$OUT.hydgrid" || { | ||
| 297 | echo "e2e FAIL: hydrate-create: the second tile's bar does not read \` 2 \`:" | ||
| 298 | cat "$OUT.hydgrid"; exit 1; } | ||
| 299 | grep -q -- " 3 .*#hyghost \[refused\]" "$OUT.hydgrid" || { | ||
| 300 | echo "e2e FAIL: hydrate-create: a refused tile lost its chord digit:" | ||
| 301 | cat "$OUT.hydgrid"; exit 1; } | ||
| 302 | # The OTHER road a restored line takes: `Ctrl-\ w`. A no-argv `mux wall` | ||
| 303 | # runs the startup tile loop; the fold runs `hydrate()`, a different call | ||
| 304 | # site of the same rule, and the ruling covers both. Same daemon, same | ||
| 305 | # state home, one more ptyclient — a `mux TARGET` is the entry tile, and | ||
| 306 | # the fold brings the rest of the file in around it. | ||
| 307 | # | ||
| 308 | # The file is rewritten to name a session this daemon does not have. The | ||
| 309 | # entry attach then records its OWN line into the same file (attach | ||
| 310 | # history), which the fold dedupes by spelling — so what the fold has left | ||
| 311 | # to add is exactly the absent one, and creating it is the whole claim. | ||
| 312 | printf -- '--sock %s#hyfold\n' "$SOCK64" > "$HYWALL" | ||
| 313 | timeout 20 "$MUXA" status --sock "$SOCK64" --session hyfold > "$OUT.hydpre" 2>&1 && { | ||
| 314 | echo "e2e FAIL: hydrate-create: session hyfold existed before the fold ran:" | ||
| 315 | cat "$OUT.hydpre"; exit 1; } | ||
| 316 | 185 | ||
| 317 | set +e | ||
| 318 | XDG_STATE_HOME="$HYSTATE" timeout 90 "$PTYCLIENT" --cols 70 --rows 36 \ | ||
| 319 | --out "$OUT.hydfcap" --err "$OUT.hydfcap.err" -- \ | ||
| 320 | "$MUX" --sock "$SOCK64" --session hya > "$OUT.hydfpc" 2>&1 <<'EOF' | ||
| 321 | settle 1000 25000 | ||
| 322 | send printf 'hydf-%s\n' pre\n | ||
| 323 | expect hydf-pre 20000 | ||
| 324 | settle 600 20000 | ||
| 325 | send \x1cw | ||
| 326 | settle 1500 25000 | ||
| 327 | send \x1cd | ||
| 328 | waitexit 10000 | ||
| 329 | EOF | ||
| 330 | RC=$? | ||
| 331 | set -e | ||
| 332 | [ "$RC" -eq 0 ] || { | ||
| 333 | echo "e2e FAIL: hydrate-create: fold ptyclient exited $RC:" | ||
| 334 | cat "$OUT.hydfpc" "$OUT.hydfcap.err"; exit 1; } | ||
| 335 | |||
| 336 | # Daemon truth again: the fold-born tile created the session its line named. | ||
| 337 | timeout 20 "$MUXA" status --sock "$SOCK64" --session hyfold > "$OUT.hydfold" 2>&1 || { | ||
| 338 | echo "e2e FAIL: hydrate-create: the folded line for hyfold did not create it:" | ||
| 339 | cat "$OUT.hydfold"; exit 1; } | ||
| 340 | # ...and it is a tile on the screen, not merely a session on the daemon. | ||
| 341 | "$RENDER" --cols 70 --rows 36 < "$OUT.hydfcap" > "$OUT.hydfgrid" || { | ||
| 342 | echo "e2e FAIL: hydrate-create: fold render oracle failed" | ||
| 343 | cat "$OUT.hydfgrid"; exit 1; } | ||
| 344 | grep -q -- "#hyfold \[up\]" "$OUT.hydfgrid" || { | ||
| 345 | echo "e2e FAIL: hydrate-create: the fold-born tile never said [up]:" | ||
| 346 | cat "$OUT.hydfgrid"; exit 1; } | ||
| 347 | |||
| 348 | assert_stopped "$SOCK64" "$D65PID" "hydrate-create" "$OUT.hydstop" | ||
| 349 | D65PID="" | ||
| 350 | rm -rf "$HYSTATE" | ||
| 351 | ok "a saved local line comes back as a fresh session, by wall and by fold; a remote one joins only" | ||
| 352 | 186 | ||
| 353 | # --------------------------------------------------------------------------- | 187 | # --------------------------------------------------------------------------- |
| 354 | # A refused attach is not a shell that exited. | 188 | # A refused attach is not a shell that exited. |