f88b6984
test: the zoom claims the grid without attaching, and skips for free
a73x 2026-08-19 22:51
Commit message
test/e2e.sh
| Old | New | ||
|---|---|---|---|
| @@ -318,6 +318,20 @@ LESSDATA="${TMPDIR:-/tmp}/mux-e2e-pager-$$.txt" | |||
| 318 | SOCK36="${TMPDIR:-/tmp}/muxd-e2e-pipestdin-$$.sock" | 318 | SOCK36="${TMPDIR:-/tmp}/muxd-e2e-pipestdin-$$.sock" |
| 319 | D33PID="" | 319 | D33PID="" |
| 320 | 320 | ||
| 321 | # The zoom-as-lens pair, both on daemons of their own for SOCK32's reason: | ||
| 322 | # each asserts on grid sizes and on how many clients a session ever had at | ||
| 323 | # once, and a session another block created — or another block's client | ||
| 324 | # sitting on this daemon — would make both numbers say nothing. | ||
| 325 | SOCK37="${TMPDIR:-/tmp}/muxd-e2e-zoomskip-$$.sock" | ||
| 326 | D34PID="" | ||
| 327 | SOCK38="${TMPDIR:-/tmp}/muxd-e2e-zoommute-$$.sock" | ||
| 328 | D35PID="" | ||
| 329 | # The dead tile. Its own daemon because the leg needs a session name that | ||
| 330 | # does NOT exist — a 0x0 attach joins but never creates, so the daemon | ||
| 331 | # refuses it — and any other block's daemon might have one by that name. | ||
| 332 | SOCK39="${TMPDIR:-/tmp}/muxd-e2e-zoomdead-$$.sock" | ||
| 333 | D36PID="" | ||
| 334 | |||
| 321 | # One counter out of a MUX_PREDICT_STATS line. The client prints exactly one | 335 | # One counter out of a MUX_PREDICT_STATS line. The client prints exactly one |
| 322 | # such line on exit; every field is a key=value pair, so a rename or reorder | 336 | # such line on exit; every field is a key=value pair, so a rename or reorder |
| 323 | # in the client shows up here as an empty read rather than a wrong number. | 337 | # in the client shows up here as an empty read rather than a wrong number. |
| @@ -450,6 +464,90 @@ wait_sessions() { | |||
| 450 | exit 1 | 464 | exit 1 |
| 451 | } | 465 | } |
| 452 | 466 | ||
| 467 | # --- the wall's zoom: did the daemon see a second attach? -------------- | ||
| 468 | # | ||
| 469 | # The in-place zoom's whole claim is that it PROMOTES the tile's existing | ||
| 470 | # connection — no new attach, no dial. Proving a negative needs a | ||
| 471 | # daemon-side witness, and there are two here doing different jobs. | ||
| 472 | # | ||
| 473 | # THE ASSERTION is `attaches=`, a cumulative counter of every attach this | ||
| 474 | # daemon accepted (server.zig Stats). Read once before the wall starts and | ||
| 475 | # once after it exits, the delta is exactly how many times anything attached | ||
| 476 | # across the whole leg — which for a wall of N tiles must be N, one per tile | ||
| 477 | # at startup, and never N+1 however far the zoom moved. It is read from | ||
| 478 | # `muxd stats`, which is plain human text and connects as an OBSERVER: the | ||
| 479 | # reading itself never attaches, so it cannot pollute what it measures. | ||
| 480 | # | ||
| 481 | # THE GAUGE is `session NAME clients=`, sampled every 200ms into a file and | ||
| 482 | # asserted on its peak. It is kept because it localises a failure — it says | ||
| 483 | # WHICH session grew a second watcher and roughly when — but it is the | ||
| 484 | # weaker of the two and must never be the only one: a connection that closes | ||
| 485 | # as another opens never exceeds 1, and one that lives less than a sample | ||
| 486 | # interval is invisible to it. The counter cannot miss either. | ||
| 487 | # | ||
| 488 | # Nothing else may attach while a leg is being measured (`muxa capture` and | ||
| 489 | # `wait_grid` are clients too), so both readings bracket the ptyclient leg | ||
| 490 | # and nothing more. | ||
| 491 | # | ||
| 492 | # attaches_now SOCK — the daemon's cumulative accepted-attach count. | ||
| 493 | attaches_now() { | ||
| 494 | timeout 5 "$MUXD" stats --sock "$1" 2>/dev/null | | ||
| 495 | sed -n 's/.*[^_]attaches=\([0-9]*\).*/\1/p' | ||
| 496 | } | ||
| 497 | |||
| 498 | # assert_attach_delta BEFORE AFTER WANT LABEL — how many attaches happened. | ||
| 499 | # Empty readings fail loudly rather than arithmetically: `$(())` on an empty | ||
| 500 | # string is 0, and 0-0=0 would pass this check having measured nothing at | ||
| 501 | # all — the vacuous green this whole block exists to refuse. | ||
| 502 | assert_attach_delta() { | ||
| 503 | [ -n "$1" ] && [ -n "$2" ] || { | ||
| 504 | echo "e2e FAIL: $4: stats gave no attaches= reading (before='$1' after='$2')" | ||
| 505 | echo " — the flat-attach claim would be vacuous" | ||
| 506 | exit 1; } | ||
| 507 | [ "$(($2 - $1))" = "$3" ] || { | ||
| 508 | echo "e2e FAIL: $4: the daemon accepted $(($2 - $1)) attaches (want $3)" | ||
| 509 | echo " — the wall's tiles attach once each and the zoom never does" | ||
| 510 | exit 1; } | ||
| 511 | } | ||
| 512 | |||
| 513 | # watch_clients SOCK FILE — start sampling in the background. | ||
| 514 | WATCH_PID="" | ||
| 515 | watch_clients() { | ||
| 516 | touch "$2.on" | ||
| 517 | ( while [ -e "$2.on" ]; do | ||
| 518 | timeout 5 "$MUXD" stats --sock "$1" 2>/dev/null | ||
| 519 | sleep 0.2 | ||
| 520 | done ) > "$2" & | ||
| 521 | WATCH_PID=$! | ||
| 522 | } | ||
| 523 | |||
| 524 | # unwatch_clients FILE — stop sampling. | ||
| 525 | unwatch_clients() { | ||
| 526 | rm -f "$1.on" | ||
| 527 | wait "$WATCH_PID" 2>/dev/null || true | ||
| 528 | WATCH_PID="" | ||
| 529 | } | ||
| 530 | |||
| 531 | # assert_never_two_clients FILE NAME LABEL — the peak `clients=` this | ||
| 532 | # session ever showed is 1. Also asserts it was ever 1, which is the anchor: | ||
| 533 | # a watcher that sampled an empty file, or a wall whose tiles never attached, | ||
| 534 | # would otherwise pass this leg by having witnessed nothing at all. | ||
| 535 | # | ||
| 536 | # Localisation, not proof — `assert_attach_delta` is the proof. This says | ||
| 537 | # which session grew a second watcher; the counter says whether anything | ||
| 538 | # attached at all. | ||
| 539 | assert_never_two_clients() { | ||
| 540 | _peak=$(grep -o "session $2 clients=[0-9]*" "$1" | sed 's/.*=//' | sort -n | tail -1) | ||
| 541 | [ -n "$_peak" ] || { | ||
| 542 | echo "e2e FAIL: $3: no stats sample ever named session $2 — the watch" | ||
| 543 | echo " saw nothing, so its flat-count claim is vacuous" | ||
| 544 | exit 1; } | ||
| 545 | [ "$_peak" = "1" ] || { | ||
| 546 | echo "e2e FAIL: $3: session $2 was watched by $_peak clients at once" | ||
| 547 | echo " (want 1) — the zoom attached instead of promoting" | ||
| 548 | exit 1; } | ||
| 549 | } | ||
| 550 | |||
| 453 | # wait_pid_gone PID LABEL — poll until a tracked pid is gone (2s, the same | 551 | # wait_pid_gone PID LABEL — poll until a tracked pid is gone (2s, the same |
| 454 | # budget `muxd stop` gives itself). Its own helper rather than wait_gone's | 552 | # budget `muxd stop` gives itself). Its own helper rather than wait_gone's |
| 455 | # socket probe, because the two answer different questions: `muxd: stopped` | 553 | # socket probe, because the two answer different questions: `muxd: stopped` |
| @@ -848,6 +946,9 @@ cleanup() { | |||
| 848 | [ -n "$D31PID" ] && kill "$D31PID" 2>/dev/null || true | 946 | [ -n "$D31PID" ] && kill "$D31PID" 2>/dev/null || true |
| 849 | [ -n "$D32PID" ] && kill "$D32PID" 2>/dev/null || true | 947 | [ -n "$D32PID" ] && kill "$D32PID" 2>/dev/null || true |
| 850 | [ -n "$D33PID" ] && kill "$D33PID" 2>/dev/null || true | 948 | [ -n "$D33PID" ] && kill "$D33PID" 2>/dev/null || true |
| 949 | [ -n "$D34PID" ] && kill "$D34PID" 2>/dev/null || true | ||
| 950 | [ -n "$D35PID" ] && kill "$D35PID" 2>/dev/null || true | ||
| 951 | [ -n "$D36PID" ] && kill "$D36PID" 2>/dev/null || true | ||
| 851 | # The stops still precede the socket rm below, like SOCK14-17 above: | 952 | # The stops still precede the socket rm below, like SOCK14-17 above: |
| 852 | # unlinking a socket first would leave a live daemon nothing could reach | 953 | # unlinking a socket first would leave a live daemon nothing could reach |
| 853 | # by path. | 954 | # by path. |
| @@ -866,6 +967,9 @@ cleanup() { | |||
| 866 | [ -S "$SOCK34" ] && "$MUXD" stop --sock "$SOCK34" 2>/dev/null || true | 967 | [ -S "$SOCK34" ] && "$MUXD" stop --sock "$SOCK34" 2>/dev/null || true |
| 867 | [ -S "$SOCK35" ] && "$MUXD" stop --sock "$SOCK35" 2>/dev/null || true | 968 | [ -S "$SOCK35" ] && "$MUXD" stop --sock "$SOCK35" 2>/dev/null || true |
| 868 | [ -S "$SOCK36" ] && "$MUXD" stop --sock "$SOCK36" 2>/dev/null || true | 969 | [ -S "$SOCK36" ] && "$MUXD" stop --sock "$SOCK36" 2>/dev/null || true |
| 970 | [ -S "$SOCK37" ] && "$MUXD" stop --sock "$SOCK37" 2>/dev/null || true | ||
| 971 | [ -S "$SOCK38" ] && "$MUXD" stop --sock "$SOCK38" 2>/dev/null || true | ||
| 972 | [ -S "$SOCK39" ] && "$MUXD" stop --sock "$SOCK39" 2>/dev/null || true | ||
| 869 | 973 | ||
| 870 | # ---- the leak sweep (hygiene kit, 6a) ---- | 974 | # ---- the leak sweep (hygiene kit, 6a) ---- |
| 871 | # Here rather than at the bottom of the file, which `set -e` reaches only | 975 | # Here rather than at the bottom of the file, which `set -e` reaches only |
| @@ -879,7 +983,7 @@ cleanup() { | |||
| 879 | "$D14PID" "$D15PID" "$D16PID" "$D17PID" "$D18PID" "$D19PID" \ | 983 | "$D14PID" "$D15PID" "$D16PID" "$D17PID" "$D18PID" "$D19PID" \ |
| 880 | "$D20PID" "$D21PID" "$D22PID" "$D23PID" "$D24PID" "$D25PID" \ | 984 | "$D20PID" "$D21PID" "$D22PID" "$D23PID" "$D24PID" "$D25PID" \ |
| 881 | "$D26PID" "$D27PID" "$D28PID" "$D29PID" "$D30PID" "$D31PID" \ | 985 | "$D26PID" "$D27PID" "$D28PID" "$D29PID" "$D30PID" "$D31PID" \ |
| 882 | "$D32PID" "$D33PID" | 986 | "$D32PID" "$D33PID" "$D34PID" "$D35PID" "$D36PID" |
| 883 | _leak=0 | 987 | _leak=0 |
| 884 | leak_sweep "$_rc" || _leak=1 | 988 | leak_sweep "$_rc" || _leak=1 |
| 885 | 989 | ||
| @@ -4950,14 +5054,15 @@ assert_stopped "$SOCK31" "$D28PID" "self attach" "$OUT.sastop" | |||
| 4950 | D28PID="" | 5054 | D28PID="" |
| 4951 | ok "a session shell carries MUX_SOCK/MUX_SESSION, and mux refuses to attach to itself" | 5055 | ok "a session shell carries MUX_SOCK/MUX_SESSION, and mux refuses to attach to itself" |
| 4952 | 5056 | ||
| 4953 | # ---- the wall zooms: Enter types into the selected session -------------- | 5057 | # ---- the wall zooms IN PLACE: Enter promotes the selected tile ---------- |
| 4954 | # | 5058 | # |
| 4955 | # Scenario 33 proved the wall WATCHES two sessions. This proves the wall | 5059 | # Scenario 33 proved the wall WATCHES two sessions. This proves the wall can |
| 4956 | # can be typed through without ever forwarding a keystroke itself: `2` | 5060 | # be typed through by PROMOTING one of the connections it already holds: |
| 4957 | # selects the second stripe, `Enter` spawns a real client on this | 5061 | # `2` selects the second stripe, `Enter` resizes that tile's attach from 0x0 |
| 4958 | # terminal, and what is typed there is the shell's, not the wall's. | 5062 | # to this pty's size and starts forwarding keystrokes down it, `Ctrl-\ d` |
| 5063 | # gives the terminal back to the wall. | ||
| 4959 | # | 5064 | # |
| 4960 | # Four claims, four witnesses: | 5065 | # Five claims, five witnesses: |
| 4961 | # | 5066 | # |
| 4962 | # * the selection moved — b's label bar carries the `> ` marker, which | 5067 | # * the selection moved — b's label bar carries the `> ` marker, which |
| 4963 | # it cannot have before the `2` (a's is marked from the start, so only | 5068 | # it cannot have before the `2` (a's is marked from the start, so only |
| @@ -4965,21 +5070,29 @@ ok "a session shell carries MUX_SOCK/MUX_SESSION, and mux refuses to attach to i | |||
| 4965 | # * the zoom typed into the SELECTED session — b's grid holds wzoom-pin | 5070 | # * the zoom typed into the SELECTED session — b's grid holds wzoom-pin |
| 4966 | # and a's does not. A shell-EXPANDED marker (the M18 trick), so a hit | 5071 | # and a's does not. A shell-EXPANDED marker (the M18 trick), so a hit |
| 4967 | # is the shell's work and never an echo of what this script sent. | 5072 | # is the shell's work and never an echo of what this script sent. |
| 4968 | # * the zoom was a REAL attach, not a tile that started forwarding — | 5073 | # * the zoom CLAIMED the grid — b's grid is 100 cols wide, this pty's |
| 4969 | # b's grid is 100 cols wide, this pty's size, claimed the legitimate | 5074 | # size. That is the promote: a tile that attached 0x0 asked for the |
| 4970 | # way. a's is still 80: the wall's own tiles attach 0x0 and claimed | 5075 | # terminal's size before its first keystroke, so the client that types |
| 4971 | # nothing, scenario 33's passivity check made again with a full-size | 5076 | # is a full-size one and latest-wins has nothing to complain about. |
| 4972 | # client in the same process tree. | 5077 | # * ...and it claimed it WITHOUT a second attach. This is the assertion |
| 5078 | # that inverted when the child-spawn zoom was deleted: the old design | ||
| 5079 | # proved the zoom was a real attach (a client of its own, in the same | ||
| 5080 | # process tree), and the new one proves the opposite — the daemon's | ||
| 5081 | # cumulative `attaches=` counter moves by exactly 2 across the wall's | ||
| 5082 | # whole life, one per tile at startup, because the connection that | ||
| 5083 | # typed is the connection the tile already had. Session a's grid stays | ||
| 5084 | # 80 for the passivity half of the same claim: a tile nobody zoomed | ||
| 5085 | # claims nothing, still. | ||
| 4973 | # * the wall came BACK — session A's marker paints a SECOND time after | 5086 | # * the wall came BACK — session A's marker paints a SECOND time after |
| 4974 | # the detach, asserted both in order (the expect) and by COUNT (twice | 5087 | # the unzoom, asserted both in order (the expect) and by COUNT (twice |
| 4975 | # in the capture). Session a is the tile the zoom never touched: | 5088 | # in the capture). Session a is the tile the zoom never touched: |
| 4976 | # nothing was typed at it, its grid was never resized, so no frame | 5089 | # nothing was typed at it, its grid was never resized, so no frame |
| 4977 | # arrives to trigger a paint and only the stale-generation repaint | 5090 | # arrives to trigger a paint and only the stale-generation repaint |
| 4978 | # the zoom leaves behind can put those bytes on the screen again. | 5091 | # the demote leaves behind can put those bytes on the screen again. |
| 4979 | # Asserted on A rather than on the zoomed session for exactly that | 5092 | # Asserted on A rather than on the zoomed session for exactly that |
| 4980 | # reason — measured, not assumed: with the generation bump removed, | 5093 | # reason — measured, not assumed: with the generation bump removed, |
| 4981 | # b's stripe repaints anyway (the client that detached moved b's | 5094 | # b's stripe repaints anyway (the session the zoom typed at moves on |
| 4982 | # world), and a leg anchored on b passes with the lever gone. | 5095 | # its own), and a leg anchored on b passes with the lever gone. |
| 4983 | # | 5096 | # |
| 4984 | # The wall is entered on ONE expect, not one per tile, for scenario 33's | 5097 | # The wall is entered on ONE expect, not one per tile, for scenario 33's |
| 4985 | # reason learned here the hard way: tile threads race, `expect` is a | 5098 | # reason learned here the hard way: tile threads race, `expect` is a |
| @@ -4987,14 +5100,13 @@ ok "a session shell carries MUX_SOCK/MUX_SESSION, and mux refuses to attach to i | |||
| 4987 | # it painted first — a leg that then hangs on a wall that painted | 5100 | # it painted first — a leg that then hangs on a wall that painted |
| 4988 | # perfectly. One anchor cannot be buried, and the settle after it is what | 5101 | # perfectly. One anchor cannot be buried, and the settle after it is what |
| 4989 | # says both stripes have finished. Only ONE stripe is expected after the | 5102 | # says both stripes have finished. Only ONE stripe is expected after the |
| 4990 | # detach for the same reason. | 5103 | # unzoom for the same reason. |
| 4991 | # | 5104 | # |
| 4992 | # Both `\x1b[?1049h` expects are alternate-screen TRANSITIONS, not the | 5105 | # There is no `\x1b[?1049h` anchor any more, and its absence is the point: |
| 4993 | # wall's own entry: the first is the zoomed client's (the wall left the | 5106 | # the child-spawn zoom tore this terminal down and rebuilt it twice, so an |
| 4994 | # alternate screen to hand over a sane tty), the second is the wall | 5107 | # alternate-screen TRANSITION was the honest thing to wait on. An in-place |
| 4995 | # re-entering after `\x1cd`. Anchoring on them rather than on a settle is | 5108 | # zoom never leaves the alternate screen at all — it repaints inside it — |
| 4996 | # what keeps the leg honest while a terminal is torn down and rebuilt | 5109 | # so the anchors are the session's own bytes arriving full-screen. |
| 4997 | # twice. | ||
| 4998 | "$MUXD" run --sock "$SOCK32" --shell /bin/sh > "$OUT.wz.d" 2>&1 & | 5110 | "$MUXD" run --sock "$SOCK32" --shell /bin/sh > "$OUT.wz.d" 2>&1 & |
| 4999 | D29PID=$! | 5111 | D29PID=$! |
| 5000 | wait_sock "$SOCK32" "$OUT.wz.d" "wall-zoom daemon never bound" | 5112 | wait_sock "$SOCK32" "$OUT.wz.d" "wall-zoom daemon never bound" |
| @@ -5009,6 +5121,13 @@ wait_grid "$SOCK32" "wza-pin" "wall zoom: session a's marker" a | |||
| 5009 | timeout 40 "$MUX" --sock "$SOCK32" --session b > "$OUT.wzb" 2> "$OUT.wzb.err" | 5121 | timeout 40 "$MUX" --sock "$SOCK32" --session b > "$OUT.wzb" 2> "$OUT.wzb.err" |
| 5010 | wait_grid "$SOCK32" "wzb-pin" "wall zoom: session b's marker" b | 5122 | wait_grid "$SOCK32" "wzb-pin" "wall zoom: session b's marker" b |
| 5011 | 5123 | ||
| 5124 | # Brackets the wall and nothing else: every `muxa` and `wait_grid` call in | ||
| 5125 | # this scenario is itself a client, and one inside the watch would be | ||
| 5126 | # counted as the second attach this leg exists to refute. | ||
| 5127 | # Read AFTER the two setup clients have come and gone, so the delta below | ||
| 5128 | # belongs to the wall alone. | ||
| 5129 | WZATT_BEFORE=$(attaches_now "$SOCK32") | ||
| 5130 | watch_clients "$SOCK32" "$OUT.wzwatch" | ||
| 5012 | set +e | 5131 | set +e |
| 5013 | timeout 90 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.wzcap" --err "$OUT.wzcap.err" -- \ | 5132 | timeout 90 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.wzcap" --err "$OUT.wzcap.err" -- \ |
| 5014 | "$MUX" wall "--sock $SOCK32#a" "--sock $SOCK32#b" > "$OUT.wzpc" 2>&1 <<'EOF' | 5133 | "$MUX" wall "--sock $SOCK32#a" "--sock $SOCK32#b" > "$OUT.wzpc" 2>&1 <<'EOF' |
| @@ -5017,13 +5136,11 @@ settle 700 20000 | |||
| 5017 | send 2 | 5136 | send 2 |
| 5018 | settle 500 15000 | 5137 | settle 500 15000 |
| 5019 | send \r | 5138 | send \r |
| 5020 | expect \x1b[?1049h 15000 | ||
| 5021 | settle 700 20000 | 5139 | settle 700 20000 |
| 5022 | send printf 'wzoom-%s\\n' pin\n | 5140 | send printf 'wzoom-%s\\n' pin\n |
| 5023 | expect wzoom-pin 15000 | 5141 | expect wzoom-pin 15000 |
| 5024 | settle 400 15000 | 5142 | settle 400 15000 |
| 5025 | send \x1cd | 5143 | send \x1cd |
| 5026 | expect \x1b[?1049h 15000 | ||
| 5027 | expect wza-pin 20000 | 5144 | expect wza-pin 20000 |
| 5028 | settle 500 15000 | 5145 | settle 500 15000 |
| 5029 | send q | 5146 | send q |
| @@ -5031,6 +5148,9 @@ waitexit 10000 | |||
| 5031 | EOF | 5148 | EOF |
| 5032 | RC=$? | 5149 | RC=$? |
| 5033 | set -e | 5150 | set -e |
| 5151 | unwatch_clients "$OUT.wzwatch" | ||
| 5152 | # Read BEFORE the muxa captures below, every one of which is an attach. | ||
| 5153 | WZATT_AFTER=$(attaches_now "$SOCK32") | ||
| 5034 | [ "$RC" -eq 0 ] || { | 5154 | [ "$RC" -eq 0 ] || { |
| 5035 | echo "e2e FAIL: wall zoom: ptyclient leg exited $RC (did \\r zoom, did \\x1cd come back?):" | 5155 | echo "e2e FAIL: wall zoom: ptyclient leg exited $RC (did \\r zoom, did \\x1cd come back?):" |
| 5036 | cat "$OUT.wzpc"; exit 1; } | 5156 | cat "$OUT.wzpc"; exit 1; } |
| @@ -5039,12 +5159,22 @@ grep -q -- "> --sock $SOCK32#b" "$OUT.wzcap" || { | |||
| 5039 | echo "e2e FAIL: wall zoom: session b's bar never showed the selection marker"; exit 1; } | 5159 | echo "e2e FAIL: wall zoom: session b's bar never showed the selection marker"; exit 1; } |
| 5040 | # The repaint counted rather than merely ordered: a's marker is on this | 5160 | # The repaint counted rather than merely ordered: a's marker is on this |
| 5041 | # terminal twice, once for the stripe's own paint and once for the one the | 5161 | # terminal twice, once for the stripe's own paint and once for the one the |
| 5042 | # zoom's exit demanded. `grep -o`, not `grep -c`: a stripe paint is escape | 5162 | # unzoom demanded. `grep -o`, not `grep -c`: a stripe paint is escape |
| 5043 | # sequences with no newlines in them, so a line count reads 1 either way. | 5163 | # sequences with no newlines in them, so a line count reads 1 either way. |
| 5044 | _wza_paints=$(grep -o "wza-pin" "$OUT.wzcap" | wc -l) | 5164 | _wza_paints=$(grep -o "wza-pin" "$OUT.wzcap" | wc -l) |
| 5045 | [ "$_wza_paints" -ge 2 ] || { | 5165 | [ "$_wza_paints" -ge 2 ] || { |
| 5046 | echo "e2e FAIL: wall zoom: session a's stripe painted $_wza_paints time(s), so the wall never came back:" | 5166 | echo "e2e FAIL: wall zoom: session a's stripe painted $_wza_paints time(s), so the wall never came back:" |
| 5047 | cat "$OUT.wzpc"; exit 1; } | 5167 | cat "$OUT.wzpc"; exit 1; } |
| 5168 | # No second attach, and this is the assertion that inverted: what used to | ||
| 5169 | # prove a real child client now proves there was none. TWO is the whole | ||
| 5170 | # budget — one per tile, at wall startup — and the zoom, the typing and the | ||
| 5171 | # unzoom add nothing to it. A number, not a gauge, so a client that opened | ||
| 5172 | # and closed between two samples, or overlapped another for a millisecond, | ||
| 5173 | # is counted anyway. | ||
| 5174 | assert_attach_delta "$WZATT_BEFORE" "$WZATT_AFTER" 2 "wall zoom" | ||
| 5175 | # ...and which session, if it ever were two. | ||
| 5176 | assert_never_two_clients "$OUT.wzwatch" a "wall zoom" | ||
| 5177 | assert_never_two_clients "$OUT.wzwatch" b "wall zoom" | ||
| 5048 | # What the user typed landed in the session the digit picked, and only | 5178 | # What the user typed landed in the session the digit picked, and only |
| 5049 | # there. The negative is the whole point of the digit. | 5179 | # there. The negative is the whole point of the digit. |
| 5050 | timeout 20 "$MUXA" capture --sock "$SOCK32" --session b > "$OUT.wzcapb" 2>&1 | 5180 | timeout 20 "$MUXA" capture --sock "$SOCK32" --session b > "$OUT.wzcapb" 2>&1 |
| @@ -5055,18 +5185,19 @@ timeout 20 "$MUXA" capture --sock "$SOCK32" --session a > "$OUT.wzcapa" 2>&1 | |||
| 5055 | grep -q "wzoom-pin" "$OUT.wzcapa" && { | 5185 | grep -q "wzoom-pin" "$OUT.wzcapa" && { |
| 5056 | echo "e2e FAIL: wall zoom: the zoom typed into session a, which was not selected:" | 5186 | echo "e2e FAIL: wall zoom: the zoom typed into session a, which was not selected:" |
| 5057 | cat "$OUT.wzcapa"; exit 1; } | 5187 | cat "$OUT.wzcapa"; exit 1; } |
| 5058 | # The size split: the zoom claimed the grid, the tiles never did. | 5188 | # The size split: the promoted tile claimed the grid, the unzoomed one |
| 5189 | # never did. | ||
| 5059 | timeout 20 "$MUXA" status --sock "$SOCK32" --session b > "$OUT.wzstb" 2>&1 | 5190 | timeout 20 "$MUXA" status --sock "$SOCK32" --session b > "$OUT.wzstb" 2>&1 |
| 5060 | grep -q '"cols":100' "$OUT.wzstb" || { | 5191 | grep -q '"cols":100' "$OUT.wzstb" || { |
| 5061 | echo "e2e FAIL: wall zoom: the zoomed client did not claim session b's grid:" | 5192 | echo "e2e FAIL: wall zoom: the promoted tile did not claim session b's grid:" |
| 5062 | cat "$OUT.wzstb"; exit 1; } | 5193 | cat "$OUT.wzstb"; exit 1; } |
| 5063 | timeout 20 "$MUXA" status --sock "$SOCK32" --session a > "$OUT.wzsta" 2>&1 | 5194 | timeout 20 "$MUXA" status --sock "$SOCK32" --session a > "$OUT.wzsta" 2>&1 |
| 5064 | grep -q '"cols":80' "$OUT.wzsta" || { | 5195 | grep -q '"cols":80' "$OUT.wzsta" || { |
| 5065 | echo "e2e FAIL: wall zoom: a 0x0 wall attach moved session a's grid:" | 5196 | echo "e2e FAIL: wall zoom: a tile nobody zoomed moved session a's grid:" |
| 5066 | cat "$OUT.wzsta"; exit 1; } | 5197 | cat "$OUT.wzsta"; exit 1; } |
| 5067 | assert_stopped "$SOCK32" "$D29PID" "wall zoom" "$OUT.wzstop" | 5198 | assert_stopped "$SOCK32" "$D29PID" "wall zoom" "$OUT.wzstop" |
| 5068 | D29PID="" | 5199 | D29PID="" |
| 5069 | ok "the wall zooms: Enter types into the selected session, Ctrl-\\ d comes back" | 5200 | ok "the wall zooms in place: Enter promotes the tile, Ctrl-\\ d demotes it" |
| 5070 | 5201 | ||
| 5071 | # ---- the wheel scrolls back, and an app that asks gets it instead ------- | 5202 | # ---- the wheel scrolls back, and an app that asks gets it instead ------- |
| 5072 | # | 5203 | # |
| @@ -5319,6 +5450,290 @@ assert_stopped "$SOCK36" "$D33PID" "pipe stdin" "$OUT.pipestop" | |||
| 5319 | D33PID="" | 5450 | D33PID="" |
| 5320 | ok "a client with no terminal of its own forwards SGR-shaped bytes untouched" | 5451 | ok "a client with no terminal of its own forwards SGR-shaped bytes untouched" |
| 5321 | 5452 | ||
| 5453 | # ---- the zoom SKIPS between tiles, and the daemon never notices --------- | ||
| 5454 | # | ||
| 5455 | # The headline of "zoom is a lens": every tile's replica is hot whether or | ||
| 5456 | # not it is the zoom, so moving the zoom between two tiles is a local | ||
| 5457 | # repaint. No re-dial, no attach, no snapshot round trip — which is a claim | ||
| 5458 | # about what did NOT happen, and needs a daemon-side witness rather than a | ||
| 5459 | # stopwatch. | ||
| 5460 | # | ||
| 5461 | # One ptyclient leg, four sessions' worth of typing on two tiles: | ||
| 5462 | # | ||
| 5463 | # Enter zoom tile 1 (session a) — promote | ||
| 5464 | # type mark a with zs-one, shell-expanded | ||
| 5465 | # Ctrl-\ n move the zoom to tile 2 (session b) | ||
| 5466 | # type mark b with zs-two | ||
| 5467 | # Ctrl-\ l skip back to the last tile zoomed, which is a | ||
| 5468 | # type mark a again with zs-three | ||
| 5469 | # Ctrl-\ w unzoom (the spec's spelling; scenario 39 does `d`) | ||
| 5470 | # | ||
| 5471 | # Three claims: | ||
| 5472 | # | ||
| 5473 | # * every marker landed in the session the zoom was standing in, and in | ||
| 5474 | # no other. zs-one and zs-three in a, zs-two in b, and each absent from | ||
| 5475 | # the other session — the negatives are what prove the zoom MOVED | ||
| 5476 | # rather than the wall having quietly kept typing at one connection. | ||
| 5477 | # * the daemon accepted exactly TWO attaches for the whole run — one per | ||
| 5478 | # tile, at wall startup. If `Ctrl-\ n` re-dialled, or zoomed by | ||
| 5479 | # spawning, it would be three or more, and the counter says so however | ||
| 5480 | # briefly the extra connection lived or however neatly it closed the | ||
| 5481 | # old one first. This is the assertion that inverts the child-spawn | ||
| 5482 | # era's "the zoom was a REAL attach". | ||
| 5483 | # * both sessions end up at this pty's size, because both were promoted | ||
| 5484 | # and a demote deliberately gives nothing back — `applySize` refuses | ||
| 5485 | # sub-minimum resizes, so there is no 0x0 to hand back, and a size | ||
| 5486 | # nobody types at claims nothing under latest-wins. | ||
| 5487 | "$MUXD" run --sock "$SOCK37" --shell /bin/sh > "$OUT.zs.d" 2>&1 & | ||
| 5488 | D34PID=$! | ||
| 5489 | wait_sock "$SOCK37" "$OUT.zs.d" "zoom-skip daemon never bound" | ||
| 5490 | |||
| 5491 | # Both sessions exist before the wall does, with a marker each so the wall | ||
| 5492 | # has something to paint and this leg has an anchor to enter on. | ||
| 5493 | { printf 'printf "zsa-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ | ||
| 5494 | timeout 40 "$MUX" --sock "$SOCK37" --session a > "$OUT.zsa" 2> "$OUT.zsa.err" | ||
| 5495 | wait_grid "$SOCK37" "zsa-pin" "zoom skip: session a's marker" a | ||
| 5496 | { printf 'printf "zsb-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ | ||
| 5497 | timeout 40 "$MUX" --sock "$SOCK37" --session b > "$OUT.zsb" 2> "$OUT.zsb.err" | ||
| 5498 | wait_grid "$SOCK37" "zsb-pin" "zoom skip: session b's marker" b | ||
| 5499 | |||
| 5500 | ZSATT_BEFORE=$(attaches_now "$SOCK37") | ||
| 5501 | watch_clients "$SOCK37" "$OUT.zswatch" | ||
| 5502 | set +e | ||
| 5503 | timeout 90 "$PTYCLIENT" --cols 92 --rows 30 --out "$OUT.zscap" --err "$OUT.zscap.err" -- \ | ||
| 5504 | "$MUX" wall "--sock $SOCK37#a" "--sock $SOCK37#b" > "$OUT.zspc" 2>&1 <<'EOF' | ||
| 5505 | expect zsb-pin 20000 | ||
| 5506 | settle 700 20000 | ||
| 5507 | send \r | ||
| 5508 | settle 700 20000 | ||
| 5509 | send printf 'zs-%s\\n' one\n | ||
| 5510 | expect zs-one 15000 | ||
| 5511 | settle 400 15000 | ||
| 5512 | send \x1cn | ||
| 5513 | settle 700 20000 | ||
| 5514 | send printf 'zs-%s\\n' two\n | ||
| 5515 | expect zs-two 15000 | ||
| 5516 | settle 400 15000 | ||
| 5517 | send \x1cl | ||
| 5518 | settle 700 20000 | ||
| 5519 | send printf 'zs-%s\\n' three\n | ||
| 5520 | expect zs-three 15000 | ||
| 5521 | settle 400 15000 | ||
| 5522 | send \x1cw | ||
| 5523 | expect zsb-pin 20000 | ||
| 5524 | settle 500 15000 | ||
| 5525 | send q | ||
| 5526 | waitexit 10000 | ||
| 5527 | EOF | ||
| 5528 | RC=$? | ||
| 5529 | set -e | ||
| 5530 | unwatch_clients "$OUT.zswatch" | ||
| 5531 | ZSATT_AFTER=$(attaches_now "$SOCK37") | ||
| 5532 | [ "$RC" -eq 0 ] || { | ||
| 5533 | echo "e2e FAIL: zoom skip: ptyclient leg exited $RC (did \\x1cn and \\x1cl move the zoom?):" | ||
| 5534 | cat "$OUT.zspc"; exit 1; } | ||
| 5535 | # Where each marker landed. `muxa capture` reads the session's own grid, so | ||
| 5536 | # none of this can be an echo of what this script typed at a terminal. | ||
| 5537 | timeout 20 "$MUXA" capture --sock "$SOCK37" --session a > "$OUT.zsfa" 2>&1 | ||
| 5538 | timeout 20 "$MUXA" capture --sock "$SOCK37" --session b > "$OUT.zsfb" 2>&1 | ||
| 5539 | # | ||
| 5540 | # The markers are numbered rather than named after their sessions, and | ||
| 5541 | # deliberately so: `zs-b` and `zs-back` were the first spelling, and `zs-b` | ||
| 5542 | # matched inside `zs-back` — a negative grep that could never fail, in the | ||
| 5543 | # leg whose whole content is negative greps. Numbered words share no | ||
| 5544 | # prefix, which is the property this needs. | ||
| 5545 | for _m in zs-one zs-three; do | ||
| 5546 | grep -q "$_m" "$OUT.zsfa" || { | ||
| 5547 | echo "e2e FAIL: zoom skip: session a never got $_m:" | ||
| 5548 | cat "$OUT.zsfa"; exit 1; } | ||
| 5549 | grep -q "$_m" "$OUT.zsfb" && { | ||
| 5550 | echo "e2e FAIL: zoom skip: $_m reached session b, which the zoom had left:" | ||
| 5551 | cat "$OUT.zsfb"; exit 1; } | ||
| 5552 | done | ||
| 5553 | grep -q "zs-two" "$OUT.zsfb" || { | ||
| 5554 | echo "e2e FAIL: zoom skip: Ctrl-\\ n did not put the zoom on session b:" | ||
| 5555 | cat "$OUT.zsfb"; exit 1; } | ||
| 5556 | grep -q "zs-two" "$OUT.zsfa" && { | ||
| 5557 | echo "e2e FAIL: zoom skip: zs-two reached session a, so the zoom never moved:" | ||
| 5558 | cat "$OUT.zsfa"; exit 1; } | ||
| 5559 | # The whole point, said by the daemon: two tiles, two attaches, and never a | ||
| 5560 | # third across a zoom, a skip to the other tile, a skip back and an unzoom. | ||
| 5561 | # This is the leg the counter matters most in — the two moves it makes are | ||
| 5562 | # exactly the ones a gauge could not tell from a re-dial, since a connection | ||
| 5563 | # that closes as another opens never shows two at once. | ||
| 5564 | assert_attach_delta "$ZSATT_BEFORE" "$ZSATT_AFTER" 2 "zoom skip" | ||
| 5565 | assert_never_two_clients "$OUT.zswatch" a "zoom skip" | ||
| 5566 | assert_never_two_clients "$OUT.zswatch" b "zoom skip" | ||
| 5567 | # Both promoted, both still promoted after the demote. 92 is this leg's own | ||
| 5568 | # width so the number cannot be another scenario's leftover. | ||
| 5569 | for _s in a b; do | ||
| 5570 | timeout 20 "$MUXA" status --sock "$SOCK37" --session "$_s" > "$OUT.zsst$_s" 2>&1 | ||
| 5571 | grep -q '"cols":92' "$OUT.zsst$_s" || { | ||
| 5572 | echo "e2e FAIL: zoom skip: session $_s did not keep the size its promote claimed:" | ||
| 5573 | cat "$OUT.zsst$_s"; exit 1; } | ||
| 5574 | done | ||
| 5575 | assert_stopped "$SOCK37" "$D34PID" "zoom skip" "$OUT.zsstop" | ||
| 5576 | D34PID="" | ||
| 5577 | ok "the zoom skips between tiles with Ctrl-\\ n / Ctrl-\\ l, and the daemon sees no new attach" | ||
| 5578 | |||
| 5579 | # ---- an unzoomed wall forwards nothing --------------------------------- | ||
| 5580 | # | ||
| 5581 | # The teeth of the new invariant. "An unzoomed tile claims nothing" is not | ||
| 5582 | # kept by the tile reading 0x0 — a demoted slot deliberately keeps its | ||
| 5583 | # promoted size — it is kept by the wall never forwarding a keystroke while | ||
| 5584 | # it is zoomed out. So the assertion is a marker that must NOT appear. | ||
| 5585 | # | ||
| 5586 | # A negative grep passes just as well when the leg never ran, and this suite | ||
| 5587 | # has been bitten by that before, so the same leg carries its own anchor: | ||
| 5588 | # the SAME session, the SAME terminal, one zoom later, gets a marker that | ||
| 5589 | # MUST appear. Both halves read one `muxa capture`. If the wall never came | ||
| 5590 | # up, or the ptyclient script fell over early, the positive fails loudly | ||
| 5591 | # and the negative's silence is never mistaken for evidence. | ||
| 5592 | # | ||
| 5593 | # Every letter of `wallmute` is one the unzoomed wall SWALLOWS — not one is | ||
| 5594 | # a selection key. That is the whole reason the word is spelled this way, | ||
| 5595 | # and it was measured rather than assumed: the first spelling was | ||
| 5596 | # `wallnope`, whose `n` and `p` the wall consumes as selection keys, so what | ||
| 5597 | # a leak would actually have delivered is `wallne` — and a grep for | ||
| 5598 | # `wallnope` could not have matched even with the wall forwarding | ||
| 5599 | # everything. A negative that cannot fail is not a test. | ||
| 5600 | # | ||
| 5601 | # None of these bytes may reach a shell, where ECHO would put them on the | ||
| 5602 | # grid even without a newline. No newline is sent, for a second reason: at | ||
| 5603 | # the wall, `\n` is Enter. | ||
| 5604 | "$MUXD" run --sock "$SOCK38" --shell /bin/sh > "$OUT.zm.d" 2>&1 & | ||
| 5605 | D35PID=$! | ||
| 5606 | wait_sock "$SOCK38" "$OUT.zm.d" "zoom-mute daemon never bound" | ||
| 5607 | { printf 'printf "zma-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ | ||
| 5608 | timeout 40 "$MUX" --sock "$SOCK38" --session a > "$OUT.zma" 2> "$OUT.zma.err" | ||
| 5609 | wait_grid "$SOCK38" "zma-pin" "zoom mute: session a's marker" a | ||
| 5610 | { printf 'printf "zmb-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ | ||
| 5611 | timeout 40 "$MUX" --sock "$SOCK38" --session b > "$OUT.zmb" 2> "$OUT.zmb.err" | ||
| 5612 | wait_grid "$SOCK38" "zmb-pin" "zoom mute: session b's marker" b | ||
| 5613 | |||
| 5614 | set +e | ||
| 5615 | timeout 90 "$PTYCLIENT" --cols 96 --rows 30 --out "$OUT.zmcap" --err "$OUT.zmcap.err" -- \ | ||
| 5616 | "$MUX" wall "--sock $SOCK38#a" "--sock $SOCK38#b" > "$OUT.zmpc" 2>&1 <<'EOF' | ||
| 5617 | expect zmb-pin 20000 | ||
| 5618 | settle 700 20000 | ||
| 5619 | send wallmute | ||
| 5620 | settle 700 20000 | ||
| 5621 | send 1 | ||
| 5622 | settle 500 15000 | ||
| 5623 | send \r | ||
| 5624 | settle 700 20000 | ||
| 5625 | send printf 'zmzoom-%s\\n' ok\n | ||
| 5626 | expect zmzoom-ok 15000 | ||
| 5627 | settle 400 15000 | ||
| 5628 | send \x1cw | ||
| 5629 | expect zma-pin 20000 | ||
| 5630 | settle 500 15000 | ||
| 5631 | send q | ||
| 5632 | waitexit 10000 | ||
| 5633 | EOF | ||
| 5634 | RC=$? | ||
| 5635 | set -e | ||
| 5636 | [ "$RC" -eq 0 ] || { | ||
| 5637 | echo "e2e FAIL: zoom mute: ptyclient leg exited $RC:" | ||
| 5638 | cat "$OUT.zmpc"; exit 1; } | ||
| 5639 | timeout 20 "$MUXA" capture --sock "$SOCK38" --session a > "$OUT.zmfa" 2>&1 | ||
| 5640 | timeout 20 "$MUXA" capture --sock "$SOCK38" --session b > "$OUT.zmfb" 2>&1 | ||
| 5641 | # The anchor first, so a leg that did not run cannot pass the negative by | ||
| 5642 | # having done nothing at all. | ||
| 5643 | grep -q "zmzoom-ok" "$OUT.zmfa" || { | ||
| 5644 | echo "e2e FAIL: zoom mute: the zoom never typed into session a, so the" | ||
| 5645 | echo " silent half below proves nothing:" | ||
| 5646 | cat "$OUT.zmfa"; cat "$OUT.zmpc"; exit 1; } | ||
| 5647 | for _s in a b; do | ||
| 5648 | grep -q "wallmute" "$OUT.zmf$_s" && { | ||
| 5649 | echo "e2e FAIL: zoom mute: the UNZOOMED wall forwarded keystrokes to session $_s:" | ||
| 5650 | cat "$OUT.zmf$_s"; exit 1; } | ||
| 5651 | done | ||
| 5652 | # ...and the tile nobody zoomed is untouched in the other sense too: still | ||
| 5653 | # 80 wide, the size it was created at with no tty in sight. | ||
| 5654 | timeout 20 "$MUXA" status --sock "$SOCK38" --session b > "$OUT.zmstb" 2>&1 | ||
| 5655 | grep -q '"cols":80' "$OUT.zmstb" || { | ||
| 5656 | echo "e2e FAIL: zoom mute: an unzoomed tile moved session b's grid:" | ||
| 5657 | cat "$OUT.zmstb"; exit 1; } | ||
| 5658 | assert_stopped "$SOCK38" "$D35PID" "zoom mute" "$OUT.zmstop" | ||
| 5659 | D35PID="" | ||
| 5660 | ok "an unzoomed wall forwards nothing; the zoom one keystroke later does" | ||
| 5661 | |||
| 5662 | # ---- a tile whose pump has died still says something --------------------- | ||
| 5663 | # | ||
| 5664 | # Only pumps answer `repaint_gen`, so a tile whose pump has ENDED has | ||
| 5665 | # nobody to redraw it. That was survivable while the screen was cleared | ||
| 5666 | # once per zoom; phase 1 clears it on every `n`/`p`/`l` as well, and two | ||
| 5667 | # symptoms fell out of the same hole — observed in a live capture, not | ||
| 5668 | # inferred: | ||
| 5669 | # | ||
| 5670 | # * zooming a dead tile painted an entirely blank terminal. No cursor, | ||
| 5671 | # no label, no hint: indistinguishable from a hung multiplexer. | ||
| 5672 | # * after any zoom, a dead tile's stripe and bar never came back, so the | ||
| 5673 | # wall silently lost a row for the rest of its life. | ||
| 5674 | # | ||
| 5675 | # Both are the keyboard's to fix, because it is the only thread left. The | ||
| 5676 | # tile here is dead by the most ordinary route there is: a spelling naming | ||
| 5677 | # a session that does not exist. A wall tile attaches 0x0, a 0x0 attach | ||
| 5678 | # joins but never creates, so the daemon refuses it and that pump ends — | ||
| 5679 | # `[refused]`, the state the wall already had a word for. | ||
| 5680 | # | ||
| 5681 | # Deliberately NOT asserted: that a `[reconnecting]` tile gets the same | ||
| 5682 | # treatment. It must not — it still has a thread that will repaint its hot | ||
| 5683 | # replica within a poll timeout, and the keyboard drawing over that would | ||
| 5684 | # replace something true with something stale. | ||
| 5685 | "$MUXD" run --sock "$SOCK39" --shell /bin/sh > "$OUT.zd.d" 2>&1 & | ||
| 5686 | D36PID=$! | ||
| 5687 | wait_sock "$SOCK39" "$OUT.zd.d" "zoom-dead daemon never bound" | ||
| 5688 | { printf 'printf "zdlive-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \ | ||
| 5689 | timeout 40 "$MUX" --sock "$SOCK39" --session a > "$OUT.zda" 2> "$OUT.zda.err" | ||
| 5690 | wait_grid "$SOCK39" "zdlive-pin" "zoom dead: the live session's marker" a | ||
| 5691 | |||
| 5692 | set +e | ||
| 5693 | timeout 90 "$PTYCLIENT" --cols 90 --rows 24 --out "$OUT.zdcap" --err "$OUT.zdcap.err" -- \ | ||
| 5694 | "$MUX" wall "--sock $SOCK39#a" "--sock $SOCK39#ghost" > "$OUT.zdpc" 2>&1 <<'EOF' | ||
| 5695 | expect zdlive-pin 20000 | ||
| 5696 | settle 800 20000 | ||
| 5697 | send 2 | ||
| 5698 | settle 500 15000 | ||
| 5699 | send \r | ||
| 5700 | settle 800 20000 | ||
| 5701 | send \x1cw | ||
| 5702 | settle 800 20000 | ||
| 5703 | send q | ||
| 5704 | waitexit 10000 | ||
| 5705 | EOF | ||
| 5706 | RC=$? | ||
| 5707 | set -e | ||
| 5708 | [ "$RC" -eq 0 ] || { | ||
| 5709 | echo "e2e FAIL: zoom dead: ptyclient leg exited $RC:" | ||
| 5710 | cat "$OUT.zdpc"; exit 1; } | ||
| 5711 | # The dead tile narrated its own refusal at all, which is the state word | ||
| 5712 | # the bar can only have if the pump reached the refusal path. | ||
| 5713 | grep -q -- "--sock $SOCK39#ghost \[refused\]" "$OUT.zdcap" || { | ||
| 5714 | echo "e2e FAIL: zoom dead: the dead tile's bar never said [refused]"; exit 1; } | ||
| 5715 | # Zooming it put a line on the screen instead of nothing. The way out is | ||
| 5716 | # part of the assertion: a screen that says only "refused" and offers no | ||
| 5717 | # chord is the same dead end with better manners. | ||
| 5718 | grep -q "Ctrl-\\\\ w for the wall" "$OUT.zdcap" || { | ||
| 5719 | echo "e2e FAIL: zoom dead: zooming a dead tile painted a blank terminal" | ||
| 5720 | echo " (no label, no state, no way out)"; exit 1; } | ||
| 5721 | # ...and its bar came BACK after the unzoom. Counted, not merely present: | ||
| 5722 | # one paint is the wall's first draw, which happens before any zoom cleared | ||
| 5723 | # the screen and so proves nothing about coming back. | ||
| 5724 | _zd_bars=$(grep -o -- "--sock $SOCK39#ghost \[refused\]" "$OUT.zdcap" | wc -l) | ||
| 5725 | [ "$_zd_bars" -ge 2 ] || { | ||
| 5726 | echo "e2e FAIL: zoom dead: the dead tile's bar painted $_zd_bars time(s), so it" | ||
| 5727 | echo " never came back after the zoom cleared the screen:" | ||
| 5728 | cat "$OUT.zdpc"; exit 1; } | ||
| 5729 | # The live tile is unharmed by any of it — the wall is still a wall. | ||
| 5730 | grep -q -- "--sock $SOCK39#a \[up\]" "$OUT.zdcap" || { | ||
| 5731 | echo "e2e FAIL: zoom dead: the live tile's bar never said [up]"; exit 1; } | ||
| 5732 | assert_stopped "$SOCK39" "$D36PID" "zoom dead" "$OUT.zdstop" | ||
| 5733 | D36PID="" | ||
| 5734 | ok "a tile whose pump has died narrates its zoom and gets its stripe back" | ||
| 5735 | |||
| 5736 | |||
| 5322 | # The long-lived daemon has served every scenario that wanted it; stop it | 5737 | # The long-lived daemon has served every scenario that wanted it; stop it |
| 5323 | # NOW so its allocator verdict is written while the suite is still running | 5738 | # NOW so its allocator verdict is written while the suite is still running |
| 5324 | # and can say so. SIGTERM runs the clean-shutdown path, so the defer chain | 5739 | # and can say so. SIGTERM runs the clean-shutdown path, so the defer chain |
| @@ -5390,9 +5805,20 @@ DPID="" | |||
| 5390 | # subject is a full-screen application's redraws is one this suite has | 5805 | # subject is a full-screen application's redraws is one this suite has |
| 5391 | # never claimed to reproduce byte for byte. The 43rd is the client with no | 5806 | # never claimed to reproduce byte for byte. The 43rd is the client with no |
| 5392 | # terminal of its own, and no convergence point because it HAS no terminal: | 5807 | # terminal of its own, and no convergence point because it HAS no terminal: |
| 5393 | # there is no capture to converge, which is the entire point of the leg. | 5808 | # there is no capture to converge, which is the entire point of the leg. The |
| 5394 | [ "$OK_COUNT" = "43" ] || { | 5809 | # 44th is the zoom skipping between tiles, and no convergence point for the |
| 5395 | echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 43 —" | 5810 | # CLI wall's reason a fourth time: its subject is one terminal's capture |
| 5811 | # holding two sessions' bytes, plus a COUNT the daemon kept while nobody was | ||
| 5812 | # looking — neither of which is a grid. The 45th is its negative twin, the | ||
| 5813 | # unzoomed wall that forwards nothing, and no convergence point because what | ||
| 5814 | # it asserts on is the ABSENCE of bytes from a grid, which converging two | ||
| 5815 | # grids that agree about that absence would say nothing about. The 46th is | ||
| 5816 | # the tile whose pump died, and no convergence point because its subject is | ||
| 5817 | # a session that DOES NOT EXIST: what the leg reads is what a terminal was | ||
| 5818 | # told about a refused attach, and there is no grid on either side to | ||
| 5819 | # converge. | ||
| 5820 | [ "$OK_COUNT" = "46" ] || { | ||
| 5821 | echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 46 —" | ||
| 5396 | echo " a scenario was added (update the pin) or silently lost" | 5822 | echo " a scenario was added (update the pin) or silently lost" |
| 5397 | exit 1 | 5823 | exit 1 |
| 5398 | } | 5824 | } |
| @@ -5400,4 +5826,4 @@ DPID="" | |||
| 5400 | echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 35" | 5826 | echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 35" |
| 5401 | exit 1 | 5827 | exit 1 |
| 5402 | } | 5828 | } |
| 5403 | echo "e2e OK (43 scenarios, 35 convergence points)" | 5829 | echo "e2e OK (46 scenarios, 35 convergence points)" |