a73x

9e225de2

test: the convergence, and the legs it deliberately changed

a73x   2026-08-20 07:53

Commit message
test: the convergence, and the legs it deliberately changed

Three new scenarios (52 -> 55), each pinning something only a converged
mux can do:

  * `mux TARGET` IS a wall: `Ctrl-\ w` unzooms onto the SAVED tiles (a
    tile an earlier `mux` wrote, in a process never told about it), Enter
    returns for a flat attach count, and `q` leaves mux.
  * the ring GROWS the wall: `Ctrl-\ n` onto a sibling with no tile dials
    once and writes its line; `Ctrl-\ n` back onto one that has a tile
    costs nothing. Same chord, twice, and the counter is the difference.
  * a session that ends under the zoom: alone it exits mux with the
    shell's own code, in company it drops to the wall with the dead tile
    narrating `[exited]` beside the live one.

Four existing legs evolved, and every edit is a behaviour this phase
changed on purpose:

  * M2 (`Ctrl-\ c`) and M3 (the ring) waited on a fresh `?1049h` per
    switch, because each switch WAS a fresh client process on a fresh
    terminal. The alternate screen is now entered once and never left, so
    the arrival signal is `setZoom`'s own screen clear. Waiting matters
    rather than being decoration: keystrokes sent before the handover go
    to the mailbox of the tile being left. M2's refused fourth chord waits
    on session 3's marker instead, because a `[2J` cannot tell "arrived"
    from "came straight back".
  * M4 (`Ctrl-\ w`) loses the child wall it was written for. `w` demotes
    in place, the return is Enter (`q` now LEAVES mux, which is the
    spec's model), and the leg gains a state home of its own because the
    wall an unzoom shows is the saved one — the shared XDG_STATE_HOME is
    every socket the suite ever attached to. Its passivity assertion is
    re-aimed rather than dropped: "the wall's separate 0x0 attach did not
    move the grid" becomes "unzooming gave nothing back", since there is
    no separate attach left to say it about. It also now reads the wall
    file back, which is where a chord-added tile earns its record.
  * the in-place-zoom leg unzoomed with `d`. That was the child-spawn
    era's second spelling of "come back"; `d` now means what a user's
    fingers mean by it, so the leg uses `w`.

Nothing else moved. Every other scenario — attach, detach, exit codes,
prediction, wheel, scrollback, OSC 133, bracketed paste, self-attach
refusal, attach history, reconnect — is byte-identical and green.

test/e2e.sh
Old New
@@ -280,6 +280,12 @@ D26PID=""
280 # session from another block would appear as an extra stripe and could push 280 # session from another block would appear as an extra stripe and could push
281 # the two this block asserts on off a 30-row terminal. 281 # the two this block asserts on off a 30-row terminal.
282 SOCK30="${TMPDIR:-/tmp}/muxd-e2e-wallchord-$$.sock" 282 SOCK30="${TMPDIR:-/tmp}/muxd-e2e-wallchord-$$.sock"
283 # ...and a state home of its own, from phase 3c on. `Ctrl-\ w` unzooms to
284 # THE wall — the saved file's tiles as well as the ones this run made — so a
285 # shared $XDG_STATE_HOME would put a stripe on this terminal for every
286 # socket the suite has ever attached to, nearly all of them dead. This leg
287 # is about two sessions, so it gets a wall with two sessions in it.
288 M4STATE="${TMPDIR:-/tmp}/mux-e2e-wallchord-state-$$"
283 D27PID="" 289 D27PID=""
284 290
285 # M5 (the self-attach refusal): its own daemon because the assertion reads 291 # M5 (the self-attach refusal): its own daemon because the assertion reads
@@ -375,6 +381,28 @@ WHBAD="${TMPDIR:-/tmp}/mux-e2e-wallbad-$$"
375 WHXSTATE="${TMPDIR:-/tmp}/mux-e2e-wallx-state-$$" 381 WHXSTATE="${TMPDIR:-/tmp}/mux-e2e-wallx-state-$$"
376 D37PID="" 382 D37PID=""
377 383
384 # The convergence block (phase 3c): `mux TARGET` is a wall of one tile,
385 # entered zoomed. Three daemons and three state homes, each of them its own
386 # for the same reason the attach-history block's is — what these legs read
387 # back is the wall FILE, and the shared $XDG_STATE_HOME is every socket the
388 # suite has ever attached to.
389 SOCK45="${TMPDIR:-/tmp}/muxd-e2e-converge-$$.sock"
390 CVSTATE="${TMPDIR:-/tmp}/mux-e2e-converge-state-$$"
391 CVWALL="$CVSTATE/mux/wall"
392 D45PID=""
393 # The ring-grows-the-wall leg needs TWO state homes: one for the client
394 # under test, and one for the setup client whose attach must NOT leave a
395 # tile the leg would then find already there. That absence is the case
396 # under test — a sibling the daemon knows and the wall does not.
397 SOCK46="${TMPDIR:-/tmp}/muxd-e2e-ringgrow-$$.sock"
398 RGSTATE="${TMPDIR:-/tmp}/mux-e2e-ringgrow-state-$$"
399 RGOTHER="${TMPDIR:-/tmp}/mux-e2e-ringgrow-other-$$"
400 D46PID=""
401 # The exit-semantics leg: a session ending under the zoom.
402 SOCK47="${TMPDIR:-/tmp}/muxd-e2e-exitsem-$$.sock"
403 XESTATE="${TMPDIR:-/tmp}/mux-e2e-exitsem-state-$$"
404 D47PID=""
405
378 # One counter out of a MUX_PREDICT_STATS line. The client prints exactly one 406 # One counter out of a MUX_PREDICT_STATS line. The client prints exactly one
379 # such line on exit; every field is a key=value pair, so a rename or reorder 407 # such line on exit; every field is a key=value pair, so a rename or reorder
380 # in the client shows up here as an empty read rather than a wrong number. 408 # in the client shows up here as an empty read rather than a wrong number.
@@ -1023,6 +1051,9 @@ cleanup() {
1023 [ -S "$SOCK42" ] && "$MUXD" stop --sock "$SOCK42" 2>/dev/null || true 1051 [ -S "$SOCK42" ] && "$MUXD" stop --sock "$SOCK42" 2>/dev/null || true
1024 [ -S "$SOCK43" ] && "$MUXD" stop --sock "$SOCK43" 2>/dev/null || true 1052 [ -S "$SOCK43" ] && "$MUXD" stop --sock "$SOCK43" 2>/dev/null || true
1025 [ -S "$SOCK44" ] && "$MUXD" stop --sock "$SOCK44" 2>/dev/null || true 1053 [ -S "$SOCK44" ] && "$MUXD" stop --sock "$SOCK44" 2>/dev/null || true
1054 [ -S "$SOCK45" ] && "$MUXD" stop --sock "$SOCK45" 2>/dev/null || true
1055 [ -S "$SOCK46" ] && "$MUXD" stop --sock "$SOCK46" 2>/dev/null || true
1056 [ -S "$SOCK47" ] && "$MUXD" stop --sock "$SOCK47" 2>/dev/null || true
1026 1057
1027 # ---- the leak sweep (hygiene kit, 6a) ---- 1058 # ---- the leak sweep (hygiene kit, 6a) ----
1028 # Here rather than at the bottom of the file, which `set -e` reaches only 1059 # Here rather than at the bottom of the file, which `set -e` reaches only
@@ -1182,6 +1213,19 @@ cleanup() {
1182 "$OUT.whxa" "$OUT.whxa.err" "$OUT.whxb" "$OUT.whxb.err" \ 1213 "$OUT.whxa" "$OUT.whxa.err" "$OUT.whxb" "$OUT.whxb.err" \
1183 "$OUT.whstop" 1214 "$OUT.whstop"
1184 rm -rf "$WHSTATE" "$WHBAD" "$WHXSTATE" 1215 rm -rf "$WHSTATE" "$WHBAD" "$WHXSTATE"
1216 # ...and the wall-chord leg's own state home, which holds the two-line
1217 # wall that leg builds and reads back.
1218 rm -rf "$M4STATE"
1219 # The convergence block: three daemons, three state homes, and the
1220 # captures each leg names in its own failures.
1221 rm -f "$SOCK45" "$OUT.cv.d" "$OUT.cvside" "$OUT.cvside.err" "$OUT.cvinj" \
1222 "$OUT.cvcap" "$OUT.cvcap.err" "$OUT.cvpc" "$OUT.cvstop"
1223 rm -f "$SOCK46" "$OUT.rg.d" "$OUT.rgtwo" "$OUT.rgtwo.err" \
1224 "$OUT.rgcap" "$OUT.rgcap.err" "$OUT.rgpc" "$OUT.rgcapt" "$OUT.rgcap0" \
1225 "$OUT.rgstop"
1226 rm -f "$SOCK47" "$OUT.xe.d" "$OUT.xe1" "$OUT.xe1.err" \
1227 "$OUT.xecap" "$OUT.xecap.err" "$OUT.xepc" "$OUT.xestop"
1228 rm -rf "$CVSTATE" "$RGSTATE" "$RGOTHER" "$XESTATE"
1185 # The convergence files a FAILING assert_converged leaves behind 1229 # The convergence files a FAILING assert_converged leaves behind
1186 # (.render/.dump/.rvt/.dvt/.diff for that capture) are deliberately not 1230 # (.render/.dump/.rvt/.dvt/.diff for that capture) are deliberately not
1187 # chased here: on a failing run they are the evidence. 1231 # chased here: on a failing run they are the evidence.
@@ -4777,14 +4821,25 @@ ok "Ctrl-\\ prefix: an unknown chord is swallowed, Ctrl-\\ d detaches"
4777 # that did nothing at all. 4821 # that did nothing at all.
4778 # 4822 #
4779 # The leg then keeps pressing until the daemon runs out of sessions, because 4823 # The leg then keeps pressing until the daemon runs out of sessions, because
4780 # the refusal is the branch with the interesting answer: `.refused` is the 4824 # the refusal is the branch with the interesting answer: a refused attach is
4781 # one attach failure the client recovers from instead of exiting, and 4825 # the one failure the zoom recovers from instead of exiting, and nothing
4782 # nothing else in the suite reaches it. See the block after the leg for the 4826 # else in the suite reaches it. See the block after the leg for the three
4783 # three things "recovered" has to mean. 4827 # things "recovered" has to mean.
4784 # 4828 #
4785 # The re-attach is waited for by its alternate-screen entry rather than a 4829 # PHASE 3C evolved the WAIT, and only the wait. `\x1cc` used to tear the
4786 # fixed sleep: the switch tears the terminal down and builds it back up, so 4830 # terminal down and build it back up — a fresh `?1049h` per switch — because
4787 # the second `?1049h` is the client saying it has a new session on screen. 4831 # each switch was a new client process on a new transport. Now `mux` IS the
4832 # wall: the chord adds a tile and moves the ZOOM to it, and the alternate
4833 # screen was entered once, at startup, and is never left. So the arrival
4834 # signal is the zoom's own: `setZoom` clears the screen (`\x1b[2J`) on the
4835 # thread that moves it, before the terminal changes hands.
4836 #
4837 # That needle is not merely "some paint": between the chord and the answer
4838 # the old tile is idle and paints nothing, so the next `[2J` after the chord
4839 # is the handover itself. Waiting for it MATTERS — keystrokes sent before it
4840 # would go to the mailbox of the tile the user is leaving, which is the
4841 # session they were typing in a moment ago and would silently swallow the
4842 # marker.
4788 "$MUXD" run --sock "$SOCK28" --shell /bin/sh > "$OUT.nsw.d" 2>&1 & 4843 "$MUXD" run --sock "$SOCK28" --shell /bin/sh > "$OUT.nsw.d" 2>&1 &
4789 D25PID=$! 4844 D25PID=$!
4790 wait_sock "$SOCK28" "$OUT.nsw.d" "new-session daemon never bound" 4845 wait_sock "$SOCK28" "$OUT.nsw.d" "new-session daemon never bound"
@@ -4798,22 +4853,22 @@ send printf 'm2a-%s\\n' pin\n
4798 expect m2a-pin 15000 4853 expect m2a-pin 15000
4799 settle 400 15000 4854 settle 400 15000
4800 send \x1cc 4855 send \x1cc
4801 expect \x1b[?1049h 15000 4856 expect \x1b[2J 15000
4802 settle 400 15000 4857 settle 400 15000
4803 send printf 'm2b-%s\\n' pin\n 4858 send printf 'm2b-%s\\n' pin\n
4804 expect m2b-pin 15000 4859 expect m2b-pin 15000
4805 settle 400 15000 4860 settle 400 15000
4806 send \x1cc 4861 send \x1cc
4807 expect \x1b[?1049h 15000 4862 expect \x1b[2J 15000
4808 settle 400 15000 4863 settle 400 15000
4809 send \x1cc 4864 send \x1cc
4810 expect \x1b[?1049h 15000 4865 expect \x1b[2J 15000
4811 settle 400 15000 4866 settle 400 15000
4812 send printf 'm2c-%s\\n' pin\n 4867 send printf 'm2c-%s\\n' pin\n
4813 expect m2c-pin 15000 4868 expect m2c-pin 15000
4814 settle 400 15000 4869 settle 400 15000
4815 send \x1cc 4870 send \x1cc
4816 expect \x1b[?1049h 15000 4871 expect m2c-pin 15000
4817 settle 400 15000 4872 settle 400 15000
4818 send printf 'm2d-%s\\n' pin\n 4873 send printf 'm2d-%s\\n' pin\n
4819 expect m2d-pin 15000 4874 expect m2d-pin 15000
@@ -4841,8 +4896,16 @@ grep -q "m2b-pin" "$OUT.nswcap" && {
4841 # The fourth chord had nowhere to go: max_sessions is 4 and the leg filled 4896 # The fourth chord had nowhere to go: max_sessions is 4 and the leg filled
4842 # the table (0, 1, 2, 3), so the daemon answered the attach with an 4897 # the table (0, 1, 2, 3), so the daemon answered the attach with an
4843 # exit_status before a single frame of state. That refusal is NOT fatal — 4898 # exit_status before a single frame of state. That refusal is NOT fatal —
4844 # the switch's arrival knows the name it left — and the three claims below 4899 # the tile the chord created knows which tile it was born from — and the
4845 # are what "fell back" means, none of which the log shows on its own. 4900 # three claims below are what "fell back" means, none of which the log
4901 # shows on its own.
4902 #
4903 # It is also why the fourth chord's wait is `expect m2c-pin` and not the
4904 # `[2J` the other three use: a refused tile's screen is cleared like any
4905 # other, so `[2J` cannot tell "the zoom arrived" from "the zoom came
4906 # straight back". Session 3's own marker, repainted from a replica that was
4907 # hot the whole time, can — and it is the same positive the capture
4908 # assertion below leans on.
4846 # 4909 #
4847 # * the client said so, once, in its own words. Its stderr is ptyclient's 4910 # * the client said so, once, in its own words. Its stderr is ptyclient's
4848 # --err file, not the pty, so the message is read there. 4911 # --err file, not the pty, so the message is read there.
@@ -4885,10 +4948,19 @@ ok "Ctrl-\\ c: a new session is created and switched to, the old one intact; a f
4885 # m3b-pin, and the expect would time out on it. 4948 # m3b-pin, and the expect would time out on it.
4886 # * `\x1cp` from the first slot must wrap back to the last — m3c-pin. 4949 # * `\x1cp` from the first slot must wrap back to the last — m3c-pin.
4887 # 4950 #
4888 # The markers arrive a second time because a switch is an attach and an 4951 # The markers arrive a second time because arriving somewhere repaints that
4889 # attach repaints a snapshot; ptyclient's expect cursor consumes matches, 4952 # session's grid; ptyclient's expect cursor consumes matches, so the m3a-pin
4890 # so the m3a-pin the leg waited for at the top cannot satisfy the one it 4953 # the leg waited for at the top cannot satisfy the one it waits for after
4891 # waits for after the wrap. 4954 # the wrap.
4955 #
4956 # PHASE 3C changed what a step COSTS, not where it lands. `n` used to detach,
4957 # re-dial and re-attach, and the leg waited for the new client's `?1049h`.
4958 # Now the ring moves the ZOOM: sessions 0, 1 and 2 all have tiles (the `c`
4959 # chords made them), their replicas are hot, and the step is a local repaint
4960 # at zero round trips. The alternate screen is entered once and never left,
4961 # so the arrival signal is `setZoom`'s screen clear followed by the marker
4962 # the repaint carries — and the marker is what this leg was always really
4963 # reading the answer off.
4892 # 4964 #
4893 # The captures afterwards pin which marker lives where — three sessions 4965 # The captures afterwards pin which marker lives where — three sessions
4894 # that each kept their own line, so the stepping moved the CLIENT and left 4966 # that each kept their own line, so the stepping moved the CLIENT and left
@@ -4906,23 +4978,21 @@ send printf 'm3a-%s\\n' pin\n
4906 expect m3a-pin 15000 4978 expect m3a-pin 15000
4907 settle 400 15000 4979 settle 400 15000
4908 send \x1cc 4980 send \x1cc
4909 expect \x1b[?1049h 15000 4981 expect \x1b[2J 15000
4910 settle 400 15000 4982 settle 400 15000
4911 send printf 'm3b-%s\\n' pin\n 4983 send printf 'm3b-%s\\n' pin\n
4912 expect m3b-pin 15000 4984 expect m3b-pin 15000
4913 settle 400 15000 4985 settle 400 15000
4914 send \x1cc 4986 send \x1cc
4915 expect \x1b[?1049h 15000 4987 expect \x1b[2J 15000
4916 settle 400 15000 4988 settle 400 15000
4917 send printf 'm3c-%s\\n' pin\n 4989 send printf 'm3c-%s\\n' pin\n
4918 expect m3c-pin 15000 4990 expect m3c-pin 15000
4919 settle 400 15000 4991 settle 400 15000
4920 send \x1cn 4992 send \x1cn
4921 expect \x1b[?1049h 15000
4922 expect m3a-pin 15000 4993 expect m3a-pin 15000
4923 settle 400 15000 4994 settle 400 15000
4924 send \x1cp 4995 send \x1cp
4925 expect \x1b[?1049h 15000
4926 expect m3c-pin 15000 4996 expect m3c-pin 15000
4927 settle 400 15000 4997 settle 400 15000
4928 send \x1c\x1c 4998 send \x1c\x1c
@@ -4949,26 +5019,38 @@ assert_stopped "$SOCK29" "$D26PID" "session ring" "$OUT.ringstop"
4949 D26PID="" 5019 D26PID=""
4950 ok "Ctrl-\\ n / Ctrl-\\ p: the ring steps both ways and wraps at both ends" 5020 ok "Ctrl-\\ n / Ctrl-\\ p: the ring steps both ways and wraps at both ends"
4951 5021
4952 # ---- M4: Ctrl-\ w shows the wall, and `q` gives the session back -------- 5022 # ---- M4: Ctrl-\ w unzooms to the wall, and Enter goes back in ----------
4953 # 5023 #
4954 # Scenario 33 proved `mux wall` paints several sessions on one terminal. 5024 # Scenario 33 proved `mux wall` paints several sessions on one terminal.
4955 # This is that wall reached from INSIDE a session, which is a different 5025 # This is that wall reached from inside a ZOOM, which is a different claim
4956 # claim in two places, and the leg is built around both. 5026 # in two places, and the leg is built around both.
4957 # 5027 #
4958 # The money assertion is that ONE client's capture holds BOTH markers at 5028 # The money assertion is unchanged: ONE client's capture holds BOTH markers
4959 # once: m4a-pin is the default session's, m4b-pin belongs to the session 5029 # at once. m4a-pin is the default session's, m4b-pin belongs to the session
4960 # `\x1cc` created, and no single attached session can ever paint the two 5030 # `\x1cc` created, and no single attached session can ever paint the two
4961 # together. Only the wall can, so seeing both is seeing the wall — the 5031 # together. Only the wall can, so seeing both is seeing the wall — the
4962 # chord built the spellings out of the target this client was dialling 5032 # tiles are spelled out of the target this client dialled (`--sock PATH#0`,
4963 # (`--sock PATH#0`, `--sock PATH#1`) and the child painted them. 5033 # `--sock PATH#1`), exactly as before.
4964 # 5034 #
4965 # The second claim is the return: `q` leaves the wall and the parent 5035 # PHASE 3C changed everything about HOW, and the leg says so:
4966 # re-attaches to the session it was in, NOT to the default and not to a 5036 #
4967 # new one. Its witness is m4b-pin arriving again after the next 5037 # * `\x1cw` no longer spawns a child `mux wall`. There is no child and no
4968 # `\x1b[?1049h` — a fresh alternate screen, so a fresh attach, and the 5038 # second alternate screen: `mux` IS the wall, and `w` demotes the zoom
4969 # marker in it says session 1. The re-attach is what repaints the terminal 5039 # in place. The two tiles were already there — one is the attach this
4970 # the wall child painted over; an expect that times out here is a session 5040 # client made, the other is the tile `\x1cc` added — and both replicas
4971 # that came back blank. 5041 # have been hot the whole time, which is why the stripes paint at once.
5042 # * the return is `Enter`, not `q`. Under the spec's model `q` LEAVES
5043 # MUX from the wall, and the selection follows the zoom, so Enter goes
5044 # straight back to the tile the user came out of. Its witness is
5045 # m4b-pin arriving again — repainted from the hot replica, with no
5046 # re-attach and no fresh `?1049h`, which is the whole point of the
5047 # lens. An expect that times out here is a session that came back
5048 # blank.
5049 # * a state home of its own. The wall this leg unzooms to is now THE
5050 # wall — the saved file's tiles as well as this run's — and the shared
5051 # $XDG_STATE_HOME holds a tile for every socket the suite has attached
5052 # to, most of them long dead. The leg would be showing the suite's
5053 # history rather than its own two sessions.
4972 # 5054 #
4973 # The markers are shell-EXPANDED, the M18 trick, so a hit is the shell's 5055 # The markers are shell-EXPANDED, the M18 trick, so a hit is the shell's
4974 # work and never an echo of what this script typed. 5056 # work and never an echo of what this script typed.
@@ -4991,7 +5073,8 @@ wait_sock "$SOCK30" "$OUT.wc.d" "wall-chord daemon never bound"
4991 --sock "$SOCK30" --session 0 > "$OUT.wcinj" 2>&1 ) & 5073 --sock "$SOCK30" --session 0 > "$OUT.wcinj" 2>&1 ) &
4992 WCINJPID=$! 5074 WCINJPID=$!
4993 set +e 5075 set +e
4994 timeout 60 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.wc" --err "$OUT.wc.err" \ 5076 XDG_STATE_HOME="$M4STATE" timeout 60 "$PTYCLIENT" --cols 100 --rows 30 \
5077 --out "$OUT.wc" --err "$OUT.wc.err" \
4995 -- "$MUX" --sock "$SOCK30" > "$OUT.wc.log" 2>&1 <<'EOF' 5078 -- "$MUX" --sock "$SOCK30" > "$OUT.wc.log" 2>&1 <<'EOF'
4996 expect \x1b[?1049h 15000 5079 expect \x1b[?1049h 15000
4997 settle 400 15000 5080 settle 400 15000
@@ -4999,7 +5082,7 @@ send printf 'm4a-%s\\n' pin\n
4999 expect m4a-pin 15000 5082 expect m4a-pin 15000
5000 settle 400 15000 5083 settle 400 15000
5001 send \x1cc 5084 send \x1cc
5002 expect \x1b[?1049h 15000 5085 expect \x1b[2J 15000
5003 settle 400 15000 5086 settle 400 15000
5004 send printf 'm4b-%s\\n' pin\n 5087 send printf 'm4b-%s\\n' pin\n
5005 expect m4b-pin 15000 5088 expect m4b-pin 15000
@@ -5009,8 +5092,7 @@ expect m4b-pin 20000
5009 expect m4a-pin 20000 5092 expect m4a-pin 20000
5010 expect m4c-pin 20000 5093 expect m4c-pin 20000
5011 settle 500 15000 5094 settle 500 15000
5012 send q 5095 send \r
5013 expect \x1b[?1049h 15000
5014 expect m4b-pin 15000 5096 expect m4b-pin 15000
5015 settle 400 15000 5097 settle 400 15000
5016 send \x1c\x1c 5098 send \x1c\x1c
@@ -5020,7 +5102,7 @@ RC=$?
5020 set -e 5102 set -e
5021 wait "$WCINJPID" 2>/dev/null || true 5103 wait "$WCINJPID" 2>/dev/null || true
5022 [ "$RC" -eq 0 ] || { 5104 [ "$RC" -eq 0 ] || {
5023 echo "e2e FAIL: wall chord: ptyclient leg exited $RC (did \\x1cw wall, did q return?):" 5105 echo "e2e FAIL: wall chord: ptyclient leg exited $RC (did \\x1cw unzoom, did Enter go back?):"
5024 cat "$OUT.wc.log"; cat "$OUT.wcinj"; exit 1; } 5106 cat "$OUT.wc.log"; cat "$OUT.wcinj"; exit 1; }
5025 # The two stripes, asserted on bytes only a wall can produce: a label bar 5107 # The two stripes, asserted on bytes only a wall can produce: a label bar
5026 # per tile, naming the spelling the CHORD built out of the target this 5108 # per tile, naming the spelling the CHORD built out of the target this
@@ -5041,16 +5123,32 @@ for s in 0:m4a 1:m4b; do
5041 echo "e2e FAIL: wall chord: session $_sess does not hold $_mark:" 5123 echo "e2e FAIL: wall chord: session $_sess does not hold $_mark:"
5042 cat "$OUT.wccap"; exit 1; } 5124 cat "$OUT.wccap"; exit 1; }
5043 done 5125 done
5044 # The wall's tiles attach at 0x0 and must never claim a grid — scenario 5126 # Unzooming gave NOTHING back to the daemon: a demote is client-local, so
5045 # 33's passivity check, made here because these attaches were spawned by 5127 # session 1 keeps the grid the zoom claimed for it and the stripe crops it.
5046 # the client rather than typed by a user. 5128 # That is the phase-1 invariant read from the other end — before phase 3c
5129 # the same assertion said "the wall's separate 0x0 attach did not move the
5130 # grid", and there is no separate attach any more to say it about.
5047 timeout 20 "$MUXA" status --sock "$SOCK30" --session 1 > "$OUT.wcst" 2>&1 5131 timeout 20 "$MUXA" status --sock "$SOCK30" --session 1 > "$OUT.wcst" 2>&1
5048 grep -q '"cols":100' "$OUT.wcst" || { 5132 grep -q '"cols":100' "$OUT.wcst" || {
5049 echo "e2e FAIL: wall chord: the wall's 0x0 attach moved session 1's grid:" 5133 echo "e2e FAIL: wall chord: unzooming moved session 1's grid:"
5050 cat "$OUT.wcst"; exit 1; } 5134 cat "$OUT.wcst"; exit 1; }
5135 # The wall file this run built: one line per session VISITED, and no line
5136 # for anything else. Its own state home makes that a complete statement
5137 # rather than a search — the attach wrote #0 and the `c` chord's tile
5138 # wrote #1, and phase 3c is where a chord-added tile earns its own record.
5139 _m4_n=$(wc -l < "$M4STATE/mux/wall")
5140 [ "$_m4_n" -eq 2 ] || {
5141 echo "e2e FAIL: wall chord: the wall file holds $_m4_n lines, want 2:"
5142 cat "$M4STATE/mux/wall"; exit 1; }
5143 grep -qx -- "--sock $SOCK30#0" "$M4STATE/mux/wall" || {
5144 echo "e2e FAIL: wall chord: no tile for the session mux attached to:"
5145 cat "$M4STATE/mux/wall"; exit 1; }
5146 grep -qx -- "--sock $SOCK30#1" "$M4STATE/mux/wall" || {
5147 echo "e2e FAIL: wall chord: the chord-created session earned no tile:"
5148 cat "$M4STATE/mux/wall"; exit 1; }
5051 assert_stopped "$SOCK30" "$D27PID" "wall chord" "$OUT.wcstop" 5149 assert_stopped "$SOCK30" "$D27PID" "wall chord" "$OUT.wcstop"
5052 D27PID="" 5150 D27PID=""
5053 ok "Ctrl-\\ w: the wall shows both sessions, and q gives the session back" 5151 ok "Ctrl-\\ w: the wall shows both sessions in place, and Enter goes back in"
5054 5152
5055 # ---- M5: a session shell knows where it is, and mux refuses the self-loop -- 5153 # ---- M5: a session shell knows where it is, and mux refuses the self-loop --
5056 # 5154 #
@@ -5144,9 +5242,16 @@ ok "a session shell carries MUX_SOCK/MUX_SESSION, and mux refuses to attach to i
5144 # Scenario 33 proved the wall WATCHES two sessions. This proves the wall can 5242 # Scenario 33 proved the wall WATCHES two sessions. This proves the wall can
5145 # be typed through by PROMOTING one of the connections it already holds: 5243 # be typed through by PROMOTING one of the connections it already holds:
5146 # `2` selects the second stripe, `Enter` resizes that tile's attach from 0x0 5244 # `2` selects the second stripe, `Enter` resizes that tile's attach from 0x0
5147 # to this pty's size and starts forwarding keystrokes down it, `Ctrl-\ d` 5245 # to this pty's size and starts forwarding keystrokes down it, `Ctrl-\ w`
5148 # gives the terminal back to the wall. 5246 # gives the terminal back to the wall.
5149 # 5247 #
5248 # PHASE 3C evolved that last key, and only that key. This leg used `d`,
5249 # which the child-spawn era had made a second spelling of "come back" — the
5250 # child was a real client and `Ctrl-\ d` detached it. Now `mux` IS the wall,
5251 # and `d` has to keep the meaning every user's fingers already have for it:
5252 # detach the session and leave. `w` is the model's own key and the only one
5253 # that unzooms.
5254 #
5150 # Five claims, five witnesses: 5255 # Five claims, five witnesses:
5151 # 5256 #
5152 # * the selection moved — b's label bar carries the `> ` marker, which 5257 # * the selection moved — b's label bar carries the `> ` marker, which
@@ -5225,7 +5330,7 @@ settle 700 20000
5225 send printf 'wzoom-%s\\n' pin\n 5330 send printf 'wzoom-%s\\n' pin\n
5226 expect wzoom-pin 15000 5331 expect wzoom-pin 15000
5227 settle 400 15000 5332 settle 400 15000
5228 send \x1cd 5333 send \x1cw
5229 expect wza-pin 20000 5334 expect wza-pin 20000
5230 settle 500 15000 5335 settle 500 15000
5231 send q 5336 send q
@@ -5237,7 +5342,7 @@ unwatch_clients "$OUT.wzwatch"
5237 # Read BEFORE the muxa captures below, every one of which is an attach. 5342 # Read BEFORE the muxa captures below, every one of which is an attach.
5238 WZATT_AFTER=$(attaches_now "$SOCK32") 5343 WZATT_AFTER=$(attaches_now "$SOCK32")
5239 [ "$RC" -eq 0 ] || { 5344 [ "$RC" -eq 0 ] || {
5240 echo "e2e FAIL: wall zoom: ptyclient leg exited $RC (did \\r zoom, did \\x1cd come back?):" 5345 echo "e2e FAIL: wall zoom: ptyclient leg exited $RC (did \\r zoom, did \\x1cw come back?):"
5241 cat "$OUT.wzpc"; exit 1; } 5346 cat "$OUT.wzpc"; exit 1; }
5242 # The move itself, on bytes only a selected bar can carry. 5347 # The move itself, on bytes only a selected bar can carry.
5243 grep -q -- "> --sock $SOCK32#b" "$OUT.wzcap" || { 5348 grep -q -- "> --sock $SOCK32#b" "$OUT.wzcap" || {
@@ -6425,6 +6530,286 @@ assert_stopped "$SOCK40" "$D37PID" "x forgets" "$OUT.whstop"
6425 D37PID="" 6530 D37PID=""
6426 ok "x forgets a tile and leaves its session running; a refusal records nothing" 6531 ok "x forgets a tile and leaves its session running; a refusal records nothing"
6427 6532
6533 # ---- `mux TARGET` IS a wall, entered zoomed ------------------------------
6534 #
6535 # The convergence's headline (wall-home-screen spec, phase 3): `mux` dials
6536 # and attaches exactly as it always did, and what it runs afterwards is the
6537 # WALL, seeded with that one tile and zoomed into it. So `Ctrl-\ w` does not
6538 # spawn anything — it demotes, and what appears underneath is THE wall: the
6539 # tile this run is standing on plus the ones the saved file already holds.
6540 #
6541 # Four claims, and each needs a different kind of witness:
6542 #
6543 # * the wall the unzoom shows is the SAVED one. A tile written by an
6544 # EARLIER `mux` (the `#side` attach below, into this leg's own state
6545 # home) paints a stripe here, in a process that was never told about
6546 # it on its command line. A wall that only ever showed this run's own
6547 # tile would satisfy every other assertion in this leg.
6548 # * going back in is `Enter`, and it costs NOTHING at the daemon. The
6549 # attach counter is sampled across the return and must not move: the
6550 # replica was hot the whole time the tile was a stripe, so the zoom is
6551 # a local repaint. This is the phase-1 claim reached through the phase-3
6552 # door, which is the only part that is new.
6553 # * `q` from the wall leaves MUX. It used to leave a child wall and hand
6554 # the session back; there is no child now, and `q` is the way out.
6555 # * ...and the attach that opened all this earned exactly one line in the
6556 # file, spelled `#0`, beside the `#side` that was already there.
6557 #
6558 # A state home of its own, for the M4 reason: the wall an unzoom shows is
6559 # the saved one, and the shared $XDG_STATE_HOME is every socket the suite
6560 # has ever attached to.
6561 "$MUXD" run --sock "$SOCK45" --shell /bin/sh > "$OUT.cv.d" 2>&1 &
6562 D45PID=$!
6563 wait_sock "$SOCK45" "$OUT.cv.d" "convergence daemon never bound"
6564
6565 # The tile that is already on the wall before this leg's client starts. A
6566 # piped `mux`, so its attach writes the line and then goes: what the leg
6567 # asserts on is the FILE it left behind, not a process still holding it.
6568 { printf 'printf "cvside-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \
6569 XDG_STATE_HOME="$CVSTATE" timeout 40 "$MUX" --sock "$SOCK45" --session side \
6570 > "$OUT.cvside" 2> "$OUT.cvside.err"
6571 wait_grid "$SOCK45" "cvside-pin" "convergence: the saved tile's marker" side
6572 grep -qx -- "--sock $SOCK45#side" "$CVWALL" || {
6573 echo "e2e FAIL: convergence: the setup attach wrote no tile to unzoom onto:"
6574 cat "$CVWALL" 2>&1; exit 1; }
6575
6576 # The whole run's budget, and every one of the three is named: the entry
6577 # tile's attach, the saved tile's when the unzoom hydrates it, and the
6578 # `muxa send` below, which is an attach like any other. Enter is the step
6579 # that must add nothing, and a cumulative counter is what says so — a gauge
6580 # would let a dial that opened and closed between two samples hide inside
6581 # it.
6582 CVATT_BEFORE=$(attaches_now "$SOCK45")
6583 ( sleep 6; "$MUXA" send 'printf "cvret-%s\n" pin\n' \
6584 --sock "$SOCK45" --session 0 > "$OUT.cvinj" 2>&1 ) &
6585 CVINJPID=$!
6586 set +e
6587 XDG_STATE_HOME="$CVSTATE" timeout 90 "$PTYCLIENT" --cols 100 --rows 30 \
6588 --out "$OUT.cvcap" --err "$OUT.cvcap.err" \
6589 -- "$MUX" --sock "$SOCK45" > "$OUT.cvpc" 2>&1 <<'EOF'
6590 expect \x1b[?1049h 15000
6591 settle 400 15000
6592 send printf 'cvhome-%s\\n' pin\n
6593 expect cvhome-pin 15000
6594 settle 400 15000
6595 send \x1cw
6596 expect cvside-pin 20000
6597 settle 900 20000
6598 send \r
6599 expect cvret-pin 20000
6600 settle 500 15000
6601 send \x1cw
6602 settle 800 20000
6603 send q
6604 waitexit 10000
6605 EOF
6606 RC=$?
6607 set -e
6608 wait "$CVINJPID" 2>/dev/null || true
6609 [ "$RC" -eq 0 ] || {
6610 echo "e2e FAIL: convergence: ptyclient leg exited $RC (did \\x1cw unzoom, did q leave?):"
6611 cat "$OUT.cvpc"; cat "$OUT.cvinj" 2>/dev/null; exit 1; }
6612 # The saved tile really painted a STRIPE, which only a wall draws — a bar
6613 # with a spelling and a state word in it. A bare marker grep would be
6614 # satisfied by the setup attach's own capture bleeding through.
6615 grep -q -- "--sock $SOCK45#side \[up\]" "$OUT.cvcap" || {
6616 echo "e2e FAIL: convergence: the saved wall's tile never became a stripe:"
6617 cat "$OUT.cvpc"; exit 1; }
6618 grep -q -- "--sock $SOCK45#0 \[up\]" "$OUT.cvcap" || {
6619 echo "e2e FAIL: convergence: the attached tile never became a stripe:"
6620 cat "$OUT.cvpc"; exit 1; }
6621 # The return was FREE. `cvret-pin` is injected into session 0 while the
6622 # wall is up and only reaches this terminal full-screen after Enter, so its
6623 # arrival is the return itself — and the counter says the daemon heard
6624 # nothing about it. Three attaches for the whole run, all three accounted
6625 # for: the entry tile, the hydrated tile, and the injector. An Enter that
6626 # re-dialled would make it four.
6627 CVATT=$(attaches_now "$SOCK45")
6628 assert_attach_delta "$CVATT_BEFORE" "$CVATT" 3 "convergence"
6629 # The file: one line for the attach this run made, beside the one it found.
6630 _cv_n=$(wc -l < "$CVWALL")
6631 [ "$_cv_n" -eq 2 ] || {
6632 echo "e2e FAIL: convergence: the wall file holds $_cv_n lines, want 2:"
6633 cat "$CVWALL"; exit 1; }
6634 grep -qx -- "--sock $SOCK45#0" "$CVWALL" || {
6635 echo "e2e FAIL: convergence: the attach earned no tile of its own:"
6636 cat "$CVWALL"; exit 1; }
6637 assert_stopped "$SOCK45" "$D45PID" "convergence" "$OUT.cvstop"
6638 D45PID=""
6639 ok "mux is the wall: Ctrl-\\ w unzooms onto the saved tiles, Enter returns free, q leaves"
6640
6641 # ---- the ring GROWS the wall, and only when it has to --------------------
6642 #
6643 # The spec's `n`/`p` rule, which phase 3c is where it lands: those chords
6644 # move the zoom around the DAEMON'S session ring, not around the wall's
6645 # tiles. A sibling that already has a tile is an instant zoom move; one
6646 # that does not GETS one, because visiting it is an attach and attach adds.
6647 #
6648 # The leg is built so the two halves are the SAME chord pressed twice, and
6649 # the difference between them is the whole assertion:
6650 #
6651 # * `\x1cn` from session 0 lands on `two`, which this run has never seen
6652 # — its tile was written into a DIFFERENT state home by the setup
6653 # client, so the daemon knows the session and this wall does not. One
6654 # dial, one attach, one new line in the file, and the marker typed
6655 # afterwards lands in `two`'s shell.
6656 # * `\x1cn` again wraps back to 0, which now HAS a tile. Nothing is
6657 # dialled: the counter must not move across that step, and the marker
6658 # lands back in session 0.
6659 #
6660 # The attach budget for the run is therefore exactly 2 (the entry tile and
6661 # the tile the first `n` grew), and the second `n` is the one that must be
6662 # free. Sampled around the whole ptyclient run rather than around each
6663 # step, because a step that dialled and closed between two samples would
6664 # hide inside a gauge — the counter is cumulative and cannot.
6665 "$MUXD" run --sock "$SOCK46" --shell /bin/sh > "$OUT.rg.d" 2>&1 &
6666 D46PID=$!
6667 wait_sock "$SOCK46" "$OUT.rg.d" "ring-grow daemon never bound"
6668
6669 # `two` exists on the DAEMON but not in this leg's wall: a separate state
6670 # home is what makes the setup client's own tile invisible here, so the
6671 # first `n` meets a sibling with no tile — which is the case under test.
6672 { printf 'printf "rgtwo-%%s\\n" pin\n'; sleep 2; printf '\034\034'; } | \
6673 XDG_STATE_HOME="$RGOTHER" timeout 40 "$MUX" --sock "$SOCK46" --session two \
6674 > "$OUT.rgtwo" 2> "$OUT.rgtwo.err"
6675 wait_grid "$SOCK46" "rgtwo-pin" "ring grow: the untiled sibling's marker" two
6676 grep -qx -- "--sock $SOCK46#two" "$RGSTATE/mux/wall" 2>/dev/null && {
6677 echo "e2e FAIL: ring grow: the sibling already had a tile, so the ADD is vacuous:"
6678 cat "$RGSTATE/mux/wall"; exit 1; }
6679
6680 RGATT_BEFORE=$(attaches_now "$SOCK46")
6681 set +e
6682 XDG_STATE_HOME="$RGSTATE" timeout 90 "$PTYCLIENT" --cols 100 --rows 30 \
6683 --out "$OUT.rgcap" --err "$OUT.rgcap.err" \
6684 -- "$MUX" --sock "$SOCK46" > "$OUT.rgpc" 2>&1 <<'EOF'
6685 expect \x1b[?1049h 15000
6686 settle 400 15000
6687 send printf 'rgzero-%s\\n' pin\n
6688 expect rgzero-pin 15000
6689 settle 400 15000
6690 send \x1cn
6691 expect rgtwo-pin 20000
6692 settle 500 15000
6693 send printf 'rghere-%s\\n' pin\n
6694 expect rghere-pin 15000
6695 settle 400 15000
6696 send \x1cn
6697 expect rgzero-pin 20000
6698 settle 500 15000
6699 send printf 'rgback-%s\\n' pin\n
6700 expect rgback-pin 15000
6701 settle 400 15000
6702 send \x1c\x1c
6703 waitexit 10000
6704 EOF
6705 RC=$?
6706 set -e
6707 RGATT=$(attaches_now "$SOCK46")
6708 [ "$RC" -eq 0 ] || {
6709 echo "e2e FAIL: ring grow: ptyclient leg exited $RC (did \\x1cn step the ring?):"
6710 cat "$OUT.rgpc"; exit 1; }
6711 # TWO attaches for the whole run: the entry tile's, and the one the first
6712 # `n` grew. A `n` that re-dialled the tile it already had would make it
6713 # three, whichever way it closed the old one first.
6714 assert_attach_delta "$RGATT_BEFORE" "$RGATT" 2 "ring grow"
6715 # The wall grew by the truth: the sibling the ring visited earned a line.
6716 grep -qx -- "--sock $SOCK46#two" "$RGSTATE/mux/wall" || {
6717 echo "e2e FAIL: ring grow: the visited sibling earned no tile:"
6718 cat "$RGSTATE/mux/wall" 2>&1; exit 1; }
6719 _rg_n=$(wc -l < "$RGSTATE/mux/wall")
6720 [ "$_rg_n" -eq 2 ] || {
6721 echo "e2e FAIL: ring grow: the wall file holds $_rg_n lines, want 2:"
6722 cat "$RGSTATE/mux/wall"; exit 1; }
6723 # Where the markers landed says the zoom really MOVED rather than the wall
6724 # having gone on typing at one connection. Each session holds its own and
6725 # not its neighbour's.
6726 timeout 20 "$MUXA" capture --sock "$SOCK46" --session two > "$OUT.rgcapt" 2>&1
6727 grep -q "rghere-pin" "$OUT.rgcapt" || {
6728 echo "e2e FAIL: ring grow: the marker typed after the first n missed session two:"
6729 cat "$OUT.rgcapt"; exit 1; }
6730 grep -q "rgback-pin" "$OUT.rgcapt" && {
6731 echo "e2e FAIL: ring grow: the marker typed after the wrap landed in two:"
6732 cat "$OUT.rgcapt"; exit 1; }
6733 timeout 20 "$MUXA" capture --sock "$SOCK46" --session 0 > "$OUT.rgcap0" 2>&1
6734 grep -q "rgback-pin" "$OUT.rgcap0" || {
6735 echo "e2e FAIL: ring grow: the wrap did not come back to session 0:"
6736 cat "$OUT.rgcap0"; exit 1; }
6737 assert_stopped "$SOCK46" "$D46PID" "ring grow" "$OUT.rgstop"
6738 D46PID=""
6739 ok "Ctrl-\\ n grows the wall for a sibling with no tile, and is free for one that has"
6740
6741 # ---- a session that ends under the zoom ---------------------------------
6742 #
6743 # The exit rule the convergence had to choose, stated in decisions.md and
6744 # pinned here: a session that ends under the zoom ENDS MUX when it was the
6745 # wall's only tile, and DROPS TO THE WALL when it was not.
6746 #
6747 # Both halves in one block, because either alone is satisfied by a bug:
6748 #
6749 # * one tile. `exit 7` in a piped `mux` — the shape every script in the
6750 # world uses — and the code comes back 7. A wall that always dropped to
6751 # itself would hang here forever, which is why the timeout's 124 is a
6752 # failure this leg names.
6753 # * two tiles. `\x1cc` adds one and zooms it, the shell in it exits, and
6754 # mux is still running, on the wall, with the dead tile narrating
6755 # `[exited]` beside the live one. Then `q` leaves — exit 0, not 7 and
6756 # not the shell's anything, because leaving the wall is the user's act
6757 # and not the session's.
6758 "$MUXD" run --sock "$SOCK47" --shell /bin/sh > "$OUT.xe.d" 2>&1 &
6759 D47PID=$!
6760 wait_sock "$SOCK47" "$OUT.xe.d" "exit-semantics daemon never bound"
6761
6762 set +e
6763 { printf 'exit 7\n'; sleep 3; } | XDG_STATE_HOME="$XESTATE" timeout 40 \
6764 "$MUX" --sock "$SOCK47" > "$OUT.xe1" 2> "$OUT.xe1.err"
6765 RC=$?
6766 set -e
6767 [ "$RC" -eq 7 ] || {
6768 echo "e2e FAIL: exit semantics: the only tile's shell exited 7 and mux exited $RC"
6769 echo " (124 means it dropped to a wall nobody could leave):"
6770 cat "$OUT.xe1.err"; exit 1; }
6771
6772 set +e
6773 XDG_STATE_HOME="$XESTATE" timeout 90 "$PTYCLIENT" --cols 100 --rows 30 \
6774 --out "$OUT.xecap" --err "$OUT.xecap.err" \
6775 -- "$MUX" --sock "$SOCK47" > "$OUT.xepc" 2>&1 <<'EOF'
6776 expect \x1b[?1049h 15000
6777 settle 400 15000
6778 send printf 'xehome-%s\\n' pin\n
6779 expect xehome-pin 15000
6780 settle 400 15000
6781 send \x1cc
6782 expect \x1b[2J 15000
6783 settle 600 15000
6784 send exit 3\n
6785 expect xehome-pin 20000
6786 settle 800 20000
6787 send q
6788 waitexit 10000
6789 EOF
6790 RC=$?
6791 set -e
6792 [ "$RC" -eq 0 ] || {
6793 echo "e2e FAIL: exit semantics: the multi-tile leg exited $RC (did the zoom"
6794 echo " drop to the wall, and did q leave it?):"
6795 cat "$OUT.xepc"; exit 1; }
6796 # The drop landed on a WALL — a bar per tile — and the dead one says what
6797 # became of it. `[exited]` is the word phase 1 gave a pump whose session
6798 # ended, and it can only be on this terminal if the keyboard painted for a
6799 # tile with no pump left.
6800 grep -q -- "--sock $SOCK47#1 \[exited\]" "$OUT.xecap" || {
6801 echo "e2e FAIL: exit semantics: the dead tile never narrated its exit:"
6802 cat "$OUT.xepc"; exit 1; }
6803 grep -q -- "--sock $SOCK47#0 \[up\]" "$OUT.xecap" || {
6804 echo "e2e FAIL: exit semantics: the surviving tile never got its stripe back:"
6805 cat "$OUT.xepc"; exit 1; }
6806 # The session that did NOT end is still there: a shell exiting under the
6807 # zoom ends that session and nothing else.
6808 wait_grid "$SOCK47" "xehome-pin" "exit semantics: session 0 outlived its neighbour"
6809 assert_stopped "$SOCK47" "$D47PID" "exit semantics" "$OUT.xestop"
6810 D47PID=""
6811 ok "a session ending under the zoom exits mux alone, and drops to the wall beside company"
6812
6428 6813
6429 6814
6430 # The long-lived daemon has served every scenario that wanted it; stop it 6815 # The long-lived daemon has served every scenario that wanted it; stop it
@@ -6515,9 +6900,14 @@ DPID=""
6515 # grid records what was written down about it. The 48th is `x`, and no 6900 # grid records what was written down about it. The 48th is `x`, and no
6516 # convergence point either: what it asserts on is that file shrinking by 6901 # convergence point either: what it asserts on is that file shrinking by
6517 # one line while the session it named goes on answering, which is two 6902 # one line while the session it named goes on answering, which is two
6518 # facts a grid comparison speaks to neither of. 6903 # facts a grid comparison speaks to neither of. The 49th, 50th and 51st are
6519 [ "$OK_COUNT" = "52" ] || { 6904 # the convergence block (phase 3c), and no convergence point between the
6520 echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 52 —" 6905 # three of them: what they assert on is a ptyclient capture, a cumulative
6906 # attach COUNTER, the wall file and an exit CODE — a screen, a number, a
6907 # file and a status, and the last of them names a session that no longer
6908 # exists to dump.
6909 [ "$OK_COUNT" = "55" ] || {
6910 echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 55 —"
6521 echo " a scenario was added (update the pin) or silently lost" 6911 echo " a scenario was added (update the pin) or silently lost"
6522 exit 1 6912 exit 1
6523 } 6913 }
@@ -6525,4 +6915,4 @@ DPID=""
6525 echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 35" 6915 echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 35"
6526 exit 1 6916 exit 1
6527 } 6917 }
6528 echo "e2e OK (52 scenarios, 35 convergence points)" 6918 echo "e2e OK (55 scenarios, 35 convergence points)"