55fba86d
test: the e2e wall legs learn the focus model
a73x 2026-08-24 11:32
Commit message
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)" |