4bcb5c2a
test: the M18 wall leg is one daemon whose sessions are its tiles
a73x 2026-08-29 12:14
Commit message
test/e2e_05_session.sh
| Old | New | ||
|---|---|---|---|
| @@ -206,22 +206,19 @@ D18PID="" | |||
| 206 | ok "two sessions on one daemon are two shells; one dies without the other" | 206 | ok "two sessions on one daemon are two shells; one dies without the other" |
| 207 | ok "a daemon outlives its last session and is born into again" | 207 | ok "a daemon outlives its last session and is born into again" |
| 208 | 208 | ||
| 209 | # --- M18 on the wall: one socket, two tiles, two sessions --------------- | 209 | # --- M18 on the wall: one daemon, two sessions, two tiles --------------- |
| 210 | # What the milestone was FOR (decision 2): the wall showing the same host | 210 | # What the milestone was FOR (decision 2): the wall showing the same host |
| 211 | # twice. Both tiles name the same socket path and differ only in the | 211 | # twice. ONE host line names the daemon and its two sessions are two |
| 212 | # `#NAME` suffix, so the hub dials it twice and the two connections are | 212 | # tiles, so the hub dials it twice and the two connections are two |
| 213 | # two sessions — the browser's spelling of everything the block above | 213 | # sessions — the browser's spelling of everything the block above proved |
| 214 | # proved with a CLI. | 214 | # with a CLI. |
| 215 | start_daemon "$SOCK22" "$OUT.m18w.d" "M18 wall daemon never bound" --shell /bin/sh | 215 | start_daemon "$SOCK22" "$OUT.m18w.d" "M18 wall daemon never bound" --shell /bin/sh |
| 216 | D19PID=$DPID | 216 | D19PID=$DPID |
| 217 | "$MUX" web --sock "$SOCK22#a" --sock "$SOCK22#b" --port "$WPORT3" > "$OUT.m18wh" 2>&1 & | ||
| 218 | W3PID=$! | ||
| 219 | defer_kill "$W3PID" | ||
| 220 | wait_for "$OUT.m18wh" "serving" 10 || { | ||
| 221 | echo "e2e FAIL: M18 wall hub never reported serving"; cat "$OUT.m18wh"; exit 1; } | ||
| 222 | 217 | ||
| 223 | # Content for each session, planted by a CLI client and left behind: the | 218 | # The sessions FIRST: a tile is a live session on a listed daemon, so |
| 224 | # tiles are passive 0x0 wall tiles and can never type anything themselves. | 219 | # there is nothing for the hub to show until these exist. Their content is |
| 220 | # planted here too and left behind — the tiles are passive 0x0 wall tiles | ||
| 221 | # and can never type anything themselves. | ||
| 225 | pipe_mux "$OUT.m18wa" "$OUT.m18wa.err" timeout 40 "$MUX" --sock "$SOCK22" --session a | 222 | pipe_mux "$OUT.m18wa" "$OUT.m18wa.err" timeout 40 "$MUX" --sock "$SOCK22" --session a |
| 226 | pipe_send 'printf "wall-a-%%s\\n" pin\n' | 223 | pipe_send 'printf "wall-a-%%s\\n" pin\n' |
| 227 | await_out "$OUT.m18wa" "wall-a-pin" "M18 wall: session a's marker never reached the client" | 224 | await_out "$OUT.m18wa" "wall-a-pin" "M18 wall: session a's marker never reached the client" |
| @@ -233,18 +230,44 @@ await_out "$OUT.m18wb" "wall-b-pin" "M18 wall: session b's marker never reached | |||
| 233 | pipe_detach | 230 | pipe_detach |
| 234 | wait_grid "$SOCK22" "wall-b-pin" "M18 wall: session b's marker" b | 231 | wait_grid "$SOCK22" "wall-b-pin" "M18 wall: session b's marker" b |
| 235 | 232 | ||
| 236 | # Tile 0 is session a's; tile 1 is session b's. The stand-in spells the | 233 | # Its own state home: `mux web --sock S` RECORDS S in the hosts file it |
| 237 | # name the way mux.js does — bytes appended after the fixed 20 — and | 234 | # then serves, and every other leg's daemons are in the shared one. |
| 238 | # attaches at 0x0, the passivity contract: both sessions already exist, so | 235 | hostroom m18wall |
| 239 | # these are joins, and a slot that claimed no size can never move the grid. | 236 | XDG_STATE_HOME="$HOSTROOM" "$MUX" web --sock "$SOCK22" --port "$WPORT3" > "$OUT.m18wh" 2>&1 & |
| 240 | for _t in 0 1; do | 237 | W3PID=$! |
| 241 | case "$_t" in | 238 | defer_kill "$W3PID" |
| 242 | 0) _sn=a; _mine=wall-a-pin; _theirs=wall-b-pin ;; | 239 | M18ORIG="http://127.0.0.1:$WPORT3" |
| 243 | *) _sn=b; _mine=wall-b-pin; _theirs=wall-a-pin ;; | 240 | # Three tiles: the daemon's own default session and the two above. `tile 2:` |
| 241 | # is the last of them, so waiting on it is waiting for the whole list. | ||
| 242 | wait_for "$OUT.m18wh" "tile 2:" 15 || { | ||
| 243 | echo "e2e FAIL: M18 wall hub never announced its tiles"; cat "$OUT.m18wh"; exit 1; } | ||
| 244 | |||
| 245 | # ONE label, three sessions beside it — which is the milestone's whole | ||
| 246 | # claim, read off the page's own answer. | ||
| 247 | M18WANT="[{\"label\":\"--sock $SOCK22\",\"session\":\"0\"}," | ||
| 248 | M18WANT="$M18WANT{\"label\":\"--sock $SOCK22\",\"session\":\"a\"}," | ||
| 249 | M18WANT="$M18WANT{\"label\":\"--sock $SOCK22\",\"session\":\"b\"}]" | ||
| 250 | [ "$(tiles_shape "$M18ORIG")" = "$M18WANT" ] || { | ||
| 251 | echo "e2e FAIL: M18 wall: one host's sessions are not its tiles" | ||
| 252 | echo " got: $(tiles_shape "$M18ORIG")" | ||
| 253 | echo " want: $M18WANT"; exit 1; } | ||
| 254 | |||
| 255 | # The stand-in spells the name the way mux.js does — bytes appended after | ||
| 256 | # the fixed 20 — and attaches at 0x0, the passivity contract: both | ||
| 257 | # sessions already exist, so these are joins, and a slot that claimed no | ||
| 258 | # size can never move the grid. | ||
| 259 | for _sn in a b; do | ||
| 260 | case "$_sn" in | ||
| 261 | a) _mine=wall-a-pin; _theirs=wall-b-pin ;; | ||
| 262 | *) _mine=wall-b-pin; _theirs=wall-a-pin ;; | ||
| 244 | esac | 263 | esac |
| 264 | _t=$(tile_id_of "$M18ORIG" "--sock $SOCK22" "$_sn") | ||
| 265 | [ -n "$_t" ] || { | ||
| 266 | echo "e2e FAIL: M18 wall: session $_sn has no tile; the wall holds:" | ||
| 267 | curl -s "$M18ORIG/tiles"; exit 1; } | ||
| 245 | set +e | 268 | set +e |
| 246 | timeout 40 "$WSCLIENT" --port "$WPORT3" --tile "$_t" \ | 269 | timeout 40 "$WSCLIENT" --port "$WPORT3" --tile "$_t" \ |
| 247 | --out "$OUT.m18ws$_t" --err "$OUT.m18ws$_t.err" <<EOF | 270 | --out "$OUT.m18ws$_sn" --err "$OUT.m18ws$_sn.err" <<EOF |
| 248 | attach 0 0 $_sn | 271 | attach 0 0 $_sn |
| 249 | expectstate up 10000 | 272 | expectstate up 10000 |
| 250 | expectgrid $_mine 15000 | 273 | expectgrid $_mine 15000 |
| @@ -255,17 +278,17 @@ EOF | |||
| 255 | set -e | 278 | set -e |
| 256 | [ "$RC" -eq 0 ] || { | 279 | [ "$RC" -eq 0 ] || { |
| 257 | echo "e2e FAIL: M18 wall: tile $_t (session $_sn) wsclient exited $RC" | 280 | echo "e2e FAIL: M18 wall: tile $_t (session $_sn) wsclient exited $RC" |
| 258 | cat -v "$OUT.m18ws$_t.err" 2>/dev/null; cat "$OUT.m18wh"; exit 1; } | 281 | cat -v "$OUT.m18ws$_sn.err" 2>/dev/null; cat "$OUT.m18wh"; exit 1; } |
| 259 | 282 | ||
| 260 | # The replica converges on ITS session's grid... | 283 | # The replica converges on ITS session's grid... |
| 261 | assert_ws_converged "$OUT.m18ws$_t" "$SOCK22" "M18 wall: tile $_t" "$_sn" | 284 | assert_ws_converged "$OUT.m18ws$_sn" "$SOCK22" "M18 wall: tile $_t" "$_sn" |
| 262 | # ...and the other session's content never crossed into it. The | 285 | # ...and the other session's content never crossed into it. The |
| 263 | # convergence check above is a diff against one grid and would pass on | 286 | # convergence check above is a diff against one grid and would pass on |
| 264 | # a hub that fanned every session's frames to every tile only if that | 287 | # a hub that fanned every session's frames to every tile only if that |
| 265 | # grid happened to match; this grep is the direct question. | 288 | # grid happened to match; this grep is the direct question. |
| 266 | if grep -q "$_theirs" "$OUT.m18ws$_t"; then | 289 | if grep -q "$_theirs" "$OUT.m18ws$_sn"; then |
| 267 | echo "e2e FAIL: M18 wall: tile $_t (session $_sn) received the other session's content:" | 290 | echo "e2e FAIL: M18 wall: tile $_t (session $_sn) received the other session's content:" |
| 268 | cat "$OUT.m18ws$_t"; exit 1 | 291 | cat "$OUT.m18ws$_sn"; exit 1 |
| 269 | fi | 292 | fi |
| 270 | done | 293 | done |
| 271 | 294 | ||
| @@ -274,7 +297,7 @@ wait_pid_gone "$W3PID" "M18 wall: hub killed by tracked pid" | |||
| 274 | W3PID="" | 297 | W3PID="" |
| 275 | assert_stopped "$SOCK22" "$D19PID" "M18 wall" "$OUT.m18wstop" | 298 | assert_stopped "$SOCK22" "$D19PID" "M18 wall" "$OUT.m18wstop" |
| 276 | D19PID="" | 299 | D19PID="" |
| 277 | ok "the wall shows one host twice: two tiles, two sessions, one socket" | 300 | ok "the wall shows one daemon's sessions as its tiles: three tiles, one socket, no crosstalk" |
| 278 | 301 | ||
| 279 | # --- M18 over QUIC: two dials are two sessions, and a name means one ----- | 302 | # --- M18 over QUIC: two dials are two sessions, and a name means one ----- |
| 280 | # --- session whichever transport carried the attach ---------------------- | 303 | # --- session whichever transport carried the attach ---------------------- |
test/e2e_06_web.sh
| Old | New | ||
|---|---|---|---|
| @@ -20,9 +20,9 @@ defer_sock "$SOCK20" | |||
| 20 | WPORT=$(( 41000 + ($$ % 4000) )) | 20 | WPORT=$(( 41000 + ($$ % 4000) )) |
| 21 | WPORT2=$(( 46000 + ($$ % 4000) )) | 21 | WPORT2=$(( 46000 + ($$ % 4000) )) |
| 22 | # The hosts-wall leg: TWO daemons — a wall of one host is blind to host | 22 | # The hosts-wall leg: TWO daemons — a wall of one host is blind to host |
| 23 | # order and to "only its own list may drop a tile" — a hub restarted once, | 23 | # order and to "only its own list may drop a tile" — one hub, and a STATE |
| 24 | # and a STATE HOME of its own on the 61000 band, the last 5000-spaced one | 24 | # HOME of its own on the 61000 band, the last 5000-spaced one that fits |
| 25 | # that fits under 65535 (the QUIC block's 56000 being the one before). The | 25 | # under 65535 (the QUIC block's 56000 being the one before). The |
| 26 | # state home is the load-bearing part: this leg reads the HOSTS file back | 26 | # state home is the load-bearing part: this leg reads the HOSTS file back |
| 27 | # as an artifact, and $XDG_STATE_HOME above is shared with every other | 27 | # as an artifact, and $XDG_STATE_HOME above is shared with every other |
| 28 | # scenario — every `mux` attach records its daemon there, so a file read | 28 | # scenario — every `mux` attach records its daemon there, so a file read |
| @@ -267,6 +267,33 @@ pipe_send 'printf "host-%%s\\n" mk\n' | |||
| 267 | wait_grid "$SOCK25" "host-mk" "host wall: session b exists" b | 267 | wait_grid "$SOCK25" "host-mk" "host wall: session b exists" b |
| 268 | pipe_detach "host wall: the session-b maker" | 268 | pipe_detach "host wall: the session-b maker" |
| 269 | 269 | ||
| 270 | # An EMPTY file is an empty wall, not a refusal — and specifically not the | ||
| 271 | # local socket: nothing asked for a daemon, and a read never starts one. | ||
| 272 | # Run before the argv hub so the file this leg then builds is built from | ||
| 273 | # nothing, and the one stderr line is asserted where it is the only line. | ||
| 274 | mkdir -p "$DWSTATE/mux" | ||
| 275 | : > "$DWSTATE/mux/hosts" | ||
| 276 | XDG_STATE_HOME="$DWSTATE" "$MUX" web --port "$WPORT4" > "$OUT.dwempty" 2>&1 & | ||
| 277 | W5PID=$! | ||
| 278 | defer_kill "$W5PID" | ||
| 279 | wait_for "$OUT.dwempty" "serving" 10 || { | ||
| 280 | echo "e2e FAIL: host wall: the empty-file hub never served"; cat "$OUT.dwempty"; exit 1; } | ||
| 281 | grep -q "no hosts" "$OUT.dwempty" || { | ||
| 282 | echo "e2e FAIL: host wall: an empty wall said nothing about being empty:" | ||
| 283 | cat "$OUT.dwempty"; exit 1; } | ||
| 284 | [ "$(curl -s "http://127.0.0.1:$WPORT4/tiles")" = "[]" ] || { | ||
| 285 | echo "e2e FAIL: host wall: the empty-file hub served tiles:" | ||
| 286 | curl -s "http://127.0.0.1:$WPORT4/tiles"; exit 1; } | ||
| 287 | # ...and it added nothing. A hub that fell back to the local socket would | ||
| 288 | # have written a line here, and every later assertion in this leg would be | ||
| 289 | # reading a file with a daemon in it that nobody asked for. | ||
| 290 | [ ! -s "$DWSTATE/mux/hosts" ] || { | ||
| 291 | echo "e2e FAIL: host wall: the empty-file hub wrote a host line:" | ||
| 292 | cat "$DWSTATE/mux/hosts"; exit 1; } | ||
| 293 | softkill "$W5PID" || true | ||
| 294 | wait_pid_gone "$W5PID" "host wall: empty-file hub killed by tracked pid" | ||
| 295 | W5PID="" | ||
| 296 | |||
| 270 | # Argv is RECORDED, not a view of its own: `mux web "--sock A" "--sock B"` | 297 | # Argv is RECORDED, not a view of its own: `mux web "--sock A" "--sock B"` |
| 271 | # is `mux hosts add` twice and then a hub on the file. | 298 | # is `mux hosts add` twice and then a hub on the file. |
| 272 | XDG_STATE_HOME="$DWSTATE" "$MUX" web "--sock $SOCK25" "--sock $SOCK76" --port "$WPORT4" > "$OUT.dwh" 2>&1 & | 299 | XDG_STATE_HOME="$DWSTATE" "$MUX" web "--sock $SOCK25" "--sock $SOCK76" --port "$WPORT4" > "$OUT.dwh" 2>&1 & |
| @@ -301,15 +328,8 @@ for _t in 0 1 2; do | |||
| 301 | done | 328 | done |
| 302 | # One string, not three greps: which tile sits where is the claim, and | 329 | # One string, not three greps: which tile sits where is the claim, and |
| 303 | # separate greps would pass on a wall holding them in any order at all. | 330 | # separate greps would pass on a wall holding them in any order at all. |
| 304 | dw_shape() { curl -s "$DWORIG/tiles" | sed 's/"id":[0-9]*,//g'; } | 331 | dw_shape() { tiles_shape "$DWORIG"; } |
| 305 | dw_id_of() { # LABEL SESSION -> the hub's id for that tile, or empty | 332 | dw_id_of() { tile_id_of "$DWORIG" "$1" "$2"; } |
| 306 | # One line per tile object, then the id off the line that carries BOTH | ||
| 307 | # this label and this session: a label alone names two tiles whenever a | ||
| 308 | # daemon has two sessions, which is the case this leg is built on. | ||
| 309 | curl -s "$DWORIG/tiles" | tr '{' '\n' | | ||
| 310 | grep -F "\"label\":\"$1\",\"session\":\"$2\"}" | | ||
| 311 | sed -n 's/^"id":\([0-9][0-9]*\),.*/\1/p' | head -1 | ||
| 312 | } | ||
| 313 | DWWANT="[{\"label\":\"--sock $SOCK25\",\"session\":\"0\"}," | 333 | DWWANT="[{\"label\":\"--sock $SOCK25\",\"session\":\"0\"}," |
| 314 | DWWANT="$DWWANT{\"label\":\"--sock $SOCK25\",\"session\":\"b\"}," | 334 | DWWANT="$DWWANT{\"label\":\"--sock $SOCK25\",\"session\":\"b\"}," |
| 315 | DWWANT="$DWWANT{\"label\":\"--sock $SOCK76\",\"session\":\"0\"}]" | 335 | DWWANT="$DWWANT{\"label\":\"--sock $SOCK76\",\"session\":\"0\"}]" |
test/e2e_lib.sh
| Old | New | ||
|---|---|---|---|
| @@ -867,6 +867,21 @@ assert_ws_converged() { | |||
| 867 | fi | 867 | fi |
| 868 | } | 868 | } |
| 869 | 869 | ||
| 870 | # tile_id_of ORIGIN LABEL SESSION — the hub's id for one tile of `GET | ||
| 871 | # /tiles`, or empty. Ids are BIRTH order and one poller thread per host | ||
| 872 | # races the others, so no leg may predict them; the label AND the session | ||
| 873 | # together are what names a tile, because a daemon with two sessions wears | ||
| 874 | # one label twice. | ||
| 875 | tile_id_of() { | ||
| 876 | curl -s "$1/tiles" | tr '{' '\n' | | ||
| 877 | grep -F "\"label\":\"$2\",\"session\":\"$3\"}" | | ||
| 878 | sed -n 's/^"id":\([0-9][0-9]*\),.*/\1/p' | head -1 | ||
| 879 | } | ||
| 880 | |||
| 881 | # tiles_shape ORIGIN — `GET /tiles` with the ids struck out: the ORDER and | ||
| 882 | # the labels are a leg's to assert, the numbering is not. | ||
| 883 | tiles_shape() { curl -s "$1/tiles" | sed 's/"id":[0-9]*,//g'; } | ||
| 884 | |||
| 870 | # Scenario checkpoints. The suite's final line asserts the COUNT of these | 885 | # Scenario checkpoints. The suite's final line asserts the COUNT of these |
| 871 | # against a literal: adding or removing a scenario means updating that | 886 | # against a literal: adding or removing a scenario means updating that |
| 872 | # literal, and the friction is the feature — a scenario that silently | 887 | # literal, and the friction is the feature — a scenario that silently |