a73x

55fba86d

test: the e2e wall legs learn the focus model

a73x   2026-08-24 11:32

Commit message
test: the e2e wall legs learn the focus model

Rewritten to the prefix chords: focus-skip, the wheel and app-mouse
pair, the dead tile, x-forgets, mux TARGET, click-to-focus and drags,
ring-grows, session-ends. Every bare `send q`/`send x` becomes
\x1cd/\x1cx. Deleted with the behaviour: M4, the in-place zoom,
unzoomed-forwards-nothing, the hidden tile, the unzoomed mouse filter,
and the CLI wall's 0x0 passivity check — wall tiles claim their rect
now. The trap re-binds the deleted legs' vars empty so `set -u` cannot
turn a cleanup into a false failure; scenario pin 67 -> 61. M1's
unknown-key probe moves from `x` to `z` — `x` is a bound chord now and
was forgetting the leg's only tile.

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

test/e2e.sh
Old New
@@ -304,18 +304,13 @@ D25PID=""
304 SOCK29="${TMPDIR:-/tmp}/muxd-e2e-ring-$$.sock" 304 SOCK29="${TMPDIR:-/tmp}/muxd-e2e-ring-$$.sock"
305 D26PID="" 305 D26PID=""
306 306
307 # Ctrl-\ w (the wall from inside a session): its own daemon for the SOCK29 307 # The zoom-era wall legs these belonged to are gone, but the cleanup trap
308 # reason exactly — the wall shows every session this daemon has, so a stray 308 # cleanup trap still names every DxxPID and SOCKxx in bare form and the
309 # session from another block would appear as an extra stripe and could push 309 # script runs under `set -u`. Bound empty so an unbound-variable abort never
310 # the two this block asserts on off a 30-row terminal. 310 # replaces a real failure's exit code; the legs that filled them are deleted.
311 SOCK30="${TMPDIR:-/tmp}/muxd-e2e-wallchord-$$.sock" 311 SOCK30="" SOCK32="" SOCK38="" SOCK43="" SOCK44=""
312 # ...and a state home of its own, from phase 3c on. `Ctrl-\ w` unzooms to 312 M4STATE="" ZHASH="" ZHBSH="" ZHFLAG="" ZHSCRIPT=""
313 # THE wall — the saved file's tiles as well as the ones this run made — so a 313 D27PID="" D29PID="" D35PID="" D40PID="" D41PID=""
314 # shared $XDG_STATE_HOME would put a stripe on this terminal for every
315 # socket the suite has ever attached to, nearly all of them dead. This leg
316 # is about two sessions, so it gets a wall with two sessions in it.
317 M4STATE="${TMPDIR:-/tmp}/mux-e2e-wallchord-state-$$"
318 D27PID=""
319 314
320 # M5 (the self-attach refusal): its own daemon because the assertion reads 315 # M5 (the self-attach refusal): its own daemon because the assertion reads
321 # the SOCKET PATH out of a session shell's environment and compares it to 316 # the SOCKET PATH out of a session shell's environment and compares it to
@@ -324,13 +319,6 @@ D27PID=""
324 SOCK31="${TMPDIR:-/tmp}/muxd-e2e-selfattach-$$.sock" 319 SOCK31="${TMPDIR:-/tmp}/muxd-e2e-selfattach-$$.sock"
325 D28PID="" 320 D28PID=""
326 321
327 # The wall's zoom: its own daemon because the leg asserts on a session's
328 # GRID SIZE — the zoomed client claims it, the tiles must not — and a
329 # session another block created at another size would make that number
330 # say nothing about this one.
331 SOCK32="${TMPDIR:-/tmp}/muxd-e2e-wallzoom-$$.sock"
332 D29PID=""
333
334 # The wheel pair. Each needs a scrollback of its own to scroll (or to prove 322 # The wheel pair. Each needs a scrollback of its own to scroll (or to prove
335 # it did not), which the long-lived /bin/sh daemon has no way to hold still, 323 # it did not), which the long-lived /bin/sh daemon has no way to hold still,
336 # and the second one needs a session that ASKS for the mouse — a mode that 324 # and the second one needs a session that ASKS for the mouse — a mode that
@@ -353,44 +341,30 @@ LESSDATA="${TMPDIR:-/tmp}/mux-e2e-pager-$$.txt"
353 SOCK36="${TMPDIR:-/tmp}/muxd-e2e-pipestdin-$$.sock" 341 SOCK36="${TMPDIR:-/tmp}/muxd-e2e-pipestdin-$$.sock"
354 D33PID="" 342 D33PID=""
355 343
356 # The zoom-as-lens pair, both on daemons of their own for SOCK32's reason: 344 # The focus-skip leg, on a daemon of its own for the grid-size reason: it
357 # each asserts on grid sizes and on how many clients a session ever had at 345 # asserts on grid sizes and on how many clients a session ever had at once,
358 # once, and a session another block created — or another block's client 346 # and a session another block created — or another block's client sitting
359 # sitting on this daemon — would make both numbers say nothing. 347 # on this daemon — would make both numbers say nothing.
360 SOCK37="${TMPDIR:-/tmp}/muxd-e2e-zoomskip-$$.sock" 348 SOCK37="${TMPDIR:-/tmp}/muxd-e2e-focusskip-$$.sock"
361 D34PID="" 349 D34PID=""
362 SOCK38="${TMPDIR:-/tmp}/muxd-e2e-zoommute-$$.sock"
363 D35PID=""
364 # The dead tile. Its own daemon because the leg needs a session name that 350 # The dead tile. Its own daemon because the leg needs a session name that
365 # does NOT exist — a 0x0 attach joins but never creates, so the daemon 351 # does NOT exist — the daemon refuses the attach — and any other block's
366 # refuses it — and any other block's daemon might have one by that name. 352 # daemon might have one by that name.
367 SOCK39="${TMPDIR:-/tmp}/muxd-e2e-zoomdead-$$.sock" 353 SOCK39="${TMPDIR:-/tmp}/muxd-e2e-deadtile-$$.sock"
368 D36PID="" 354 D36PID=""
369 355
370 # The wheel inside a zoom (phase 3b). Two daemons for the wheel pair's own 356 # The wheel inside the focused tile. Two daemons for the wheel pair's own
371 # reason, turned up one notch: each needs a session whose SHELL is fixed for 357 # reason, turned up one notch: each needs a session whose SHELL is fixed for
372 # the session's whole life — one that scrolls and one that ASKS for the 358 # the session's whole life — one that scrolls and one that ASKS for the
373 # mouse — and a mode another scenario's session set would follow the wall's 359 # mouse — and a mode another scenario's session set would follow the wall's
374 # terminal around. Both use the daemon's DEFAULT session, so the wall 360 # terminal around. Both use the daemon's DEFAULT session, so the wall
375 # spelling is a bare `--sock PATH` and nothing has to create anything. 361 # spelling is a bare `--sock PATH` and nothing has to create anything.
376 SOCK41="${TMPDIR:-/tmp}/muxd-e2e-zoomwheel-$$.sock" 362 SOCK41="${TMPDIR:-/tmp}/muxd-e2e-wallwheel-$$.sock"
377 D38PID="" 363 D38PID=""
378 ZWHEELSH="${TMPDIR:-/tmp}/mux-e2e-zoomwheel-$$.sh" 364 ZWHEELSH="${TMPDIR:-/tmp}/mux-e2e-wallwheel-$$.sh"
379 SOCK42="${TMPDIR:-/tmp}/muxd-e2e-zoomappmouse-$$.sock" 365 SOCK42="${TMPDIR:-/tmp}/muxd-e2e-wallappmouse-$$.sock"
380 D39PID="" 366 D39PID=""
381 ZMOUSESH="${TMPDIR:-/tmp}/mux-e2e-zoomappmouse-$$.sh" 367 ZMOUSESH="${TMPDIR:-/tmp}/mux-e2e-wallappmouse-$$.sh"
382
383 # A tile the zoom is NOT on. Two daemons because the leg needs two sessions
384 # with DIFFERENT shells — one typed at through the zoom, one blocking on a
385 # flag that shell creates — and a daemon has one shell for all its sessions.
386 SOCK43="${TMPDIR:-/tmp}/muxd-e2e-hidetile-a-$$.sock"
387 D40PID=""
388 ZHASH="${TMPDIR:-/tmp}/mux-e2e-hidetile-a-$$.sh"
389 SOCK44="${TMPDIR:-/tmp}/muxd-e2e-hidetile-b-$$.sock"
390 D41PID=""
391 ZHBSH="${TMPDIR:-/tmp}/mux-e2e-hidetile-b-$$.sh"
392 ZHFLAG="${TMPDIR:-/tmp}/mux-e2e-hidetile-$$.flag"
393 ZHSCRIPT="${TMPDIR:-/tmp}/mux-e2e-hidetile-$$.script"
394 368
395 # The wall as attach HISTORY (phase 2). A daemon AND a state home of its 369 # The wall as attach HISTORY (phase 2). A daemon AND a state home of its
396 # own, for the dynamic-wall leg's reason turned up one notch: what these 370 # own, for the dynamic-wall leg's reason turned up one notch: what these
@@ -410,8 +384,8 @@ WHBAD="${TMPDIR:-/tmp}/mux-e2e-wallbad-$$"
410 WHXSTATE="${TMPDIR:-/tmp}/mux-e2e-wallx-state-$$" 384 WHXSTATE="${TMPDIR:-/tmp}/mux-e2e-wallx-state-$$"
411 D37PID="" 385 D37PID=""
412 386
413 # The convergence block (phase 3c): `mux TARGET` is a wall of one tile, 387 # The convergence block: `mux TARGET` is a wall of one tile whose rect is
414 # entered zoomed. Three daemons and three state homes, each of them its own 388 # the whole terminal. Three daemons and three state homes, each of them its own
415 # for the same reason the attach-history block's is — what these legs read 389 # for the same reason the attach-history block's is — what these legs read
416 # back is the wall FILE, and the shared $XDG_STATE_HOME is every socket the 390 # back is the wall FILE, and the shared $XDG_STATE_HOME is every socket the
417 # suite has ever attached to. 391 # suite has ever attached to.
@@ -427,7 +401,7 @@ SOCK46="${TMPDIR:-/tmp}/muxd-e2e-ringgrow-$$.sock"
427 RGSTATE="${TMPDIR:-/tmp}/mux-e2e-ringgrow-state-$$" 401 RGSTATE="${TMPDIR:-/tmp}/mux-e2e-ringgrow-state-$$"
428 RGOTHER="${TMPDIR:-/tmp}/mux-e2e-ringgrow-other-$$" 402 RGOTHER="${TMPDIR:-/tmp}/mux-e2e-ringgrow-other-$$"
429 D46PID="" 403 D46PID=""
430 # The exit-semantics leg: a session ending under the zoom. 404 # The exit-semantics leg: a session ending in the focused tile.
431 SOCK47="${TMPDIR:-/tmp}/muxd-e2e-exitsem-$$.sock" 405 SOCK47="${TMPDIR:-/tmp}/muxd-e2e-exitsem-$$.sock"
432 XESTATE="${TMPDIR:-/tmp}/mux-e2e-exitsem-state-$$" 406 XESTATE="${TMPDIR:-/tmp}/mux-e2e-exitsem-state-$$"
433 D47PID="" 407 D47PID=""
@@ -766,17 +740,17 @@ wait_sessions() {
766 exit 1 740 exit 1
767 } 741 }
768 742
769 # --- the wall's zoom: did the daemon see a second attach? -------------- 743 # --- tiles attach once each: did the daemon see a second? -------------
770 # 744 #
771 # The in-place zoom's whole claim is that it PROMOTES the tile's existing 745 # The focus model's whole claim is that a tile's connection is its own —
772 # connection — no new attach, no dial. Proving a negative needs a 746 # no new attach, no dial on a focus move. Proving a negative needs a
773 # daemon-side witness, and there are two here doing different jobs. 747 # daemon-side witness, and there are two here doing different jobs.
774 # 748 #
775 # THE ASSERTION is `attaches=`, a cumulative counter of every attach this 749 # THE ASSERTION is `attaches=`, a cumulative counter of every attach this
776 # daemon accepted (server.zig Stats). Read once before the wall starts and 750 # daemon accepted (server.zig Stats). Read once before the wall starts and
777 # once after it exits, the delta is exactly how many times anything attached 751 # once after it exits, the delta is exactly how many times anything attached
778 # across the whole leg — which for a wall of N tiles must be N, one per tile 752 # across the whole leg — which for a wall of N tiles must be N, one per tile
779 # at startup, and never N+1 however far the zoom moved. It is read from 753 # at startup, and never N+1 however far the focus moved. It is read from
780 # `muxd stats`, which is plain human text and connects as an OBSERVER: the 754 # `muxd stats`, which is plain human text and connects as an OBSERVER: the
781 # reading itself never attaches, so it cannot pollute what it measures. 755 # reading itself never attaches, so it cannot pollute what it measures.
782 # 756 #
@@ -808,7 +782,7 @@ assert_attach_delta() {
808 exit 1; } 782 exit 1; }
809 [ "$(($2 - $1))" = "$3" ] || { 783 [ "$(($2 - $1))" = "$3" ] || {
810 echo "e2e FAIL: $4: the daemon accepted $(($2 - $1)) attaches (want $3)" 784 echo "e2e FAIL: $4: the daemon accepted $(($2 - $1)) attaches (want $3)"
811 echo " — the wall's tiles attach once each and the zoom never does" 785 echo " — the wall's tiles attach once each and a focus move never does"
812 exit 1; } 786 exit 1; }
813 } 787 }
814 788
@@ -846,7 +820,7 @@ assert_never_two_clients() {
846 exit 1; } 820 exit 1; }
847 [ "$_peak" = "1" ] || { 821 [ "$_peak" = "1" ] || {
848 echo "e2e FAIL: $3: session $2 was watched by $_peak clients at once" 822 echo "e2e FAIL: $3: session $2 was watched by $_peak clients at once"
849 echo " (want 1) — the zoom attached instead of promoting" 823 echo " (want 1) — a focus move attached instead of being local"
850 exit 1; } 824 exit 1; }
851 } 825 }
852 826
@@ -1514,7 +1488,7 @@ cleanup() {
1514 "$OUT.flip.d" \ 1488 "$OUT.flip.d" \
1515 "$OUT.flipa.env" "$OUT.flipa" "$OUT.flipa.err" "$OUT.flipa.log" \ 1489 "$OUT.flipa.env" "$OUT.flipa" "$OUT.flipa.err" "$OUT.flipa.log" \
1516 "$OUT.flipb.env" "$OUT.flipb" "$OUT.flipb.err" "$OUT.flipb.log" 1490 "$OUT.flipb.env" "$OUT.flipb" "$OUT.flipb.err" "$OUT.flipb.log"
1517 # The M1-M6 prefix/wall/zoom captures and the agent legs' three. Absent 1491 # The M1-M6 prefix/wall captures and the agent legs' three. Absent
1518 # from this trap until 2026-08-22, which is why every green run since 1492 # from this trap until 2026-08-22, which is why every green run since
1519 # those features landed left 102 files in $TMPDIR — enough, by 08-19, that 1493 # those features landed left 102 files in $TMPDIR — enough, by 08-19, that
1520 # `make soak` refused to start and no soak had run since. The guard below 1494 # `make soak` refused to start and no soak had run since. The guard below
@@ -1525,11 +1499,10 @@ cleanup() {
1525 # agent forwarding: the mute-offerer leg. 1499 # agent forwarding: the mute-offerer leg.
1526 rm -f "$OUT.agtmute" "$OUT.agtmute.err" "$OUT.agtmute.log" "$OUT.agtmute.env" \ 1500 rm -f "$OUT.agtmute" "$OUT.agtmute.err" "$OUT.agtmute.log" "$OUT.agtmute.env" \
1527 "$OUT.agtmute.d" 1501 "$OUT.agtmute.d"
1528 # the CLI wall: both halves (cw*, wc*) — captures, injections and stop files. 1502 # the CLI wall (cw*) — captures, injections and stop files.
1529 rm -f "$OUT.cwa" "$OUT.cwa.err" "$OUT.cwall.d" "$OUT.cwb" "$OUT.cwb.err" \ 1503 rm -f "$OUT.cwa" "$OUT.cwa.err" "$OUT.cwall.d" "$OUT.cwb" "$OUT.cwb.err" \
1530 "$OUT.cwcap" "$OUT.cwcap.err" "$OUT.cwinj" "$OUT.cwpc" "$OUT.cwst" \ 1504 "$OUT.cwcap" "$OUT.cwcap.err" "$OUT.cwinj" "$OUT.cwpc" "$OUT.cwst" \
1531 "$OUT.cwstop" "$OUT.wc" "$OUT.wccap" "$OUT.wc.d" "$OUT.wc.err" \ 1505 "$OUT.cwstop"
1532 "$OUT.wcinj" "$OUT.wc.log" "$OUT.wcst" "$OUT.wcstop"
1533 # the new-session-from-the-wall leg. 1506 # the new-session-from-the-wall leg.
1534 rm -f "$OUT.nsw" "$OUT.nswcap" "$OUT.nswcap3" "$OUT.nsw.d" \ 1507 rm -f "$OUT.nsw" "$OUT.nswcap" "$OUT.nswcap3" "$OUT.nsw.d" \
1535 "$OUT.nsw.err" "$OUT.nsw.log" "$OUT.nswst" "$OUT.nswst4" \ 1508 "$OUT.nsw.err" "$OUT.nsw.log" "$OUT.nswst" "$OUT.nswst4" \
@@ -1543,18 +1516,10 @@ cleanup() {
1543 # self-attach refusal, and the wall file it reads. 1516 # self-attach refusal, and the wall file it reads.
1544 rm -f "$OUT.sacap1" "$OUT.sacap2" "$OUT.sacap3" "$OUT.sa.d" "$OUT.saenv" \ 1517 rm -f "$OUT.sacap1" "$OUT.sacap2" "$OUT.sacap3" "$OUT.sa.d" "$OUT.saenv" \
1545 "$OUT.saself" "$OUT.sast" "$OUT.sastop" "$OUT.sawall" 1518 "$OUT.saself" "$OUT.sast" "$OUT.sastop" "$OUT.sawall"
1546 # the wall zoom leg. 1519 # the dead-tile leg.
1547 rm -f "$OUT.wza" "$OUT.wza.err" "$OUT.wzb" "$OUT.wzb.err" "$OUT.wzcap" \
1548 "$OUT.wzcapa" "$OUT.wzcapb" "$OUT.wzcap.err" "$OUT.wz.d" "$OUT.wzpc" \
1549 "$OUT.wzsta" "$OUT.wzstb" "$OUT.wzstop" "$OUT.wzwatch"
1550 # zoom demote.
1551 rm -f "$OUT.zda" "$OUT.zda.err" "$OUT.zdcap" "$OUT.zdcap.err" \ 1520 rm -f "$OUT.zda" "$OUT.zda.err" "$OUT.zdcap" "$OUT.zdcap.err" \
1552 "$OUT.zd.d" "$OUT.zdpc" "$OUT.zdstop" 1521 "$OUT.zd.d" "$OUT.zdpc" "$OUT.zdstop" "$OUT.zdcapa"
1553 # zoom between tiles. 1522 # focus skip, and its watchers.
1554 rm -f "$OUT.zma" "$OUT.zma.err" "$OUT.zmb" "$OUT.zmb.err" "$OUT.zmcap" \
1555 "$OUT.zmcap.err" "$OUT.zm.d" "$OUT.zmfa" "$OUT.zmfb" "$OUT.zmpc" \
1556 "$OUT.zmstb" "$OUT.zmstop"
1557 # zoom onto a session, and its watchers.
1558 rm -f "$OUT.zsa" "$OUT.zsa.err" "$OUT.zsb" "$OUT.zsb.err" "$OUT.zscap" \ 1523 rm -f "$OUT.zsa" "$OUT.zsa.err" "$OUT.zsb" "$OUT.zsb.err" "$OUT.zscap" \
1559 "$OUT.zscap.err" "$OUT.zs.d" "$OUT.zsfa" "$OUT.zsfb" "$OUT.zspc" \ 1524 "$OUT.zscap.err" "$OUT.zs.d" "$OUT.zsfa" "$OUT.zsfb" "$OUT.zspc" \
1560 "$OUT.zssta" "$OUT.zsstb" "$OUT.zsstop" "$OUT.zswatch" 1525 "$OUT.zssta" "$OUT.zsstb" "$OUT.zsstop" "$OUT.zswatch"
@@ -2234,7 +2199,6 @@ SNAPS_AFTER=$("$MUXD" stats --sock "$SOCK4" | sed -n 's/.*snapshots=\([0-9]*\).*
2234 } 2199 }
2235 assert_converged "$OUT.qr" "$SOCK4" "quic delta resume" 2200 assert_converged "$OUT.qr" "$SOCK4" "quic delta resume"
2236 2201
2237
2238 # 5. The daemon is killed outright and started again on the SAME paths — unix 2202 # 5. The daemon is killed outright and started again on the SAME paths — unix
2239 # socket and UDP port both. Two things are under test. The client must 2203 # socket and UDP port both. Two things are under test. The client must
2240 # resume into a session that no longer exists, which can only be a 2204 # resume into a session that no longer exists, which can only be a
@@ -4953,9 +4917,9 @@ ok "the wall is runtime state: add, remove, reorder, restore, argv adds"
4953 # both AT ONCE — the browser wall's claim, made by the CLI. Three facts, 4917 # both AT ONCE — the browser wall's claim, made by the CLI. Three facts,
4954 # each with its own witness: both sessions' markers painted (the snapshot 4918 # each with its own witness: both sessions' markers painted (the snapshot
4955 # path), a line injected into b WHILE the wall is attached painted too 4919 # path), a line injected into b WHILE the wall is attached painted too
4956 # (the delta path — the wall is a live replica, not a capture), and `q` 4920 # (the delta path — the wall is a live replica, not a capture), and
4957 # ended the process with exit 0 (the wall left, not died). Markers are 4921 # `\x1cd` ended the process with exit 0 (the wall left, not died). Markers
4958 # shell-EXPANDED (`%s`+pin), the M18 trick: a hit is the shell's work, 4922 # are shell-EXPANDED (`%s`+pin), the M18 trick: a hit is the shell's work,
4959 # never an echo of anything typed here. 4923 # never an echo of anything typed here.
4960 # 4924 #
4961 # The expect ORDER leans on the injection, deliberately: tile threads 4925 # The expect ORDER leans on the injection, deliberately: tile threads
@@ -4992,7 +4956,7 @@ timeout 40 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.cwcap" --err "$OUT.cwca
4992 expect cwa-pin 15000 4956 expect cwa-pin 15000
4993 expect cwb-pin 20000 4957 expect cwb-pin 20000
4994 expect cwlive-pin 5000 4958 expect cwlive-pin 5000
4995 send q 4959 send \x1cd
4996 waitexit 10000 4960 waitexit 10000
4997 EOF 4961 EOF
4998 RC=$? 4962 RC=$?
@@ -5010,16 +4974,12 @@ grep -q "cwb-pin" "$OUT.cwcap" || {
5010 echo "e2e FAIL: CLI wall: b's marker never painted"; exit 1; } 4974 echo "e2e FAIL: CLI wall: b's marker never painted"; exit 1; }
5011 grep -q "cwlive-pin" "$OUT.cwcap" || { 4975 grep -q "cwlive-pin" "$OUT.cwcap" || {
5012 echo "e2e FAIL: CLI wall: the live delta never painted"; exit 1; } 4976 echo "e2e FAIL: CLI wall: the live delta never painted"; exit 1; }
5013 # Passivity, M-web's negative twin: a wall of 0x0 attaches must never 4977 # Every tile claims its rect: a and b were created 80-wide by pipe, and
5014 # have claimed either grid — the daemon still answers with the size the 4978 # this 100-wide wall resized both on attach. There is no 0x0 passivity to
5015 # creating clients set, not a degenerate one. 4979 # assert — the wall writes the sessions it shows.
5016 "$MUXA" status --sock "$SOCK26" --session a > "$OUT.cwst" 2>&1
5017 grep -q '"cols":80' "$OUT.cwst" || {
5018 echo "e2e FAIL: CLI wall: a 0x0 wall attach moved session a's grid:"
5019 cat "$OUT.cwst"; exit 1; }
5020 assert_stopped "$SOCK26" "$D23PID" "CLI wall" "$OUT.cwstop" 4980 assert_stopped "$SOCK26" "$D23PID" "CLI wall" "$OUT.cwstop"
5021 D23PID="" 4981 D23PID=""
5022 ok "mux wall: two sessions and a live delta on one terminal, read-only" 4982 ok "mux wall: two sessions and a live delta on one terminal"
5023 4983
5024 # ---- M1: Ctrl-\ is a prefix, not an action ------------------------------ 4984 # ---- M1: Ctrl-\ is a prefix, not an action ------------------------------
5025 # 4985 #
@@ -5027,17 +4987,18 @@ ok "mux wall: two sessions and a live delta on one terminal, read-only"
5027 # sides: the chord layer eats exactly the two bytes of a chord and nothing 4987 # sides: the chord layer eats exactly the two bytes of a chord and nothing
5028 # else. 4988 # else.
5029 # 4989 #
5030 # (a) an unknown command key is swallowed WITH its prefix. `\x1cx` is sent 4990 # (a) an unknown command key is swallowed WITH its prefix. `\x1cz` is sent
5031 # at an empty prompt, then a marker command. If either byte had reached 4991 # at an empty prompt (`z` is bound to nothing; `x` would forget the
5032 # the pty the shell would have read `xprintf ...` and the marker would 4992 # tile), then a marker command. If either byte had reached the pty the
5033 # never print — so the marker is the assertion, and the explicit grep 4993 # shell would have read `zprintf ...` and the marker would never
5034 # for `xprintf` names what went wrong when it does. 4994 # print — so the marker is the assertion, and the explicit grep for
4995 # `zprintf` names what went wrong when it does.
5035 # (b) `\x1cd` detaches. The client exits 0 and the session it left behind 4996 # (b) `\x1cd` detaches. The client exits 0 and the session it left behind
5036 # still answers, which is what separates a detach from a kill. 4997 # still answers, which is what separates a detach from a kill.
5037 # 4998 #
5038 # A real pty (ptyclient) rather than a pipeline: the shell only echoes what 4999 # A real pty (ptyclient) rather than a pipeline: the shell only echoes what
5039 # it was really given when there is a terminal to echo to, and the echo is 5000 # it was really given when there is a terminal to echo to, and the echo is
5040 # what carries the stray `x` if the layer leaks one. 5001 # what carries the stray `z` if the layer leaks one.
5041 "$MUXD" run --sock "$SOCK27" --shell /bin/sh > "$OUT.pfx.d" 2>&1 & 5002 "$MUXD" run --sock "$SOCK27" --shell /bin/sh > "$OUT.pfx.d" 2>&1 &
5042 D24PID=$! 5003 D24PID=$!
5043 wait_sock "$SOCK27" "$OUT.pfx.d" "prefix daemon never bound" 5004 wait_sock "$SOCK27" "$OUT.pfx.d" "prefix daemon never bound"
@@ -5047,7 +5008,7 @@ timeout 40 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.pfx" --err "$OUT.pfx.err
5047 -- "$MUX" --sock "$SOCK27" > "$OUT.pfx.log" 2>&1 <<'EOF' 5008 -- "$MUX" --sock "$SOCK27" > "$OUT.pfx.log" 2>&1 <<'EOF'
5048 expect \x1b[?1049h 15000 5009 expect \x1b[?1049h 15000
5049 settle 400 15000 5010 settle 400 15000
5050 send \x1cx 5011 send \x1cz
5051 settle 400 15000 5012 settle 400 15000
5052 send printf 'm1-%s\\n' pin\n 5013 send printf 'm1-%s\\n' pin\n
5053 expect m1-pin 15000 5014 expect m1-pin 15000
@@ -5062,7 +5023,7 @@ set -e
5062 cat "$OUT.pfx.log"; exit 1; } 5023 cat "$OUT.pfx.log"; exit 1; }
5063 grep -q "m1-pin" "$OUT.pfx" || { 5024 grep -q "m1-pin" "$OUT.pfx" || {
5064 echo "e2e FAIL: prefix: the marker never printed"; exit 1; } 5025 echo "e2e FAIL: prefix: the marker never printed"; exit 1; }
5065 grep -q "xprintf" "$OUT.pfx" && { 5026 grep -q "zprintf" "$OUT.pfx" && {
5066 echo "e2e FAIL: prefix: the unknown command key reached the pty" 5027 echo "e2e FAIL: prefix: the unknown command key reached the pty"
5067 cat "$OUT.pfx"; exit 1; } 5028 cat "$OUT.pfx"; exit 1; }
5068 # Detached, not exited: the session outlives the client that left it. 5029 # Detached, not exited: the session outlives the client that left it.
@@ -5287,137 +5248,6 @@ assert_stopped "$SOCK29" "$D26PID" "session ring" "$OUT.ringstop"
5287 D26PID="" 5248 D26PID=""
5288 ok "Ctrl-\\ n / Ctrl-\\ p: the ring steps both ways and wraps at both ends" 5249 ok "Ctrl-\\ n / Ctrl-\\ p: the ring steps both ways and wraps at both ends"
5289 5250
5290 # ---- M4: Ctrl-\ w unzooms to the wall, and Enter goes back in ----------
5291 #
5292 # Scenario 33 proved `mux wall` paints several sessions on one terminal.
5293 # This is that wall reached from inside a ZOOM, which is a different claim
5294 # in two places, and the leg is built around both.
5295 #
5296 # The money assertion is unchanged: ONE client's capture holds BOTH markers
5297 # at once. m4a-pin is the default session's, m4b-pin belongs to the session
5298 # `\x1cc` created, and no single attached session can ever paint the two
5299 # together. Only the wall can, so seeing both is seeing the wall — the
5300 # tiles are spelled out of the target this client dialled (`--sock PATH#0`,
5301 # `--sock PATH#1`), exactly as before.
5302 #
5303 # PHASE 3C changed everything about HOW, and the leg says so:
5304 #
5305 # * `\x1cw` no longer spawns a child `mux wall`. There is no child and no
5306 # second alternate screen: `mux` IS the wall, and `w` demotes the zoom
5307 # in place. The two tiles were already there — one is the attach this
5308 # client made, the other is the tile `\x1cc` added — and both replicas
5309 # have been hot the whole time, which is why the stripes paint at once.
5310 # * the return is `Enter`, not `q`. Under the spec's model `q` LEAVES
5311 # MUX from the wall, and the selection follows the zoom, so Enter goes
5312 # straight back to the tile the user came out of. Its witness is
5313 # m4b-pin arriving again — repainted from the hot replica, with no
5314 # re-attach and no fresh `?1049h`, which is the whole point of the
5315 # lens. An expect that times out here is a session that came back
5316 # blank.
5317 # * a state home of its own. The wall this leg unzooms to is now THE
5318 # wall — the saved file's tiles as well as this run's — and the shared
5319 # $XDG_STATE_HOME holds a tile for every socket the suite has attached
5320 # to, most of them long dead. The leg would be showing the suite's
5321 # history rather than its own two sessions.
5322 #
5323 # The markers are shell-EXPANDED, the M18 trick, so a hit is the shell's
5324 # work and never an echo of what this script typed.
5325 #
5326 # The expect ORDER inside the wall leans on an injection, deliberately and
5327 # for scenario 33's reason, learned here the hard way: the tile threads
5328 # race, so session 0's stripe may paint after session 1's, and `expect` is
5329 # a stream cursor — matching the LATER marker first buries the earlier one
5330 # behind the cursor for good, and the leg times out on a wall that painted
5331 # perfectly. The `m4c-pin` injected at t+5s repaints session 0's whole
5332 # stripe long after both snapshots have landed, and that repaint carries
5333 # m4a-pin with it. So `m4b-pin` then `m4a-pin` then `m4c-pin` is ordered
5334 # under BOTH interleavings: whichever snapshot lost the race, the marker
5335 # it was carrying is still ahead of the cursor in the injected repaint.
5336 "$MUXD" run --sock "$SOCK30" --shell /bin/sh > "$OUT.wc.d" 2>&1 &
5337 D27PID=$!
5338 wait_sock "$SOCK30" "$OUT.wc.d" "wall-chord daemon never bound"
5339
5340 ( sleep 5; "$MUXA" send 'printf "m4c-%s\n" pin\n' \
5341 --sock "$SOCK30" --session 0 > "$OUT.wcinj" 2>&1 ) &
5342 WCINJPID=$!
5343 set +e
5344 XDG_STATE_HOME="$M4STATE" timeout 60 "$PTYCLIENT" --cols 100 --rows 30 \
5345 --out "$OUT.wc" --err "$OUT.wc.err" \
5346 -- "$MUX" --sock "$SOCK30" > "$OUT.wc.log" 2>&1 <<'EOF'
5347 expect \x1b[?1049h 15000
5348 settle 400 15000
5349 send printf 'm4a-%s\\n' pin\n
5350 expect m4a-pin 15000
5351 settle 400 15000
5352 send \x1cc
5353 expect \x1b[2J 15000
5354 settle 400 15000
5355 send printf 'm4b-%s\\n' pin\n
5356 expect m4b-pin 15000
5357 settle 400 15000
5358 send \x1cw
5359 expect m4b-pin 20000
5360 expect m4a-pin 20000
5361 expect m4c-pin 20000
5362 settle 500 15000
5363 send \r
5364 expect m4b-pin 15000
5365 settle 400 15000
5366 send \x1c\x1c
5367 waitexit 10000
5368 EOF
5369 RC=$?
5370 set -e
5371 wait "$WCINJPID" 2>/dev/null || true
5372 [ "$RC" -eq 0 ] || {
5373 echo "e2e FAIL: wall chord: ptyclient leg exited $RC (did \\x1cw unzoom, did Enter go back?):"
5374 cat "$OUT.wc.log"; cat "$OUT.wcinj"; exit 1; }
5375 # The two stripes, asserted on bytes only a wall can produce: a label bar
5376 # per tile, naming the spelling the CHORD built out of the target this
5377 # client was dialling. Nothing an attached session paints looks like this,
5378 # so unlike a bare marker grep these cannot be satisfied by the paints from
5379 # before the wall.
5380 grep -q -- "--sock $SOCK30#0 \[up\]" "$OUT.wc" || {
5381 echo "e2e FAIL: wall chord: no stripe for session 0"; exit 1; }
5382 grep -q -- "--sock $SOCK30#1 \[up\]" "$OUT.wc" || {
5383 echo "e2e FAIL: wall chord: no stripe for session 1"; exit 1; }
5384 # The detour left both sessions running: a wall is something the CLIENT
5385 # does, so neither shell may have been taken down by it, and each still
5386 # holds its own marker.
5387 for s in 0:m4a 1:m4b; do
5388 _sess="${s%%:*}"; _mark="${s##*:}-pin"
5389 timeout 20 "$MUXA" capture --sock "$SOCK30" --session "$_sess" > "$OUT.wccap" 2>&1
5390 grep -q "$_mark" "$OUT.wccap" || {
5391 echo "e2e FAIL: wall chord: session $_sess does not hold $_mark:"
5392 cat "$OUT.wccap"; exit 1; }
5393 done
5394 # Unzooming gave NOTHING back to the daemon: a demote is client-local, so
5395 # session 1 keeps the grid the zoom claimed for it and the stripe crops it.
5396 # That is the phase-1 invariant read from the other end — before phase 3c
5397 # the same assertion said "the wall's separate 0x0 attach did not move the
5398 # grid", and there is no separate attach any more to say it about.
5399 timeout 20 "$MUXA" status --sock "$SOCK30" --session 1 > "$OUT.wcst" 2>&1
5400 grep -q '"cols":100' "$OUT.wcst" || {
5401 echo "e2e FAIL: wall chord: unzooming moved session 1's grid:"
5402 cat "$OUT.wcst"; exit 1; }
5403 # The wall file this run built: one line per session VISITED, and no line
5404 # for anything else. Its own state home makes that a complete statement
5405 # rather than a search — the attach wrote #0 and the `c` chord's tile
5406 # wrote #1, and phase 3c is where a chord-added tile earns its own record.
5407 _m4_n=$(wc -l < "$M4STATE/mux/wall")
5408 [ "$_m4_n" -eq 2 ] || {
5409 echo "e2e FAIL: wall chord: the wall file holds $_m4_n lines, want 2:"
5410 cat "$M4STATE/mux/wall"; exit 1; }
5411 grep -qx -- "--sock $SOCK30#0" "$M4STATE/mux/wall" || {
5412 echo "e2e FAIL: wall chord: no tile for the session mux attached to:"
5413 cat "$M4STATE/mux/wall"; exit 1; }
5414 grep -qx -- "--sock $SOCK30#1" "$M4STATE/mux/wall" || {
5415 echo "e2e FAIL: wall chord: the chord-created session earned no tile:"
5416 cat "$M4STATE/mux/wall"; exit 1; }
5417 assert_stopped "$SOCK30" "$D27PID" "wall chord" "$OUT.wcstop"
5418 D27PID=""
5419 ok "Ctrl-\\ w: the wall shows both sessions in place, and Enter goes back in"
5420
5421 # ---- M5: a session shell knows where it is, and mux refuses the self-loop -- 5251 # ---- M5: a session shell knows where it is, and mux refuses the self-loop --
5422 # 5252 #
5423 # The incident: `mux --sock S` typed in a shell of session 0 on S attached 5253 # The incident: `mux --sock S` typed in a shell of session 0 on S attached
@@ -5505,162 +5335,6 @@ assert_stopped "$SOCK31" "$D28PID" "self attach" "$OUT.sastop"
5505 D28PID="" 5335 D28PID=""
5506 ok "a session shell carries MUX_SOCK/MUX_SESSION, and mux refuses to attach to itself" 5336 ok "a session shell carries MUX_SOCK/MUX_SESSION, and mux refuses to attach to itself"
5507 5337
5508 # ---- the wall zooms IN PLACE: Enter promotes the selected tile ----------
5509 #
5510 # Scenario 33 proved the wall WATCHES two sessions. This proves the wall can
5511 # be typed through by PROMOTING one of the connections it already holds:
5512 # `2` selects the second stripe, `Enter` resizes that tile's attach from 0x0
5513 # to this pty's size and starts forwarding keystrokes down it, `Ctrl-\ w`
5514 # gives the terminal back to the wall.
5515 #
5516 # PHASE 3C evolved that last key, and only that key. This leg used `d`,
5517 # which the child-spawn era had made a second spelling of "come back" — the
5518 # child was a real client and `Ctrl-\ d` detached it. Now `mux` IS the wall,
5519 # and `d` has to keep the meaning every user's fingers already have for it:
5520 # detach the session and leave. `w` is the model's own key and the only one
5521 # that unzooms.
5522 #
5523 # Five claims, five witnesses:
5524 #
5525 # * the selection moved — b's label bar carries the `> ` marker, which
5526 # it cannot have before the `2` (a's is marked from the start, so only
5527 # b's bar is evidence of a move).
5528 # * the zoom typed into the SELECTED session — b's grid holds wzoom-pin
5529 # and a's does not. A shell-EXPANDED marker (the M18 trick), so a hit
5530 # is the shell's work and never an echo of what this script sent.
5531 # * the zoom CLAIMED the grid — b's grid is 100 cols wide, this pty's
5532 # size. That is the promote: a tile that attached 0x0 asked for the
5533 # terminal's size before its first keystroke, so the client that types
5534 # is a full-size one and latest-wins has nothing to complain about.
5535 # * ...and it claimed it WITHOUT a second attach. This is the assertion
5536 # that inverted when the child-spawn zoom was deleted: the old design
5537 # proved the zoom was a real attach (a client of its own, in the same
5538 # process tree), and the new one proves the opposite — the daemon's
5539 # cumulative `attaches=` counter moves by exactly 2 across the wall's
5540 # whole life, one per tile at startup, because the connection that
5541 # typed is the connection the tile already had. Session a's grid stays
5542 # 80 for the passivity half of the same claim: a tile nobody zoomed
5543 # claims nothing, still.
5544 # * the wall came BACK — session A's marker paints a SECOND time after
5545 # the unzoom, asserted both in order (the expect) and by COUNT (twice
5546 # in the capture). Session a is the tile the zoom never touched:
5547 # nothing was typed at it, its grid was never resized, so no frame
5548 # arrives to trigger a paint and only the stale-generation repaint
5549 # the demote leaves behind can put those bytes on the screen again.
5550 # Asserted on A rather than on the zoomed session for exactly that
5551 # reason — measured, not assumed: with the generation bump removed,
5552 # b's stripe repaints anyway (the session the zoom typed at moves on
5553 # its own), and a leg anchored on b passes with the lever gone.
5554 #
5555 # The wall is entered on ONE expect, not one per tile, for scenario 33's
5556 # reason learned here the hard way: tile threads race, `expect` is a
5557 # stream cursor, and the SECOND of two markers is buried for good whenever
5558 # it painted first — a leg that then hangs on a wall that painted
5559 # perfectly. One anchor cannot be buried, and the settle after it is what
5560 # says both stripes have finished. Only ONE stripe is expected after the
5561 # unzoom for the same reason.
5562 #
5563 # There is no `\x1b[?1049h` anchor any more, and its absence is the point:
5564 # the child-spawn zoom tore this terminal down and rebuilt it twice, so an
5565 # alternate-screen TRANSITION was the honest thing to wait on. An in-place
5566 # zoom never leaves the alternate screen at all — it repaints inside it —
5567 # so the anchors are the session's own bytes arriving full-screen.
5568 "$MUXD" run --sock "$SOCK32" --shell /bin/sh > "$OUT.wz.d" 2>&1 &
5569 D29PID=$!
5570 wait_sock "$SOCK32" "$OUT.wz.d" "wall-zoom daemon never bound"
5571
5572 # Two sessions, each with its own marker, created exactly as scenario 33
5573 # creates them: no tty on either client, so both grids are the default 80
5574 # wide — the number the passivity check reads back later.
5575 pipe_mux "$OUT.wza" "$OUT.wza.err" timeout 40 "$MUX" --sock "$SOCK32" --session a
5576 pipe_send 'printf "wza-%%s\\n" pin\n'
5577 await_out "$OUT.wza" "wza-pin" "wza-pin never reached the client"
5578 pipe_detach
5579 wait_grid "$SOCK32" "wza-pin" "wall zoom: session a's marker" a
5580 pipe_mux "$OUT.wzb" "$OUT.wzb.err" timeout 40 "$MUX" --sock "$SOCK32" --session b
5581 pipe_send 'printf "wzb-%%s\\n" pin\n'
5582 await_out "$OUT.wzb" "wzb-pin" "wzb-pin never reached the client"
5583 pipe_detach
5584 wait_grid "$SOCK32" "wzb-pin" "wall zoom: session b's marker" b
5585
5586 # Brackets the wall and nothing else: every `muxa` and `wait_grid` call in
5587 # this scenario is itself a client, and one inside the watch would be
5588 # counted as the second attach this leg exists to refute.
5589 # Read AFTER the two setup clients have come and gone, so the delta below
5590 # belongs to the wall alone.
5591 WZATT_BEFORE=$(attaches_now "$SOCK32")
5592 watch_clients "$SOCK32" "$OUT.wzwatch"
5593 set +e
5594 timeout 90 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.wzcap" --err "$OUT.wzcap.err" -- \
5595 "$MUX" wall "--sock $SOCK32#a" "--sock $SOCK32#b" > "$OUT.wzpc" 2>&1 <<'EOF'
5596 expect wzb-pin 20000
5597 settle 700 20000
5598 send 2
5599 settle 500 15000
5600 send \r
5601 settle 700 20000
5602 send printf 'wzoom-%s\\n' pin\n
5603 expect wzoom-pin 15000
5604 settle 400 15000
5605 send \x1cw
5606 expect wza-pin 20000
5607 settle 500 15000
5608 send q
5609 waitexit 10000
5610 EOF
5611 RC=$?
5612 set -e
5613 unwatch_clients "$OUT.wzwatch"
5614 # Read BEFORE the muxa captures below, every one of which is an attach.
5615 WZATT_AFTER=$(attaches_now "$SOCK32")
5616 [ "$RC" -eq 0 ] || {
5617 echo "e2e FAIL: wall zoom: ptyclient leg exited $RC (did \\r zoom, did \\x1cw come back?):"
5618 cat "$OUT.wzpc"; exit 1; }
5619 # The move itself, on bytes only a selected bar can carry.
5620 grep -q -- "> --sock $SOCK32#b" "$OUT.wzcap" || {
5621 echo "e2e FAIL: wall zoom: session b's bar never showed the selection marker"; exit 1; }
5622 # The repaint counted rather than merely ordered: a's marker is on this
5623 # terminal twice, once for the stripe's own paint and once for the one the
5624 # unzoom demanded. `grep -o`, not `grep -c`: a stripe paint is escape
5625 # sequences with no newlines in them, so a line count reads 1 either way.
5626 _wza_paints=$(grep -o "wza-pin" "$OUT.wzcap" | wc -l)
5627 [ "$_wza_paints" -ge 2 ] || {
5628 echo "e2e FAIL: wall zoom: session a's stripe painted $_wza_paints time(s), so the wall never came back:"
5629 cat "$OUT.wzpc"; exit 1; }
5630 # No second attach, and this is the assertion that inverted: what used to
5631 # prove a real child client now proves there was none. TWO is the whole
5632 # budget — one per tile, at wall startup — and the zoom, the typing and the
5633 # unzoom add nothing to it. A number, not a gauge, so a client that opened
5634 # and closed between two samples, or overlapped another for a millisecond,
5635 # is counted anyway.
5636 assert_attach_delta "$WZATT_BEFORE" "$WZATT_AFTER" 2 "wall zoom"
5637 # ...and which session, if it ever were two.
5638 assert_never_two_clients "$OUT.wzwatch" a "wall zoom"
5639 assert_never_two_clients "$OUT.wzwatch" b "wall zoom"
5640 # What the user typed landed in the session the digit picked, and only
5641 # there. The negative is the whole point of the digit.
5642 timeout 20 "$MUXA" capture --sock "$SOCK32" --session b > "$OUT.wzcapb" 2>&1
5643 grep -q "wzoom-pin" "$OUT.wzcapb" || {
5644 echo "e2e FAIL: wall zoom: session b never got what the zoom typed:"
5645 cat "$OUT.wzcapb"; exit 1; }
5646 timeout 20 "$MUXA" capture --sock "$SOCK32" --session a > "$OUT.wzcapa" 2>&1
5647 grep -q "wzoom-pin" "$OUT.wzcapa" && {
5648 echo "e2e FAIL: wall zoom: the zoom typed into session a, which was not selected:"
5649 cat "$OUT.wzcapa"; exit 1; }
5650 # The size split: the promoted tile claimed the grid, the unzoomed one
5651 # never did.
5652 timeout 20 "$MUXA" status --sock "$SOCK32" --session b > "$OUT.wzstb" 2>&1
5653 grep -q '"cols":100' "$OUT.wzstb" || {
5654 echo "e2e FAIL: wall zoom: the promoted tile did not claim session b's grid:"
5655 cat "$OUT.wzstb"; exit 1; }
5656 timeout 20 "$MUXA" status --sock "$SOCK32" --session a > "$OUT.wzsta" 2>&1
5657 grep -q '"cols":80' "$OUT.wzsta" || {
5658 echo "e2e FAIL: wall zoom: a tile nobody zoomed moved session a's grid:"
5659 cat "$OUT.wzsta"; exit 1; }
5660 assert_stopped "$SOCK32" "$D29PID" "wall zoom" "$OUT.wzstop"
5661 D29PID=""
5662 ok "the wall zooms in place: Enter promotes the tile, Ctrl-\\ d demotes it"
5663
5664 # ---- the wheel scrolls back, and an app that asks gets it instead ------- 5338 # ---- the wheel scrolls back, and an app that asks gets it instead -------
5665 # 5339 #
5666 # The pair is one claim seen from both sides, so the two legs are the same 5340 # The pair is one claim seen from both sides, so the two legs are the same
@@ -5917,56 +5591,48 @@ assert_stopped "$SOCK36" "$D33PID" "pipe stdin" "$OUT.pipestop"
5917 D33PID="" 5591 D33PID=""
5918 ok "a client with no terminal of its own forwards SGR-shaped bytes untouched" 5592 ok "a client with no terminal of its own forwards SGR-shaped bytes untouched"
5919 5593
5920 # ---- the zoom SKIPS between tiles, and the daemon never notices --------- 5594 # ---- focus skips between tiles, and the daemon never notices -----------
5921 # 5595 #
5922 # The headline of "zoom is a lens": every tile's replica is hot whether or 5596 # Every tile's replica is hot whether or not it is the focus, so moving
5923 # not it is the zoom, so moving the zoom between two tiles is a local 5597 # the focus between two tiles is a local repaint. No re-dial, no attach,
5924 # repaint. No re-dial, no attach, no snapshot round trip — which is a claim 5598 # no snapshot round trip — a claim about what did NOT happen, witnessed
5925 # about what did NOT happen, and needs a daemon-side witness rather than a 5599 # daemon-side rather than by a stopwatch.
5926 # stopwatch.
5927 # 5600 #
5928 # One ptyclient leg, four sessions' worth of typing on two tiles: 5601 # One ptyclient leg, two sessions' worth of typing on two tiles:
5929 # 5602 #
5930 # Enter zoom tile 1 (session a) — promote 5603 # type mark a with zs-one (a is focused from the first byte)
5931 # type mark a with zs-one, shell-expanded 5604 # Ctrl-\ n move the focus to tile 2 (session b)
5932 # Ctrl-\ n move the zoom to tile 2 (session b)
5933 # type mark b with zs-two 5605 # type mark b with zs-two
5934 # Ctrl-\ l skip back to the last tile zoomed, which is a 5606 # Ctrl-\ l skip back to the last tile focused, which is a
5935 # type mark a again with zs-three 5607 # type mark a again with zs-three
5936 # Ctrl-\ w unzoom (the spec's spelling; scenario 39 does `d`) 5608 # Ctrl-\ d leave
5937 # 5609 #
5938 # Three claims: 5610 # Two claims:
5939 # 5611 #
5940 # * every marker landed in the session the zoom was standing in, and in 5612 # * every marker landed in the session the focus was on, and in no
5941 # no other. zs-one and zs-three in a, zs-two in b, and each absent from 5613 # other. zs-one and zs-three in a, zs-two in b, and each absent from
5942 # the other session — the negatives are what prove the zoom MOVED 5614 # the other session — the negatives are what prove the focus MOVED
5943 # rather than the wall having quietly kept typing at one connection. 5615 # rather than the wall typing on at one connection.
5944 # * the daemon accepted exactly TWO attaches for the whole run — one per 5616 # * the daemon accepted exactly TWO attaches for the whole run — one per
5945 # tile, at wall startup. If `Ctrl-\ n` re-dialled, or zoomed by 5617 # tile, at wall startup. A `Ctrl-\ n` that re-dialled would be three or
5946 # spawning, it would be three or more, and the counter says so however 5618 # more, and the cumulative counter says so however briefly the extra
5947 # briefly the extra connection lived or however neatly it closed the 5619 # connection lived. Focus is client-local and sends nothing on the wire.
5948 # old one first. This is the assertion that inverts the child-spawn
5949 # era's "the zoom was a REAL attach".
5950 # * both sessions end up at this pty's size, because both were promoted
5951 # and a demote deliberately gives nothing back — `applySize` refuses
5952 # sub-minimum resizes, so there is no 0x0 to hand back, and a size
5953 # nobody types at claims nothing under latest-wins.
5954 "$MUXD" run --sock "$SOCK37" --shell /bin/sh > "$OUT.zs.d" 2>&1 & 5620 "$MUXD" run --sock "$SOCK37" --shell /bin/sh > "$OUT.zs.d" 2>&1 &
5955 D34PID=$! 5621 D34PID=$!
5956 wait_sock "$SOCK37" "$OUT.zs.d" "zoom-skip daemon never bound" 5622 wait_sock "$SOCK37" "$OUT.zs.d" "focus-skip daemon never bound"
5957 5623
5958 # Both sessions exist before the wall does, with a marker each so the wall 5624 # Both sessions exist before the wall does, each with a marker so the wall
5959 # has something to paint and this leg has an anchor to enter on. 5625 # has something to paint and this leg has an anchor to enter on.
5960 pipe_mux "$OUT.zsa" "$OUT.zsa.err" timeout 40 "$MUX" --sock "$SOCK37" --session a 5626 pipe_mux "$OUT.zsa" "$OUT.zsa.err" timeout 40 "$MUX" --sock "$SOCK37" --session a
5961 pipe_send 'printf "zsa-%%s\\n" pin\n' 5627 pipe_send 'printf "zsa-%%s\\n" pin\n'
5962 await_out "$OUT.zsa" "zsa-pin" "zsa-pin never reached the client" 5628 await_out "$OUT.zsa" "zsa-pin" "zsa-pin never reached the client"
5963 pipe_detach 5629 pipe_detach
5964 wait_grid "$SOCK37" "zsa-pin" "zoom skip: session a's marker" a 5630 wait_grid "$SOCK37" "zsa-pin" "focus skip: session a's marker" a
5965 pipe_mux "$OUT.zsb" "$OUT.zsb.err" timeout 40 "$MUX" --sock "$SOCK37" --session b 5631 pipe_mux "$OUT.zsb" "$OUT.zsb.err" timeout 40 "$MUX" --sock "$SOCK37" --session b
5966 pipe_send 'printf "zsb-%%s\\n" pin\n' 5632 pipe_send 'printf "zsb-%%s\\n" pin\n'
5967 await_out "$OUT.zsb" "zsb-pin" "zsb-pin never reached the client" 5633 await_out "$OUT.zsb" "zsb-pin" "zsb-pin never reached the client"
5968 pipe_detach 5634 pipe_detach
5969 wait_grid "$SOCK37" "zsb-pin" "zoom skip: session b's marker" b 5635 wait_grid "$SOCK37" "zsb-pin" "focus skip: session b's marker" b
5970 5636
5971 ZSATT_BEFORE=$(attaches_now "$SOCK37") 5637 ZSATT_BEFORE=$(attaches_now "$SOCK37")
5972 watch_clients "$SOCK37" "$OUT.zswatch" 5638 watch_clients "$SOCK37" "$OUT.zswatch"
@@ -5975,8 +5641,6 @@ timeout 90 "$PTYCLIENT" --cols 92 --rows 30 --out "$OUT.zscap" --err "$OUT.zscap
5975 "$MUX" wall "--sock $SOCK37#a" "--sock $SOCK37#b" > "$OUT.zspc" 2>&1 <<'EOF' 5641 "$MUX" wall "--sock $SOCK37#a" "--sock $SOCK37#b" > "$OUT.zspc" 2>&1 <<'EOF'
5976 expect zsb-pin 20000 5642 expect zsb-pin 20000
5977 settle 700 20000 5643 settle 700 20000
5978 send \r
5979 settle 700 20000
5980 send printf 'zs-%s\\n' one\n 5644 send printf 'zs-%s\\n' one\n
5981 expect zs-one 15000 5645 expect zs-one 15000
5982 settle 400 15000 5646 settle 400 15000
@@ -5990,10 +5654,7 @@ settle 700 20000
5990 send printf 'zs-%s\\n' three\n 5654 send printf 'zs-%s\\n' three\n
5991 expect zs-three 15000 5655 expect zs-three 15000
5992 settle 400 15000 5656 settle 400 15000
5993 send \x1cw 5657 send \x1cd
5994 expect zsb-pin 20000
5995 settle 500 15000
5996 send q
5997 waitexit 10000 5658 waitexit 10000
5998 EOF 5659 EOF
5999 RC=$? 5660 RC=$?
@@ -6001,7 +5662,7 @@ set -e
6001 unwatch_clients "$OUT.zswatch" 5662 unwatch_clients "$OUT.zswatch"
6002 ZSATT_AFTER=$(attaches_now "$SOCK37") 5663 ZSATT_AFTER=$(attaches_now "$SOCK37")
6003 [ "$RC" -eq 0 ] || { 5664 [ "$RC" -eq 0 ] || {
6004 echo "e2e FAIL: zoom skip: ptyclient leg exited $RC (did \\x1cn and \\x1cl move the zoom?):" 5665 echo "e2e FAIL: focus skip: ptyclient leg exited $RC (did \\x1cn and \\x1cl move the focus?):"
6005 cat "$OUT.zspc"; exit 1; } 5666 cat "$OUT.zspc"; exit 1; }
6006 # Where each marker landed. `muxa capture` reads the session's own grid, so 5667 # Where each marker landed. `muxa capture` reads the session's own grid, so
6007 # none of this can be an echo of what this script typed at a terminal. 5668 # none of this can be an echo of what this script typed at a terminal.
@@ -6015,196 +5676,48 @@ timeout 20 "$MUXA" capture --sock "$SOCK37" --session b > "$OUT.zsfb" 2>&1
6015 # prefix, which is the property this needs. 5676 # prefix, which is the property this needs.
6016 for _m in zs-one zs-three; do 5677 for _m in zs-one zs-three; do
6017 grep -q "$_m" "$OUT.zsfa" || { 5678 grep -q "$_m" "$OUT.zsfa" || {
6018 echo "e2e FAIL: zoom skip: session a never got $_m:" 5679 echo "e2e FAIL: focus skip: session a never got $_m:"
6019 cat "$OUT.zsfa"; exit 1; } 5680 cat "$OUT.zsfa"; exit 1; }
6020 grep -q "$_m" "$OUT.zsfb" && { 5681 grep -q "$_m" "$OUT.zsfb" && {
6021 echo "e2e FAIL: zoom skip: $_m reached session b, which the zoom had left:" 5682 echo "e2e FAIL: focus skip: $_m reached session b, which the focus had left:"
6022 cat "$OUT.zsfb"; exit 1; } 5683 cat "$OUT.zsfb"; exit 1; }
6023 done 5684 done
6024 grep -q "zs-two" "$OUT.zsfb" || { 5685 grep -q "zs-two" "$OUT.zsfb" || {
6025 echo "e2e FAIL: zoom skip: Ctrl-\\ n did not put the zoom on session b:" 5686 echo "e2e FAIL: focus skip: Ctrl-\\ n did not put the focus on session b:"
6026 cat "$OUT.zsfb"; exit 1; } 5687 cat "$OUT.zsfb"; exit 1; }
6027 grep -q "zs-two" "$OUT.zsfa" && { 5688 grep -q "zs-two" "$OUT.zsfa" && {
6028 echo "e2e FAIL: zoom skip: zs-two reached session a, so the zoom never moved:" 5689 echo "e2e FAIL: focus skip: zs-two reached session a, so the focus never moved:"
6029 cat "$OUT.zsfa"; exit 1; } 5690 cat "$OUT.zsfa"; exit 1; }
6030 # The whole point, said by the daemon: two tiles, two attaches, and never a 5691 # The whole point, said by the daemon: two tiles, two attaches, and never a
6031 # third across a zoom, a skip to the other tile, a skip back and an unzoom. 5692 # third across a focus move each way. This is the leg the counter matters
6032 # This is the leg the counter matters most in — the two moves it makes are 5693 # most in — the two moves it makes are exactly the ones a gauge could not
6033 # exactly the ones a gauge could not tell from a re-dial, since a connection 5694 # tell from a re-dial, since a connection that closes as another opens
6034 # that closes as another opens never shows two at once. 5695 # never shows two at once.
6035 assert_attach_delta "$ZSATT_BEFORE" "$ZSATT_AFTER" 2 "zoom skip" 5696 assert_attach_delta "$ZSATT_BEFORE" "$ZSATT_AFTER" 2 "focus skip"
6036 assert_never_two_clients "$OUT.zswatch" a "zoom skip" 5697 assert_never_two_clients "$OUT.zswatch" a "focus skip"
6037 assert_never_two_clients "$OUT.zswatch" b "zoom skip" 5698 assert_never_two_clients "$OUT.zswatch" b "focus skip"
6038 # Both promoted, both still promoted after the demote. 92 is this leg's own 5699 # Both tiles claimed their rect: 92 cols each (this leg's own width), the
6039 # width so the number cannot be another scenario's leftover. 5700 # size the wall attached at. A focus move resizes nothing.
6040 for _s in a b; do 5701 for _s in a b; do
6041 timeout 20 "$MUXA" status --sock "$SOCK37" --session "$_s" > "$OUT.zsst$_s" 2>&1 5702 timeout 20 "$MUXA" status --sock "$SOCK37" --session "$_s" > "$OUT.zsst$_s" 2>&1
6042 grep -q '"cols":92' "$OUT.zsst$_s" || { 5703 grep -q '"cols":92' "$OUT.zsst$_s" || {
6043 echo "e2e FAIL: zoom skip: session $_s did not keep the size its promote claimed:" 5704 echo "e2e FAIL: focus skip: session $_s is not at the width its tile claimed:"
6044 cat "$OUT.zsst$_s"; exit 1; } 5705 cat "$OUT.zsst$_s"; exit 1; }
6045 done 5706 done
6046 5707 assert_stopped "$SOCK37" "$D34PID" "focus skip" "$OUT.zsstop"
6047 # The terminal handover, decoded out of the same capture. Three zoom moves
6048 # happened above (in, across, back) plus an unzoom, and each one hands the
6049 # terminal from one session to another. Which order the two halves land in
6050 # is not cosmetic: if the incoming tile's CLAIM lands before the outgoing
6051 # tile's RELEASE, the release unsets the modes the claim just set and the
6052 # zoomed session spends the rest of its turn with mouse reporting off — the
6053 # pre-3b bug, and the wheel silently stops working.
6054 #
6055 # It was a real race and not a theoretical one. With the release left to the
6056 # demoted tile's PUMP (two threads, no ordering between them), this decode
6057 # read CCRRR — claim-before-release — in four runs out of six on this
6058 # machine; it is benign only when the incoming level-set happens to land
6059 # afterwards and repair it, and a pump stalled in a dial backoff makes the
6060 # window seconds wide. The fix is that the thread MOVING the zoom writes the
6061 # release itself, under `paint_mu`, before the store the next pump reads
6062 # (wallview `setFocus`), so the order is structural rather than lucky.
6063 #
6064 # Both needles are exact byte strings that nothing else emits. The claim is
6065 # the only place those three escapes are adjacent — the level-set that
6066 # follows it puts `?1003l` and `?1005l` between the last two — and the
6067 # release is a nine-escape run written whole.
6068 _ZS_CLAIM=$(printf '\033[?1000h\033[?1002h\033[?1006h')
6069 _ZS_REL=$(printf '\033[?2004l\033[?9l\033[?1000l\033[?1002l\033[?1003l\033[?1005l\033[?1006l\033[?1015l\033[?1016l')
6070 # Byte offsets, merged and sorted, then read as a word. `grep -oba` is what
6071 # makes this possible at all: the capture is binary, these are escape
6072 # sequences with no newlines, and a line-oriented grep would see one line.
6073 _zs_order=$( { grep -obaF -- "$_ZS_CLAIM" "$OUT.zscap" | sed 's/:.*/ C/' || true; \
6074 grep -obaF -- "$_ZS_REL" "$OUT.zscap" | sed 's/:.*/ R/' || true; } \
6075 | sort -n | awk '{printf "%s", $2}' )
6076 # CCRCRCRCR, and every letter is accounted for. The WALL claims these modes
6077 # for itself now — it reads mouse reports while unzoomed — so the run opens
6078 # with its claim (C) before Enter's promote claims on top (C). Then the
6079 # three handovers, each release before the next claim (RC RC), then the
6080 # unzoom's release followed by the wall taking its own modes back (RC), and
6081 # last the R of the wall's exit.
6082 #
6083 # The property under test is unchanged and is the adjacency: no C may sit
6084 # where an R belongs, because a claim landing before the outgoing release
6085 # is a session that spends its turn with the mouse off.
6086 case "$_zs_order" in
6087 CCRCRCRCR*) ;;
6088 *)
6089 echo "e2e FAIL: zoom skip: the terminal handover was $_zs_order, not CCRCRCRCR —"
6090 echo " an incoming tile claimed the terminal before the outgoing one"
6091 echo " released it, so the release unset the modes the claim had just"
6092 echo " set and the zoomed session lost its mouse:"
6093 cat "$OUT.zspc"; exit 1 ;;
6094 esac
6095 assert_stopped "$SOCK37" "$D34PID" "zoom skip" "$OUT.zsstop"
6096 D34PID="" 5708 D34PID=""
6097 ok "the zoom skips between tiles with Ctrl-\\ n / Ctrl-\\ l, and the daemon sees no new attach" 5709 ok "focus skips between tiles with Ctrl-\\ n / Ctrl-\\ l, and the daemon sees no new attach"
6098
6099 # ---- an unzoomed wall forwards nothing ---------------------------------
6100 #
6101 # The teeth of the new invariant. "An unzoomed tile claims nothing" is not
6102 # kept by the tile reading 0x0 — a demoted slot deliberately keeps its
6103 # promoted size — it is kept by the wall never forwarding a keystroke while
6104 # it is zoomed out. So the assertion is a marker that must NOT appear.
6105 #
6106 # A negative grep passes just as well when the leg never ran, and this suite
6107 # has been bitten by that before, so the same leg carries its own anchor:
6108 # the SAME session, the SAME terminal, one zoom later, gets a marker that
6109 # MUST appear. Both halves read one `muxa capture`. If the wall never came
6110 # up, or the ptyclient script fell over early, the positive fails loudly
6111 # and the negative's silence is never mistaken for evidence.
6112 #
6113 # Every letter of `wallmute` is one the unzoomed wall SWALLOWS — not one is
6114 # a selection key. That is the whole reason the word is spelled this way,
6115 # and it was measured rather than assumed: the first spelling was
6116 # `wallnope`, whose `n` and `p` the wall consumes as selection keys, so what
6117 # a leak would actually have delivered is `wallne` — and a grep for
6118 # `wallnope` could not have matched even with the wall forwarding
6119 # everything. A negative that cannot fail is not a test.
6120 #
6121 # None of these bytes may reach a shell, where ECHO would put them on the
6122 # grid even without a newline. No newline is sent, for a second reason: at
6123 # the wall, `\n` is Enter.
6124 "$MUXD" run --sock "$SOCK38" --shell /bin/sh > "$OUT.zm.d" 2>&1 &
6125 D35PID=$!
6126 wait_sock "$SOCK38" "$OUT.zm.d" "zoom-mute daemon never bound"
6127 pipe_mux "$OUT.zma" "$OUT.zma.err" timeout 40 "$MUX" --sock "$SOCK38" --session a
6128 pipe_send 'printf "zma-%%s\\n" pin\n'
6129 await_out "$OUT.zma" "zma-pin" "zma-pin never reached the client"
6130 pipe_detach
6131 wait_grid "$SOCK38" "zma-pin" "zoom mute: session a's marker" a
6132 pipe_mux "$OUT.zmb" "$OUT.zmb.err" timeout 40 "$MUX" --sock "$SOCK38" --session b
6133 pipe_send 'printf "zmb-%%s\\n" pin\n'
6134 await_out "$OUT.zmb" "zmb-pin" "zmb-pin never reached the client"
6135 pipe_detach
6136 wait_grid "$SOCK38" "zmb-pin" "zoom mute: session b's marker" b
6137 5710
6138 set +e 5711 # ---- the wheel inside the focused tile ---------------------------------
6139 timeout 90 "$PTYCLIENT" --cols 96 --rows 30 --out "$OUT.zmcap" --err "$OUT.zmcap.err" -- \ 5712 #
6140 "$MUX" wall "--sock $SOCK38#a" "--sock $SOCK38#b" > "$OUT.zmpc" 2>&1 <<'EOF' 5713 # A tile's input goes through the same `interact.Core` a plain client's
6141 expect zmb-pin 20000 5714 # does, so the wheel arrives with it: the focused tile scrolls its
6142 settle 700 20000 5715 # session's history on the wheel and never reaches its pty. Same arithmetic
6143 send wallmute 5716 # as the plain client's wheel leg (scenario 42) and deliberately the same
6144 settle 700 20000 5717 # pty size, so a difference here is the wall's and not the geometry's:
6145 send 1 5718 # eight notches in one write are 24 rows, exactly a screenful on 80x24, and
6146 settle 500 15000 5719 # the page that lands holds `wln60` — a line that is in the history and in
6147 send \r 5720 # nothing painted before it.
6148 settle 700 20000
6149 send printf 'zmzoom-%s\\n' ok\n
6150 expect zmzoom-ok 15000
6151 settle 400 15000
6152 send \x1cw
6153 expect zma-pin 20000
6154 settle 500 15000
6155 send q
6156 waitexit 10000
6157 EOF
6158 RC=$?
6159 set -e
6160 [ "$RC" -eq 0 ] || {
6161 echo "e2e FAIL: zoom mute: ptyclient leg exited $RC:"
6162 cat "$OUT.zmpc"; exit 1; }
6163 timeout 20 "$MUXA" capture --sock "$SOCK38" --session a > "$OUT.zmfa" 2>&1
6164 timeout 20 "$MUXA" capture --sock "$SOCK38" --session b > "$OUT.zmfb" 2>&1
6165 # The anchor first, so a leg that did not run cannot pass the negative by
6166 # having done nothing at all.
6167 grep -q "zmzoom-ok" "$OUT.zmfa" || {
6168 echo "e2e FAIL: zoom mute: the zoom never typed into session a, so the"
6169 echo " silent half below proves nothing:"
6170 cat "$OUT.zmfa"; cat "$OUT.zmpc"; exit 1; }
6171 for _s in a b; do
6172 grep -q "wallmute" "$OUT.zmf$_s" && {
6173 echo "e2e FAIL: zoom mute: the UNZOOMED wall forwarded keystrokes to session $_s:"
6174 cat "$OUT.zmf$_s"; exit 1; }
6175 done
6176 # ...and the tile nobody zoomed is untouched in the other sense too: still
6177 # 80 wide, the size it was created at with no tty in sight.
6178 timeout 20 "$MUXA" status --sock "$SOCK38" --session b > "$OUT.zmstb" 2>&1
6179 grep -q '"cols":80' "$OUT.zmstb" || {
6180 echo "e2e FAIL: zoom mute: an unzoomed tile moved session b's grid:"
6181 cat "$OUT.zmstb"; exit 1; }
6182 assert_stopped "$SOCK38" "$D35PID" "zoom mute" "$OUT.zmstop"
6183 D35PID=""
6184 ok "an unzoomed wall forwards nothing; the zoom one keystroke later does"
6185
6186 # ---- the wheel inside a zoom, and the mouse handed back on the way out --
6187 #
6188 # What phase 3b is FOR. A zoomed tile used to be keyboard-only: the wall
6189 # hand-rolled a subset of the client's input path, and the wheel — which
6190 # needs the session's own modes, the tile's scrollback view and the tile's
6191 # transport all in one place — was not in that subset. Now the tile's input
6192 # goes through the same `interact.Core` a plain client's does, so the wheel
6193 # arrives with it.
6194 #
6195 # Two claims, one run, because the second is the first read backwards and
6196 # has to be anchored on it:
6197 #
6198 # * ZOOMED, the wheel scrolls the SESSION's history and never reaches its
6199 # pty. Same arithmetic as the plain client's wheel leg (scenario 42) and
6200 # deliberately the same pty size, so a difference here is the wall's and
6201 # not the geometry's: eight notches in one write are 24 rows, exactly a
6202 # screenful on 80x24, and the page that lands holds `wln60` — a line
6203 # that is in the history and in nothing painted before it.
6204 # * DEMOTED, the wheel reaches nothing at all, and the terminal is left
6205 # with no mouse mode enabled. The demote is what pairs with the promote's
6206 # claim, and it is the pair that turns over many times per wall where a
6207 # client's turns over once per process.
6208 # 5721 #
6209 # The lines are `wln1`..`wln100` rather than bare numbers because this 5722 # The lines are `wln1`..`wln100` rather than bare numbers because this
6210 # terminal is a WALL: its label bar carries the socket path, which carries 5723 # terminal is a WALL: its label bar carries the socket path, which carries
@@ -6213,7 +5726,8 @@ ok "an unzoomed wall forwards nothing; the zoom one keystroke later does"
6213 # 5726 #
6214 # The wall's spelling is a bare `--sock PATH` — the daemon's DEFAULT 5727 # The wall's spelling is a bare `--sock PATH` — the daemon's DEFAULT
6215 # session, so nothing has to be created and the session's shell is fixed for 5728 # session, so nothing has to be created and the session's shell is fixed for
6216 # its whole life. 5729 # its whole life. One tile, so no label bar: the geometry is the plain
5730 # client's.
6217 cat > "$ZWHEELSH" <<'EOF' 5731 cat > "$ZWHEELSH" <<'EOF'
6218 #!/bin/sh 5732 #!/bin/sh
6219 seq 1 100 | sed 's/^/wln/' 5733 seq 1 100 | sed 's/^/wln/'
@@ -6222,12 +5736,12 @@ EOF
6222 chmod +x "$ZWHEELSH" 5736 chmod +x "$ZWHEELSH"
6223 "$MUXD" run --sock "$SOCK41" --shell "$ZWHEELSH" > "$OUT.zw.d" 2>&1 & 5737 "$MUXD" run --sock "$SOCK41" --shell "$ZWHEELSH" > "$OUT.zw.d" 2>&1 &
6224 D38PID=$! 5738 D38PID=$!
6225 wait_sock "$SOCK41" "$OUT.zw.d" "zoom-wheel daemon never bound" 5739 wait_sock "$SOCK41" "$OUT.zw.d" "wheel daemon never bound"
6226 # The wall attaches only once seq has finished, scenario 42's reason: the 5740 # The wall attaches only once seq has finished, scenario 42's reason: the
6227 # page the wheel fetches has to be content this tile was never sent. 5741 # page the wheel fetches has to be content this tile was never sent.
6228 i=0 5742 i=0
6229 until "$MUXD" dump --sock "$SOCK41" | grep -q "wln100"; do 5743 until "$MUXD" dump --sock "$SOCK41" | grep -q "wln100"; do
6230 i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: zoom wheel: session never finished seq"; exit 1; } 5744 i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: wheel: session never finished seq"; exit 1; }
6231 sleep 0.1 5745 sleep 0.1
6232 done 5746 done
6233 set +e 5747 set +e
@@ -6235,8 +5749,6 @@ timeout 90 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.zwcap" --err "$OUT.zwcap
6235 "$MUX" wall "--sock $SOCK41" > "$OUT.zwpc" 2>&1 <<'EOF' 5749 "$MUX" wall "--sock $SOCK41" > "$OUT.zwpc" 2>&1 <<'EOF'
6236 expect wln100 20000 5750 expect wln100 20000
6237 settle 700 20000 5751 settle 700 20000
6238 send \r
6239 settle 900 20000
6240 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 5752 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
6241 expect wln60 15000 5753 expect wln60 15000
6242 send \x1b[<65;10;5M\x1b[<65;10;5M\x1b[<65;10;5M\x1b[<65;10;5M\x1b[<65;10;5M\x1b[<65;10;5M\x1b[<65;10;5M\x1b[<65;10;5M 5754 send \x1b[<65;10;5M\x1b[<65;10;5M\x1b[<65;10;5M\x1b[<65;10;5M\x1b[<65;10;5M\x1b[<65;10;5M\x1b[<65;10;5M\x1b[<65;10;5M
@@ -6245,74 +5757,43 @@ settle 500 15000
6245 send zwheelpin\n 5757 send zwheelpin\n
6246 expect zwheelpin 15000 5758 expect zwheelpin 15000
6247 settle 500 15000 5759 settle 500 15000
6248 send \x1cw 5760 send \x1cd
6249 expect wln100 20000
6250 settle 900 20000
6251 send \x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5Mwallwheel
6252 settle 900 20000
6253 send q
6254 waitexit 10000 5761 waitexit 10000
6255 EOF 5762 EOF
6256 RC=$? 5763 RC=$?
6257 set -e 5764 set -e
6258 [ "$RC" -eq 0 ] || { 5765 [ "$RC" -eq 0 ] || {
6259 echo "e2e FAIL: zoom wheel: ptyclient leg exited $RC (did the zoomed wheel scroll, and come back?):" 5766 echo "e2e FAIL: wheel: ptyclient leg exited $RC (did the focused wheel scroll?):"
6260 cat "$OUT.zwpc"; exit 1; } 5767 cat "$OUT.zwpc"; exit 1; }
6261 # The anchor first, and it is a POSITIVE: a keystroke typed at the zoom 5768 # The anchor first, and it is a POSITIVE: a keystroke typed at the focused
6262 # reached this session's pty. Everything silent below is measured against 5769 # tile reached this session's pty. The silent half below is measured against
6263 # it, so a leg that fell over early can never pass by having done nothing. 5770 # it, so a leg that fell over early can never pass by having done nothing.
6264 timeout 20 "$MUXA" capture --sock "$SOCK41" > "$OUT.zwcapg" 2>&1 5771 timeout 20 "$MUXA" capture --sock "$SOCK41" > "$OUT.zwcapg" 2>&1
6265 grep -q "zwheelpin" "$OUT.zwcapg" || { 5772 grep -q "zwheelpin" "$OUT.zwcapg" || {
6266 echo "e2e FAIL: zoom wheel: the zoom never typed into the session, so the" 5773 echo "e2e FAIL: wheel: the focus never typed into the session, so the"
6267 echo " silent halves below prove nothing:" 5774 echo " silent half below proves nothing:"
6268 cat "$OUT.zwcapg"; cat "$OUT.zwpc"; exit 1; } 5775 cat "$OUT.zwcapg"; cat "$OUT.zwpc"; exit 1; }
6269 # Twenty mouse reports were typed at a shell that echoes everything it is 5776 # The wheel reports were typed at a shell that echoes everything it is
6270 # given, and the session's grid must hold none: zoomed they were the 5777 # given, and the session's grid must hold none: the wheel is the client's
6271 # client's own wheel, demoted they were never forwarded at all. 5778 # own, never forwarded to the pty.
6272 grep -qF -- "[<64;" "$OUT.zwcapg" && { 5779 grep -qF -- "[<64;" "$OUT.zwcapg" && {
6273 echo "e2e FAIL: zoom wheel: a mouse report reached the pty as input:" 5780 echo "e2e FAIL: wheel: a mouse report reached the pty as input:"
6274 cat "$OUT.zwcapg"; exit 1; }
6275 ok "a zoomed tile scrolls its session's history on the wheel, and the pty sees none of it"
6276
6277 # The demoted half, on the same capture. Every letter of `wallwheel` is one
6278 # an unzoomed wall swallows — none is a selection key, `q` or `x` — which is
6279 # the same care scenario 45 spells out: a negative that cannot fail is not a
6280 # test.
6281 grep -q "wallwheel" "$OUT.zwcapg" && {
6282 echo "e2e FAIL: zoom wheel: the DEMOTED wall forwarded keystrokes to the session:"
6283 cat "$OUT.zwcapg"; exit 1; } 5781 cat "$OUT.zwcapg"; exit 1; }
6284 # ...and the SESSION's mouse was taken off it. `?1000l` is written exactly 5782 assert_stopped "$SOCK41" "$D38PID" "wheel" "$OUT.zwstop"
6285 # twice on this run — once by the demote and once by the wall's own exit —
6286 # and the promote writes only `h`s, so a count of one is a demote that left
6287 # the session's modes on the user's terminal for the rest of the wall's
6288 # life. What the demote then puts back is the WALL's own capture set, which
6289 # is the wall reading mouse reports on its own account and not a leak; the
6290 # exit's teardown is what has to leave the terminal clean. Counted with
6291 # `grep -o`: these are escape sequences inside a paint, with no newlines to
6292 # make a line count mean anything.
6293 _zw_off=$(grep -oa "$(printf '\033')\[?1000l" "$OUT.zwcap" | wc -l)
6294 [ "$_zw_off" -ge 2 ] || {
6295 echo "e2e FAIL: zoom wheel: mouse reporting was turned off $_zw_off time(s);"
6296 echo " the demote must take it off as well as the wall's exit:"
6297 cat "$OUT.zwpc"; exit 1; }
6298 assert_stopped "$SOCK41" "$D38PID" "zoom wheel" "$OUT.zwstop"
6299 D38PID="" 5783 D38PID=""
6300 ok "a demoted tile forwards nothing and leaves no SESSION mouse mode on the wall's terminal" 5784 ok "the focused tile scrolls its session's history on the wheel, and the pty sees none of it"
6301 5785
6302 # ---- an application in a ZOOMED tile owns the mouse --------------------- 5786 # ---- an application in the focused tile owns the mouse -----------------
6303 # 5787 #
6304 # Scenario 43 said this about a plain client; this says it about a tile, 5788 # Scenario 43 said this about a plain client; this says it about a tile.
6305 # where it is a stronger claim than it looks. A tile learns the session's 5789 # A tile learns the session's modes while it is a STRIPE — `interact.Core`
6306 # modes while it is a STRIPE — `interact.Core` decodes every `term_modes` 5790 # decodes every `term_modes` whatever the tile's claim is, and only the
6307 # whatever the tile's claim is, and only the WRITE to the terminal is gated 5791 # WRITE to the terminal is gated — so the handover cannot come from a frame:
6308 # — and the promote is answered by `resyncSnapshot`, which carries no modes 5792 # it has to come from what the Core already knew, level-set onto the
6309 # at all. So the handover cannot come from a frame: it has to come from what 5793 # terminal at the moment of the claim.
6310 # the Core already knew, level-set onto the terminal at the moment of the
6311 # claim. Measured before that existed: the session's own mode never reached
6312 # this capture and the wheel was eaten as scrollback.
6313 # 5794 #
6314 # 1003 is what makes the leg sharp, and it has to be 1003: the wall claims 5795 # 1003 is what makes the leg sharp, and it has to be 1003: the wall claims
6315 # 1000+1002+1006 for itself and a promoted tile claims the same set, so 5796 # 1000+1002+1006 for itself and a focused tile claims the same set, so
6316 # either of those would appear on this terminal with no session involved. 5797 # either of those would appear on this terminal with no session involved.
6317 # 1003 is in nobody's own set, so a `?1003h` here can only be the session's, 5798 # 1003 is in nobody's own set, so a `?1003h` here can only be the session's,
6318 # mirrored. 5799 # mirrored.
@@ -6326,207 +5807,65 @@ EOF
6326 chmod +x "$ZMOUSESH" 5807 chmod +x "$ZMOUSESH"
6327 "$MUXD" run --sock "$SOCK42" --shell "$ZMOUSESH" > "$OUT.zm2.d" 2>&1 & 5808 "$MUXD" run --sock "$SOCK42" --shell "$ZMOUSESH" > "$OUT.zm2.d" 2>&1 &
6328 D39PID=$! 5809 D39PID=$!
6329 wait_sock "$SOCK42" "$OUT.zm2.d" "zoom-app-mouse daemon never bound" 5810 wait_sock "$SOCK42" "$OUT.zm2.d" "app-mouse daemon never bound"
6330 i=0 5811 i=0
6331 until "$MUXD" dump --sock "$SOCK42" | grep -q "mapp-holds-the-mouse"; do 5812 until "$MUXD" dump --sock "$SOCK42" | grep -q "mapp-holds-the-mouse"; do
6332 i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: zoom app mouse: session never armed"; exit 1; } 5813 i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: app mouse: session never armed"; exit 1; }
6333 sleep 0.1 5814 sleep 0.1
6334 done 5815 done
6335 # The echo is the assertion: cat is in canonical mode with ECHOCTL, so bytes 5816 # The echo is the assertion: cat is in canonical mode with ECHOCTL, so bytes
6336 # that reach the pty come back as `^[[<64;10;5M` and bytes that do not, do 5817 # that reach the pty come back as `^[[<64;10;5M` and bytes that do not, do
6337 # not. 5818 # not. The tile claims the terminal on its first pass, so the modes are
5819 # level-set before the wheel is sent.
6338 set +e 5820 set +e
6339 timeout 90 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.zm2cap" --err "$OUT.zm2cap.err" -- \ 5821 timeout 90 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.zm2cap" --err "$OUT.zm2cap.err" -- \
6340 "$MUX" wall "--sock $SOCK42" > "$OUT.zm2pc" 2>&1 <<'EOF' 5822 "$MUX" wall "--sock $SOCK42" > "$OUT.zm2pc" 2>&1 <<'EOF'
6341 expect mapp-holds-the-mouse 20000 5823 expect mapp-holds-the-mouse 20000
6342 settle 700 20000 5824 settle 700 20000
6343 send \r
6344 settle 900 20000
6345 send \x1b[<64;10;5M 5825 send \x1b[<64;10;5M
6346 expect [<64;10;5M 15000 5826 expect [<64;10;5M 15000
6347 settle 500 15000 5827 settle 500 15000
6348 send \x1cw 5828 send \x1cd
6349 settle 700 20000
6350 send q
6351 waitexit 10000 5829 waitexit 10000
6352 EOF 5830 EOF
6353 RC=$? 5831 RC=$?
6354 set -e 5832 set -e
6355 [ "$RC" -eq 0 ] || { 5833 [ "$RC" -eq 0 ] || {
6356 echo "e2e FAIL: zoom app mouse: ptyclient leg exited $RC (did the wheel reach the app?):" 5834 echo "e2e FAIL: app mouse: ptyclient leg exited $RC (did the wheel reach the app?):"
6357 cat "$OUT.zm2pc"; exit 1; } 5835 cat "$OUT.zm2pc"; exit 1; }
6358 # The session's own grid, not just this terminal: the echo is the pty saying 5836 # The session's own grid, not just this terminal: the echo is the pty saying
6359 # it received the bytes. 5837 # it received the bytes.
6360 timeout 20 "$MUXA" capture --sock "$SOCK42" > "$OUT.zm2capg" 2>&1 5838 timeout 20 "$MUXA" capture --sock "$SOCK42" > "$OUT.zm2capg" 2>&1
6361 grep -qF -- "[<64;10;5M" "$OUT.zm2capg" || { 5839 grep -qF -- "[<64;10;5M" "$OUT.zm2capg" || {
6362 echo "e2e FAIL: zoom app mouse: the wheel never reached the application's pty:" 5840 echo "e2e FAIL: app mouse: the wheel never reached the application's pty:"
6363 cat "$OUT.zm2capg"; exit 1; } 5841 cat "$OUT.zm2capg"; exit 1; }
6364 # The mirror: this terminal was asked for the SESSION's modes at the 5842 # The mirror: this terminal was asked for the SESSION's modes at the claim,
6365 # promote, not for the tile's own set — and 1003 is outside that set. 5843 # not for the tile's own set — and 1003 is outside that set.
6366 grep -qaF "$(printf '\033')[?1003h" "$OUT.zm2cap" || { 5844 grep -qaF "$(printf '\033')[?1003h" "$OUT.zm2cap" || {
6367 echo "e2e FAIL: zoom app mouse: the promote never mirrored the session's any-motion mode:" 5845 echo "e2e FAIL: app mouse: the claim never mirrored the session's any-motion mode:"
6368 cat "$OUT.zm2pc"; exit 1; } 5846 cat "$OUT.zm2pc"; exit 1; }
6369 # And the negative that makes the pair a pair: this session has the same 77 5847 # And the negative that makes the pair a pair: this session has the same 77
6370 # rows of history as the leg above, and the same wheel byte moved none of it. 5848 # rows of history as the leg above, and the same wheel byte moved none of it.
6371 grep -qa "mln60" "$OUT.zm2cap" && { 5849 grep -qa "mln60" "$OUT.zm2cap" && {
6372 echo "e2e FAIL: zoom app mouse: the tile scrolled back on a wheel the app owned:" 5850 echo "e2e FAIL: app mouse: the tile scrolled back on a wheel the app owned:"
6373 cat "$OUT.zm2pc"; exit 1; } 5851 cat "$OUT.zm2pc"; exit 1; }
6374 assert_stopped "$SOCK42" "$D39PID" "zoom app mouse" "$OUT.zm2stop" 5852 assert_stopped "$SOCK42" "$D39PID" "app mouse" "$OUT.zm2stop"
6375 D39PID="" 5853 D39PID=""
6376 ok "an application in a zoomed tile gets the wheel, and the tile does not" 5854 ok "an application in the focused tile gets the wheel, and the tile does not"
6377 5855
6378 # ---- a tile the zoom is not on paints NOTHING -------------------------- 5856 # ---- a tile whose pump has died still says something -------------------
6379 # 5857 #
6380 # The other half of "zoom is a lens": while one tile owns the terminal, the 5858 # A dead tile keeps its rect and narrates on its label bar — the only
6381 # tiles that do not own it must be silent — not merely cropped to their 5859 # paint a pump with nobody left to answer `repaint_gen` ever does. The
6382 # stripes, SILENT. A hidden tile's session goes on producing frames the 5860 # keyboard is the only thread left, and it draws that bar so the wall
6383 # whole time (that is the point of keeping every pump hot), and each one is 5861 # never silently loses a row.
6384 # a full-screen repaint waiting to happen on somebody else's zoom. 5862 #
6385 # 5863 # The tile here is dead by the most ordinary route: a spelling naming a
6386 # Phase 1 spelled that decision at four call sites inside the pump; phase 3b 5864 # session that does not exist. The daemon refuses the attach and that pump
6387 # spells it once, in the paint sink every Core paint now asks 5865 # ends — `[refused]`, the state the wall already had a word for. Its bar
6388 # (`tilePaintBegin`). One place is worth a test that fires when it goes 5866 # says so from the first paint, and the live tile beside it is unharmed:
6389 # wrong, and nothing in this suite could see it before: a capture is a byte 5867 # the wall is still a wall, and the focus stays on the tile that can be
6390 # STREAM, so a stripe and a full-screen paint of the same rows leave the 5868 # typed at.
6391 # same bytes, and overpainting is invisible to grep. What IS visible is a
6392 # marker arriving at a moment it should not have arrived at all.
6393 #
6394 # So the leg is a COUNT, and the marker is produced on demand rather than on
6395 # a timer:
6396 #
6397 # * two daemons, two default sessions, one wall. The second session's
6398 # shell blocks on a flag file and prints `bhidden-pin` when it appears.
6399 # * the zoom goes to the FIRST tile, and it is the first session's own
6400 # shell — typed at, through the zoom — that creates the flag. So the
6401 # hidden tile's output happens strictly INSIDE the zoom, with no sleep
6402 # to race and no ordering to hope for.
6403 # * the capture is then SPLIT at `zhzoom-ok`, which the zoomed session
6404 # prints immediately before the unzoom chord. Before that split
6405 # `bhidden-pin` must appear ZERO times — that is "silent while hidden",
6406 # stated exactly. After it, at least once: the stripe repaint from the
6407 # replica that was hot the entire time, which is also what proves the
6408 # hidden session really did have something to say.
6409 #
6410 # What is deliberately NOT pinned is the count after the unzoom. A demoted
6411 # tile has two legal paint paths — a frame arriving, and `repaint_gen` — so a
6412 # second stripe paint is a repaint schedule rather than a broken invariant,
6413 # and pinning it to one failed this leg about three runs in five.
6414 #
6415 # The zoomed session's own marker is therefore load-bearing twice: it is the
6416 # split point, and a leg that lost the zoom fails loudly on its expect
6417 # instead of passing the count for the wrong reason.
6418 cat > "$ZHASH" <<'EOF'
6419 #!/bin/sh
6420 printf 'zha-%s\n' pin
6421 exec /bin/sh
6422 EOF
6423 chmod +x "$ZHASH"
6424 # Written with an UNQUOTED heredoc: the flag path carries this run's pid and
6425 # has to be baked in. Nothing else in it is a shell expansion.
6426 cat > "$ZHBSH" <<EOF
6427 #!/bin/sh
6428 printf 'zhb-%s\n' pin
6429 while [ ! -f "$ZHFLAG" ]; do sleep 0.2; done
6430 printf 'bhidden-%s\n' pin
6431 exec /bin/cat
6432 EOF
6433 chmod +x "$ZHBSH"
6434 rm -f "$ZHFLAG"
6435 "$MUXD" run --sock "$SOCK43" --shell "$ZHASH" > "$OUT.zh.da" 2>&1 &
6436 D40PID=$!
6437 wait_sock "$SOCK43" "$OUT.zh.da" "hidden-tile daemon A never bound"
6438 "$MUXD" run --sock "$SOCK44" --shell "$ZHBSH" > "$OUT.zh.db" 2>&1 &
6439 D41PID=$!
6440 wait_sock "$SOCK44" "$OUT.zh.db" "hidden-tile daemon B never bound"
6441 # ONE anchor for the wall, scenario 33's lesson: tile threads race and
6442 # `expect` is a stream cursor, so the second of two markers is buried for
6443 # good whenever it painted first. The settle after it is what says both
6444 # stripes have finished.
6445 #
6446 # The ptyclient script is a FILE rather than a heredoc for the same reason
6447 # the B shell is unquoted: `$ZHFLAG` has to expand, and this suite's
6448 # scenario heredocs are quoted precisely so that backslash escapes reach
6449 # ptyclient untouched. Nothing here needs one that bash would eat.
6450 cat > "$ZHSCRIPT" <<EOF
6451 expect zhb-pin 20000
6452 settle 900 20000
6453 send \r
6454 settle 900 20000
6455 send : > "$ZHFLAG"\n
6456 settle 1500 20000
6457 send echo zhzoom-ok\n
6458 expect zhzoom-ok 15000
6459 settle 700 20000
6460 send \x1cw
6461 expect bhidden-pin 20000
6462 settle 900 20000
6463 send q
6464 waitexit 10000
6465 EOF
6466 set +e
6467 timeout 90 "$PTYCLIENT" --cols 96 --rows 30 --out "$OUT.zhcap" --err "$OUT.zhcap.err" -- \
6468 "$MUX" wall "--sock $SOCK43" "--sock $SOCK44" > "$OUT.zhpc" 2>&1 < "$ZHSCRIPT"
6469 RC=$?
6470 set -e
6471 [ "$RC" -eq 0 ] || {
6472 echo "e2e FAIL: hidden tile: ptyclient leg exited $RC (did the zoom take, did the flag land?):"
6473 cat "$OUT.zhpc"; exit 1; }
6474 # The flag really was created by the ZOOMED session's shell, so the hidden
6475 # session really did have something to say while it was hidden. Without this
6476 # the count below would pass on a session that simply never printed.
6477 [ -f "$ZHFLAG" ] || {
6478 echo "e2e FAIL: hidden tile: the zoom never created the flag, so the hidden"
6479 echo " session never printed and the count proves nothing:"
6480 cat "$OUT.zhpc"; exit 1; }
6481 # The money assertion. `grep -o`, not `grep -c`: a stripe paint is escape
6482 # sequences with no newlines in them, so a line count reads 1 either way.
6483 #
6484 # Which is also why the split is a BYTE offset and not `sed '1,/marker/p'`:
6485 # the same absent newlines make this whole capture one enormous line, so a
6486 # line-addressed split hands back the entire file and the "before" half
6487 # quietly becomes the whole run.
6488 _zh_off=$(grep -oab "zhzoom-ok" "$OUT.zhcap" | head -1 | cut -d: -f1)
6489 [ -n "$_zh_off" ] || {
6490 echo "e2e FAIL: hidden tile: the zoomed session's marker is not in the capture,"
6491 echo " so there is no before/after to split on:"
6492 cat "$OUT.zhpc"; exit 1; }
6493 _zh_pre=$(head -c "$_zh_off" "$OUT.zhcap" | grep -oa "bhidden-pin" | wc -l)
6494 _zh_post=$(tail -c "+$((_zh_off + 1))" "$OUT.zhcap" | grep -oa "bhidden-pin" | wc -l)
6495 [ "$_zh_pre" -eq 0 ] || {
6496 echo "e2e FAIL: hidden tile: the hidden session's marker reached this terminal"
6497 echo " $_zh_pre time(s) WHILE THE ZOOM WAS UP; a tile the zoom is not"
6498 echo " on is silent, and that is a tile painting over the session the"
6499 echo " user was typing at:"
6500 cat "$OUT.zhpc"; exit 1; }
6501 [ "$_zh_post" -ge 1 ] || {
6502 echo "e2e FAIL: hidden tile: the marker never reached this terminal after the"
6503 echo " unzoom, so the stripe repaint from the hot replica never"
6504 echo " happened and the silence above proves nothing:"
6505 cat "$OUT.zhpc"; exit 1; }
6506 assert_stopped "$SOCK43" "$D40PID" "hidden tile A" "$OUT.zhstopa"
6507 D40PID=""
6508 assert_stopped "$SOCK44" "$D41PID" "hidden tile B" "$OUT.zhstopb"
6509 D41PID=""
6510 ok "a tile the zoom is not on paints nothing at all, however loud its session gets"
6511
6512 # ---- a tile whose pump has died still says something ---------------------
6513 #
6514 # Only pumps answer `repaint_gen`, so a tile whose pump has ENDED has
6515 # nobody to redraw it. That was survivable while the screen was cleared
6516 # once per zoom; phase 1 clears it on every `n`/`p`/`l` as well, and two
6517 # symptoms fell out of the same hole — observed in a live capture, not
6518 # inferred:
6519 #
6520 # * zooming a dead tile painted an entirely blank terminal. No cursor,
6521 # no label, no hint: indistinguishable from a hung multiplexer.
6522 # * after any zoom, a dead tile's stripe and bar never came back, so the
6523 # wall silently lost a row for the rest of its life.
6524 #
6525 # Both are the keyboard's to fix, because it is the only thread left. The
6526 # tile here is dead by the most ordinary route there is: a spelling naming
6527 # a session that does not exist. A wall tile attaches 0x0, a 0x0 attach
6528 # joins but never creates, so the daemon refuses it and that pump ends —
6529 # `[refused]`, the state the wall already had a word for.
6530 # 5869 #
6531 # Deliberately NOT asserted: that a `[reconnecting]` tile gets the same 5870 # Deliberately NOT asserted: that a `[reconnecting]` tile gets the same
6532 # treatment. It must not — it still has a thread that will repaint its hot 5871 # treatment. It must not — it still has a thread that will repaint its hot
@@ -6534,56 +5873,47 @@ ok "a tile the zoom is not on paints nothing at all, however loud its session ge
6534 # replace something true with something stale. 5873 # replace something true with something stale.
6535 "$MUXD" run --sock "$SOCK39" --shell /bin/sh > "$OUT.zd.d" 2>&1 & 5874 "$MUXD" run --sock "$SOCK39" --shell /bin/sh > "$OUT.zd.d" 2>&1 &
6536 D36PID=$! 5875 D36PID=$!
6537 wait_sock "$SOCK39" "$OUT.zd.d" "zoom-dead daemon never bound" 5876 wait_sock "$SOCK39" "$OUT.zd.d" "dead-tile daemon never bound"
6538 pipe_mux "$OUT.zda" "$OUT.zda.err" timeout 40 "$MUX" --sock "$SOCK39" --session a 5877 pipe_mux "$OUT.zda" "$OUT.zda.err" timeout 40 "$MUX" --sock "$SOCK39" --session a
6539 pipe_send 'printf "zdlive-%%s\\n" pin\n' 5878 pipe_send 'printf "zdlive-%%s\\n" pin\n'
6540 await_out "$OUT.zda" "zdlive-pin" "zdlive-pin never reached the client" 5879 await_out "$OUT.zda" "zdlive-pin" "zdlive-pin never reached the client"
6541 pipe_detach 5880 pipe_detach
6542 wait_grid "$SOCK39" "zdlive-pin" "zoom dead: the live session's marker" a 5881 wait_grid "$SOCK39" "zdlive-pin" "dead tile: the live session's marker" a
6543 5882
6544 set +e 5883 set +e
6545 timeout 90 "$PTYCLIENT" --cols 90 --rows 24 --out "$OUT.zdcap" --err "$OUT.zdcap.err" -- \ 5884 timeout 90 "$PTYCLIENT" --cols 90 --rows 24 --out "$OUT.zdcap" --err "$OUT.zdcap.err" -- \
6546 "$MUX" wall "--sock $SOCK39#a" "--sock $SOCK39#ghost" > "$OUT.zdpc" 2>&1 <<'EOF' 5885 "$MUX" wall "--sock $SOCK39#a" "--sock $SOCK39#ghost" > "$OUT.zdpc" 2>&1 <<'EOF'
6547 expect zdlive-pin 20000 5886 expect zdlive-pin 20000
6548 settle 800 20000 5887 settle 800 20000
6549 send 2 5888 send printf 'zdagain-%s\\n' pin\n
6550 settle 500 15000 5889 expect zdagain-pin 15000
6551 send \r 5890 settle 400 15000
6552 settle 800 20000 5891 send \x1cd
6553 send \x1cw
6554 settle 800 20000
6555 send q
6556 waitexit 10000 5892 waitexit 10000
6557 EOF 5893 EOF
6558 RC=$? 5894 RC=$?
6559 set -e 5895 set -e
6560 [ "$RC" -eq 0 ] || { 5896 [ "$RC" -eq 0 ] || {
6561 echo "e2e FAIL: zoom dead: ptyclient leg exited $RC:" 5897 echo "e2e FAIL: dead tile: ptyclient leg exited $RC:"
6562 cat "$OUT.zdpc"; exit 1; } 5898 cat "$OUT.zdpc"; exit 1; }
6563 # The dead tile narrated its own refusal at all, which is the state word 5899 # The dead tile narrated its own refusal: the state word the bar can only
6564 # the bar can only have if the pump reached the refusal path. 5900 # have if the pump reached the refusal path. Painted from the first
5901 # Painted from the first relayout, and never cleared.
6565 grep -q -- "--sock $SOCK39#ghost \[refused\]" "$OUT.zdcap" || { 5902 grep -q -- "--sock $SOCK39#ghost \[refused\]" "$OUT.zdcap" || {
6566 echo "e2e FAIL: zoom dead: the dead tile's bar never said [refused]"; exit 1; } 5903 echo "e2e FAIL: dead tile: the dead tile's bar never said [refused]"; exit 1; }
6567 # Zooming it put a line on the screen instead of nothing. The way out is 5904 # The live tile is unharmed and answering — the wall is still a wall.
6568 # part of the assertion: a screen that says only "refused" and offers no
6569 # chord is the same dead end with better manners.
6570 grep -q "Ctrl-\\\\ w for the wall" "$OUT.zdcap" || {
6571 echo "e2e FAIL: zoom dead: zooming a dead tile painted a blank terminal"
6572 echo " (no label, no state, no way out)"; exit 1; }
6573 # ...and its bar came BACK after the unzoom. Counted, not merely present:
6574 # one paint is the wall's first draw, which happens before any zoom cleared
6575 # the screen and so proves nothing about coming back.
6576 _zd_bars=$(grep -o -- "--sock $SOCK39#ghost \[refused\]" "$OUT.zdcap" | wc -l)
6577 [ "$_zd_bars" -ge 2 ] || {
6578 echo "e2e FAIL: zoom dead: the dead tile's bar painted $_zd_bars time(s), so it"
6579 echo " never came back after the zoom cleared the screen:"
6580 cat "$OUT.zdpc"; exit 1; }
6581 # The live tile is unharmed by any of it — the wall is still a wall.
6582 grep -q -- "--sock $SOCK39#a \[up\]" "$OUT.zdcap" || { 5905 grep -q -- "--sock $SOCK39#a \[up\]" "$OUT.zdcap" || {
6583 echo "e2e FAIL: zoom dead: the live tile's bar never said [up]"; exit 1; } 5906 echo "e2e FAIL: dead tile: the live tile's bar never said [up]"; exit 1; }
6584 assert_stopped "$SOCK39" "$D36PID" "zoom dead" "$OUT.zdstop" 5907 # The focus stayed on the live tile: the marker typed after the wall came
5908 # up landed in a, which is the tile that can be typed at. A focus that
5909 # landed on the dead one would have dropped these bytes.
5910 timeout 20 "$MUXA" capture --sock "$SOCK39" --session a > "$OUT.zdcapa" 2>&1
5911 grep -q "zdagain-pin" "$OUT.zdcapa" || {
5912 echo "e2e FAIL: dead tile: the live session never got the focused marker:"
5913 cat "$OUT.zdcapa"; exit 1; }
5914 assert_stopped "$SOCK39" "$D36PID" "dead tile" "$OUT.zdstop"
6585 D36PID="" 5915 D36PID=""
6586 ok "a tile whose pump has died narrates its zoom and gets its stripe back" 5916 ok "a tile whose pump has died narrates on its bar, and the focus stays on the live one"
6587 5917
6588 # ---- the wall is attach HISTORY ----------------------------------------- 5918 # ---- the wall is attach HISTORY -----------------------------------------
6589 # 5919 #
@@ -6735,9 +6065,9 @@ ok "the wall is attach history: mux adds, muxa never does, add/rm edit it"
6735 # ---- `x` forgets a tile, and never kills its session -------------------- 6065 # ---- `x` forgets a tile, and never kills its session --------------------
6736 # 6066 #
6737 # The wall this leg puts up is one nothing typed by hand: two attaches 6067 # The wall this leg puts up is one nothing typed by hand: two attaches
6738 # BUILT it, which is the model's whole claim. Then `x` is asked for the two 6068 # BUILT it, which is the model's whole claim. Then `Ctrl-\ x` is asked for
6739 # things it must do and the one it must not — remove the line, re-cut the 6069 # the two things it must do and the one it must not — remove the line,
6740 # wall, and leave the session running ("remove is detach"). 6070 # re-cut the wall, and leave the session running ("remove is detach").
6741 # 6071 #
6742 # Two ptyclient runs rather than one, because the file is the artifact: 6072 # Two ptyclient runs rather than one, because the file is the artifact:
6743 # what a run did is only readable after it has exited, so the one-tile 6073 # what a run did is only readable after it has exited, so the one-tile
@@ -6768,46 +6098,49 @@ _whx_n=$(wc -l < "$WHXSTATE/mux/wall")
6768 # whichever stripe painted second leaves the other one's marker BEHIND the 6098 # whichever stripe painted second leaves the other one's marker BEHIND the
6769 # cursor. Two consecutive expects against two pumps is a coin flip that 6099 # cursor. Two consecutive expects against two pumps is a coin flip that
6770 # spends its whole budget waiting for bytes that already went past. What 6100 # spends its whole budget waiting for bytes that already went past. What
6771 # the `x` below needs is a wall that is up and settled, which the single 6101 # the `Ctrl-\ x` below needs is a wall that is up and settled, which the
6772 # expect plus `settle` gives; the tile it forgets is the SELECTED one, 6102 # single expect plus `settle` gives; the tile it forgets is the FOCUSED
6773 # tile 1, whose own marker is asserted from the file afterwards. 6103 # one, tile 0, whose line the file is then asserted NOT to hold.
6774 set +e 6104 set +e
6775 XDG_STATE_HOME="$WHXSTATE" timeout 60 "$PTYCLIENT" --cols 100 --rows 30 \ 6105 XDG_STATE_HOME="$WHXSTATE" timeout 60 "$PTYCLIENT" --cols 100 --rows 30 \
6776 --out "$OUT.whxcap" --err "$OUT.whxcap.err" -- \ 6106 --out "$OUT.whxcap" --err "$OUT.whxcap.err" -- \
6777 "$MUX" wall > "$OUT.whxpc" 2>&1 <<'EOF' 6107 "$MUX" wall > "$OUT.whxpc" 2>&1 <<'EOF'
6778 expect whxb-pin 20000 6108 expect whxb-pin 20000
6779 settle 800 20000 6109 settle 800 20000
6780 send x 6110 send \x1cx
6781 settle 800 20000 6111 settle 800 20000
6782 send q 6112 send \x1cd
6783 waitexit 10000 6113 waitexit 10000
6784 EOF 6114 EOF
6785 RC=$? 6115 RC=$?
6786 set -e 6116 set -e
6787 [ "$RC" -eq 0 ] || { 6117 [ "$RC" -eq 0 ] || {
6788 echo "e2e FAIL: x forgets: ptyclient leg exited $RC (did x forget the tile?):" 6118 echo "e2e FAIL: x forgets: ptyclient leg exited $RC (did \\x1cx forget the tile?):"
6789 cat "$OUT.whxpc"; exit 1; } 6119 cat "$OUT.whxpc"; exit 1; }
6790 # The line is gone and the survivor kept its place — `x` is an ordered 6120 # The line is gone and the survivor kept its place — `Ctrl-\ x` is an
6791 # removal, not a rewrite of the wall. 6121 # ordered removal, not a rewrite of the wall.
6792 _whx_n=$(wc -l < "$WHXSTATE/mux/wall") 6122 _whx_n=$(wc -l < "$WHXSTATE/mux/wall")
6793 [ "$_whx_n" -eq 1 ] || { 6123 [ "$_whx_n" -eq 1 ] || {
6794 echo "e2e FAIL: x forgets: the wall file holds $_whx_n lines after one x:" 6124 echo "e2e FAIL: x forgets: the wall file holds $_whx_n lines after one \\x1cx:"
6795 cat "$WHXSTATE/mux/wall"; exit 1; } 6125 cat "$WHXSTATE/mux/wall"; exit 1; }
6796 grep -qx -- "--sock $SOCK40#xb" "$WHXSTATE/mux/wall" || { 6126 grep -qx -- "--sock $SOCK40#xb" "$WHXSTATE/mux/wall" || {
6797 echo "e2e FAIL: x forgets: the wrong line survived:" 6127 echo "e2e FAIL: x forgets: the wrong line survived:"
6798 cat "$WHXSTATE/mux/wall"; exit 1; } 6128 cat "$WHXSTATE/mux/wall"; exit 1; }
6799 # The survivor's stripe came BACK at its new rows: counted, because one 6129 # The survivor took the whole terminal: its marker painted a SECOND time
6800 # paint is the wall's first draw and says nothing about the re-cut. 6130 # after the re-cut, counted because one paint is the wall's first draw and
6801 _whx_bars=$(grep -o -- "--sock $SOCK40#xb \[up\]" "$OUT.whxcap" | wc -l) 6131 # says nothing about the hole closing around the survivor.
6132 _whx_bars=$(grep -o "whxb-pin" "$OUT.whxcap" | wc -l)
6802 [ "$_whx_bars" -ge 2 ] || { 6133 [ "$_whx_bars" -ge 2 ] || {
6803 echo "e2e FAIL: x forgets: the surviving tile's bar painted $_whx_bars time(s)," 6134 echo "e2e FAIL: x forgets: the surviving tile's marker painted $_whx_bars time(s),"
6804 echo " so the wall was never re-cut around the hole:" 6135 echo " so the wall was never re-cut around the hole:"
6805 cat "$OUT.whxpc"; exit 1; } 6136 cat "$OUT.whxpc"; exit 1; }
6806 # ...and the SESSION is untouched: it still answers, and it still holds 6137 # ...and the SESSION is untouched: it still answers, and it still holds
6807 # what it held. "Remove is detach" — the tile went, the session did not. 6138 # what it held. "Remove is detach" — the tile went, the session did not.
6139 # xa was 80-wide from its pipe attach; this 100-wide wall resized it on
6140 # hydrate, and the detach left that size behind.
6808 XDG_STATE_HOME="$WHSTATE" "$MUXA" status --sock "$SOCK40" --session xa > "$OUT.whxst" 2>&1 6141 XDG_STATE_HOME="$WHSTATE" "$MUXA" status --sock "$SOCK40" --session xa > "$OUT.whxst" 2>&1
6809 grep -q '"cols":80' "$OUT.whxst" || { 6142 grep -q '"cols":100' "$OUT.whxst" || {
6810 echo "e2e FAIL: x forgets: session xa stopped answering — x killed it:" 6143 echo "e2e FAIL: x forgets: session xa stopped answering — \\x1cx killed it:"
6811 cat "$OUT.whxst"; exit 1; } 6144 cat "$OUT.whxst"; exit 1; }
6812 wait_grid "$SOCK40" "whxa-pin" "x forgets: xa's grid outlived its tile" xa 6145 wait_grid "$SOCK40" "whxa-pin" "x forgets: xa's grid outlived its tile" xa
6813 6146
@@ -6818,9 +6151,9 @@ XDG_STATE_HOME="$WHXSTATE" timeout 60 "$PTYCLIENT" --cols 100 --rows 30 \
6818 "$MUX" wall > "$OUT.whxpc2" 2>&1 <<'EOF' 6151 "$MUX" wall > "$OUT.whxpc2" 2>&1 <<'EOF'
6819 expect whxb-pin 20000 6152 expect whxb-pin 20000
6820 settle 800 20000 6153 settle 800 20000
6821 send x 6154 send \x1cx
6822 settle 800 20000 6155 settle 800 20000
6823 send q 6156 send \x1cd
6824 waitexit 10000 6157 waitexit 10000
6825 EOF 6158 EOF
6826 RC=$? 6159 RC=$?
@@ -6832,7 +6165,7 @@ grep -q "nothing left to show" "$OUT.whxcap2" || {
6832 echo "e2e FAIL: x forgets: forgetting the last tile left a blank terminal" 6165 echo "e2e FAIL: x forgets: forgetting the last tile left a blank terminal"
6833 echo " with nothing said:"; cat "$OUT.whxpc2"; exit 1; } 6166 echo " with nothing said:"; cat "$OUT.whxpc2"; exit 1; }
6834 [ ! -s "$WHXSTATE/mux/wall" ] || { 6167 [ ! -s "$WHXSTATE/mux/wall" ] || {
6835 echo "e2e FAIL: x forgets: the last x left lines behind:" 6168 echo "e2e FAIL: x forgets: the last \\x1cx left lines behind:"
6836 cat "$WHXSTATE/mux/wall"; exit 1; } 6169 cat "$WHXSTATE/mux/wall"; exit 1; }
6837 # The phantom tile, while this daemon is FULL (0, 1, xa, xb — max_sessions 6170 # The phantom tile, while this daemon is FULL (0, 1, xa, xb — max_sessions
6838 # is 4): a FIRST attach to a fifth name is refused, and a refusal must 6171 # is 4): a FIRST attach to a fifth name is refused, and a refusal must
@@ -6855,36 +6188,37 @@ set -e
6855 cat "$WHWALL"; exit 1; } 6188 cat "$WHWALL"; exit 1; }
6856 assert_stopped "$SOCK40" "$D37PID" "x forgets" "$OUT.whstop" 6189 assert_stopped "$SOCK40" "$D37PID" "x forgets" "$OUT.whstop"
6857 D37PID="" 6190 D37PID=""
6858 ok "x forgets a tile and leaves its session running; a refusal records nothing" 6191 ok "\\x1cx forgets a tile and leaves its session running; a refusal records nothing"
6859 6192
6860 # ---- `mux TARGET` IS a wall, entered zoomed ------------------------------ 6193 # ---- `mux TARGET` is a wall of one tile ---------------------------------
6861 # 6194 #
6862 # The convergence's headline (wall-home-screen spec, phase 3): `mux` dials 6195 # The convergence's headline: `mux` dials and attaches exactly as it always
6863 # and attaches exactly as it always did, and what it runs afterwards is the 6196 # did, and what it runs is the WALL — one tile whose rect is the whole
6864 # WALL, seeded with that one tile and zoomed into it. So `Ctrl-\ w` does not 6197 # terminal, no label bar, byte-identical to a plain client. `Ctrl-\ w`
6865 # spawn anything — it demotes, and what appears underneath is THE wall: the 6198 # folds the SAVED wall into this one: it hydrates the file's tiles beside
6866 # tile this run is standing on plus the ones the saved file already holds. 6199 # the tile this run is standing on, and what appears is THE wall — this
6200 # run's tile plus the ones the saved file already holds.
6867 # 6201 #
6868 # Four claims, and each needs a different kind of witness: 6202 # Four claims, and each needs a different kind of witness:
6869 # 6203 #
6870 # * the wall the unzoom shows is the SAVED one. A tile written by an 6204 # * the wall `Ctrl-\ w` folds in is the SAVED one. A tile written by an
6871 # EARLIER `mux` (the `#side` attach below, into this leg's own state 6205 # EARLIER `mux` (the `#side` attach below, into this leg's own state
6872 # home) paints a stripe here, in a process that was never told about 6206 # home) paints a stripe here, in a process that was never told about
6873 # it on its command line. A wall that only ever showed this run's own 6207 # it on its command line. A wall that only ever showed this run's own
6874 # tile would satisfy every other assertion in this leg. 6208 # tile would satisfy every other assertion in this leg.
6875 # * going back in is `Enter`, and it costs NOTHING at the daemon. The 6209 # * the focus stays on the entry tile, and that costs NOTHING at the
6876 # attach counter is sampled across the return and must not move: the 6210 # daemon. The attach counter is sampled across the fold and the
6877 # replica was hot the whole time the tile was a stripe, so the zoom is 6211 # injection and moves only for the two real attaches (the entry tile
6878 # a local repaint. This is the phase-1 claim reached through the phase-3 6212 # and the saved tile the fold hydrated): focus is client-local and
6879 # door, which is the only part that is new. 6213 # sends nothing on the wire.
6880 # * `q` from the wall leaves MUX. It used to leave a child wall and hand 6214 # * `Ctrl-\ d` leaves MUX. There is no child and no second screen; the
6881 # the session back; there is no child now, and `q` is the way out. 6215 # one-tile wall is the program, and the detach chord is the way out.
6882 # * ...and the attach that opened all this earned exactly one line in the 6216 # * ...and the attach that opened all this earned exactly one line in the
6883 # file, spelled `#0`, beside the `#side` that was already there. 6217 # file, spelled `#0`, beside the `#side` that was already there.
6884 # 6218 #
6885 # A state home of its own, for the M4 reason: the wall an unzoom shows is 6219 # A state home of its own: the wall `Ctrl-\ w` folds in is the saved one,
6886 # the saved one, and the shared $XDG_STATE_HOME is every socket the suite 6220 # and the shared $XDG_STATE_HOME is every socket the suite has ever
6887 # has ever attached to. 6221 # attached to.
6888 "$MUXD" run --sock "$SOCK45" --shell /bin/sh > "$OUT.cv.d" 2>&1 & 6222 "$MUXD" run --sock "$SOCK45" --shell /bin/sh > "$OUT.cv.d" 2>&1 &
6889 D45PID=$! 6223 D45PID=$!
6890 wait_sock "$SOCK45" "$OUT.cv.d" "convergence daemon never bound" 6224 wait_sock "$SOCK45" "$OUT.cv.d" "convergence daemon never bound"
@@ -6898,15 +6232,16 @@ await_out "$OUT.cvside" "cvside-pin" "cvside-pin never reached the client"
6898 pipe_detach 6232 pipe_detach
6899 wait_grid "$SOCK45" "cvside-pin" "convergence: the saved tile's marker" side 6233 wait_grid "$SOCK45" "cvside-pin" "convergence: the saved tile's marker" side
6900 grep -qx -- "--sock $SOCK45#side" "$CVWALL" || { 6234 grep -qx -- "--sock $SOCK45#side" "$CVWALL" || {
6901 echo "e2e FAIL: convergence: the setup attach wrote no tile to unzoom onto:" 6235 echo "e2e FAIL: convergence: the setup attach wrote no tile to fold in:"
6902 cat "$CVWALL" 2>&1; exit 1; } 6236 cat "$CVWALL" 2>&1; exit 1; }
6903 6237
6904 # The whole run's budget, and every one of the three is named: the entry 6238 # The whole run's budget, and every one of the three is named: the entry
6905 # tile's attach, the saved tile's when the unzoom hydrates it, and the 6239 # tile's attach, the saved tile's when the fold hydrates it, and the
6906 # `muxa send` below, which is an attach like any other. Enter is the step 6240 # `muxa send` below, which is an attach like any other. The fold itself
6907 # that must add nothing, and a cumulative counter is what says so — a gauge 6241 # is a real attach (it dials the saved tile's session); the focus move and
6908 # would let a dial that opened and closed between two samples hide inside 6242 # the leave add nothing, and a cumulative counter is what says so — a
6909 # it. 6243 # gauge would let a dial that opened and closed between two samples hide
6244 # inside it.
6910 CVATT_BEFORE=$(attaches_now "$SOCK45") 6245 CVATT_BEFORE=$(attaches_now "$SOCK45")
6911 ( sleep 6; "$MUXA" send 'printf "cvret-%s\n" pin\n' \ 6246 ( sleep 6; "$MUXA" send 'printf "cvret-%s\n" pin\n' \
6912 --sock "$SOCK45" --session 0 > "$OUT.cvinj" 2>&1 ) & 6247 --sock "$SOCK45" --session 0 > "$OUT.cvinj" 2>&1 ) &
@@ -6923,19 +6258,16 @@ settle 400 15000
6923 send \x1cw 6258 send \x1cw
6924 expect cvside-pin 20000 6259 expect cvside-pin 20000
6925 settle 900 20000 6260 settle 900 20000
6926 send \r
6927 expect cvret-pin 20000 6261 expect cvret-pin 20000
6928 settle 500 15000 6262 settle 500 15000
6929 send \x1cw 6263 send \x1cd
6930 settle 800 20000
6931 send q
6932 waitexit 10000 6264 waitexit 10000
6933 EOF 6265 EOF
6934 RC=$? 6266 RC=$?
6935 set -e 6267 set -e
6936 wait "$CVINJPID" 2>/dev/null || true 6268 wait "$CVINJPID" 2>/dev/null || true
6937 [ "$RC" -eq 0 ] || { 6269 [ "$RC" -eq 0 ] || {
6938 echo "e2e FAIL: convergence: ptyclient leg exited $RC (did \\x1cw unzoom, did q leave?):" 6270 echo "e2e FAIL: convergence: ptyclient leg exited $RC (did \\x1cw fold, did \\x1cd leave?):"
6939 cat "$OUT.cvpc"; cat "$OUT.cvinj" 2>/dev/null; exit 1; } 6271 cat "$OUT.cvpc"; cat "$OUT.cvinj" 2>/dev/null; exit 1; }
6940 # The saved tile really painted a STRIPE, which only a wall draws — a bar 6272 # The saved tile really painted a STRIPE, which only a wall draws — a bar
6941 # with a spelling and a state word in it. A bare marker grep would be 6273 # with a spelling and a state word in it. A bare marker grep would be
@@ -6946,11 +6278,9 @@ grep -q -- "--sock $SOCK45#side \[up\]" "$OUT.cvcap" || {
6946 grep -q -- "--sock $SOCK45#0 \[up\]" "$OUT.cvcap" || { 6278 grep -q -- "--sock $SOCK45#0 \[up\]" "$OUT.cvcap" || {
6947 echo "e2e FAIL: convergence: the attached tile never became a stripe:" 6279 echo "e2e FAIL: convergence: the attached tile never became a stripe:"
6948 cat "$OUT.cvpc"; exit 1; } 6280 cat "$OUT.cvpc"; exit 1; }
6949 # The return was FREE. `cvret-pin` is injected into session 0 while the 6281 # The fold was a real attach (the saved tile dialled) and the injection was
6950 # wall is up and only reaches this terminal full-screen after Enter, so its 6282 # another; the focus move and the leave added nothing. THREE is the whole
6951 # arrival is the return itself — and the counter says the daemon heard 6283 # budget — entry tile, hydrated saved tile, injector — and a focus that
6952 # nothing about it. Three attaches for the whole run, all three accounted
6953 # for: the entry tile, the hydrated tile, and the injector. An Enter that
6954 # re-dialled would make it four. 6284 # re-dialled would make it four.
6955 CVATT=$(attaches_now "$SOCK45") 6285 CVATT=$(attaches_now "$SOCK45")
6956 assert_attach_delta "$CVATT_BEFORE" "$CVATT" 3 "convergence" 6286 assert_attach_delta "$CVATT_BEFORE" "$CVATT" 3 "convergence"
@@ -6964,13 +6294,13 @@ grep -qx -- "--sock $SOCK45#0" "$CVWALL" || {
6964 cat "$CVWALL"; exit 1; } 6294 cat "$CVWALL"; exit 1; }
6965 assert_stopped "$SOCK45" "$D45PID" "convergence" "$OUT.cvstop" 6295 assert_stopped "$SOCK45" "$D45PID" "convergence" "$OUT.cvstop"
6966 D45PID="" 6296 D45PID=""
6967 ok "mux is the wall: Ctrl-\\ w unzooms onto the saved tiles, Enter returns free, q leaves" 6297 ok "mux is the wall: Ctrl-\\ w folds in the saved tiles, the focus stays put, Ctrl-\\ d leaves"
6968 6298
6969 # ---- the ring GROWS the wall, and only when it has to -------------------- 6299 # ---- the ring GROWS the wall, and only when it has to --------------------
6970 # 6300 #
6971 # The spec's `n`/`p` rule, which phase 3c is where it lands: those chords 6301 # The spec's `n`/`p` rule, which phase 3c is where it lands: those chords
6972 # move the zoom around the DAEMON'S session ring, not around the wall's 6302 # move the focus around the DAEMON'S session ring, not around the wall's
6973 # tiles. A sibling that already has a tile is an instant zoom move; one 6303 # tiles. A sibling that already has a tile is an instant focus move; one
6974 # that does not GETS one, because visiting it is an attach and attach adds. 6304 # that does not GETS one, because visiting it is an attach and attach adds.
6975 # 6305 #
6976 # The leg is built so the two halves are the SAME chord pressed twice, and 6306 # The leg is built so the two halves are the SAME chord pressed twice, and
@@ -7049,7 +6379,7 @@ _rg_n=$(wc -l < "$RGSTATE/mux/wall")
7049 [ "$_rg_n" -eq 2 ] || { 6379 [ "$_rg_n" -eq 2 ] || {
7050 echo "e2e FAIL: ring grow: the wall file holds $_rg_n lines, want 2:" 6380 echo "e2e FAIL: ring grow: the wall file holds $_rg_n lines, want 2:"
7051 cat "$RGSTATE/mux/wall"; exit 1; } 6381 cat "$RGSTATE/mux/wall"; exit 1; }
7052 # Where the markers landed says the zoom really MOVED rather than the wall 6382 # Where the markers landed says the focus really MOVED rather than the wall
7053 # having gone on typing at one connection. Each session holds its own and 6383 # having gone on typing at one connection. Each session holds its own and
7054 # not its neighbour's. 6384 # not its neighbour's.
7055 timeout 20 "$MUXA" capture --sock "$SOCK46" --session two > "$OUT.rgcapt" 2>&1 6385 timeout 20 "$MUXA" capture --sock "$SOCK46" --session two > "$OUT.rgcapt" 2>&1
@@ -7067,11 +6397,11 @@ assert_stopped "$SOCK46" "$D46PID" "ring grow" "$OUT.rgstop"
7067 D46PID="" 6397 D46PID=""
7068 ok "Ctrl-\\ n grows the wall for a sibling with no tile, and is free for one that has" 6398 ok "Ctrl-\\ n grows the wall for a sibling with no tile, and is free for one that has"
7069 6399
7070 # ---- a session that ends under the zoom --------------------------------- 6400 # ---- a session that ends in the focused tile ---------------------------
7071 # 6401 #
7072 # The exit rule the convergence had to choose, stated in decisions.md and 6402 # The exit rule the convergence had to choose, stated in decisions.md and
7073 # pinned here: a session that ends under the zoom ENDS MUX when it was the 6403 # pinned here: a session that ends in the focused tile ENDS MUX when it was
7074 # wall's only tile, and DROPS TO THE WALL when it was not. 6404 # the wall's only tile, and LEAVES THE FOCUS TO A NEIGHBOUR when it was not.
7075 # 6405 #
7076 # Both halves in one block, because either alone is satisfied by a bug: 6406 # Both halves in one block, because either alone is satisfied by a bug:
7077 # 6407 #
@@ -7081,11 +6411,11 @@ ok "Ctrl-\\ n grows the wall for a sibling with no tile, and is free for one tha
7081 # comes back 0, because the wall it dropped to reads this script's 6411 # comes back 0, because the wall it dropped to reads this script's
7082 # closed stdin as a wall nobody is left to steer. Zero is not seven and 6412 # closed stdin as a wall nobody is left to steer. Zero is not seven and
7083 # the leg says so; 124 is the other shape the same bug can take. 6413 # the leg says so; 124 is the other shape the same bug can take.
7084 # * two tiles. `\x1cc` adds one and zooms it, the shell in it exits, and 6414 # * two tiles. `\x1cc` adds one and focuses it, the shell in it exits, and
7085 # mux is still running, on the wall, with the dead tile narrating 6415 # mux is still running, on the wall, with the dead tile narrating
7086 # `[exited]` beside the live one. Then `q` leaves — exit 0, not 7 and 6416 # `[exited]` beside the live one. Then `\x1cd` leaves — exit 0, not 7
7087 # not the shell's anything, because leaving the wall is the user's act 6417 # and not the shell's anything, because leaving is the user's act and
7088 # and not the session's. 6418 # not the session's.
7089 # * two tiles and a PIPED client whose stdin has already gone. The case 6419 # * two tiles and a PIPED client whose stdin has already gone. The case
7090 # review reproduced as a hang, and the reason the rule's second half is 6420 # review reproduced as a hang, and the reason the rule's second half is
7091 # stated in `endAction` rather than at the read that notices EOF. 6421 # stated in `endAction` rather than at the read that notices EOF.
@@ -7115,16 +6445,16 @@ settle 600 15000
7115 send exit 3\n 6445 send exit 3\n
7116 expect xehome-pin 20000 6446 expect xehome-pin 20000
7117 settle 800 20000 6447 settle 800 20000
7118 send q 6448 send \x1cd
7119 waitexit 10000 6449 waitexit 10000
7120 EOF 6450 EOF
7121 RC=$? 6451 RC=$?
7122 set -e 6452 set -e
7123 [ "$RC" -eq 0 ] || { 6453 [ "$RC" -eq 0 ] || {
7124 echo "e2e FAIL: exit semantics: the multi-tile leg exited $RC (did the zoom" 6454 echo "e2e FAIL: exit semantics: the multi-tile leg exited $RC (did the focus"
7125 echo " drop to the wall, and did q leave it?):" 6455 echo " move to the neighbour, and did \x1cd leave it?):"
7126 cat "$OUT.xepc"; exit 1; } 6456 cat "$OUT.xepc"; exit 1; }
7127 # The drop landed on a WALL — a bar per tile — and the dead one says what 6457 # The focus landed on a WALL — a bar per tile — and the dead one says what
7128 # became of it. `[exited]` is the word phase 1 gave a pump whose session 6458 # became of it. `[exited]` is the word phase 1 gave a pump whose session
7129 # ended, and it can only be on this terminal if the keyboard painted for a 6459 # ended, and it can only be on this terminal if the keyboard painted for a
7130 # tile with no pump left. 6460 # tile with no pump left.
@@ -7134,16 +6464,16 @@ grep -q -- "--sock $SOCK47#1 \[exited\]" "$OUT.xecap" || {
7134 grep -q -- "--sock $SOCK47#0 \[up\]" "$OUT.xecap" || { 6464 grep -q -- "--sock $SOCK47#0 \[up\]" "$OUT.xecap" || {
7135 echo "e2e FAIL: exit semantics: the surviving tile never got its stripe back:" 6465 echo "e2e FAIL: exit semantics: the surviving tile never got its stripe back:"
7136 cat "$OUT.xepc"; exit 1; } 6466 cat "$OUT.xepc"; exit 1; }
7137 # The session that did NOT end is still there: a shell exiting under the 6467 # The session that did NOT end is still there: a shell exiting in the
7138 # zoom ends that session and nothing else. 6468 # focused tile ends that session and nothing else.
7139 wait_grid "$SOCK47" "xehome-pin" "exit semantics: session 0 outlived its neighbour" 6469 wait_grid "$SOCK47" "xehome-pin" "exit semantics: session 0 outlived its neighbour"
7140 6470
7141 # The third case, and the one review REPRODUCED as a hang: two tiles, but 6471 # The third case, and the one review REPRODUCED as a hang: two tiles, but
7142 # a PIPED client whose stdin has already closed when the zoomed session 6472 # a PIPED client whose stdin has already closed when the focused session
7143 # ends. "Drop to the wall" is not an answer there — the wall it drops to 6473 # ends. Moving the focus to a neighbour is not an answer there — a wall
7144 # has no keyboard, so mux waited in poll forever with the exit code in 6474 # nobody is left to steer has no keyboard, so mux waited in poll forever
7145 # hand (RC=124, measured). The rule is the same rule with its second half 6475 # with the exit code in hand (RC=124, measured). The rule is the same rule
7146 # stated: an ending nobody can leave is mux's ending. 6476 # with its second half stated: an ending nobody can leave is mux's ending.
7147 # 6477 #
7148 # The gap is built rather than raced: the shell reads `sleep 4` and 6478 # The gap is built rather than raced: the shell reads `sleep 4` and
7149 # `exit 3` from a stdin that closes IMMEDIATELY after, so the pipe is long 6479 # `exit 3` from a stdin that closes IMMEDIATELY after, so the pipe is long
@@ -7160,7 +6490,7 @@ XETIME=$(( $(date +%s) - XETIME_START ))
7160 [ "$RC" -eq 3 ] || { 6490 [ "$RC" -eq 3 ] || {
7161 echo "e2e FAIL: exit semantics: a piped two-tile client whose session ended" 6491 echo "e2e FAIL: exit semantics: a piped two-tile client whose session ended"
7162 echo " exited $RC after ${XETIME}s, want 3 — 124 is the wall it" 6492 echo " exited $RC after ${XETIME}s, want 3 — 124 is the wall it"
7163 echo " dropped to with nobody left to leave it:" 6493 echo " left to a neighbour with nobody left to steer it:"
7164 cat "$OUT.xe2.err"; exit 1; } 6494 cat "$OUT.xe2.err"; exit 1; }
7165 # Promptly, not eventually: the code is right in both readings, but a 6495 # Promptly, not eventually: the code is right in both readings, but a
7166 # client that only exits because the timeout reaped it is the bug wearing 6496 # client that only exits because the timeout reaped it is the bug wearing
@@ -7171,8 +6501,7 @@ XETIME=$(( $(date +%s) - XETIME_START ))
7171 echo " own session had ended — it exited, but it did not notice"; exit 1; } 6501 echo " own session had ended — it exited, but it did not notice"; exit 1; }
7172 assert_stopped "$SOCK47" "$D47PID" "exit semantics" "$OUT.xestop" 6502 assert_stopped "$SOCK47" "$D47PID" "exit semantics" "$OUT.xestop"
7173 D47PID="" 6503 D47PID=""
7174 ok "a session ending under the zoom exits mux alone, and drops to the wall beside company" 6504 ok "a session ending in the focused tile exits mux alone, and leaves the focus to a neighbour"
7175
7176 6505
7177 # --- agent forwarding: a real key in the client's agent answers in the session 6506 # --- agent forwarding: a real key in the client's agent answers in the session
7178 # 6507 #
@@ -7446,7 +6775,6 @@ AGENT48PID=""
7446 # that quietly stops being true. 6775 # that quietly stops being true.
7447 ok "agent forwarding: no offerer means a fast refusal, not a hang (${AMS}ms)" 6776 ok "agent forwarding: no offerer means a fast refusal, not a hang (${AMS}ms)"
7448 6777
7449
7450 # --- ...and the agent that answers is whoever typed last ------------------- 6778 # --- ...and the agent that answers is whoever typed last -------------------
7451 # 6779 #
7452 # Two clients on ONE session, each offering its OWN ssh-agent holding its 6780 # Two clients on ONE session, each offering its OWN ssh-agent holding its
@@ -7599,116 +6927,38 @@ kill "$AGENT49BPID" 2>/dev/null || true
7599 AGENT49BPID="" 6927 AGENT49BPID=""
7600 ok "agent forwarding: the agent that answers is whoever typed last" 6928 ok "agent forwarding: the agent that answers is whoever typed last"
7601 6929
7602 6930 # ---- the SOCK50 cluster: a daemon, two named sessions ------------------
7603 # ---- a mouse report at the UNZOOMED wall is discarded, not typed --------
7604 #
7605 # The wall asks its own terminal for mouse reporting now, so reports reach
7606 # the unzoomed key loop where nothing used to be able to. That loop reads
7607 # every byte as a wall key, and an SGR report is made of characters it acts
7608 # on: `\x1b[<0;2;1M` carries a `2`, and a digit jumps the selection to that
7609 # tile. Left unfiltered, one click or one wheel spin over a stripe walks the
7610 # selection somewhere the user did not put it, and the next Enter zooms the
7611 # wrong session.
7612 # 6931 #
7613 # Which tile the selection is ON is not read off the label bar here — that 6932 # A daemon and two named sessions (a, b) shared by the click leg below and
7614 # would be asserting against a paint. It is read off the DAEMON: Enter zooms 6933 # the drag legs after it. The wall-mouse filter this cluster used to open
7615 # whatever is selected, so a marker typed afterwards lands in the selected 6934 # with is gone — every click and drag routes to the focused tile's own
7616 # session and `muxa capture` says which one that was. The wall painting is 6935 # Core, hit-tested by rect — so what remains here is the setup: a daemon,
7617 # not in the loop at all. 6936 # a marker per session, and the tiles a `mux wall` over them paints.
7618 #
7619 # Two runs, because a negative that cannot fail is not a test. The first
7620 # sends the reports and the second sends a bare `2` in their place; if `2`
7621 # did not move the selection, the pair would pass having proved nothing
7622 # about the filter.
7623 "$MUXD" run --sock "$SOCK50" --shell /bin/sh > "$OUT.wmse.d" 2>&1 & 6937 "$MUXD" run --sock "$SOCK50" --shell /bin/sh > "$OUT.wmse.d" 2>&1 &
7624 D48PID=$! 6938 D48PID=$!
7625 wait_sock "$SOCK50" "$OUT.wmse.d" "wall-mouse daemon never bound" 6939 wait_sock "$SOCK50" "$OUT.wmse.d" "wall-cluster daemon never bound"
7626
7627 { printf 'printf "wma-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 6940 { printf 'printf "wma-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \
7628 timeout 40 "$MUX" --sock "$SOCK50" --session a > "$OUT.wmsa" 2> "$OUT.wmsa.err" 6941 timeout 40 "$MUX" --sock "$SOCK50" --session a > "$OUT.wmsa" 2> "$OUT.wmsa.err"
7629 wait_grid "$SOCK50" "wma-pin" "wall mouse: session a's marker" a 6942 wait_grid "$SOCK50" "wma-pin" "wall cluster: session a's marker" a
7630 { printf 'printf "wmb-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ 6943 { printf 'printf "wmb-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \
7631 timeout 40 "$MUX" --sock "$SOCK50" --session b > "$OUT.wmsb" 2> "$OUT.wmsb.err" 6944 timeout 40 "$MUX" --sock "$SOCK50" --session b > "$OUT.wmsb" 2> "$OUT.wmsb.err"
7632 wait_grid "$SOCK50" "wmb-pin" "wall mouse: session b's marker" b 6945 wait_grid "$SOCK50" "wmb-pin" "wall cluster: session b's marker" b
7633
7634 # A press, a drag and a release at column 3, then a wheel notch — every
7635 # shape the filter has to take out of the key stream. The wall opens with
7636 # tile 1 (session a) selected and row 2 is inside tile a's own stripe, so
7637 # what the click means is "stay", which is what makes this the leg about
7638 # the KEYS: each report ENDS on a `2`, and that is not decoration. The key
7639 # loop acts on every digit as it passes, so a report ending on `1` would
7640 # jump to b and back to a and leave the selection where it started —
7641 # passing this leg with the filter deleted. Measured, not reasoned about:
7642 # with `\x1b[<0;2;1M` this leg passed against a loop that read the reports
7643 # as keys. Row 2 is the only parameter free to carry that `2`, since
7644 # column 3 and the button words have to spell something else. The legs
7645 # below are the other half: a click that MOVES the selection.
7646 set +e
7647 timeout 60 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.wmcap" --err "$OUT.wmcap.err" -- \
7648 "$MUX" wall "--sock $SOCK50#a" "--sock $SOCK50#b" > "$OUT.wmpc" 2>&1 <<'EOF'
7649 expect wmb-pin 20000
7650 settle 700 20000
7651 send \x1b[<0;3;2M\x1b[<32;3;2M\x1b[<0;3;2m\x1b[<64;3;2M
7652 settle 700 20000
7653 send \r
7654 settle 700 20000
7655 send printf 'wm-%s\\n' one\n
7656 expect wm-one 15000
7657 settle 400 15000
7658 send \x1cd
7659 waitexit 10000
7660 EOF
7661 RC=$?
7662 set -e
7663 [ "$RC" -eq 0 ] || {
7664 echo "e2e FAIL: wall mouse: ptyclient leg exited $RC (did the wall survive a mouse report?):"
7665 cat "$OUT.wmpc"; exit 1; }
7666 timeout 20 "$MUXA" capture --sock "$SOCK50" --session a > "$OUT.wmfa" 2>&1
7667 timeout 20 "$MUXA" capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1
7668 grep -q "wm-one" "$OUT.wmfa" || {
7669 echo "e2e FAIL: wall mouse: the selection left session a, so a report was read as keys:"
7670 cat "$OUT.wmfa"; exit 1; }
7671 grep -q "wm-one" "$OUT.wmfb" && {
7672 echo "e2e FAIL: wall mouse: a mouse report jumped the selection to session b:"
7673 cat "$OUT.wmfb"; exit 1; }
7674
7675 # The control. Same wall, same Enter, and a `2` where the reports were: this
7676 # one MUST land in b, or the negative above was a digit that never worked.
7677 set +e
7678 timeout 60 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.wmcap2" --err "$OUT.wmcap2.err" -- \
7679 "$MUX" wall "--sock $SOCK50#a" "--sock $SOCK50#b" > "$OUT.wmpc2" 2>&1 <<'EOF'
7680 expect wmb-pin 20000
7681 settle 700 20000
7682 send 2
7683 settle 700 20000
7684 send \r
7685 settle 700 20000
7686 send printf 'wm-%s\\n' two\n
7687 expect wm-two 15000
7688 settle 400 15000
7689 send \x1cd
7690 waitexit 10000
7691 EOF
7692 RC=$?
7693 set -e
7694 [ "$RC" -eq 0 ] || {
7695 echo "e2e FAIL: wall mouse: control leg exited $RC (did a bare 2 move the selection?):"
7696 cat "$OUT.wmpc2"; exit 1; }
7697 timeout 20 "$MUXA" capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1
7698 grep -q "wm-two" "$OUT.wmfb" || {
7699 echo "e2e FAIL: wall mouse: a bare 2 did not move the selection, so the leg above proves nothing:"
7700 cat "$OUT.wmfb"; exit 1; }
7701 ok "a mouse report at the unzoomed wall is swallowed; the same digit typed alone still jumps"
7702 6946
7703 # ...and now the same report SELECTS. Two tiles over 30 rows is two 6947 # ...and a click in a tile's rect FOCUSES it. Two tiles over 30 rows is two
7704 # fifteen-row stripes, each opening with a label bar, so terminal row 19 is 6948 # fifteen-row stripes, each opening with a label bar, so terminal row 19 is
7705 # inside session b's stripe and terminal row 1 is session a's BAR. 6949 # inside session b's content and terminal row 2 is inside session a's.
7706 # 6950 #
7707 # The row numbers are chosen the way the leg above chose its `2`. Read as 6951 # Which tile the focus is ON is read off the DAEMON, never off the paint: a
7708 # keys, `\x1b[<0;3;19M` offers `0`, `3`, `1` and `9`: `1` jumps to session a 6952 # marker typed after the click lands in the focused session and `muxa
7709 # and the rest name no tile, so a selection that ends on b cannot have got 6953 # capture` says which one that was. The report bytes themselves go through
7710 # there by a digit. Which session it ended on is read off the DAEMON again, 6954 # to the focused tile's Core unchanged, so the Core that owns the drag sees
7711 # never off the paint. 6955 # them.
6956 #
6957 # Two runs. The first clicks b's stripe from a fresh wall (focus starts on
6958 # a) and proves the click moved the focus to b; the second focuses b with
6959 # `Ctrl-\ 2` and clicks a's stripe, proving a click moves the focus the
6960 # other way. A negative that cannot fail is not a test, so each carries the
6961 # marker that MUST land in the focused session.
7712 set +e 6962 set +e
7713 timeout 60 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.wmcap3" --err "$OUT.wmcap3.err" -- \ 6963 timeout 60 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.wmcap3" --err "$OUT.wmcap3.err" -- \
7714 "$MUX" wall "--sock $SOCK50#a" "--sock $SOCK50#b" > "$OUT.wmpc3" 2>&1 <<'EOF' 6964 "$MUX" wall "--sock $SOCK50#a" "--sock $SOCK50#b" > "$OUT.wmpc3" 2>&1 <<'EOF'
@@ -7716,8 +6966,6 @@ expect wmb-pin 20000
7716 settle 700 20000 6966 settle 700 20000
7717 send \x1b[<0;3;19M\x1b[<0;3;19m 6967 send \x1b[<0;3;19M\x1b[<0;3;19m
7718 settle 700 20000 6968 settle 700 20000
7719 send \r
7720 settle 700 20000
7721 send printf 'wm-%s\\n' three\n 6969 send printf 'wm-%s\\n' three\n
7722 expect wm-three 15000 6970 expect wm-three 15000
7723 settle 400 15000 6971 settle 400 15000
@@ -7727,33 +6975,27 @@ EOF
7727 RC=$? 6975 RC=$?
7728 set -e 6976 set -e
7729 [ "$RC" -eq 0 ] || { 6977 [ "$RC" -eq 0 ] || {
7730 echo "e2e FAIL: wall click: ptyclient leg exited $RC (did the click zoom anything?):" 6978 echo "e2e FAIL: wall click: ptyclient leg exited $RC (did the click focus b?):"
7731 cat "$OUT.wmpc3"; exit 1; } 6979 cat "$OUT.wmpc3"; exit 1; }
7732 timeout 20 "$MUXA" capture --sock "$SOCK50" --session a > "$OUT.wmfa" 2>&1 6980 timeout 20 "$MUXA" capture --sock "$SOCK50" --session a > "$OUT.wmfa" 2>&1
7733 timeout 20 "$MUXA" capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1 6981 timeout 20 "$MUXA" capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1
7734 grep -q "wm-three" "$OUT.wmfb" || { 6982 grep -q "wm-three" "$OUT.wmfb" || {
7735 echo "e2e FAIL: wall click: a click in session b's stripe did not select it:" 6983 echo "e2e FAIL: wall click: a click in session b's stripe did not focus it:"
7736 cat "$OUT.wmfb"; exit 1; } 6984 cat "$OUT.wmfb"; exit 1; }
7737 grep -q "wm-three" "$OUT.wmfa" && { 6985 grep -q "wm-three" "$OUT.wmfa" && {
7738 echo "e2e FAIL: wall click: the selection stayed on session a:" 6986 echo "e2e FAIL: wall click: the focus stayed on session a:"
7739 cat "$OUT.wmfa"; exit 1; } 6987 cat "$OUT.wmfa"; exit 1; }
7740 6988
7741 # A click on a LABEL BAR names no session line, so it moves nothing — the 6989 # The other direction: focus b explicitly, then click a's content and watch
7742 # bar is exactly where a hand rests between two stripes. Asserted against a 6990 # the focus come back to a.
7743 # selection that is already somewhere else, because "nothing moved" is only
7744 # a claim when there was somewhere to move from: `2` puts the selection on
7745 # b, and the click that follows is on session a's bar. Its own bytes would
7746 # not leave it there — a `1` read as a key jumps straight to a.
7747 set +e 6991 set +e
7748 timeout 60 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.wmcap4" --err "$OUT.wmcap4.err" -- \ 6992 timeout 60 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.wmcap4" --err "$OUT.wmcap4.err" -- \
7749 "$MUX" wall "--sock $SOCK50#a" "--sock $SOCK50#b" > "$OUT.wmpc4" 2>&1 <<'EOF' 6993 "$MUX" wall "--sock $SOCK50#a" "--sock $SOCK50#b" > "$OUT.wmpc4" 2>&1 <<'EOF'
7750 expect wmb-pin 20000 6994 expect wmb-pin 20000
7751 settle 700 20000 6995 settle 700 20000
7752 send 2 6996 send \x1c2
7753 settle 700 20000 6997 settle 700 20000
7754 send \x1b[<0;3;1M\x1b[<0;3;1m 6998 send \x1b[<0;3;2M\x1b[<0;3;2m
7755 settle 700 20000
7756 send \r
7757 settle 700 20000 6999 settle 700 20000
7758 send printf 'wm-%s\\n' four\n 7000 send printf 'wm-%s\\n' four\n
7759 expect wm-four 15000 7001 expect wm-four 15000
@@ -7768,14 +7010,13 @@ set -e
7768 cat "$OUT.wmpc4"; exit 1; } 7010 cat "$OUT.wmpc4"; exit 1; }
7769 timeout 20 "$MUXA" capture --sock "$SOCK50" --session a > "$OUT.wmfa" 2>&1 7011 timeout 20 "$MUXA" capture --sock "$SOCK50" --session a > "$OUT.wmfa" 2>&1
7770 timeout 20 "$MUXA" capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1 7012 timeout 20 "$MUXA" capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1
7771 grep -q "wm-four" "$OUT.wmfb" || { 7013 grep -q "wm-four" "$OUT.wmfa" || {
7772 echo "e2e FAIL: wall click: a click on a label bar moved the selection:" 7014 echo "e2e FAIL: wall click: a click in session a's stripe did not focus it:"
7773 cat "$OUT.wmfa"; exit 1; }
7774 grep -q "wm-four" "$OUT.wmfa" && {
7775 echo "e2e FAIL: wall click: a label-bar click selected the tile it belongs to:"
7776 cat "$OUT.wmfa"; exit 1; } 7015 cat "$OUT.wmfa"; exit 1; }
7777 ok "a click selects the stripe under it, and a click on a label bar selects nothing" 7016 grep -q "wm-four" "$OUT.wmfb" && {
7778 7017 echo "e2e FAIL: wall click: the click never moved the focus off session b:"
7018 cat "$OUT.wmfb"; exit 1; }
7019 ok "a click in a tile's rect focuses it, from wherever the focus was"
7779 7020
7780 # ---- a drag copies, and the copy leaves as OSC 52 ---------------------- 7021 # ---- a drag copies, and the copy leaves as OSC 52 ----------------------
7781 # 7022 #
@@ -7785,12 +7026,12 @@ ok "a click selects the stripe under it, and a click on a label bar selects noth
7785 # it lands on the CLIENT's tty as OSC 52, which the fixture holding that 7026 # it lands on the CLIENT's tty as OSC 52, which the fixture holding that
7786 # tty can read, so the assertion is the copied text itself. 7027 # tty can read, so the assertion is the copied text itself.
7787 # 7028 #
7788 # Zoomed, because that is where the geometry is knowable: `mux TARGET` is a 7029 # A one-tile wall, because that is where the geometry is knowable: `mux
7789 # wall of one tile entered zoomed and `renderClipped` is top-anchored, so a 7030 # TARGET` is a wall of one tile whose rect is the whole terminal (no label
7790 # terminal row IS a grid row and the text can be put on one by name. The 7031 # bar), so a terminal row IS a grid row and the text can be put on one by
7791 # session is given its content with an explicit cursor address rather than 7032 # name. The session is given its content with an explicit cursor address
7792 # by printing lines, so the row this drag crosses does not depend on where 7033 # rather than by printing lines, so the row this drag crosses does not
7793 # a prompt happened to leave the cursor. 7034 # depend on where a prompt happened to leave the cursor.
7794 # 7035 #
7795 # The typed line is `ZZ-%s` and the printed line is `ZZ-COPYME`, so the 7036 # The typed line is `ZZ-%s` and the printed line is `ZZ-COPYME`, so the
7796 # expect below cannot be satisfied by the shell ECHOING the command back. 7037 # expect below cannot be satisfied by the shell ECHOING the command back.
@@ -7878,7 +7119,6 @@ set -e
7878 assert_converged "$OUT.swcap" "$SOCK50" "wide drag" 40 12 7119 assert_converged "$OUT.swcap" "$SOCK50" "wide drag" 40 12
7879 ok "a drag across wide cells leaves the client's screen converged" 7120 ok "a drag across wide cells leaves the client's screen converged"
7880 7121
7881
7882 assert_stopped "$SOCK50" "$D48PID" "wall mouse" "$OUT.wmstop" 7122 assert_stopped "$SOCK50" "$D48PID" "wall mouse" "$OUT.wmstop"
7883 D48PID="" 7123 D48PID=""
7884 ok "a drag copies on release, and a click copies nothing" 7124 ok "a drag copies on release, and a click copies nothing"
@@ -8030,8 +7270,6 @@ wait_pid_gone "$D42PID" "agent-mute: the session ended and the daemon should fol
8030 D42PID="" 7270 D42PID=""
8031 ok "agent forwarding: a mute offerer is hung up on, then no longer offered (${MUTE2MS}ms)" 7271 ok "agent forwarding: a mute offerer is hung up on, then no longer offered (${MUTE2MS}ms)"
8032 7272
8033
8034
8035 # The long-lived daemon has served every scenario that wanted it; stop it 7273 # The long-lived daemon has served every scenario that wanted it; stop it
8036 # NOW so its allocator verdict is written while the suite is still running 7274 # NOW so its allocator verdict is written while the suite is still running
8037 # and can say so. SIGTERM runs the clean-shutdown path, so the defer chain 7275 # and can say so. SIGTERM runs the clean-shutdown path, so the defer chain
@@ -8087,11 +7325,10 @@ DPID=""
8087 # refusal, and no convergence point because it has no client to converge: 7325 # refusal, and no convergence point because it has no client to converge:
8088 # the leg exists to prove no client was ever created, and its evidence is a 7326 # the leg exists to prove no client was ever created, and its evidence is a
8089 # status field and a shell's exit code, neither of which is a grid. The 7327 # status field and a shell's exit code, neither of which is a grid. The
8090 # 39th is the wall's zoom, and no convergence point for the CLI wall's 7328 # 39th is the wall's focus move, and no convergence point for the CLI wall's
8091 # reason a third time: its subject is a ptyclient capture — one terminal 7329 # reason a third time: its subject is a ptyclient capture — one terminal
8092 # torn down and rebuilt around a child client — and the two grid checks it 7330 # holding two sessions' bytes — plus a COUNT the daemon kept while nobody
8093 # does make are `muxa status` sizes, which assert_converged does not speak 7331 # was looking, neither of which is a grid. The 40th is the wheel, and no convergence point because
8094 # about at all. The 40th is the wheel, and no convergence point because
8095 # what it asserts on is a SCROLLBACK view — rows the live grid does not 7332 # what it asserts on is a SCROLLBACK view — rows the live grid does not
8096 # hold, which is the whole reason to scroll — plus the absence of bytes 7333 # hold, which is the whole reason to scroll — plus the absence of bytes
8097 # from a grid, which converging two identical empties would say nothing 7334 # from a grid, which converging two identical empties would say nothing
@@ -8106,13 +7343,13 @@ DPID=""
8106 # never claimed to reproduce byte for byte. The 43rd is the client with no 7343 # never claimed to reproduce byte for byte. The 43rd is the client with no
8107 # terminal of its own, and no convergence point because it HAS no terminal: 7344 # terminal of its own, and no convergence point because it HAS no terminal:
8108 # there is no capture to converge, which is the entire point of the leg. The 7345 # there is no capture to converge, which is the entire point of the leg. The
8109 # 44th is the zoom skipping between tiles, and no convergence point for the 7346 # 44th is the focus skipping between tiles, and no convergence point for the
8110 # CLI wall's reason a fourth time: its subject is one terminal's capture 7347 # CLI wall's reason a fourth time: its subject is one terminal's capture
8111 # holding two sessions' bytes, plus a COUNT the daemon kept while nobody was 7348 # holding two sessions' bytes, plus a COUNT the daemon kept while nobody was
8112 # looking — neither of which is a grid. The 45th is its negative twin, the 7349 # looking — neither of which is a grid. The 45th is its negative twin, the wall that forwards nothing, and no
8113 # unzoomed wall that forwards nothing, and no convergence point because what 7350 # convergence point because what it asserts on is the ABSENCE of bytes from
8114 # it asserts on is the ABSENCE of bytes from a grid, which converging two 7351 # a grid, which converging two grids that agree about that absence would say
8115 # grids that agree about that absence would say nothing about. The 46th is 7352 # nothing about. The 46th is
8116 # the tile whose pump died, and no convergence point because its subject is 7353 # the tile whose pump died, and no convergence point because its subject is
8117 # a session that DOES NOT EXIST: what the leg reads is what a terminal was 7354 # a session that DOES NOT EXIST: what the leg reads is what a terminal was
8118 # told about a refused attach, and there is no grid on either side to 7355 # told about a refused attach, and there is no grid on either side to
@@ -8178,8 +7415,8 @@ DPID=""
8178 # it is the one leg that holds an ssh-agent under SIGSTOP, and a trap that 7415 # it is the one leg that holds an ssh-agent under SIGSTOP, and a trap that
8179 # has to CONT before it kills is cheaper to reason about with nothing 7416 # has to CONT before it kills is cheaper to reason about with nothing
8180 # after it. 7417 # after it.
8181 [ "$OK_COUNT" = "67" ] || { 7418 [ "$OK_COUNT" = "61" ] || {
8182 echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 67 —" 7419 echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 61 —"
8183 echo " a scenario was added (update the pin) or silently lost" 7420 echo " a scenario was added (update the pin) or silently lost"
8184 exit 1 7421 exit 1
8185 } 7422 }
@@ -8187,4 +7424,4 @@ DPID=""
8187 echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 37" 7424 echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 37"
8188 exit 1 7425 exit 1
8189 } 7426 }
8190 echo "e2e OK (65 scenarios, 37 convergence points)" 7427 echo "e2e OK (60 scenarios, 37 convergence points)"