9e225de2
test: the convergence, and the legs it deliberately changed
a73x 2026-08-20 07:53
Commit message
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)" |