66e13fe3
test: one binary through the suites, and the daemon's exe asked of the kernel
a73x 2026-08-28 22:34
Commit message
Makefile
| Old | New | ||
|---|---|---|---|
| @@ -34,8 +34,7 @@ INSTDIR ?= dist/install | |||
| 34 | install: | 34 | install: |
| 35 | $(ZIG) build -Doptimize=ReleaseSafe -p $(INSTDIR) | 35 | $(ZIG) build -Doptimize=ReleaseSafe -p $(INSTDIR) |
| 36 | install -d $(BINDIR) | 36 | install -d $(BINDIR) |
| 37 | install -m755 $(INSTDIR)/bin/muxd $(INSTDIR)/bin/mux \ | 37 | install -m755 $(INSTDIR)/bin/mux $(BINDIR)/ |
| 38 | $(INSTDIR)/bin/muxa $(INSTDIR)/bin/muxweb $(BINDIR)/ | ||
| 39 | 38 | ||
| 40 | # The artifact a release publishes, built the way every release since | 39 | # The artifact a release publishes, built the way every release since |
| 41 | # v0.0.1-4 has been: static musl so one binary runs on any x86_64 Linux, | 40 | # v0.0.1-4 has been: static musl so one binary runs on any x86_64 Linux, |
| @@ -43,34 +42,30 @@ install: | |||
| 43 | # percent and they survive stripping — only DWARF goes, 17MB to ~3MB each. | 42 | # percent and they survive stripping — only DWARF goes, 17MB to ~3MB each. |
| 44 | # | 43 | # |
| 45 | # TARRED, and that is the point of having a target at all. Publishing the | 44 | # TARRED, and that is the point of having a target at all. Publishing the |
| 46 | # four binaries bare loses the exec bit: they download 0644, and the first | 45 | # binary bare loses the exec bit: it downloads 0644, and the first |
| 47 | # thing anyone does on a fresh box is a chmod nothing documents. A tarball | 46 | # thing anyone does on a fresh box is a chmod nothing documents. A tarball |
| 48 | # carries the mode, so `tar xzf - -C ~/.local/bin` over ssh is the whole | 47 | # carries the mode, so `tar xzf - -C ~/.local/bin` over ssh is the whole |
| 49 | # remote install. | 48 | # remote install. |
| 50 | # | 49 | # |
| 51 | # Flat rather than a versioned directory inside, for the same reason: the | 50 | # Flat rather than a versioned directory inside, for the same reason: the |
| 52 | # common move is putting muxd on a box, and one pipe should finish it. | 51 | # common move is putting `mux` on a box, and one pipe should finish it. |
| 53 | VERSION := $(shell sed -n 's/^[[:space:]]*const version = "\(.*\)";/\1/p' build.zig | head -1) | 52 | VERSION := $(shell sed -n 's/^[[:space:]]*const version = "\(.*\)";/\1/p' build.zig | head -1) |
| 54 | RELDIR ?= dist | 53 | RELDIR ?= dist |
| 55 | RELBIN = $(RELDIR)/v$(VERSION) | 54 | RELBIN = $(RELDIR)/v$(VERSION) |
| 56 | RELTAR = $(RELDIR)/mux-v$(VERSION)-x86_64-linux-musl.tar.gz | 55 | RELTAR = $(RELDIR)/mux-v$(VERSION)-x86_64-linux-musl.tar.gz |
| 57 | # The version guard below asks the STRIPPED artifacts what they are, so a | 56 | # The version guard below asks the STRIPPED artifact what it is, so a |
| 58 | # stale stage directory cannot ship under a bumped number. muxa has no | 57 | # stale stage directory cannot ship under a bumped number. |
| 59 | # --version (it is verbs and JSON), so it is built and shipped but not asked. | ||
| 60 | release: | 58 | release: |
| 61 | @test -n "$(VERSION)" || { echo "release: no version found in build.zig"; exit 1; } | 59 | @test -n "$(VERSION)" || { echo "release: no version found in build.zig"; exit 1; } |
| 62 | $(ZIG) build -Dtarget=x86_64-linux-musl -Doptimize=ReleaseSafe -p $(RELDIR)/stage | 60 | $(ZIG) build -Dtarget=x86_64-linux-musl -Doptimize=ReleaseSafe -p $(RELDIR)/stage |
| 63 | rm -rf $(RELBIN) $(RELTAR) | 61 | rm -rf $(RELBIN) $(RELTAR) |
| 64 | install -d $(RELBIN) | 62 | install -d $(RELBIN) |
| 65 | install -m755 $(RELDIR)/stage/bin/muxd $(RELDIR)/stage/bin/mux \ | 63 | install -m755 $(RELDIR)/stage/bin/mux $(RELBIN)/ |
| 66 | $(RELDIR)/stage/bin/muxa $(RELDIR)/stage/bin/muxweb $(RELBIN)/ | 64 | strip $(RELBIN)/mux |
| 67 | strip $(RELBIN)/muxd $(RELBIN)/mux $(RELBIN)/muxa $(RELBIN)/muxweb | 65 | @got=$$($(RELBIN)/mux --version | awk '{print $$2}'); \ |
| 68 | @for b in muxd mux muxa muxweb; do \ | ||
| 69 | got=$$($(RELBIN)/$$b --version | awk '{print $$2}'); \ | ||
| 70 | [ "$$got" = "$(VERSION)" ] || { \ | 66 | [ "$$got" = "$(VERSION)" ] || { \ |
| 71 | echo "release: $$b reports $$got, expected $(VERSION)"; exit 1; }; \ | 67 | echo "release: mux reports $$got, expected $(VERSION)"; exit 1; } |
| 72 | done | 68 | tar czf $(RELTAR) -C $(RELBIN) mux |
| 73 | tar czf $(RELTAR) -C $(RELBIN) muxd mux muxa muxweb | ||
| 74 | @echo | 69 | @echo |
| 75 | @sha256sum $(RELTAR) | 70 | @sha256sum $(RELTAR) |
| 76 | @echo "install: tar xzf $(RELTAR) -C ~/.local/bin" | 71 | @echo "install: tar xzf $(RELTAR) -C ~/.local/bin" |
| @@ -105,7 +100,7 @@ bench: | |||
| 105 | # number this target established. | 100 | # number this target established. |
| 106 | # | 101 | # |
| 107 | # The trade is line granularity: an optimised build inlines, so lines merge | 102 | # The trade is line granularity: an optimised build inlines, so lines merge |
| 108 | # into their callers and the denominator shrinks. Measured on muxd: 1639 | 103 | # into their callers and the denominator shrinks. Measured on the daemon: 1639 |
| 109 | # instrumented lines here against 2651 in Debug. | 104 | # instrumented lines here against 2651 in Debug. |
| 110 | # | 105 | # |
| 111 | # Outside `ci` deliberately — it needs an optional system package and is | 106 | # Outside `ci` deliberately — it needs an optional system package and is |
| @@ -113,16 +108,14 @@ bench: | |||
| 113 | COVDIR ?= dist/coverage | 108 | COVDIR ?= dist/coverage |
| 114 | coverage: | 109 | coverage: |
| 115 | $(ZIG) build -Doptimize=ReleaseSafe -p $(COVDIR) | 110 | $(ZIG) build -Doptimize=ReleaseSafe -p $(COVDIR) |
| 116 | ./test/coverage.sh $(COVDIR)/bin/muxd $(COVDIR)/bin/mux \ | 111 | ./test/coverage.sh $(COVDIR)/bin/mux \ |
| 117 | $(COVDIR)/bin/rawmode $(COVDIR)/bin/delaypipe $(COVDIR)/bin/render \ | 112 | $(COVDIR)/bin/rawmode $(COVDIR)/bin/delaypipe $(COVDIR)/bin/render \ |
| 118 | $(COVDIR)/bin/ptyclient $(COVDIR)/bin/wsclient $(COVDIR)/bin/muxweb \ | 113 | $(COVDIR)/bin/ptyclient $(COVDIR)/bin/wsclient |
| 119 | $(COVDIR)/bin/muxa | ||
| 120 | 114 | ||
| 121 | THRUDIR ?= dist/throughput | 115 | THRUDIR ?= dist/throughput |
| 122 | throughput: | 116 | throughput: |
| 123 | $(ZIG) build -Doptimize=ReleaseSafe -p $(THRUDIR) | 117 | $(ZIG) build -Doptimize=ReleaseSafe -p $(THRUDIR) |
| 124 | ./test/throughput.sh $(THRUDIR)/bin/muxd $(THRUDIR)/bin/mux \ | 118 | ./test/throughput.sh $(THRUDIR)/bin/mux $(THRUDIR)/bin/ptyclient |
| 125 | $(THRUDIR)/bin/muxa $(THRUDIR)/bin/ptyclient | ||
| 126 | 119 | ||
| 127 | check: | 120 | check: |
| 128 | $(ZIG) build check | 121 | $(ZIG) build check |
test/agent.sh
| Old | New | ||
|---|---|---|---|
| @@ -1,6 +1,6 @@ | |||
| 1 | #!/bin/sh | 1 | #!/bin/sh |
| 2 | # End-to-end for the agent surface: muxd daemon + muxa client, binary level. | 2 | # End-to-end for the agent surface: `mux d` and `mux a`, binary level. |
| 3 | # Every verb muxa has prints one JSON object, so every assertion here is made | 3 | # Every verb mux a has prints one JSON object, so every assertion here is made |
| 4 | # against a PARSED object rather than a grep of the line — a field that got | 4 | # against a PARSED object rather than a grep of the line — a field that got |
| 5 | # renamed, or a number that became a string, is a failure this suite can see. | 5 | # renamed, or a number that became a string, is a failure this suite can see. |
| 6 | # | 6 | # |
| @@ -10,19 +10,17 @@ | |||
| 10 | # report two, not the first one and a silence. | 10 | # report two, not the first one and a silence. |
| 11 | set -u | 11 | set -u |
| 12 | 12 | ||
| 13 | # Binaries: the two under test, defaulting to the build output next to this | 13 | # The binary under test, defaulting to the build output next to this |
| 14 | # script's repo. Positional overrides keep e2e.sh's convention for a caller | 14 | # script's repo. A positional override keeps e2e.sh's convention for a caller |
| 15 | # (build.zig, a packaging check) that wants to name them explicitly. | 15 | # (build.zig, a packaging check) that wants to name it explicitly. |
| 16 | ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) | 16 | ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) |
| 17 | MUXD="${1:-$ROOT/zig-out/bin/muxd}" | 17 | MUX="${1:-$ROOT/zig-out/bin/mux}" |
| 18 | MUXA="${2:-$ROOT/zig-out/bin/muxa}" | 18 | [ -x "$MUX" ] || { echo "agent FAIL: no mux at $MUX (run: zig build)"; exit 1; } |
| 19 | [ -x "$MUXD" ] || { echo "agent FAIL: no muxd at $MUXD (run: zig build)"; exit 1; } | ||
| 20 | [ -x "$MUXA" ] || { echo "agent FAIL: no muxa at $MUXA (run: zig build)"; exit 1; } | ||
| 21 | 19 | ||
| 22 | # The two tools every scenario needs, checked here rather than per-scenario | 20 | # The two tools every scenario needs, checked here rather than per-scenario |
| 23 | # because a box without them cannot run ANY of this: python3 parses every | 21 | # because a box without them cannot run ANY of this: python3 parses every |
| 24 | # assertion (see jget) and builds the relay, and `timeout` is what makes a hung | 22 | # assertion (see jget) and builds the relay, and `timeout` is what makes a hung |
| 25 | # muxa a failure instead of a wedged suite. Missing either is a refusal to run, | 23 | # mux a a failure instead of a wedged suite. Missing either is a refusal to run, |
| 26 | # not a skip — nine SKIP lines and exit 0 would be this suite reporting success | 24 | # not a skip — nine SKIP lines and exit 0 would be this suite reporting success |
| 27 | # for work it did not do. | 25 | # for work it did not do. |
| 28 | for _tool in python3 timeout; do | 26 | for _tool in python3 timeout; do |
| @@ -46,7 +44,7 @@ XDG_CACHE_HOME="$TMP/cache" | |||
| 46 | export XDG_CONFIG_HOME XDG_STATE_HOME XDG_CACHE_HOME | 44 | export XDG_CONFIG_HOME XDG_STATE_HOME XDG_CACHE_HOME |
| 47 | SHELL=/bin/sh | 45 | SHELL=/bin/sh |
| 48 | export SHELL | 46 | export SHELL |
| 49 | # muxa reads MUX_KEY_FILE BEFORE the default key path, so an operator who | 47 | # mux a reads MUX_KEY_FILE BEFORE the default key path, so an operator who |
| 50 | # happens to have one exported would silently change which key every QUIC | 48 | # happens to have one exported would silently change which key every QUIC |
| 51 | # scenario below presents — and they would still pass, against the wrong key. | 49 | # scenario below presents — and they would still pass, against the wrong key. |
| 52 | unset MUX_KEY_FILE | 50 | unset MUX_KEY_FILE |
| @@ -108,12 +106,12 @@ cleanup() { | |||
| 108 | "$RELAY_PID" "$SINK_PID" "$CLI_PID"; do | 106 | "$RELAY_PID" "$SINK_PID" "$CLI_PID"; do |
| 109 | [ -n "$p" ] && kill "$p" 2>/dev/null | 107 | [ -n "$p" ] && kill "$p" 2>/dev/null |
| 110 | done | 108 | done |
| 111 | # ...and by socket, for the window between `muxd start`'s fork and the | 109 | # ...and by socket, for the window between `mux d start`'s fork and the |
| 112 | # up-line this script reads its pid from. `muxd stop` on a path nobody | 110 | # up-line this script reads its pid from. `mux d stop` on a path nobody |
| 113 | # serves is a no-op. These must precede the rm -rf: unlinking the sockets | 111 | # serves is a no-op. These must precede the rm -rf: unlinking the sockets |
| 114 | # first would leave a live daemon nothing could reach by path. | 112 | # first would leave a live daemon nothing could reach by path. |
| 115 | for s in "$SOCK_MARKS" "$SOCK_TUI" "$SOCK_SETTLE" "$SOCK_TEAR" "$SOCK_QUIET"; do | 113 | for s in "$SOCK_MARKS" "$SOCK_TUI" "$SOCK_SETTLE" "$SOCK_TEAR" "$SOCK_QUIET"; do |
| 116 | [ -S "$s" ] && "$MUXD" stop --sock "$s" >/dev/null 2>&1 | 114 | [ -S "$s" ] && "$MUX" d stop --sock "$s" >/dev/null 2>&1 |
| 117 | done | 115 | done |
| 118 | rm -rf "$TMP" | 116 | rm -rf "$TMP" |
| 119 | return 0 | 117 | return 0 |
| @@ -186,7 +184,7 @@ skip() { SKIPS=$((SKIPS + 1)); echo "agent SKIP: $1"; } | |||
| 186 | # | 184 | # |
| 187 | # It also reaps the scenario's in-flight client, because this is the ONLY path | 185 | # It also reaps the scenario's in-flight client, because this is the ONLY path |
| 188 | # out of a QUIC scenario that skips the `wait` below it. Without this a failing | 186 | # out of a QUIC scenario that skips the `wait` below it. Without this a failing |
| 189 | # tear scenario leaves a muxa still awaiting on the relay, and the scenario | 187 | # tear scenario leaves a mux a still awaiting on the relay, and the scenario |
| 190 | # after it counts that stranger's flows as its own — one failure would read as | 188 | # after it counts that stranger's flows as its own — one failure would read as |
| 191 | # two, and the second one would be fiction. | 189 | # two, and the second one would be fiction. |
| 192 | WHY="" | 190 | WHY="" |
| @@ -213,7 +211,7 @@ run_scenario() { | |||
| 213 | } | 211 | } |
| 214 | 212 | ||
| 215 | # One field out of a JSON object, re-encoded as JSON: `null`, `true`, `0`, and | 213 | # One field out of a JSON object, re-encoded as JSON: `null`, `true`, `0`, and |
| 216 | # `"marks"` WITH its quotes. The quotes are the point. muxa's contract is a | 214 | # `"marks"` WITH its quotes. The quotes are the point. mux a's contract is a |
| 217 | # typed one — exit_code is a number, alt_screen a boolean, mechanism a string — | 215 | # typed one — exit_code is a number, alt_screen a boolean, mechanism a string — |
| 218 | # and a `str(v)` here would print all three the same way, so a daemon that | 216 | # and a `str(v)` here would print all three the same way, so a daemon that |
| 219 | # started spelling exit_code as "0" or alt_screen as "true" would sail past | 217 | # started spelling exit_code as "0" or alt_screen as "true" would sail past |
| @@ -222,7 +220,7 @@ run_scenario() { | |||
| 222 | # | 220 | # |
| 223 | # The three sentinels cannot collide with any of that: a field whose value were | 221 | # The three sentinels cannot collide with any of that: a field whose value were |
| 224 | # literally the text `<missing>` re-encodes to `"<missing>"`, quotes and all. A | 222 | # literally the text `<missing>` re-encodes to `"<missing>"`, quotes and all. A |
| 225 | # muxa that printed a stack trace fails as `<unparseable>`, not as a mismatch. | 223 | # mux a that printed a stack trace fails as `<unparseable>`, not as a mismatch. |
| 226 | jget() { | 224 | jget() { |
| 227 | python3 - "$1" "$2" <<'PY' | 225 | python3 - "$1" "$2" <<'PY' |
| 228 | import json, sys | 226 | import json, sys |
| @@ -263,7 +261,7 @@ wait_for() { | |||
| 263 | # among them (hygiene kit, 6a). One per daemon, by tag; see start_daemon. | 261 | # among them (hygiene kit, 6a). One per daemon, by tag; see start_daemon. |
| 264 | daemon_log() { echo "$TMP/state/$1/mux/muxd.log"; } | 262 | daemon_log() { echo "$TMP/state/$1/mux/muxd.log"; } |
| 265 | 263 | ||
| 266 | # After a clean `muxd stop`, the daemon's whole lifecycle has run and its | 264 | # After a clean `mux d stop`, the daemon's whole lifecycle has run and its |
| 267 | # log carries the allocator's verdict (hygiene kit, 6a). The check runs only | 265 | # log carries the allocator's verdict (hygiene kit, 6a). The check runs only |
| 268 | # after the process is actually gone, or the grep races the exit path it is | 266 | # after the process is actually gone, or the grep races the exit path it is |
| 269 | # asserting about. | 267 | # asserting about. |
| @@ -279,10 +277,10 @@ leakcheck() { | |||
| 279 | # An expired poll is not a clean answer: the verdict is written on the way | 277 | # An expired poll is not a clean answer: the verdict is written on the way |
| 280 | # out, so a daemon still alive here has written nothing and the grep below | 278 | # out, so a daemon still alive here has written nothing and the grep below |
| 281 | # would read a stale log and call it healthy — swallowing both the | 279 | # would read a stale log and call it healthy — swallowing both the |
| 282 | # unreadable verdict AND the `muxd stop` that failed to kill anything. | 280 | # unreadable verdict AND the `mux d stop` that failed to kill anything. |
| 283 | kill -0 "$1" 2>/dev/null && { why "daemon still alive ${_i}x50ms after stop — leak verdict unreadable"; return 1; } | 281 | kill -0 "$1" 2>/dev/null && { why "daemon still alive ${_i}x50ms after stop — leak verdict unreadable"; return 1; } |
| 284 | _dl=$(daemon_log "$2") | 282 | _dl=$(daemon_log "$2") |
| 285 | # A missing log is a broken convention, not a clean daemon: `muxd start` | 283 | # A missing log is a broken convention, not a clean daemon: `mux d start` |
| 286 | # creates this file before it forks, so every daemon that ever existed has | 284 | # creates this file before it forks, so every daemon that ever existed has |
| 287 | # one. Passing on its absence is how this gate would go quietly vacuous. | 285 | # one. Passing on its absence is how this gate would go quietly vacuous. |
| 288 | [ -f "$_dl" ] || { why "no daemon log at $_dl — the leak verdict was never captured"; return 1; } | 286 | [ -f "$_dl" ] || { why "no daemon log at $_dl — the leak verdict was never captured"; return 1; } |
| @@ -294,10 +292,10 @@ leakcheck() { | |||
| 294 | # script guessed from a process name: the suite kills what it started, and a | 292 | # script guessed from a process name: the suite kills what it started, and a |
| 295 | # name match can only ever name a bystander. | 293 | # name match can only ever name a bystander. |
| 296 | # | 294 | # |
| 297 | # $_log catches what `muxd start` ITSELF prints (the up-line, or the refusal). | 295 | # $_log catches what `mux d start` ITSELF prints (the up-line, or the refusal). |
| 298 | # The daemon's own output goes somewhere else entirely: start detaches the | 296 | # The daemon's own output goes somewhere else entirely: start detaches the |
| 299 | # child onto $XDG_STATE_HOME/mux/muxd.log — a path computed from the | 297 | # child onto $XDG_STATE_HOME/mux/muxd.log — a path computed from the |
| 300 | # environment `muxd start` is run with — and TRUNCATES it at every spawn. One | 298 | # environment `mux d start` is run with — and TRUNCATES it at every spawn. One |
| 301 | # state home for the whole suite would therefore leave exactly one daemon's | 299 | # state home for the whole suite would therefore leave exactly one daemon's |
| 302 | # allocator verdict readable, the last one's, with every earlier verdict | 300 | # allocator verdict readable, the last one's, with every earlier verdict |
| 303 | # overwritten by the next scenario's spawn. So each daemon gets a state home | 301 | # overwritten by the next scenario's spawn. So each daemon gets a state home |
| @@ -308,17 +306,17 @@ start_daemon() { | |||
| 308 | shift | 306 | shift |
| 309 | _tag=$(basename "$_log" .log) | 307 | _tag=$(basename "$_log" .log) |
| 310 | mkdir -p "$TMP/state/$_tag/mux" || return 1 | 308 | mkdir -p "$TMP/state/$_tag/mux" || return 1 |
| 311 | XDG_STATE_HOME="$TMP/state/$_tag" "$MUXD" start "$@" >"$_log" 2>&1 | 309 | XDG_STATE_HOME="$TMP/state/$_tag" "$MUX" d start "$@" >"$_log" 2>&1 |
| 312 | sed -n 's/^up .*pid=\([0-9]*\).*/\1/p' "$_log" | head -1 | 310 | sed -n 's/^up .*pid=\([0-9]*\).*/\1/p' "$_log" | head -1 |
| 313 | } | 311 | } |
| 314 | 312 | ||
| 315 | # Poll until muxa can answer on a socket: the session's shell has to have been | 313 | # Poll until mux a can answer on a socket: the session's shell has to have been |
| 316 | # exec'd and the daemon's listener accepted before any assertion means | 314 | # exec'd and the daemon's listener accepted before any assertion means |
| 317 | # anything, and how long that takes is the box's business, not a constant here. | 315 | # anything, and how long that takes is the box's business, not a constant here. |
| 318 | wait_ready() { | 316 | wait_ready() { |
| 319 | _i=0 | 317 | _i=0 |
| 320 | while [ "$_i" -lt 100 ]; do | 318 | while [ "$_i" -lt 100 ]; do |
| 321 | "$MUXA" status --sock "$1" --timeout 1000 >/dev/null 2>&1 && return 0 | 319 | "$MUX" a status --sock "$1" --timeout 1000 >/dev/null 2>&1 && return 0 |
| 322 | sleep 0.05 | 320 | sleep 0.05 |
| 323 | _i=$((_i + 1)) | 321 | _i=$((_i + 1)) |
| 324 | done | 322 | done |
| @@ -358,16 +356,16 @@ start_ready() { | |||
| 358 | # | 356 | # |
| 359 | # The retry is the load-bearing half. free_port proved the number bindable a | 357 | # The retry is the load-bearing half. free_port proved the number bindable a |
| 360 | # moment ago, and between that proof and this bind a concurrent suite or an | 358 | # moment ago, and between that proof and this bind a concurrent suite or an |
| 361 | # outgoing connection can take it — muxd holds no SO_REUSEADDR and refuses to | 359 | # outgoing connection can take it — the daemon holds no SO_REUSEADDR and refuses to |
| 362 | # share a port rather than split its datagrams, so it prints `a daemon is | 360 | # share a port rather than split its datagrams, so it prints `a daemon is |
| 363 | # already listening on udp ...` into its own log and exits, and `muxd start` | 361 | # already listening on udp ...` into its own log and exits, and `mux d start` |
| 364 | # reports no up-line. That refusal is the ONLY failure another port can fix, | 362 | # reports no up-line. That refusal is the ONLY failure another port can fix, |
| 365 | # and it is told apart from the rest by the daemon's own words: anything else | 363 | # and it is told apart from the rest by the daemon's own words: anything else |
| 366 | # stops here and is reported, because a muxd that cannot start is a defect and | 364 | # stops here and is reported, because a daemon that cannot start is a defect and |
| 367 | # marching it through twelve ports would only bury it under a port sweep. | 365 | # marching it through twelve ports would only bury it under a port sweep. |
| 368 | # | 366 | # |
| 369 | # PORTVAR is written back with the port the daemon reported an up-line on, so | 367 | # PORTVAR is written back with the port the daemon reported an up-line on, so |
| 370 | # everything downstream — muxa's --quic, the relay's target — dials what was | 368 | # everything downstream — mux a's --quic, the relay's target — dials what was |
| 371 | # BOUND rather than what this suite first derived. | 369 | # BOUND rather than what this suite first derived. |
| 372 | start_quic() { | 370 | start_quic() { |
| 373 | _qpv="$1" | 371 | _qpv="$1" |
| @@ -417,24 +415,24 @@ scen_marks() { | |||
| 417 | [ -x /bin/bash ] || { WHY="no /bin/bash to inject marks into"; return 2; } | 415 | [ -x /bin/bash ] || { WHY="no /bin/bash to inject marks into"; return 2; } |
| 418 | start_ready D_MARKS "$TMP/marks.log" "$SOCK_MARKS" --sock "$SOCK_MARKS" --shell /bin/bash || return 1 | 416 | start_ready D_MARKS "$TMP/marks.log" "$SOCK_MARKS" --sock "$SOCK_MARKS" --shell /bin/bash || return 1 |
| 419 | 417 | ||
| 420 | timeout 20 "$MUXA" run --sock "$SOCK_MARKS" --timeout 8000 'true' >"$TMP/m1" 2>&1 | 418 | timeout 20 "$MUX" a run --sock "$SOCK_MARKS" --timeout 8000 'true' >"$TMP/m1" 2>&1 |
| 421 | _rc=$? | 419 | _rc=$? |
| 422 | [ "$_rc" -eq 0 ] || why "run 'true' exited $_rc [$(tr -d '\n' < "$TMP/m1")]" || return 1 | 420 | [ "$_rc" -eq 0 ] || why "run 'true' exited $_rc [$(tr -d '\n' < "$TMP/m1")]" || return 1 |
| 423 | want "$TMP/m1" reason '"returned"' || return 1 | 421 | want "$TMP/m1" reason '"returned"' || return 1 |
| 424 | want "$TMP/m1" mechanism '"marks"' || return 1 | 422 | want "$TMP/m1" mechanism '"marks"' || return 1 |
| 425 | want "$TMP/m1" exit_code 0 || return 1 | 423 | want "$TMP/m1" exit_code 0 || return 1 |
| 426 | 424 | ||
| 427 | timeout 20 "$MUXA" run --sock "$SOCK_MARKS" --timeout 8000 'false' >"$TMP/m2" 2>&1 | 425 | timeout 20 "$MUX" a run --sock "$SOCK_MARKS" --timeout 8000 'false' >"$TMP/m2" 2>&1 |
| 428 | want "$TMP/m2" reason '"returned"' || return 1 | 426 | want "$TMP/m2" reason '"returned"' || return 1 |
| 429 | want "$TMP/m2" mechanism '"marks"' || return 1 | 427 | want "$TMP/m2" mechanism '"marks"' || return 1 |
| 430 | # The command failed; muxa did not. A nonzero exit_code is an ANSWER, and | 428 | # The command failed; mux a did not. A nonzero exit_code is an ANSWER, and |
| 431 | # an agent that branches on muxa's own status must not see it as an error. | 429 | # an agent that branches on mux a's own status must not see it as an error. |
| 432 | want "$TMP/m2" exit_code 1 || return 1 | 430 | want "$TMP/m2" exit_code 1 || return 1 |
| 433 | 431 | ||
| 434 | # A marker the session's shell cannot expand differently than we spell it, | 432 | # A marker the session's shell cannot expand differently than we spell it, |
| 435 | # and one that is unique per run so a stale grid can never satisfy it. | 433 | # and one that is unique per run so a stale grid can never satisfy it. |
| 436 | _mark="out-$$" | 434 | _mark="out-$$" |
| 437 | timeout 20 "$MUXA" run --sock "$SOCK_MARKS" --timeout 8000 "echo $_mark" >"$TMP/m3" 2>&1 | 435 | timeout 20 "$MUX" a run --sock "$SOCK_MARKS" --timeout 8000 "echo $_mark" >"$TMP/m3" 2>&1 |
| 438 | want "$TMP/m3" reason '"returned"' || return 1 | 436 | want "$TMP/m3" reason '"returned"' || return 1 |
| 439 | want "$TMP/m3" mechanism '"marks"' || return 1 | 437 | want "$TMP/m3" mechanism '"marks"' || return 1 |
| 440 | want "$TMP/m3" exit_code 0 || return 1 | 438 | want "$TMP/m3" exit_code 0 || return 1 |
| @@ -443,7 +441,7 @@ scen_marks() { | |||
| 443 | # into it is the bug this equality is here to catch. | 441 | # into it is the bug this equality is here to catch. |
| 444 | want "$TMP/m3" output "\"$_mark\"" || return 1 | 442 | want "$TMP/m3" output "\"$_mark\"" || return 1 |
| 445 | 443 | ||
| 446 | "$MUXD" stop --sock "$SOCK_MARKS" >/dev/null 2>&1 | 444 | "$MUX" d stop --sock "$SOCK_MARKS" >/dev/null 2>&1 |
| 447 | leakcheck "$D_MARKS" marks || return 1 | 445 | leakcheck "$D_MARKS" marks || return 1 |
| 448 | D_MARKS="" | 446 | D_MARKS="" |
| 449 | return 0 | 447 | return 0 |
| @@ -452,7 +450,7 @@ run_scenario "marks: exit codes and output come back from a bash session" scen_m | |||
| 452 | 450 | ||
| 453 | # --- The ephemeral TUI, which two scenarios share -------------------------- | 451 | # --- The ephemeral TUI, which two scenarios share -------------------------- |
| 454 | # The spec's field specimen: a daemon whose session is not a shell at all but a | 452 | # The spec's field specimen: a daemon whose session is not a shell at all but a |
| 455 | # throwaway full-screen program. `muxd start` has no `--` argv, and --shell | 453 | # throwaway full-screen program. `mux d start` has no `--` argv, and --shell |
| 456 | # execs whatever path it is given, so a one-line wrapper carries the argument. | 454 | # execs whatever path it is given, so a one-line wrapper carries the argument. |
| 457 | # HOME points into the tmpdir for e2e.sh's $SHELL reason: ~/.lesskey and | 455 | # HOME points into the tmpdir for e2e.sh's $SHELL reason: ~/.lesskey and |
| 458 | # ~/.vimrc are arbitrary code on the session under test. | 456 | # ~/.vimrc are arbitrary code on the session under test. |
| @@ -460,7 +458,7 @@ run_scenario "marks: exit codes and output come back from a bash session" scen_m | |||
| 460 | # The outcome splits three ways, and the split is the point: no TUI on the box | 458 | # The outcome splits three ways, and the split is the point: no TUI on the box |
| 461 | # is the environment's business and skips, but a TUI that is here and whose | 459 | # is the environment's business and skips, but a TUI that is here and whose |
| 462 | # daemon did not come up is a DEFECT and must fail. Blanking D_TUI for both | 460 | # daemon did not come up is a DEFECT and must fail. Blanking D_TUI for both |
| 463 | # would report a broken muxd as a skip — and would also lose the pid, orphaning | 461 | # would report a broken daemon as a skip — and would also lose the pid, orphaning |
| 464 | # a daemon that is merely unresponsive rather than dead. | 462 | # a daemon that is merely unresponsive rather than dead. |
| 465 | TUI_BIN="" | 463 | TUI_BIN="" |
| 466 | TUI_QUIT="" | 464 | TUI_QUIT="" |
| @@ -520,7 +518,7 @@ tui_gate() { | |||
| 520 | # command ever produced. | 518 | # command ever produced. |
| 521 | scen_altguard() { | 519 | scen_altguard() { |
| 522 | tui_gate || return $? | 520 | tui_gate || return $? |
| 523 | timeout 20 "$MUXA" run --sock "$SOCK_TUI" --timeout 1500 'true' >"$TMP/g1" 2>&1 | 521 | timeout 20 "$MUX" a run --sock "$SOCK_TUI" --timeout 1500 'true' >"$TMP/g1" 2>&1 |
| 524 | _rc=$? | 522 | _rc=$? |
| 525 | want "$TMP/g1" reason '"timeout"' || return 1 | 523 | want "$TMP/g1" reason '"timeout"' || return 1 |
| 526 | # Exit 3 is the whole point of having a code for it: `returned` and | 524 | # Exit 3 is the whole point of having a code for it: `returned` and |
| @@ -533,14 +531,14 @@ run_scenario "alt-screen: run times out rather than fabricating a return" scen_a | |||
| 533 | # --- 3: the ephemeral TUI, driven and quit --------------------------------- | 531 | # --- 3: the ephemeral TUI, driven and quit --------------------------------- |
| 534 | scen_tui() { | 532 | scen_tui() { |
| 535 | tui_gate || return $? | 533 | tui_gate || return $? |
| 536 | timeout 20 "$MUXA" status --sock "$SOCK_TUI" --timeout 5000 >"$TMP/t1" 2>&1 | 534 | timeout 20 "$MUX" a status --sock "$SOCK_TUI" --timeout 5000 >"$TMP/t1" 2>&1 |
| 537 | want "$TMP/t1" alt_screen true || return 1 | 535 | want "$TMP/t1" alt_screen true || return 1 |
| 538 | # Marks are a shell's doing. A program that is not a shell cannot have | 536 | # Marks are a shell's doing. A program that is not a shell cannot have |
| 539 | # them, and claiming otherwise is what scenario 2 would then read. | 537 | # them, and claiming otherwise is what scenario 2 would then read. |
| 540 | _mech=$(jget "$TMP/t1" mechanism) | 538 | _mech=$(jget "$TMP/t1" mechanism) |
| 541 | [ "$_mech" != '"marks"' ] || why "mechanism=marks on a TUI that no shell started" || return 1 | 539 | [ "$_mech" != '"marks"' ] || why "mechanism=marks on a TUI that no shell started" || return 1 |
| 542 | 540 | ||
| 543 | timeout 10 "$MUXA" send --sock "$SOCK_TUI" -- "$TUI_QUIT" >"$TMP/t2" 2>&1 | 541 | timeout 10 "$MUX" a send --sock "$SOCK_TUI" -- "$TUI_QUIT" >"$TMP/t2" 2>&1 |
| 544 | want "$TMP/t2" sent true || return 1 | 542 | want "$TMP/t2" sent true || return 1 |
| 545 | 543 | ||
| 546 | # The SESSION ended and the daemon did NOT: `x` ends a session, never a | 544 | # The SESSION ended and the daemon did NOT: `x` ends a session, never a |
| @@ -549,30 +547,30 @@ scen_tui() { | |||
| 549 | # have said `sessions=0` in the first place, and the `kill -0` is what | 547 | # have said `sessions=0` in the first place, and the `kill -0` is what |
| 550 | # says the process is here rather than merely that the socket is. | 548 | # says the process is here rather than merely that the socket is. |
| 551 | _i=0 | 549 | _i=0 |
| 552 | while ! "$MUXD" stats --sock "$SOCK_TUI" 2>/dev/null | grep -q 'sessions=0'; do | 550 | while ! "$MUX" d stats --sock "$SOCK_TUI" 2>/dev/null | grep -q 'sessions=0'; do |
| 553 | _i=$((_i + 1)) | 551 | _i=$((_i + 1)) |
| 554 | [ "$_i" -lt 100 ] || why "the TUI quit but its daemon still holds a session 5s later" || return 1 | 552 | [ "$_i" -lt 100 ] || why "the TUI quit but its daemon still holds a session 5s later" || return 1 |
| 555 | sleep 0.05 | 553 | sleep 0.05 |
| 556 | done | 554 | done |
| 557 | kill -0 "$D_TUI" 2>/dev/null || | 555 | kill -0 "$D_TUI" 2>/dev/null || |
| 558 | why "the TUI daemon (pid $D_TUI) left with its last session — only muxd stop ends one" || return 1 | 556 | why "the TUI daemon (pid $D_TUI) left with its last session — only mux d stop ends one" || return 1 |
| 559 | 557 | ||
| 560 | # The corpse the contract below needs, made deliberately: a daemon that | 558 | # The corpse the contract below needs, made deliberately: a daemon that |
| 561 | # is really gone is what an agent's next call has to meet, and nothing | 559 | # is really gone is what an agent's next call has to meet, and nothing |
| 562 | # but a stop produces one now. | 560 | # but a stop produces one now. |
| 563 | "$MUXD" stop --sock "$SOCK_TUI" >/dev/null 2>&1 | 561 | "$MUX" d stop --sock "$SOCK_TUI" >/dev/null 2>&1 |
| 564 | _i=0 | 562 | _i=0 |
| 565 | while kill -0 "$D_TUI" 2>/dev/null; do | 563 | while kill -0 "$D_TUI" 2>/dev/null; do |
| 566 | _i=$((_i + 1)) | 564 | _i=$((_i + 1)) |
| 567 | [ "$_i" -lt 100 ] || why "muxd stop left the TUI daemon (pid $D_TUI) up 5s later" || return 1 | 565 | [ "$_i" -lt 100 ] || why "mux d stop left the TUI daemon (pid $D_TUI) up 5s later" || return 1 |
| 568 | sleep 0.05 | 566 | sleep 0.05 |
| 569 | done | 567 | done |
| 570 | D_TUI="" | 568 | D_TUI="" |
| 571 | 569 | ||
| 572 | # ...and the next call against the corpse is a JSON OBJECT, which is the | 570 | # ...and the next call against the corpse is a JSON OBJECT, which is the |
| 573 | # contract an agent depends on: there is no reply muxa can give that an | 571 | # contract an agent depends on: there is no reply mux a can give that an |
| 574 | # agent has to parse as prose, and no path here that panics. | 572 | # agent has to parse as prose, and no path here that panics. |
| 575 | timeout 10 "$MUXA" status --sock "$SOCK_TUI" --timeout 2000 >"$TMP/t3" 2>&1 | 573 | timeout 10 "$MUX" a status --sock "$SOCK_TUI" --timeout 2000 >"$TMP/t3" 2>&1 |
| 576 | _rc=$? | 574 | _rc=$? |
| 577 | [ "$_rc" -ne 0 ] || why "status against a dead daemon exited 0 [$(tr -d '\n' < "$TMP/t3")]" || return 1 | 575 | [ "$_rc" -ne 0 ] || why "status against a dead daemon exited 0 [$(tr -d '\n' < "$TMP/t3")]" || return 1 |
| 578 | _err=$(jget "$TMP/t3" error) | 576 | _err=$(jget "$TMP/t3" error) |
| @@ -598,7 +596,7 @@ run_scenario "ephemeral TUI: alt_screen seen, quit driven, death reported as JSO | |||
| 598 | scen_settle() { | 596 | scen_settle() { |
| 599 | start_ready D_SETTLE "$TMP/settle.log" "$SOCK_SETTLE" --sock "$SOCK_SETTLE" --shell /bin/sh || return 1 | 597 | start_ready D_SETTLE "$TMP/settle.log" "$SOCK_SETTLE" --sock "$SOCK_SETTLE" --shell /bin/sh || return 1 |
| 600 | 598 | ||
| 601 | timeout 20 "$MUXA" run --sock "$SOCK_SETTLE" --settle 300 --timeout 10000 'sleep 1' >"$TMP/s1" 2>&1 | 599 | timeout 20 "$MUX" a run --sock "$SOCK_SETTLE" --settle 300 --timeout 10000 'sleep 1' >"$TMP/s1" 2>&1 |
| 602 | _rc=$? | 600 | _rc=$? |
| 603 | [ "$_rc" -eq 0 ] || why "run exited $_rc [$(tr -d '\n' < "$TMP/s1")]" || return 1 | 601 | [ "$_rc" -eq 0 ] || why "run exited $_rc [$(tr -d '\n' < "$TMP/s1")]" || return 1 |
| 604 | _reason=$(jget "$TMP/s1" reason) | 602 | _reason=$(jget "$TMP/s1" reason) |
| @@ -606,12 +604,12 @@ scen_settle() { | |||
| 606 | '"settled"'|'"returned"') ;; | 604 | '"settled"'|'"returned"') ;; |
| 607 | *) why "reason=$_reason, want \"settled\" or \"returned\" [$(tr -d '\n' < "$TMP/s1")]" || return 1 ;; | 605 | *) why "reason=$_reason, want \"settled\" or \"returned\" [$(tr -d '\n' < "$TMP/s1")]" || return 1 ;; |
| 608 | esac | 606 | esac |
| 609 | # No marks means no exit code, and muxa says so with a null rather than a | 607 | # No marks means no exit code, and mux a says so with a null rather than a |
| 610 | # zero — an agent must never read "it worked" out of a mechanism that | 608 | # zero — an agent must never read "it worked" out of a mechanism that |
| 611 | # cannot know. | 609 | # cannot know. |
| 612 | want "$TMP/s1" exit_code null || return 1 | 610 | want "$TMP/s1" exit_code null || return 1 |
| 613 | 611 | ||
| 614 | "$MUXD" stop --sock "$SOCK_SETTLE" >/dev/null 2>&1 | 612 | "$MUX" d stop --sock "$SOCK_SETTLE" >/dev/null 2>&1 |
| 615 | leakcheck "$D_SETTLE" settle || return 1 | 613 | leakcheck "$D_SETTLE" settle || return 1 |
| 616 | D_SETTLE="" | 614 | D_SETTLE="" |
| 617 | return 0 | 615 | return 0 |
| @@ -622,7 +620,7 @@ run_scenario "settle: a markless sleep returns an answer, never a timeout" scen_ | |||
| 622 | # Everything below needs a key, a daemon holding a UDP port, and — for the two | 620 | # Everything below needs a key, a daemon holding a UDP port, and — for the two |
| 623 | # tear scenarios — a path this suite can break on purpose WITHOUT root. The | 621 | # tear scenarios — a path this suite can break on purpose WITHOUT root. The |
| 624 | # relay is that path: a UDP forwarder in front of the daemon's port, which | 622 | # relay is that path: a UDP forwarder in front of the daemon's port, which |
| 625 | # muxa dials instead. Tearing is a control file, not a signal and not a kill, | 623 | # mux a dials instead. Tearing is a control file, not a signal and not a kill, |
| 626 | # because the flow has to keep being ABSORBED after it breaks: a relay that | 624 | # because the flow has to keep being ABSORBED after it breaks: a relay that |
| 627 | # died would have the kernel answer with ICMP port-unreachable, and a refusal | 625 | # died would have the kernel answer with ICMP port-unreachable, and a refusal |
| 628 | # is the fast path, not the loss this is modelling. | 626 | # is the fast path, not the loss this is modelling. |
| @@ -678,14 +676,14 @@ PY | |||
| 678 | # rather than skips. There is no environmental escape hatch left at this point: | 676 | # rather than skips. There is no environmental escape hatch left at this point: |
| 679 | # python3 and `timeout` were made hard prerequisites at the top of the file, so | 677 | # python3 and `timeout` were made hard prerequisites at the top of the file, so |
| 680 | # what remains — keygen refusing, the key not landing where keygen said it did, | 678 | # what remains — keygen refusing, the key not landing where keygen said it did, |
| 681 | # the QUIC daemon not coming up, the relay not binding — is either muxd | 679 | # the QUIC daemon not coming up, the relay not binding — is either the daemon |
| 682 | # misbehaving or this box handing out a port twice. A skip here would be the | 680 | # misbehaving or this box handing out a port twice. A skip here would be the |
| 683 | # one door in the suite wide enough for a real regression to walk through | 681 | # one door in the suite wide enough for a real regression to walk through |
| 684 | # wearing green: `muxd --quic` breaking outright would have reported "4 passed, | 682 | # wearing green: `mux d --quic` breaking outright would have reported "4 passed, |
| 685 | # 5 skipped" and exited 0, which is CI saying yes to a broken binary. | 683 | # 5 skipped" and exited 0, which is CI saying yes to a broken binary. |
| 686 | QUIC_FAIL="" | 684 | QUIC_FAIL="" |
| 687 | if ! "$MUXD" keygen >"$TMP/keygen.log" 2>&1; then | 685 | if ! "$MUX" d keygen >"$TMP/keygen.log" 2>&1; then |
| 688 | QUIC_FAIL="muxd keygen failed [$(tr -d '\n' < "$TMP/keygen.log")]" | 686 | QUIC_FAIL="mux d keygen failed [$(tr -d '\n' < "$TMP/keygen.log")]" |
| 689 | elif ! cp "$XDG_CONFIG_HOME/mux/key" "$KEY" 2>/dev/null; then | 687 | elif ! cp "$XDG_CONFIG_HOME/mux/key" "$KEY" 2>/dev/null; then |
| 690 | QUIC_FAIL="keygen wrote no key where it said it did [$(tr -d '\n' < "$TMP/keygen.log")]" | 688 | QUIC_FAIL="keygen wrote no key where it said it did [$(tr -d '\n' < "$TMP/keygen.log")]" |
| 691 | fi | 689 | fi |
| @@ -693,7 +691,7 @@ fi | |||
| 693 | if [ -z "$QUIC_FAIL" ]; then | 691 | if [ -z "$QUIC_FAIL" ]; then |
| 694 | # The reduced idle is the schedule the tear scenarios wait for. It is a | 692 | # The reduced idle is the schedule the tear scenarios wait for. It is a |
| 695 | # transport parameter, so the NEGOTIATED value is the min of the two ends | 693 | # transport parameter, so the NEGOTIATED value is the min of the two ends |
| 696 | # and this daemon's 4s governs both — muxa has no idle flag of its own, | 694 | # and this daemon's 4s governs both — mux a has no idle flag of its own, |
| 697 | # and waiting out its 15s default twice would be most of this suite's | 695 | # and waiting out its 15s default twice would be most of this suite's |
| 698 | # runtime. The quiet-await scenario below deliberately does not use it. | 696 | # runtime. The quiet-await scenario below deliberately does not use it. |
| 699 | start_quic PORT_TEAR D_TEAR "$TMP/tear.log" "$SOCK_TEAR" --shell /bin/bash \ | 697 | start_quic PORT_TEAR D_TEAR "$TMP/tear.log" "$SOCK_TEAR" --shell /bin/bash \ |
| @@ -714,7 +712,7 @@ quic_gate() { | |||
| 714 | 712 | ||
| 715 | # --- 5: a tear mid-await heals, and the command still ran exactly once ------ | 713 | # --- 5: a tear mid-await heals, and the command still ran exactly once ------ |
| 716 | # The two claims an agent's whole reconnect story rests on. The reply that | 714 | # The two claims an agent's whole reconnect story rests on. The reply that |
| 717 | # arrives after the heal carries the ORIGINAL command's return — muxa re-issued | 715 | # arrives after the heal carries the ORIGINAL command's return — mux a re-issued |
| 718 | # its await from the watermark it already held, so the daemon answered about | 716 | # its await from the watermark it already held, so the daemon answered about |
| 719 | # the same command rather than starting a new wait — and the command ran once, | 717 | # the same command rather than starting a new wait — and the command ran once, |
| 720 | # which is the no-input-resend rule: a client that re-sent its cmdline on | 718 | # which is the no-input-resend rule: a client that re-sent its cmdline on |
| @@ -726,7 +724,7 @@ scen_tear_heal() { | |||
| 726 | rm -f "$_tally" | 724 | rm -f "$_tally" |
| 727 | # Counted in the FILESYSTEM, not in the grid: a grid count would also see | 725 | # Counted in the FILESYSTEM, not in the grid: a grid count would also see |
| 728 | # the echoed command line, and a wrapped row would make it a guess. | 726 | # the echoed command line, and a wrapped row would make it a guess. |
| 729 | timeout 40 "$MUXA" run --quic "127.0.0.1:$PORT_RELAY" --key "$KEY" --timeout 25000 \ | 727 | timeout 40 "$MUX" a run --quic "127.0.0.1:$PORT_RELAY" --key "$KEY" --timeout 25000 \ |
| 730 | "sleep 12; echo ran >> $_tally" >"$TMP/q1" 2>&1 & | 728 | "sleep 12; echo ran >> $_tally" >"$TMP/q1" 2>&1 & |
| 731 | CLI_PID=$! | 729 | CLI_PID=$! |
| 732 | # Tear once the await is genuinely in flight — proved by the relay having | 730 | # Tear once the await is genuinely in flight — proved by the relay having |
| @@ -745,7 +743,7 @@ scen_tear_heal() { | |||
| 745 | want "$TMP/q1" exit_code 0 || return 1 | 743 | want "$TMP/q1" exit_code 0 || return 1 |
| 746 | 744 | ||
| 747 | # A second flow through the relay is the reconnect, observed from outside | 745 | # A second flow through the relay is the reconnect, observed from outside |
| 748 | # muxa. Without it the reply could only mean the tear never landed, and | 746 | # mux a. Without it the reply could only mean the tear never landed, and |
| 749 | # this scenario would be asserting nothing at all. | 747 | # this scenario would be asserting nothing at all. |
| 750 | grep -q "relay: flow 2" "$RELAY_LOG" || | 748 | grep -q "relay: flow 2" "$RELAY_LOG" || |
| 751 | why "no second flow: the reply came back without a redial [$(cat "$RELAY_LOG")]" || return 1 | 749 | why "no second flow: the reply came back without a redial [$(cat "$RELAY_LOG")]" || return 1 |
| @@ -773,7 +771,7 @@ scen_tear_fatal() { | |||
| 773 | # comes and report THAT as this scenario's failure. | 771 | # comes and report THAT as this scenario's failure. |
| 774 | _flow=$(( $(grep -c "relay: flow" "$RELAY_LOG") + 1 )) | 772 | _flow=$(( $(grep -c "relay: flow" "$RELAY_LOG") + 1 )) |
| 775 | _t0=$(now_ms) | 773 | _t0=$(now_ms) |
| 776 | timeout 30 "$MUXA" run --quic "127.0.0.1:$PORT_RELAY" --key "$KEY" --timeout 12000 \ | 774 | timeout 30 "$MUX" a run --quic "127.0.0.1:$PORT_RELAY" --key "$KEY" --timeout 12000 \ |
| 777 | 'sleep 20' >"$TMP/q2" 2>&1 & | 775 | 'sleep 20' >"$TMP/q2" 2>&1 & |
| 778 | CLI_PID=$! | 776 | CLI_PID=$! |
| 779 | wait_for "$RELAY_LOG" "relay: flow $_flow" 10 || why "the client never opened a new flow [$(cat "$RELAY_LOG")]" || return 1 | 777 | wait_for "$RELAY_LOG" "relay: flow $_flow" 10 || why "the client never opened a new flow [$(cat "$RELAY_LOG")]" || return 1 |
| @@ -786,11 +784,11 @@ scen_tear_fatal() { | |||
| 786 | CLI_PID="" | 784 | CLI_PID="" |
| 787 | _spent=$(( $(now_ms) - _t0 )) | 785 | _spent=$(( $(now_ms) - _t0 )) |
| 788 | [ "$_rc" -ne 0 ] || why "run exited 0 with the path gone [$(tr -d '\n' < "$TMP/q2")]" || return 1 | 786 | [ "$_rc" -ne 0 ] || why "run exited 0 with the path gone [$(tr -d '\n' < "$TMP/q2")]" || return 1 |
| 789 | [ "$_rc" -ne 124 ] || why "muxa hung past the outer 30s bound" || return 1 | 787 | [ "$_rc" -ne 124 ] || why "mux a hung past the outer 30s bound" || return 1 |
| 790 | # --timeout plus muxa's 2s grace over the daemon's own window, plus room | 788 | # --timeout plus mux a's 2s grace over the daemon's own window, plus room |
| 791 | # for the box. Anything near 30s means the deadline was not honoured. | 789 | # for the box. Anything near 30s means the deadline was not honoured. |
| 792 | [ "$_spent" -lt 20000 ] || why "took ${_spent}ms for a 12000ms timeout" || return 1 | 790 | [ "$_spent" -lt 20000 ] || why "took ${_spent}ms for a 12000ms timeout" || return 1 |
| 793 | # The COMPOSED narrative, not merely a prefix of it. muxa has three endings | 791 | # The COMPOSED narrative, not merely a prefix of it. mux a has three endings |
| 794 | # for a lost connection and only one of them is honest here: this client | 792 | # for a lost connection and only one of them is honest here: this client |
| 795 | # redialled and the redial could not complete. A bare `connection lost` | 793 | # redialled and the redial could not complete. A bare `connection lost` |
| 796 | # means nothing tried to redial, and `connection lost again, after the one | 794 | # means nothing tried to redial, and `connection lost again, after the one |
| @@ -816,7 +814,7 @@ scen_keepalive() { | |||
| 816 | start_quic PORT_QUIET D_QUIET "$TMP/quiet.log" "$SOCK_QUIET" --shell /bin/bash \ | 814 | start_quic PORT_QUIET D_QUIET "$TMP/quiet.log" "$SOCK_QUIET" --shell /bin/bash \ |
| 817 | --key "$KEY" || return 1 | 815 | --key "$KEY" || return 1 |
| 818 | 816 | ||
| 819 | timeout 40 "$MUXA" await --quic "127.0.0.1:$PORT_QUIET" --key "$KEY" --timeout 20000 >"$TMP/q3" 2>&1 | 817 | timeout 40 "$MUX" a await --quic "127.0.0.1:$PORT_QUIET" --key "$KEY" --timeout 20000 >"$TMP/q3" 2>&1 |
| 820 | _rc=$? | 818 | _rc=$? |
| 821 | [ "$_rc" -eq 3 ] || why "await exited $_rc, want 3 [$(tr -d '\n' < "$TMP/q3")]" || return 1 | 819 | [ "$_rc" -eq 3 ] || why "await exited $_rc, want 3 [$(tr -d '\n' < "$TMP/q3")]" || return 1 |
| 822 | want "$TMP/q3" reason '"timeout"' || return 1 | 820 | want "$TMP/q3" reason '"timeout"' || return 1 |
| @@ -826,7 +824,7 @@ scen_keepalive() { | |||
| 826 | [ "$_dur" -ge 18000 ] 2>/dev/null || | 824 | [ "$_dur" -ge 18000 ] 2>/dev/null || |
| 827 | why "duration_ms=$_dur — the wait did not survive the 15s idle timeout" || return 1 | 825 | why "duration_ms=$_dur — the wait did not survive the 15s idle timeout" || return 1 |
| 828 | 826 | ||
| 829 | "$MUXD" stop --sock "$SOCK_QUIET" >/dev/null 2>&1 | 827 | "$MUX" d stop --sock "$SOCK_QUIET" >/dev/null 2>&1 |
| 830 | leakcheck "$D_QUIET" quiet || return 1 | 828 | leakcheck "$D_QUIET" quiet || return 1 |
| 831 | D_QUIET="" | 829 | D_QUIET="" |
| 832 | return 0 | 830 | return 0 |
| @@ -853,7 +851,7 @@ run_scenario "quic: a quiet 20s await outlives the 15s idle timeout" scen_keepal | |||
| 853 | # scenario destroys what it runs on, so it goes last among the three regardless. | 851 | # scenario destroys what it runs on, so it goes last among the three regardless. |
| 854 | scen_session_exit() { | 852 | scen_session_exit() { |
| 855 | quic_gate || return $? | 853 | quic_gate || return $? |
| 856 | timeout 30 "$MUXA" run --quic "127.0.0.1:$PORT_TEAR" --key "$KEY" --timeout 12000 \ | 854 | timeout 30 "$MUX" a run --quic "127.0.0.1:$PORT_TEAR" --key "$KEY" --timeout 12000 \ |
| 857 | 'exit 5' >"$TMP/q4" 2>&1 | 855 | 'exit 5' >"$TMP/q4" 2>&1 |
| 858 | _rc=$? | 856 | _rc=$? |
| 859 | # An ANSWER, not a failure: the command is over and this is how. | 857 | # An ANSWER, not a failure: the command is over and this is how. |
| @@ -864,14 +862,14 @@ scen_session_exit() { | |||
| 864 | } | 862 | } |
| 865 | run_scenario "quic: a session that exits 5 reports 5, not a lost connection" scen_session_exit | 863 | run_scenario "quic: a session that exits 5 reports 5, not a lost connection" scen_session_exit |
| 866 | # The pid is KEPT. The scenario ended this daemon's only session, and a | 864 | # The pid is KEPT. The scenario ended this daemon's only session, and a |
| 867 | # daemon lives until `muxd stop` — so it is still running, and the sweep | 865 | # daemon lives until `mux d stop` — so it is still running, and the sweep |
| 868 | # below has to wait for its leak verdict like any other daemon's. | 866 | # below has to wait for its leak verdict like any other daemon's. |
| 869 | 867 | ||
| 870 | # --- 9: a destination that swallows still honours --timeout ---------------- | 868 | # --- 9: a destination that swallows still honours --timeout ---------------- |
| 871 | # Not a refusal: a never-listening port answers with ICMP and muxa fails | 869 | # Not a refusal: a never-listening port answers with ICMP and mux a fails |
| 872 | # instantly, which proves nothing about the deadline. A UDP listener that reads | 870 | # instantly, which proves nothing about the deadline. A UDP listener that reads |
| 873 | # and never replies makes the HANDSHAKE hang, and the only thing that can end | 871 | # and never replies makes the HANDSHAKE hang, and the only thing that can end |
| 874 | # it is muxa's own clock. | 872 | # it is mux a's own clock. |
| 875 | scen_blackhole() { | 873 | scen_blackhole() { |
| 876 | quic_gate || return $? | 874 | quic_gate || return $? |
| 877 | # The relay, blackholing from birth: its control file exists before it | 875 | # The relay, blackholing from birth: its control file exists before it |
| @@ -882,16 +880,16 @@ scen_blackhole() { | |||
| 882 | wait_for "$SINK_LOG" "relay up" 5 || why "the sink never bound $PORT_SINK [$(cat "$SINK_LOG")]" || return 1 | 880 | wait_for "$SINK_LOG" "relay up" 5 || why "the sink never bound $PORT_SINK [$(cat "$SINK_LOG")]" || return 1 |
| 883 | 881 | ||
| 884 | _t0=$(now_ms) | 882 | _t0=$(now_ms) |
| 885 | timeout 20 "$MUXA" status --quic "127.0.0.1:$PORT_SINK" --key "$KEY" --timeout 2000 >"$TMP/q5" 2>&1 | 883 | timeout 20 "$MUX" a status --quic "127.0.0.1:$PORT_SINK" --key "$KEY" --timeout 2000 >"$TMP/q5" 2>&1 |
| 886 | _rc=$? | 884 | _rc=$? |
| 887 | _spent=$(( $(now_ms) - _t0 )) | 885 | _spent=$(( $(now_ms) - _t0 )) |
| 888 | kill "$SINK_PID" 2>/dev/null | 886 | kill "$SINK_PID" 2>/dev/null |
| 889 | SINK_PID="" | 887 | SINK_PID="" |
| 890 | 888 | ||
| 891 | [ "$_rc" -ne 0 ] || why "status exited 0 against a blackhole [$(tr -d '\n' < "$TMP/q5")]" || return 1 | 889 | [ "$_rc" -ne 0 ] || why "status exited 0 against a blackhole [$(tr -d '\n' < "$TMP/q5")]" || return 1 |
| 892 | [ "$_rc" -ne 124 ] || why "muxa hung past the outer 20s bound" || return 1 | 890 | [ "$_rc" -ne 124 ] || why "mux a hung past the outer 20s bound" || return 1 |
| 893 | # The ceiling that must NOT be hit is the 15s handshake idle timeout: a | 891 | # The ceiling that must NOT be hit is the 15s handshake idle timeout: a |
| 894 | # muxa that ignored --timeout would land there, and this bound is under it | 892 | # mux a that ignored --timeout would land there, and this bound is under it |
| 895 | # by enough that only the flag can explain the number. | 893 | # by enough that only the flag can explain the number. |
| 896 | [ "$_spent" -lt 6000 ] || why "took ${_spent}ms for a 2000ms timeout — the flag was not honoured" || return 1 | 894 | [ "$_spent" -lt 6000 ] || why "took ${_spent}ms for a 2000ms timeout — the flag was not honoured" || return 1 |
| 897 | _err=$(jget "$TMP/q5" error) | 895 | _err=$(jget "$TMP/q5" error) |
| @@ -903,14 +901,14 @@ scen_blackhole() { | |||
| 903 | } | 901 | } |
| 904 | run_scenario "quic: a blackholed destination fails on --timeout, not on the idle ceiling" scen_blackhole | 902 | run_scenario "quic: a blackholed destination fails on --timeout, not on the idle ceiling" scen_blackhole |
| 905 | 903 | ||
| 906 | # --- 10: a live daemon actually answers muxa status --quic ----------------- | 904 | # --- 10: a live daemon actually answers mux a status --quic ----------------- |
| 907 | # The blackhole probe above proves muxa REFUSES a daemon it cannot reach — | 905 | # The blackhole probe above proves mux a REFUSES a daemon it cannot reach — |
| 908 | # on its own that is consistent with two very different bugs: "refuses dead | 906 | # on its own that is consistent with two very different bugs: "refuses dead |
| 909 | # daemons" (the claim) and "refuses every daemon, QUIC included" (what an | 907 | # daemons" (the claim) and "refuses every daemon, QUIC included" (what an |
| 910 | # M18 review found: handleFrame's status_req arm dropped a session-less | 908 | # M18 review found: handleFrame's status_req arm dropped a session-less |
| 911 | # slot outright via `orelse return`, so a QUIC connection — promoted to a | 909 | # slot outright via `orelse return`, so a QUIC connection — promoted to a |
| 912 | # client slot at handshake, before any attach gives it a session — never | 910 | # client slot at handshake, before any attach gives it a session — never |
| 913 | # got an answer, and `muxa status --quic` against a perfectly live daemon | 911 | # got an answer, and `mux a status --quic` against a perfectly live daemon |
| 914 | # just sat until its own --timeout). This is the other half of that pair: | 912 | # just sat until its own --timeout). This is the other half of that pair: |
| 915 | # the same verb, against a daemon that DOES answer, must actually get a | 913 | # the same verb, against a daemon that DOES answer, must actually get a |
| 916 | # reply. SOCK_QUIET/PORT_QUIET are free again — scenario 7 already stopped | 914 | # reply. SOCK_QUIET/PORT_QUIET are free again — scenario 7 already stopped |
| @@ -921,11 +919,11 @@ scen_quic_status_live() { | |||
| 921 | start_quic PORT_QUIET D_QUIET "$TMP/quiet2.log" "$SOCK_QUIET" --shell /bin/sh \ | 919 | start_quic PORT_QUIET D_QUIET "$TMP/quiet2.log" "$SOCK_QUIET" --shell /bin/sh \ |
| 922 | --key "$KEY" || return 1 | 920 | --key "$KEY" || return 1 |
| 923 | 921 | ||
| 924 | timeout 20 "$MUXA" status --quic "127.0.0.1:$PORT_QUIET" --key "$KEY" --timeout 5000 >"$TMP/q6" 2>&1 | 922 | timeout 20 "$MUX" a status --quic "127.0.0.1:$PORT_QUIET" --key "$KEY" --timeout 5000 >"$TMP/q6" 2>&1 |
| 925 | _rc=$? | 923 | _rc=$? |
| 926 | [ "$_rc" -eq 0 ] || why "status exited $_rc against a live daemon [$(tr -d '\n' < "$TMP/q6")]" || return 1 | 924 | [ "$_rc" -eq 0 ] || why "status exited $_rc against a live daemon [$(tr -d '\n' < "$TMP/q6")]" || return 1 |
| 927 | # Not just "exit 0" — a real StatusReply decoded into the JSON shape | 925 | # Not just "exit 0" — a real StatusReply decoded into the JSON shape |
| 928 | # muxa's own contract promises, which a silently-empty object could | 926 | # mux a's own contract promises, which a silently-empty object could |
| 929 | # still slip past a bare exit-code check. | 927 | # still slip past a bare exit-code check. |
| 930 | _cols=$(jget "$TMP/q6" cols) | 928 | _cols=$(jget "$TMP/q6" cols) |
| 931 | case "$_cols" in | 929 | case "$_cols" in |
| @@ -933,12 +931,12 @@ scen_quic_status_live() { | |||
| 933 | why "no cols in the status reply [$(tr -d '\n' < "$TMP/q6")]" || return 1 ;; | 931 | why "no cols in the status reply [$(tr -d '\n' < "$TMP/q6")]" || return 1 ;; |
| 934 | esac | 932 | esac |
| 935 | 933 | ||
| 936 | "$MUXD" stop --sock "$SOCK_QUIET" >/dev/null 2>&1 | 934 | "$MUX" d stop --sock "$SOCK_QUIET" >/dev/null 2>&1 |
| 937 | leakcheck "$D_QUIET" quiet2 || return 1 | 935 | leakcheck "$D_QUIET" quiet2 || return 1 |
| 938 | D_QUIET="" | 936 | D_QUIET="" |
| 939 | return 0 | 937 | return 0 |
| 940 | } | 938 | } |
| 941 | run_scenario "quic: muxa status succeeds against a live daemon (not just refuses dead ones)" scen_quic_status_live | 939 | run_scenario "quic: mux a status succeeds against a live daemon (not just refuses dead ones)" scen_quic_status_live |
| 942 | 940 | ||
| 943 | # The count, pinned against a literal for e2e.sh's reason: a scenario that | 941 | # The count, pinned against a literal for e2e.sh's reason: a scenario that |
| 944 | # silently stops running is the failure mode no assertion inside it can catch. | 942 | # silently stops running is the failure mode no assertion inside it can catch. |
| @@ -955,13 +953,13 @@ fi | |||
| 955 | # lands here is everything else — the QUIC daemon scenario 8 emptied, and any | 953 | # lands here is everything else — the QUIC daemon scenario 8 emptied, and any |
| 956 | # daemon a FAILING scenario left behind, which is exactly the case that used | 954 | # daemon a FAILING scenario left behind, which is exactly the case that used |
| 957 | # to go unswept. Emptying a daemon does not remove it from this list: nothing | 955 | # to go unswept. Emptying a daemon does not remove it from this list: nothing |
| 958 | # but `muxd stop` ends one. | 956 | # but `mux d stop` ends one. |
| 959 | # | 957 | # |
| 960 | # After the count pin on purpose: a leak is not a scenario, and folding it | 958 | # After the count pin on purpose: a leak is not a scenario, and folding it |
| 961 | # into PASSES+FAILS+SKIPS would make that pin's number stop meaning "nine | 959 | # into PASSES+FAILS+SKIPS would make that pin's number stop meaning "nine |
| 962 | # scenarios ran". | 960 | # scenarios ran". |
| 963 | for _s in "$SOCK_MARKS" "$SOCK_TUI" "$SOCK_SETTLE" "$SOCK_TEAR" "$SOCK_QUIET"; do | 961 | for _s in "$SOCK_MARKS" "$SOCK_TUI" "$SOCK_SETTLE" "$SOCK_TEAR" "$SOCK_QUIET"; do |
| 964 | [ -S "$_s" ] && "$MUXD" stop --sock "$_s" >/dev/null 2>&1 | 962 | [ -S "$_s" ] && "$MUX" d stop --sock "$_s" >/dev/null 2>&1 |
| 965 | done | 963 | done |
| 966 | # The verdict is written on the way out, so a daemon still running has not | 964 | # The verdict is written on the way out, so a daemon still running has not |
| 967 | # written one yet. Bounded, and bounded rather than `wait`ed on because these | 965 | # written one yet. Bounded, and bounded rather than `wait`ed on because these |
test/bench.sh
| Old | New | ||
|---|---|---|---|
| @@ -2,8 +2,7 @@ | |||
| 2 | # M4 bytes-on-wire measurement: a typing-heavy workload, then compare | 2 | # M4 bytes-on-wire measurement: a typing-heavy workload, then compare |
| 3 | # delta bytes actually sent against the measured full-snapshot equivalent. | 3 | # delta bytes actually sent against the measured full-snapshot equivalent. |
| 4 | set -eu | 4 | set -eu |
| 5 | MUXD="$1" | 5 | MUX="$1" |
| 6 | MUX="$2" | ||
| 7 | SOCK="${TMPDIR:-/tmp}/muxd-bench-$$.sock" | 6 | SOCK="${TMPDIR:-/tmp}/muxd-bench-$$.sock" |
| 8 | # Attaches record wall tiles; keep them out of the developer's real wall file. | 7 | # Attaches record wall tiles; keep them out of the developer's real wall file. |
| 9 | XDG_STATE_HOME="${TMPDIR:-/tmp}/muxd-bench-$$-state"; export XDG_STATE_HOME | 8 | XDG_STATE_HOME="${TMPDIR:-/tmp}/muxd-bench-$$-state"; export XDG_STATE_HOME |
| @@ -11,7 +10,7 @@ XDG_STATE_HOME="${TMPDIR:-/tmp}/muxd-bench-$$-state"; export XDG_STATE_HOME | |||
| 11 | cleanup() { kill "$DPID" 2>/dev/null || true; rm -rf "$SOCK" "$XDG_STATE_HOME"; } | 10 | cleanup() { kill "$DPID" 2>/dev/null || true; rm -rf "$SOCK" "$XDG_STATE_HOME"; } |
| 12 | trap cleanup EXIT INT TERM | 11 | trap cleanup EXIT INT TERM |
| 13 | 12 | ||
| 14 | "$MUXD" run --sock "$SOCK" --shell /bin/sh & | 13 | "$MUX" d run --sock "$SOCK" --shell /bin/sh & |
| 15 | DPID=$! | 14 | DPID=$! |
| 16 | i=0 | 15 | i=0 |
| 17 | while [ ! -S "$SOCK" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done | 16 | while [ ! -S "$SOCK" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done |
| @@ -27,7 +26,7 @@ while [ ! -S "$SOCK" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done | |||
| 27 | printf '\034\034' | 26 | printf '\034\034' |
| 28 | } | "$MUX" --sock "$SOCK" > /dev/null | 27 | } | "$MUX" --sock "$SOCK" > /dev/null |
| 29 | 28 | ||
| 30 | STATS="$("$MUXD" stats --sock "$SOCK")" | 29 | STATS="$("$MUX" d stats --sock "$SOCK")" |
| 31 | echo "$STATS" | 30 | echo "$STATS" |
| 32 | 31 | ||
| 33 | DELTA=$(echo "$STATS" | sed -n 's/.*[^_]delta_bytes=\([0-9]*\).*/\1/p') | 32 | DELTA=$(echo "$STATS" | sed -n 's/.*[^_]delta_bytes=\([0-9]*\).*/\1/p') |
test/coverage.sh
| Old | New | ||
|---|---|---|---|
| @@ -14,7 +14,7 @@ | |||
| 14 | # | 14 | # |
| 15 | # - kcov cannot nest. It sets PTRACE_O_TRACEFORK, so every descendant of | 15 | # - kcov cannot nest. It sets PTRACE_O_TRACEFORK, so every descendant of |
| 16 | # a traced process is ALREADY traced, and an inner kcov's TRACEME is | 16 | # a traced process is ALREADY traced, and an inner kcov's TRACEME is |
| 17 | # refused with EPERM. `mux --via` spawns a muxd, which is exactly that | 17 | # refused with EPERM. `mux --via` spawns a daemon, which is exactly that |
| 18 | # shape. MUX_KCOV_ACTIVE makes a nested shim exec the real binary bare: | 18 | # shape. MUX_KCOV_ACTIVE makes a nested shim exec the real binary bare: |
| 19 | # those lines are attributed to whichever ancestor is traced, or lost, | 19 | # those lines are attributed to whichever ancestor is traced, or lost, |
| 20 | # but the suite never dies. | 20 | # but the suite never dies. |
| @@ -22,13 +22,13 @@ | |||
| 22 | # the one signal a wrapper cannot forward, so a leg that kill -9s a | 22 | # the one signal a wrapper cannot forward, so a leg that kill -9s a |
| 23 | # daemon would leave the real one alive holding its socket. e2e.sh's | 23 | # daemon would leave the real one alive holding its socket. e2e.sh's |
| 24 | # hardkill answers that; see the comment there. | 24 | # hardkill answers that; see the comment there. |
| 25 | # - kcov waits for the LAST traced descendant, and `muxd start` daemonises: | 25 | # - kcov waits for the LAST traced descendant, and `mux d start` daemonises: |
| 26 | # its launcher forks, the parent exits, and the daemon reparents to init. | 26 | # its launcher forks, the parent exits, and the daemon reparents to init. |
| 27 | # Traced, that daemon outlives the scenario, so kcov never exits and the | 27 | # Traced, that daemon outlives the scenario, so kcov never exits and the |
| 28 | # suite's `wait` on it never returns — a hang at the auto-start leg that | 28 | # suite's `wait` on it never returns — a hang at the auto-start leg that |
| 29 | # reads as a slow run, and one that ignores SIGTERM, so `timeout` will not | 29 | # reads as a slow run, and one that ignores SIGTERM, so `timeout` will not |
| 30 | # free it either. The shims therefore run `start` BARE, exporting | 30 | # free it either. The shims therefore run `start` BARE, exporting |
| 31 | # MUX_KCOV_ACTIVE so the `muxd run` it re-execs stays bare too. The cost | 31 | # MUX_KCOV_ACTIVE so the `mux d run` it re-execs stays bare too. The cost |
| 32 | # is the auto-started daemons' lines; the alternative was a suite that | 32 | # is the auto-started daemons' lines; the alternative was a suite that |
| 33 | # could not finish. `endpoint` is the same shape one hop out: it spawns | 33 | # could not finish. `endpoint` is the same shape one hop out: it spawns |
| 34 | # the daemon it announces, and the ssh handoff client then kill()s its | 34 | # the daemon it announces, and the ssh handoff client then kill()s its |
| @@ -48,11 +48,8 @@ | |||
| 48 | # tears down with SIGKILL would otherwise contribute nothing at all. | 48 | # tears down with SIGKILL would otherwise contribute nothing at all. |
| 49 | 49 | ||
| 50 | set -eu | 50 | set -eu |
| 51 | MUXD="$1"; MUX="$2"; RAWMODE="$3"; DELAYPIPE="$4"; RENDER="$5"; PTYCLIENT="$6" | 51 | MUX="$1"; RAWMODE="$2"; DELAYPIPE="$3"; RENDER="$4"; PTYCLIENT="$5" |
| 52 | WSCLIENT="$7"; MUXWEB="$8" | 52 | WSCLIENT="$6" |
| 53 | # Same list as the e2e and soak steps, muxa included — see the note in | ||
| 54 | # build.zig; all three must be handed the same binaries in the same order. | ||
| 55 | MUXA="$9" | ||
| 56 | 53 | ||
| 57 | command -v kcov > /dev/null 2>&1 || { | 54 | command -v kcov > /dev/null 2>&1 || { |
| 58 | echo "coverage FAIL: this target needs kcov (pacman -S kcov)" | 55 | echo "coverage FAIL: this target needs kcov (pacman -S kcov)" |
| @@ -69,62 +66,61 @@ RAW="$OUT/raw" | |||
| 69 | rm -rf "$OUT" | 66 | rm -rf "$OUT" |
| 70 | mkdir -p "$SHIM" "$RAW" | 67 | mkdir -p "$SHIM" "$RAW" |
| 71 | 68 | ||
| 72 | # One shim per wrapped binary, each writing to its own $$-suffixed directory: | 69 | # ONE shim, named `mux`, writing to a $$-suffixed directory per process: |
| 73 | # kcov cannot have two live processes share one output dir, and this suite | 70 | # kcov cannot have two live processes share one output dir, and this suite |
| 74 | # runs a dozen daemons and hundreds of clients. They are merged below. | 71 | # runs a dozen daemons and hundreds of clients. They are merged below. |
| 75 | # COV_WRAP names which binaries to trace. `mux` is NOT in the default set, | ||
| 76 | # and that is a limitation rather than a preference: a traced `mux` that | ||
| 77 | # auto-starts a daemon can never exit. The daemon reparents to init but stays | ||
| 78 | # TRACED — PTRACE_O_TRACEFORK follows it there — and kcov waits for its last | ||
| 79 | # traced descendant, so it waits on a process designed never to end. The | ||
| 80 | # auto-start and `--via` legs wedge on this, and a SIGTERM will not free it. | ||
| 81 | # | 72 | # |
| 82 | # Excluding the `start` verb (below) does not cover it: the daemon is spawned | 73 | # COV_WRAP names which MODES to trace, and the shim tests the mode word |
| 83 | # by the client, so `start` never appears on the shim's command line at all. | 74 | # ($1) exactly rather than matching anywhere in argv — a session named "d" |
| 84 | # The verb slot is tested exactly ($1), not matched anywhere in argv — a | 75 | # would otherwise run its client traced in silence. The client mode is NOT |
| 85 | # session named "start" would otherwise run its daemon untraced in silence. --exit-first-process is | 76 | # in the default set, and that is a limitation rather than a preference: a |
| 86 | # kcov's answer and does fix this one, but it reparents the tracer, and then | 77 | # traced client that auto-starts a daemon can never exit. The daemon is now |
| 87 | # the real process is no longer a child of the pid the suite holds — which | 78 | # a FORK of the client (spawn.zig) rather than an exec of a sibling, so it |
| 88 | # breaks hardkill and moves the hang one leg later. Daemons carry server.zig, | 79 | # is traced whenever the client is, stays traced after it reparents to |
| 89 | # so tracing them and not the client is the side of the trade worth taking. | 80 | # init, and kcov waits for its last traced descendant — a process designed |
| 81 | # never to end. The auto-start and `--via` legs wedge on this, and a | ||
| 82 | # SIGTERM will not free it. --exit-first-process is kcov's answer and does | ||
| 83 | # fix this one, but it reparents the tracer, and then the real process is | ||
| 84 | # no longer a child of the pid the suite holds — which breaks hardkill and | ||
| 85 | # moves the hang one leg later. Daemons carry server.zig, so tracing them | ||
| 86 | # and not the client is the side of the trade worth taking. | ||
| 90 | # | 87 | # |
| 91 | # Set COV_WRAP="mux" to measure the client over a slice that avoids those | 88 | # Set COV_WRAP="client" to measure the client over a slice that avoids |
| 92 | # legs (E2E_STOP_AFTER pairs with it), and merge the two runs by hand. | 89 | # those legs (E2E_STOP_AFTER pairs with it), and merge the two runs by hand. |
| 93 | WRAP="${COV_WRAP:-muxd muxa muxweb}" | 90 | WRAP="${COV_WRAP:-d a web}" |
| 94 | # Validated in THIS shell, before anything is generated. The obvious spelling | 91 | # Validated in THIS shell, before anything is generated. The obvious spelling |
| 95 | # puts the check inside the loop's word list, where `exit 1` only leaves the | 92 | # puts the check inside the case below, where `exit 1` only leaves the |
| 96 | # command substitution: the message prints, the suite runs anyway, and the | 93 | # command substitution: the message prints, the suite runs anyway, and the |
| 97 | # whole thing exits 0 — a FAIL that does not fail. | 94 | # whole thing exits 0 — a FAIL that does not fail. |
| 98 | for w in $WRAP; do | 95 | for w in $WRAP; do |
| 99 | case "$w" in | 96 | case "$w" in |
| 100 | muxd|mux|muxa|muxweb) ;; | 97 | d|a|web|client) ;; |
| 101 | *) echo "coverage FAIL: COV_WRAP names unknown binary '$w'" | 98 | *) echo "coverage FAIL: COV_WRAP names unknown mode '$w'" |
| 102 | echo " known: muxd mux muxa muxweb" | 99 | echo " known: d a web client" |
| 103 | exit 1 ;; | 100 | exit 1 ;; |
| 104 | esac | 101 | esac |
| 105 | done | 102 | done |
| 106 | 103 | ||
| 104 | real=$(cd "$(dirname "$MUX")" && pwd)/$(basename "$MUX") | ||
| 105 | # `client` is spelled as the empty mode inside the shim: the client is the | ||
| 106 | # mode you reach by typing no letter at all. | ||
| 107 | TRACED="" | ||
| 107 | for w in $WRAP; do | 108 | for w in $WRAP; do |
| 108 | case "$w" in | 109 | case "$w" in client) TRACED="$TRACED :" ;; *) TRACED="$TRACED $w" ;; esac |
| 109 | muxd) real=$MUXD ;; | 110 | done |
| 110 | mux) real=$MUX ;; | 111 | cat > "$SHIM/mux" <<SHIM_EOF |
| 111 | muxa) real=$MUXA ;; | ||
| 112 | muxweb) real=$MUXWEB ;; | ||
| 113 | esac | ||
| 114 | real=$(cd "$(dirname "$real")" && pwd)/$(basename "$real") | ||
| 115 | cat > "$SHIM/$w" <<SHIM_EOF | ||
| 116 | #!/bin/sh | 112 | #!/bin/sh |
| 117 | [ -n "\$MUX_KCOV_ACTIVE" ] && exec "$real" "\$@" | 113 | [ -n "\$MUX_KCOV_ACTIVE" ] && exec "$real" "\$@" |
| 114 | case "\${1:-}" in d|a|web) _m=\$1; _v=\${2:-} ;; *) _m=: ; _v=\${1:-} ;; esac | ||
| 115 | case " $TRACED " in *" \$_m "*) ;; *) exec "$real" "\$@" ;; esac | ||
| 116 | case "\$_v" in start|endpoint) exec "$real" "\$@" ;; esac | ||
| 118 | MUX_KCOV_ACTIVE=1; export MUX_KCOV_ACTIVE | 117 | MUX_KCOV_ACTIVE=1; export MUX_KCOV_ACTIVE |
| 119 | case "\${1:-}" in start|endpoint) exec "$real" "\$@" ;; esac | 118 | exec kcov --collect-only "--include-path=$SRC" "$RAW/mux.\$\$" "$real" "\$@" |
| 120 | exec kcov --collect-only "--include-path=$SRC" "$RAW/$w.\$\$" "$real" "\$@" | ||
| 121 | SHIM_EOF | 119 | SHIM_EOF |
| 122 | chmod +x "$SHIM/$w" | 120 | chmod +x "$SHIM/mux" |
| 123 | done | ||
| 124 | 121 | ||
| 125 | # e2e.sh prepends `dirname "$MUXD"` to PATH so an auto-started daemon is the | 122 | # e2e.sh prepends `dirname "$MUX"` to PATH, which is $SHIM here — so the |
| 126 | # one under test. That directory is $SHIM here, which is what makes the | 123 | # `mux` an ssh shim runs by name is wrapped like the one the suite spells. |
| 127 | # daemons the suite starts BY NAME rather than by path get wrapped too. | ||
| 128 | # Scenario boundaries, stamped by e2e.sh's ok(). kcov writes a database when | 124 | # Scenario boundaries, stamped by e2e.sh's ok(). kcov writes a database when |
| 129 | # a traced process EXITS, so a database's mtime and this file together say | 125 | # a traced process EXITS, so a database's mtime and this file together say |
| 130 | # which scenario was running when it was written — which is the only | 126 | # which scenario was running when it was written — which is the only |
| @@ -142,13 +138,9 @@ export E2E_TIME_SCALE="${COV_TIME_SCALE:-4}" | |||
| 142 | 138 | ||
| 143 | # The upgrade legs copy a candidate binary and watch it exec; a shim cannot be | 139 | # The upgrade legs copy a candidate binary and watch it exec; a shim cannot be |
| 144 | # exec'd into. The real path goes beside the shim. | 140 | # exec'd into. The real path goes beside the shim. |
| 145 | MUXD_ELF="$MUXD"; export MUXD_ELF | 141 | MUX_ELF="$MUX"; export MUX_ELF |
| 146 | set +e | 142 | set +e |
| 147 | # Each argument is the shim if that binary is wrapped, the real thing if not. | 143 | "$E2E" "$SHIM/mux" "$RAWMODE" "$DELAYPIPE" "$RENDER" "$PTYCLIENT" "$WSCLIENT" |
| 148 | pick() { if [ -x "$SHIM/$1" ]; then echo "$SHIM/$1"; else echo "$2"; fi; } | ||
| 149 | "$E2E" "$(pick muxd "$MUXD")" "$(pick mux "$MUX")" "$RAWMODE" "$DELAYPIPE" \ | ||
| 150 | "$RENDER" "$PTYCLIENT" "$WSCLIENT" "$(pick muxweb "$MUXWEB")" \ | ||
| 151 | "$(pick muxa "$MUXA")" | ||
| 152 | E2E_RC=$? | 144 | E2E_RC=$? |
| 153 | set -e | 145 | set -e |
| 154 | 146 | ||
test/delaypipe.zig
| Old | New | ||
|---|---|---|---|
| @@ -9,7 +9,7 @@ | |||
| 9 | //! `--via` runs its argument through `/bin/sh -c`, so two of these compose | 9 | //! `--via` runs its argument through `/bin/sh -c`, so two of these compose |
| 10 | //! into a symmetric delay with a shell pipeline and nothing else: | 10 | //! into a symmetric delay with a shell pipeline and nothing else: |
| 11 | //! | 11 | //! |
| 12 | //! mux --via "delaypipe | muxd proxy --sock S | delaypipe" | 12 | //! mux --via "delaypipe | mux d proxy --sock S | delaypipe" |
| 13 | //! | 13 | //! |
| 14 | //! The client's bytes go through the first before reaching the daemon, the | 14 | //! The client's bytes go through the first before reaching the daemon, the |
| 15 | //! daemon's answers through the second on the way back: DELAY_MS each way, | 15 | //! daemon's answers through the second on the way back: DELAY_MS each way, |
test/e2e.sh
| Old | New | ||
|---|---|---|---|
| @@ -1,39 +1,32 @@ | |||
| 1 | #!/bin/sh | 1 | #!/bin/sh |
| 2 | # End-to-end: muxd daemon + mux client. Input flows client -> daemon -> pty; | 2 | # End-to-end: the `mux d` daemon and the `mux` client, one binary. Input |
| 3 | # output flows pty -> engine -> snapshot -> client replica -> client stdout. | 3 | # flows client -> daemon -> pty; output flows pty -> engine -> snapshot -> |
| 4 | # client replica -> client stdout. | ||
| 4 | set -eu | 5 | set -eu |
| 5 | MUXD="$1" | 6 | MUX="$1" |
| 6 | # The upgrade candidate must be an ELF: the daemon execs it and the leg reads | 7 | # The upgrade candidate must be an ELF: the daemon execs it and the leg reads |
| 7 | # the exec'd process's /proc exe link. Under the coverage tracer $MUXD is a | 8 | # the exec'd process's /proc exe link. Under the coverage tracer $MUX is a |
| 8 | # shim script, so coverage.sh names the binary behind it here. | 9 | # shim script, so coverage.sh names the binary behind it here. |
| 9 | MUXD_ELF="${MUXD_ELF:-$MUXD}" | 10 | MUX_ELF="${MUX_ELF:-$MUX}" |
| 10 | MUX="$2" | ||
| 11 | # M9 prediction helpers: a deterministic stand-in for an editor, and a pipe | 11 | # M9 prediction helpers: a deterministic stand-in for an editor, and a pipe |
| 12 | # that makes a slow round trip without netem or root. | 12 | # that makes a slow round trip without netem or root. |
| 13 | RAWMODE="$3" | 13 | RAWMODE="$2" |
| 14 | DELAYPIPE="$4" | 14 | DELAYPIPE="$3" |
| 15 | # M11 convergence: replays a client capture into a grid (test/render.zig). | 15 | # M11 convergence: replays a client capture into a grid (test/render.zig). |
| 16 | RENDER="$5" | 16 | RENDER="$4" |
| 17 | # M12 pty fixture: runs the client on a real pty (test/ptyclient.zig). | 17 | # M12 pty fixture: runs the client on a real pty (test/ptyclient.zig). |
| 18 | PTYCLIENT="$6" | 18 | PTYCLIENT="$5" |
| 19 | # M-web: the scripted WebSocket replica client and the hub it speaks to | 19 | # M-web: the scripted WebSocket replica client the hub speaks to |
| 20 | # (test/wsclient.zig, src/cli/webhub_main.zig). | 20 | # (test/wsclient.zig). |
| 21 | WSCLIENT="$7" | 21 | WSCLIENT="$6" |
| 22 | MUXWEB="$8" | ||
| 23 | # M18: the agent client, used by one block — the out-of-band verbs that | ||
| 24 | # address a session by name without attaching to it. Everything else muxa | ||
| 25 | # does is test/agent.sh's business. | ||
| 26 | MUXA="$9" | ||
| 27 | 22 | ||
| 28 | # Every muxd this suite reaches for must be the one it was handed. A client | 23 | # The ssh recipes a handoff leg drives name `mux` and let the remote's PATH |
| 29 | # whose daemon is not already up AUTO-STARTS one by name, off $PATH — so on | 24 | # find it, so the build's own bin directory goes in front of the shims that |
| 30 | # a box with a release installed, a leg that forgot its `muxd run &` silently | 25 | # stand in for a remote. The client's LOCAL auto-start no longer reads PATH |
| 31 | # grades the INSTALLED binary and passes on features the build under test is | 26 | # at all — it forks into this binary's own daemon (spawn.zig) — so a leg |
| 32 | # the only one to have. (That is not hypothetical: the agent-refusal leg did | 27 | # that forgets its `mux d run &` can no longer silently grade an installed |
| 33 | # exactly this against a v0.0.1-10 with no agent code in it.) Prepending the | 28 | # release, which it did against a v0.0.1-10 with no agent code in it. |
| 34 | # build's own bin directory makes the accident harmless — the daemon that | 29 | PATH="$(cd "$(dirname "$MUX")" && pwd):$PATH" |
| 35 | # comes up is still the one under test. | ||
| 36 | PATH="$(cd "$(dirname "$MUXD")" && pwd):$PATH" | ||
| 37 | export PATH | 30 | export PATH |
| 38 | 31 | ||
| 39 | # The one thing this suite needs that it does not build: a real editor, for | 32 | # The one thing this suite needs that it does not build: a real editor, for |
| @@ -82,7 +75,7 @@ for _agtool in ssh-agent ssh-add ssh-keygen; do | |||
| 82 | done | 75 | done |
| 83 | 76 | ||
| 84 | # And the fifth, required for the fourth's reason: the upgrade legs assert | 77 | # And the fifth, required for the fourth's reason: the upgrade legs assert |
| 85 | # that `muxa` still answers under the `marks` mechanism after the exec, and | 78 | # that `mux a` still answers under the `marks` mechanism after the exec, and |
| 86 | # marks exist only in a shell mux injects OSC 133 into (shellint's | 79 | # marks exist only in a shell mux injects OSC 133 into (shellint's |
| 87 | # `bash_init`). Run against /bin/sh those legs would still go green having | 80 | # `bash_init`). Run against /bin/sh those legs would still go green having |
| 88 | # proved nothing about the one piece of session state the manifest carries | 81 | # proved nothing about the one piece of session state the manifest carries |
test/e2e_01_boot.sh
| Old | New | ||
|---|---|---|---|
| @@ -27,7 +27,7 @@ SOCK10="${TMPDIR:-/tmp}/muxd-e2e-flagwins-$$.sock" | |||
| 27 | defer_sock "$SOCK10" | 27 | defer_sock "$SOCK10" |
| 28 | QPORT2=$(( 26000 + ($$ % 4000) )) | 28 | QPORT2=$(( 26000 + ($$ % 4000) )) |
| 29 | QPORT3=$(( 31000 + ($$ % 4000) )) | 29 | QPORT3=$(( 31000 + ($$ % 4000) )) |
| 30 | # M10 `muxd start`. These daemons are spawned DETACHED, so the suite never | 30 | # M10 `mux d start`. These daemons are spawned DETACHED, so the suite never |
| 31 | # holds their pids as shell jobs — it reads them off the up-line and kills | 31 | # holds their pids as shell jobs — it reads them off the up-line and kills |
| 32 | # by that tracked pid, never by name. | 32 | # by that tracked pid, never by name. |
| 33 | SOCK8="${TMPDIR:-/tmp}/muxd-e2e-start-$$.sock" | 33 | SOCK8="${TMPDIR:-/tmp}/muxd-e2e-start-$$.sock" |
| @@ -40,18 +40,18 @@ QPORT4=$(( 36000 + ($$ % 4000) )) | |||
| 40 | 40 | ||
| 41 | # --- M10: --version answers "did the scp land" without a daemon anywhere. | 41 | # --- M10: --version answers "did the scp land" without a daemon anywhere. |
| 42 | # No convergence: no daemon and no client, so there is no grid on either side. | 42 | # No convergence: no daemon and no client, so there is no grid on either side. |
| 43 | "$MUXD" --version | grep -q '^muxd 0\.' || { echo "e2e FAIL: muxd --version"; exit 1; } | 43 | "$MUX" d --version | grep -q '^mux 0\.' || { echo "e2e FAIL: mux d --version"; exit 1; } |
| 44 | "$MUX" --version | grep -q '^mux 0\.' || { echo "e2e FAIL: mux --version"; exit 1; } | 44 | "$MUX" --version | grep -q '^mux 0\.' || { echo "e2e FAIL: mux --version"; exit 1; } |
| 45 | # Both must report the SAME version: the greps above pass just as happily for | 45 | # Both must report the SAME version: the greps above pass just as happily for |
| 46 | # a binary that re-hardcoded a literal instead of reading build.zig's constant, | 46 | # a binary that re-hardcoded a literal instead of reading build.zig's constant, |
| 47 | # which is exactly the drift --version exists to rule out. | 47 | # which is exactly the drift --version exists to rule out. |
| 48 | [ "$("$MUXD" --version | cut -d' ' -f2)" = "$("$MUX" --version | cut -d' ' -f2)" ] || { | 48 | [ "$("$MUX" d --version | cut -d' ' -f2)" = "$("$MUX" --version | cut -d' ' -f2)" ] || { |
| 49 | echo "e2e FAIL: version drift between binaries"; exit 1; } | 49 | echo "e2e FAIL: version drift between binaries"; exit 1; } |
| 50 | ok "--version on both binaries" | 50 | ok "--version on both binaries" |
| 51 | 51 | ||
| 52 | # --- M10: keygen writes 0600, prints the path, refuses a second run. | 52 | # --- M10: keygen writes 0600, prints the path, refuses a second run. |
| 53 | # No convergence: keygen touches the filesystem, never a session. | 53 | # No convergence: keygen touches the filesystem, never a session. |
| 54 | KEYOUT=$("$MUXD" keygen) | 54 | KEYOUT=$("$MUX" d keygen) |
| 55 | [ "$KEYOUT" = "$XDG_CONFIG_HOME/mux/key" ] || { | 55 | [ "$KEYOUT" = "$XDG_CONFIG_HOME/mux/key" ] || { |
| 56 | echo "e2e FAIL: keygen printed '$KEYOUT'"; exit 1; } | 56 | echo "e2e FAIL: keygen printed '$KEYOUT'"; exit 1; } |
| 57 | PERMS=$(stat -c %a "$KEYOUT") | 57 | PERMS=$(stat -c %a "$KEYOUT") |
| @@ -59,7 +59,7 @@ PERMS=$(stat -c %a "$KEYOUT") | |||
| 59 | DPERMS=$(stat -c %a "$XDG_CONFIG_HOME/mux") | 59 | DPERMS=$(stat -c %a "$XDG_CONFIG_HOME/mux") |
| 60 | [ "$DPERMS" = "700" ] || { echo "e2e FAIL: key dir perms $DPERMS, want 700"; exit 1; } | 60 | [ "$DPERMS" = "700" ] || { echo "e2e FAIL: key dir perms $DPERMS, want 700"; exit 1; } |
| 61 | SUM1=$(sha256sum "$KEYOUT") | 61 | SUM1=$(sha256sum "$KEYOUT") |
| 62 | if "$MUXD" keygen > /dev/null 2>&1; then | 62 | if "$MUX" d keygen > /dev/null 2>&1; then |
| 63 | echo "e2e FAIL: second keygen did not refuse"; exit 1 | 63 | echo "e2e FAIL: second keygen did not refuse"; exit 1 |
| 64 | fi | 64 | fi |
| 65 | SUM2=$(sha256sum "$KEYOUT") | 65 | SUM2=$(sha256sum "$KEYOUT") |
| @@ -78,7 +78,7 @@ await_out "$OUT" "e2e-works" "client render missing output" | |||
| 78 | pipe_detach | 78 | pipe_detach |
| 79 | 79 | ||
| 80 | # 2. The daemon kept the session; its grid must match. | 80 | # 2. The daemon kept the session; its grid must match. |
| 81 | "$MUXD" dump --sock "$SOCK" | grep -q "e2e-works" || { | 81 | "$MUX" d dump --sock "$SOCK" | grep -q "e2e-works" || { |
| 82 | echo "e2e FAIL: daemon grid missing output"; exit 1; | 82 | echo "e2e FAIL: daemon grid missing output"; exit 1; |
| 83 | } | 83 | } |
| 84 | 84 | ||
| @@ -169,17 +169,17 @@ kill -0 "$D1PID" || { echo "e2e FAIL: daemon died in two-client scenario"; exit | |||
| 169 | assert_converged "$OUT.b" "$SOCK" "two clients" | 169 | assert_converged "$OUT.b" "$SOCK" "two clients" |
| 170 | rm_swept "$OUT.a" "$OUT.b" | 170 | rm_swept "$OUT.a" "$OUT.b" |
| 171 | 171 | ||
| 172 | # --- M6: the same protocol over an arbitrary byte pipe. `muxd proxy` is a | 172 | # --- M6: the same protocol over an arbitrary byte pipe. `mux d proxy` is a |
| 173 | # frame-agnostic stdio<->socket pump; if the session works through it, the | 173 | # frame-agnostic stdio<->socket pump; if the session works through it, the |
| 174 | # transport really is a swap. The string is one shell word here and is split | 174 | # transport really is a swap. The string is one shell word here and is split |
| 175 | # by the /bin/sh -c that mux spawns, so $MUXD must contain no spaces — it is | 175 | # by the /bin/sh -c that mux spawns, so $MUX d must contain no spaces — it is |
| 176 | # the build tree's artifact path, which does not. | 176 | # the build tree's artifact path, which does not. |
| 177 | # | 177 | # |
| 178 | # XDG_RUNTIME_DIR is pointed at nothing so the test cannot pass by environment | 178 | # XDG_RUNTIME_DIR is pointed at nothing so the test cannot pass by environment |
| 179 | # luck: if --via ever silently fell back to the default socket path, that path | 179 | # luck: if --via ever silently fell back to the default socket path, that path |
| 180 | # would resolve to a directory that does not exist and the session would fail | 180 | # would resolve to a directory that does not exist and the session would fail |
| 181 | # instead of quietly attaching over the local socket. | 181 | # instead of quietly attaching over the local socket. |
| 182 | pipe_mux "$OUT.via" "" env XDG_RUNTIME_DIR=/nonexistent-mux-e2e "$MUX" --via "$MUXD proxy --sock $SOCK" | 182 | pipe_mux "$OUT.via" "" env XDG_RUNTIME_DIR=/nonexistent-mux-e2e "$MUX" --via "$MUX d proxy --sock $SOCK" |
| 183 | pipe_send 'printf "m6-%%s\\n" via-pipe\n' | 183 | pipe_send 'printf "m6-%%s\\n" via-pipe\n' |
| 184 | await_out "$OUT.via" "m6-via-pipe" "--via transport" | 184 | await_out "$OUT.via" "m6-via-pipe" "--via transport" |
| 185 | pipe_detach | 185 | pipe_detach |
| @@ -197,7 +197,7 @@ set -e | |||
| 197 | [ "$VRC" = "1" ] || { echo "e2e FAIL: dead --via exit $VRC, want 1"; exit 1; } | 197 | [ "$VRC" = "1" ] || { echo "e2e FAIL: dead --via exit $VRC, want 1"; exit 1; } |
| 198 | grep -q "transport command failed before a session started" "$OUT.via" || { | 198 | grep -q "transport command failed before a session started" "$OUT.via" || { |
| 199 | echo "e2e FAIL: --via death message:"; cat "$OUT.via"; exit 1; } | 199 | echo "e2e FAIL: --via death message:"; cat "$OUT.via"; exit 1; } |
| 200 | grep -q "connection to muxd lost" "$OUT.via" && { | 200 | grep -q "connection to the daemon lost" "$OUT.via" && { |
| 201 | echo "e2e FAIL: the old lie is still printed"; cat "$OUT.via"; exit 1; } | 201 | echo "e2e FAIL: the old lie is still printed"; cat "$OUT.via"; exit 1; } |
| 202 | rm_swept "$OUT.via" | 202 | rm_swept "$OUT.via" |
| 203 | ok "--via failure says what happened" | 203 | ok "--via failure says what happened" |
| @@ -227,7 +227,7 @@ grep -q "transport command failed before a session started" "$OUT.dead" || { | |||
| 227 | # just as well if BOTH lines are printed, so the old one must be absent from | 227 | # just as well if BOTH lines are printed, so the old one must be absent from |
| 228 | # the very capture that just satisfied it. The dropped parenthetical guessed | 228 | # the very capture that just satisfied it. The dropped parenthetical guessed |
| 229 | # a cause the transport's own stderr had already named. | 229 | # a cause the transport's own stderr had already named. |
| 230 | grep -q "is muxd installed on the host" "$OUT.dead" && { | 230 | grep -q "is mux installed on the host" "$OUT.dead" && { |
| 231 | echo "e2e FAIL: old lostMsg wording still emitted alongside the new pin" | 231 | echo "e2e FAIL: old lostMsg wording still emitted alongside the new pin" |
| 232 | cat "$OUT.dead"; exit 1; } | 232 | cat "$OUT.dead"; exit 1; } |
| 233 | # Free ride on this capture, for a property with nothing to do with the exit | 233 | # Free ride on this capture, for a property with nothing to do with the exit |
| @@ -248,12 +248,12 @@ grep -qaF "$(printf '\033[22;0t')" "$OUT.dead" && { | |||
| 248 | # it replaced was a 2s poll ending in "daemon did not answer", a timeout | 248 | # it replaced was a 2s poll ending in "daemon did not answer", a timeout |
| 249 | # story about a path that was doomed at parse. | 249 | # story about a path that was doomed at parse. |
| 250 | LONGSOCK="/tmp/$(printf 'a%.0s' $(seq 1 110)).sock" | 250 | LONGSOCK="/tmp/$(printf 'a%.0s' $(seq 1 110)).sock" |
| 251 | "$MUXD" dump --sock "$LONGSOCK" > "$OUT.long" 2>&1 && { echo "e2e FAIL: muxd accepted long sock"; exit 1; } | 251 | "$MUX" d dump --sock "$LONGSOCK" > "$OUT.long" 2>&1 && { echo "e2e FAIL: mux d accepted long sock"; exit 1; } |
| 252 | grep -q "muxd: socket path too long" "$OUT.long" || { echo "e2e FAIL:"; cat "$OUT.long"; exit 1; } | 252 | grep -q "mux d: socket path too long" "$OUT.long" || { echo "e2e FAIL:"; cat "$OUT.long"; exit 1; } |
| 253 | "$MUX" --sock "$LONGSOCK" > "$OUT.longc" 2>&1 && { echo "e2e FAIL: mux accepted long sock"; exit 1; } | 253 | "$MUX" --sock "$LONGSOCK" > "$OUT.longc" 2>&1 && { echo "e2e FAIL: mux accepted long sock"; exit 1; } |
| 254 | grep -q "mux: socket path too long" "$OUT.longc" || { echo "e2e FAIL:"; cat "$OUT.longc"; exit 1; } | 254 | grep -q "mux: socket path too long" "$OUT.longc" || { echo "e2e FAIL:"; cat "$OUT.longc"; exit 1; } |
| 255 | grep -q "daemon did not answer" "$OUT.longc" && { echo "e2e FAIL: still the timeout story"; cat "$OUT.longc"; exit 1; } | 255 | grep -q "daemon did not answer" "$OUT.longc" && { echo "e2e FAIL: still the timeout story"; cat "$OUT.longc"; exit 1; } |
| 256 | "$MUXD" --version --sock "$LONGSOCK" >/dev/null 2>&1 || { echo "e2e FAIL: --version refused over sock length"; exit 1; } | 256 | "$MUX" d --version --sock "$LONGSOCK" >/dev/null 2>&1 || { echo "e2e FAIL: --version refused over sock length"; exit 1; } |
| 257 | 257 | ||
| 258 | rm_swept "$OUT.dead" "$OUT.long" "$OUT.longc" | 258 | rm_swept "$OUT.dead" "$OUT.long" "$OUT.longc" |
| 259 | 259 | ||
| @@ -296,7 +296,7 @@ rm_swept "$OUT.abort" "$SOCK2" | |||
| 296 | # still across the tear proves the resume was a delta — which is the whole | 296 | # still across the tear proves the resume was a delta — which is the whole |
| 297 | # milestone claim. | 297 | # milestone claim. |
| 298 | pipe_mux "$OUT.m7" "$OUT.m7.err" env XDG_RUNTIME_DIR=/nonexistent-mux-e2e timeout 40 \ | 298 | pipe_mux "$OUT.m7" "$OUT.m7.err" env XDG_RUNTIME_DIR=/nonexistent-mux-e2e timeout 40 \ |
| 299 | "$MUX" --via "$MUXD proxy --sock $SOCK" | 299 | "$MUX" --via "$MUX d proxy --sock $SOCK" |
| 300 | pipe_send 'printf "m7-%%s\\n" before\n' | 300 | pipe_send 'printf "m7-%%s\\n" before\n' |
| 301 | 301 | ||
| 302 | # Tear only once the session is provably live and idle: the marker is the | 302 | # Tear only once the session is provably live and idle: the marker is the |
| @@ -305,7 +305,7 @@ wait_for "$OUT.m7" "m7-before" 20 || { | |||
| 305 | echo "e2e FAIL: m7 pre-tear output never arrived" | 305 | echo "e2e FAIL: m7 pre-tear output never arrived" |
| 306 | cat "$OUT.m7" "$OUT.m7.err" 2>/dev/null; exit 1; | 306 | cat "$OUT.m7" "$OUT.m7.err" 2>/dev/null; exit 1; |
| 307 | } | 307 | } |
| 308 | SNAPS_BEFORE=$("$MUXD" stats --sock "$SOCK" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') | 308 | SNAPS_BEFORE=$("$MUX" d stats --sock "$SOCK" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') |
| 309 | PAINTS_BEFORE=$(repaints "$OUT.m7") | 309 | PAINTS_BEFORE=$(repaints "$OUT.m7") |
| 310 | PPID_PROXY=$(proxy_pid "$SOCK") | 310 | PPID_PROXY=$(proxy_pid "$SOCK") |
| 311 | [ -n "$PPID_PROXY" ] || { echo "e2e FAIL: could not find the proxy to kill"; exit 1; } | 311 | [ -n "$PPID_PROXY" ] || { echo "e2e FAIL: could not find the proxy to kill"; exit 1; } |
| @@ -318,7 +318,7 @@ await_repaint "$OUT.m7" "$PAINTS_BEFORE" "m7 client never resumed after the tran | |||
| 318 | pipe_send 'printf "m7-%%s\\n" after\n' | 318 | pipe_send 'printf "m7-%%s\\n" after\n' |
| 319 | await_out "$OUT.m7" "m7-after" "m7 client did not resume after the transport was killed" | 319 | await_out "$OUT.m7" "m7-after" "m7 client did not resume after the transport was killed" |
| 320 | pipe_detach "m7 client" | 320 | pipe_detach "m7 client" |
| 321 | SNAPS_AFTER=$("$MUXD" stats --sock "$SOCK" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') | 321 | SNAPS_AFTER=$("$MUX" d stats --sock "$SOCK" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') |
| 322 | [ -n "$SNAPS_BEFORE" ] && [ -n "$SNAPS_AFTER" ] || { | 322 | [ -n "$SNAPS_BEFORE" ] && [ -n "$SNAPS_AFTER" ] || { |
| 323 | echo "e2e FAIL: could not read the snapshots counter"; exit 1; | 323 | echo "e2e FAIL: could not read the snapshots counter"; exit 1; |
| 324 | } | 324 | } |
| @@ -359,7 +359,7 @@ pipe_detach "m7b client" | |||
| 359 | # pre-restart marker cannot be in its grid. If it were, we would be looking | 359 | # pre-restart marker cannot be in its grid. If it were, we would be looking |
| 360 | # at a client that reconnected to something with the old session's history — | 360 | # at a client that reconnected to something with the old session's history — |
| 361 | # i.e. the epoch fence letting a stale seq through. | 361 | # i.e. the epoch fence letting a stale seq through. |
| 362 | if "$MUXD" dump --sock "$SOCK3" | grep -q "m7b-pre-restart"; then | 362 | if "$MUX" d dump --sock "$SOCK3" | grep -q "m7b-pre-restart"; then |
| 363 | echo "e2e FAIL: restarted daemon's grid still holds the pre-restart marker" | 363 | echo "e2e FAIL: restarted daemon's grid still holds the pre-restart marker" |
| 364 | exit 1 | 364 | exit 1 |
| 365 | fi | 365 | fi |
| @@ -368,7 +368,7 @@ fi | |||
| 368 | # counter sees it: the reconnecting client is this daemon's sole client, so a | 368 | # counter sees it: the reconnecting client is this daemon's sole client, so a |
| 369 | # snapshot in its stats is proof it was resynced from scratch rather than | 369 | # snapshot in its stats is proof it was resynced from scratch rather than |
| 370 | # resumed off a seq that belongs to a session that no longer exists. | 370 | # resumed off a seq that belongs to a session that no longer exists. |
| 371 | SNAPS_NEW=$("$MUXD" stats --sock "$SOCK3" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') | 371 | SNAPS_NEW=$("$MUX" d stats --sock "$SOCK3" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') |
| 372 | [ -n "$SNAPS_NEW" ] || { echo "e2e FAIL: could not read the restarted daemon's counters"; exit 1; } | 372 | [ -n "$SNAPS_NEW" ] || { echo "e2e FAIL: could not read the restarted daemon's counters"; exit 1; } |
| 373 | [ "$SNAPS_NEW" -ge 1 ] || { | 373 | [ "$SNAPS_NEW" -ge 1 ] || { |
| 374 | echo "e2e FAIL: restarted daemon served no snapshot ($SNAPS_NEW); the stale seq was honoured" | 374 | echo "e2e FAIL: restarted daemon served no snapshot ($SNAPS_NEW); the stale seq was honoured" |
| @@ -379,7 +379,7 @@ rm_swept "$OUT.m7b" "$OUT.m7b.err" | |||
| 379 | 379 | ||
| 380 | # --- M8: the --quic flags. Every refusal must cost nothing — no session | 380 | # --- M8: the --quic flags. Every refusal must cost nothing — no session |
| 381 | # socket, no shell, no stack trace — and the accepted case must leave a | 381 | # socket, no shell, no stack trace — and the accepted case must leave a |
| 382 | # daemon that is both listening on UDP and still an ordinary muxd. | 382 | # daemon that is both listening on UDP and still an ordinary daemon. |
| 383 | # | 383 | # |
| 384 | # The client half of this (attaching over quic://) is Task 3; what is proven | 384 | # The client half of this (attaching over quic://) is Task 3; what is proven |
| 385 | # here is the daemon's side of the command line. | 385 | # here is the daemon's side of the command line. |
| @@ -399,15 +399,15 @@ refuse() { | |||
| 399 | # Timed out rather than trusted to exit: every case here is a refusal, so | 399 | # Timed out rather than trusted to exit: every case here is a refusal, so |
| 400 | # a regression that ACCEPTS one would otherwise run a daemon forever and | 400 | # a regression that ACCEPTS one would otherwise run a daemon forever and |
| 401 | # hang the suite instead of failing it. 124 is a distinguishable answer. | 401 | # hang the suite instead of failing it. 124 is a distinguishable answer. |
| 402 | env XDG_CONFIG_HOME="$_cfg" timeout 10 "$MUXD" run --sock "$SOCK4" \ | 402 | env XDG_CONFIG_HOME="$_cfg" timeout 10 "$MUX" d run --sock "$SOCK4" \ |
| 403 | --shell /bin/sh "$@" > "$OUT.q" 2>&1 | 403 | --shell /bin/sh "$@" > "$OUT.q" 2>&1 |
| 404 | _rc=$? | 404 | _rc=$? |
| 405 | set -e | 405 | set -e |
| 406 | [ "$_rc" -eq "$_want" ] || { | 406 | [ "$_rc" -eq "$_want" ] || { |
| 407 | echo "e2e FAIL: muxd run $* exited $_rc (want $_want)"; cat "$OUT.q"; exit 1; | 407 | echo "e2e FAIL: mux d run $* exited $_rc (want $_want)"; cat "$OUT.q"; exit 1; |
| 408 | } | 408 | } |
| 409 | [ ! -e "$SOCK4" ] || { | 409 | [ ! -e "$SOCK4" ] || { |
| 410 | echo "e2e FAIL: muxd run $* was refused but left $SOCK4 behind"; exit 1; | 410 | echo "e2e FAIL: mux d run $* was refused but left $SOCK4 behind"; exit 1; |
| 411 | } | 411 | } |
| 412 | # One line of complaint plus the usage block, and not a stack trace: a | 412 | # One line of complaint plus the usage block, and not a stack trace: a |
| 413 | # Zig panic runs to dozens of lines and names a source file, which is | 413 | # Zig panic runs to dozens of lines and names a source file, which is |
| @@ -419,10 +419,10 @@ refuse() { | |||
| 419 | # balloon with it and quietly stop catching a panic. The absolute one | 419 | # balloon with it and quietly stop catching a panic. The absolute one |
| 420 | # cannot drift, and 15 sits above any plausible complaint-plus-usage | 420 | # cannot drift, and 15 sits above any plausible complaint-plus-usage |
| 421 | # and below the ~20 lines of the smallest Debug panic. | 421 | # and below the ~20 lines of the smallest Debug panic. |
| 422 | _usage_lines=$("$MUXD" 2>&1 | wc -l) | 422 | _usage_lines=$("$MUX" d 2>&1 | wc -l) |
| 423 | _lines=$(wc -l < "$OUT.q") | 423 | _lines=$(wc -l < "$OUT.q") |
| 424 | { [ "$_lines" -le $((_usage_lines + 2)) ] && [ "$_lines" -le 15 ]; } || { | 424 | { [ "$_lines" -le $((_usage_lines + 2)) ] && [ "$_lines" -le 15 ]; } || { |
| 425 | echo "e2e FAIL: muxd run $* answered with more than a message:"; cat "$OUT.q"; exit 1; | 425 | echo "e2e FAIL: mux d run $* answered with more than a message:"; cat "$OUT.q"; exit 1; |
| 426 | } | 426 | } |
| 427 | } | 427 | } |
| 428 | 428 | ||
| @@ -447,9 +447,9 @@ refuse 1 "$XDG_CONFIG_HOME" --quic "127.0.0.1:$QPORT" --key "$QKEY.bad" | |||
| 447 | refuse 1 "$XDG_CONFIG_HOME" --quic "127.0.0.1:$QPORT" --key "$QKEY.missing" | 447 | refuse 1 "$XDG_CONFIG_HOME" --quic "127.0.0.1:$QPORT" --key "$QKEY.missing" |
| 448 | 448 | ||
| 449 | # The key-missing message must name the way out, not just the absence. | 449 | # The key-missing message must name the way out, not just the absence. |
| 450 | env XDG_CONFIG_HOME="$NOKEY_CFG" "$MUXD" run --sock "$SOCK4.nokey" \ | 450 | env XDG_CONFIG_HOME="$NOKEY_CFG" "$MUX" d run --sock "$SOCK4.nokey" \ |
| 451 | --quic "127.0.0.1:$QPORT" 2> "$OUT.nokey" || true | 451 | --quic "127.0.0.1:$QPORT" 2> "$OUT.nokey" || true |
| 452 | grep -q "muxd keygen" "$OUT.nokey" || { | 452 | grep -q "mux d keygen" "$OUT.nokey" || { |
| 453 | echo "e2e FAIL: key-missing message does not name keygen"; cat "$OUT.nokey"; exit 1; } | 453 | echo "e2e FAIL: key-missing message does not name keygen"; cat "$OUT.nokey"; exit 1; } |
| 454 | rm -rf "$NOKEY_CFG" | 454 | rm -rf "$NOKEY_CFG" |
| 455 | ok "no key anywhere is refused, and says how to make one" | 455 | ok "no key anywhere is refused, and says how to make one" |
| @@ -477,7 +477,7 @@ grep -qi " $QHEX " /proc/net/udp || { | |||
| 477 | set +e | 477 | set +e |
| 478 | # Same reasoning as refuse(): if the second daemon ever succeeds it runs | 478 | # Same reasoning as refuse(): if the second daemon ever succeeds it runs |
| 479 | # until killed, so the failure has to be a timeout rather than a hang. | 479 | # until killed, so the failure has to be a timeout rather than a hang. |
| 480 | timeout 10 "$MUXD" run --sock "$SOCK4.second" --shell /bin/sh \ | 480 | timeout 10 "$MUX" d run --sock "$SOCK4.second" --shell /bin/sh \ |
| 481 | --quic "127.0.0.1:$QPORT" --key "$QKEY" --quic-idle-ms 15000 > "$OUT.q" 2>&1 | 481 | --quic "127.0.0.1:$QPORT" --key "$QKEY" --quic-idle-ms 15000 > "$OUT.q" 2>&1 |
| 482 | RC=$? | 482 | RC=$? |
| 483 | set -e | 483 | set -e |
| @@ -502,7 +502,7 @@ pipe_detach | |||
| 502 | grep -q "quic-flags-ok" "$OUT.q" || { | 502 | grep -q "quic-flags-ok" "$OUT.q" || { |
| 503 | echo "e2e FAIL: --quic daemon did not serve an ordinary socket client"; cat "$OUT.q"; exit 1; | 503 | echo "e2e FAIL: --quic daemon did not serve an ordinary socket client"; cat "$OUT.q"; exit 1; |
| 504 | } | 504 | } |
| 505 | "$MUXD" dump --sock "$SOCK4" | grep -q "quic-flags-ok" || { | 505 | "$MUX" d dump --sock "$SOCK4" | grep -q "quic-flags-ok" || { |
| 506 | echo "e2e FAIL: --quic daemon's grid missing output"; exit 1; | 506 | echo "e2e FAIL: --quic daemon's grid missing output"; exit 1; |
| 507 | } | 507 | } |
| 508 | kill -0 "$D4PID" || { echo "e2e FAIL: --quic daemon died"; exit 1; } | 508 | kill -0 "$D4PID" || { echo "e2e FAIL: --quic daemon died"; exit 1; } |
| @@ -521,7 +521,7 @@ grep -q "quic-attach-ok" "$OUT.qc" || { | |||
| 521 | echo "e2e FAIL: quic:// client render missing output" | 521 | echo "e2e FAIL: quic:// client render missing output" |
| 522 | cat "$OUT.qc" "$OUT.qc.err" 2>/dev/null; exit 1; | 522 | cat "$OUT.qc" "$OUT.qc.err" 2>/dev/null; exit 1; |
| 523 | } | 523 | } |
| 524 | "$MUXD" dump --sock "$SOCK4" | grep -q "quic-attach-ok" || { | 524 | "$MUX" d dump --sock "$SOCK4" | grep -q "quic-attach-ok" || { |
| 525 | echo "e2e FAIL: daemon grid missing the quic:// client's output"; exit 1; | 525 | echo "e2e FAIL: daemon grid missing the quic:// client's output"; exit 1; |
| 526 | } | 526 | } |
| 527 | assert_converged "$OUT.qc" "$SOCK4" "quic attach" | 527 | assert_converged "$OUT.qc" "$SOCK4" "quic attach" |
| @@ -530,7 +530,7 @@ assert_converged "$OUT.qc" "$SOCK4" "quic attach" | |||
| 530 | # find it — and must be served as a fresh attach (its own snapshot), | 530 | # find it — and must be served as a fresh attach (its own snapshot), |
| 531 | # because a client that detached deliberately holds nothing to resume | 531 | # because a client that detached deliberately holds nothing to resume |
| 532 | # from. The counter is what tells that apart from a delta. | 532 | # from. The counter is what tells that apart from a delta. |
| 533 | SNAPS_RA=$("$MUXD" stats --sock "$SOCK4" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') | 533 | SNAPS_RA=$("$MUX" d stats --sock "$SOCK4" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') |
| 534 | pipe_mux "$OUT.qc" "$OUT.qc.err" timeout 30 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" --quic-idle-ms 15000 | 534 | pipe_mux "$OUT.qc" "$OUT.qc.err" timeout 30 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" --quic-idle-ms 15000 |
| 535 | pipe_send 'printf "quic-%%s\\n" reattached\n' | 535 | pipe_send 'printf "quic-%%s\\n" reattached\n' |
| 536 | await_out "$OUT.qc" "quic-reattached" "quic-reattached never reached the client" | 536 | await_out "$OUT.qc" "quic-reattached" "quic-reattached never reached the client" |
| @@ -545,13 +545,13 @@ grep -q "quic-reattached" "$OUT.qc" || { | |||
| 545 | echo "e2e FAIL: quic:// reattach could not run a command" | 545 | echo "e2e FAIL: quic:// reattach could not run a command" |
| 546 | cat "$OUT.qc" "$OUT.qc.err" 2>/dev/null; exit 1; | 546 | cat "$OUT.qc" "$OUT.qc.err" 2>/dev/null; exit 1; |
| 547 | } | 547 | } |
| 548 | SNAPS_RA2=$("$MUXD" stats --sock "$SOCK4" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') | 548 | SNAPS_RA2=$("$MUX" d stats --sock "$SOCK4" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') |
| 549 | [ "$((SNAPS_RA2 - SNAPS_RA))" -eq 1 ] || { | 549 | [ "$((SNAPS_RA2 - SNAPS_RA))" -eq 1 ] || { |
| 550 | echo "e2e FAIL: reattach served $((SNAPS_RA2 - SNAPS_RA)) snapshots (want exactly 1)" | 550 | echo "e2e FAIL: reattach served $((SNAPS_RA2 - SNAPS_RA)) snapshots (want exactly 1)" |
| 551 | exit 1; | 551 | exit 1; |
| 552 | } | 552 | } |
| 553 | # ...and the slot it held is free again now that it has gone. | 553 | # ...and the slot it held is free again now that it has gone. |
| 554 | CLIENTS=$("$MUXD" stats --sock "$SOCK4" | sed -n 's/.*clients=\([0-9]*\).*/\1/p') | 554 | CLIENTS=$("$MUX" d stats --sock "$SOCK4" | sed -n 's/.*clients=\([0-9]*\).*/\1/p') |
| 555 | [ "$CLIENTS" = "0" ] || { | 555 | [ "$CLIENTS" = "0" ] || { |
| 556 | echo "e2e FAIL: $CLIENTS client slots still held after a clean detach (want 0)"; exit 1; | 556 | echo "e2e FAIL: $CLIENTS client slots still held after a clean detach (want 0)"; exit 1; |
| 557 | } | 557 | } |
| @@ -632,7 +632,7 @@ QMS=$(( (QT1 - QT0) / 1000000 )) | |||
| 632 | # stops answering, the client declares the transport dead and reconnects. | 632 | # stops answering, the client declares the transport dead and reconnects. |
| 633 | # Deterministic, needs no privileges, and unlike kill -9 it leaves the | 633 | # Deterministic, needs no privileges, and unlike kill -9 it leaves the |
| 634 | # session alive so the resume can be a DELTA rather than a fresh snapshot. | 634 | # session alive so the resume can be a DELTA rather than a fresh snapshot. |
| 635 | SNAPS_BEFORE=$("$MUXD" stats --sock "$SOCK4" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') | 635 | SNAPS_BEFORE=$("$MUX" d stats --sock "$SOCK4" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') |
| 636 | [ -n "$SNAPS_BEFORE" ] || { echo "e2e FAIL: could not read snapshots before the tear"; exit 1; } | 636 | [ -n "$SNAPS_BEFORE" ] || { echo "e2e FAIL: could not read snapshots before the tear"; exit 1; } |
| 637 | 637 | ||
| 638 | pipe_mux "$OUT.qr" "$OUT.qr.err" timeout 40 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" \ | 638 | pipe_mux "$OUT.qr" "$OUT.qr.err" timeout 40 "$MUX" "quic://127.0.0.1:$QPORT" --key "$QKEY" \ |
| @@ -657,7 +657,7 @@ pipe_detach "quic client" | |||
| 657 | # renders identically to a delta, so markers cannot tell them apart. One | 657 | # renders identically to a delta, so markers cannot tell them apart. One |
| 658 | # more snapshot is the fresh attach at the start of this scenario; a second | 658 | # more snapshot is the fresh attach at the start of this scenario; a second |
| 659 | # would mean the reconnect was resynced from scratch instead of resumed. | 659 | # would mean the reconnect was resynced from scratch instead of resumed. |
| 660 | SNAPS_AFTER=$("$MUXD" stats --sock "$SOCK4" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') | 660 | SNAPS_AFTER=$("$MUX" d stats --sock "$SOCK4" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') |
| 661 | [ -n "$SNAPS_AFTER" ] || { echo "e2e FAIL: could not read snapshots after the tear"; exit 1; } | 661 | [ -n "$SNAPS_AFTER" ] || { echo "e2e FAIL: could not read snapshots after the tear"; exit 1; } |
| 662 | [ "$((SNAPS_AFTER - SNAPS_BEFORE))" -eq 1 ] || { | 662 | [ "$((SNAPS_AFTER - SNAPS_BEFORE))" -eq 1 ] || { |
| 663 | echo "e2e FAIL: quic reconnect served $((SNAPS_AFTER - SNAPS_BEFORE)) snapshots (want 1:" | 663 | echo "e2e FAIL: quic reconnect served $((SNAPS_AFTER - SNAPS_BEFORE)) snapshots (want 1:" |
| @@ -692,7 +692,7 @@ D4PID=$DPID | |||
| 692 | # Nearly vacuous, and kept only because a MISSING file would still be worth | 692 | # Nearly vacuous, and kept only because a MISSING file would still be worth |
| 693 | # saying out loud: kill -9 leaves the old socket file behind, so this | 693 | # saying out loud: kill -9 leaves the old socket file behind, so this |
| 694 | # passes on the dead daemon's leavings. What proves the unix socket serves | 694 | # passes on the dead daemon's leavings. What proves the unix socket serves |
| 695 | # again is further down, where `muxd dump` and `muxd stats` answer on it; | 695 | # again is further down, where `mux d dump` and `mux d stats` answer on it; |
| 696 | # what proves the daemon serves SESSIONS again is the post-restart marker | 696 | # what proves the daemon serves SESSIONS again is the post-restart marker |
| 697 | # the resumed client gets back. | 697 | # the resumed client gets back. |
| 698 | # The UDP port really came back, and to THIS daemon. Without SO_REUSEADDR a | 698 | # The UDP port really came back, and to THIS daemon. Without SO_REUSEADDR a |
| @@ -733,7 +733,7 @@ pipe_detach "quic client" | |||
| 733 | # A new daemon means a new shell, so the old marker cannot be in its grid. | 733 | # A new daemon means a new shell, so the old marker cannot be in its grid. |
| 734 | # If it were, we would be looking at a client that resumed off a seq | 734 | # If it were, we would be looking at a client that resumed off a seq |
| 735 | # belonging to a session that no longer exists. | 735 | # belonging to a session that no longer exists. |
| 736 | if "$MUXD" dump --sock "$SOCK4" | grep -q "quic-pre-restart"; then | 736 | if "$MUX" d dump --sock "$SOCK4" | grep -q "quic-pre-restart"; then |
| 737 | echo "e2e FAIL: restarted daemon's grid still holds the pre-restart marker" | 737 | echo "e2e FAIL: restarted daemon's grid still holds the pre-restart marker" |
| 738 | exit 1 | 738 | exit 1 |
| 739 | fi | 739 | fi |
| @@ -741,7 +741,7 @@ fi | |||
| 741 | # reconnecting client is this daemon's only client, so a snapshot in its | 741 | # reconnecting client is this daemon's only client, so a snapshot in its |
| 742 | # stats is proof it was resynced from scratch rather than handed a delta off | 742 | # stats is proof it was resynced from scratch rather than handed a delta off |
| 743 | # a stale seq. | 743 | # a stale seq. |
| 744 | SNAPS_NQ=$("$MUXD" stats --sock "$SOCK4" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') | 744 | SNAPS_NQ=$("$MUX" d stats --sock "$SOCK4" | sed -n 's/.*snapshots=\([0-9]*\).*/\1/p') |
| 745 | [ -n "$SNAPS_NQ" ] || { echo "e2e FAIL: could not read the restarted daemon's counters"; exit 1; } | 745 | [ -n "$SNAPS_NQ" ] || { echo "e2e FAIL: could not read the restarted daemon's counters"; exit 1; } |
| 746 | [ "$SNAPS_NQ" -ge 1 ] || { | 746 | [ "$SNAPS_NQ" -ge 1 ] || { |
| 747 | echo "e2e FAIL: restarted daemon served no snapshot ($SNAPS_NQ); the stale seq was honoured" | 747 | echo "e2e FAIL: restarted daemon served no snapshot ($SNAPS_NQ); the stale seq was honoured" |
| @@ -757,7 +757,7 @@ D4PID="" | |||
| 757 | # 1. MUX_KEY_FILE with no --key anywhere. The daemon's env support had no | 757 | # 1. MUX_KEY_FILE with no --key anywhere. The daemon's env support had no |
| 758 | # automated coverage at all: envKey() reads the real environment, so | 758 | # automated coverage at all: envKey() reads the real environment, so |
| 759 | # only a daemon started with it set can prove the path works. | 759 | # only a daemon started with it set can prove the path works. |
| 760 | env MUX_KEY_FILE="$QKEY" "$MUXD" run --sock "$SOCK9" --shell /bin/sh \ | 760 | env MUX_KEY_FILE="$QKEY" "$MUX" d run --sock "$SOCK9" --shell /bin/sh \ |
| 761 | --quic "127.0.0.1:$QPORT2" --quic-idle-ms 3000 > "$OUT.d9.d" 2>&1 & | 761 | --quic "127.0.0.1:$QPORT2" --quic-idle-ms 3000 > "$OUT.d9.d" 2>&1 & |
| 762 | D9PID=$! | 762 | D9PID=$! |
| 763 | defer_kill "$D9PID" | 763 | defer_kill "$D9PID" |
| @@ -779,7 +779,7 @@ D9PID="" | |||
| 779 | # This is the shape that catches the two being swapped at the call site | 779 | # This is the shape that catches the two being swapped at the call site |
| 780 | # in run() — pickKey's unit test pins the ordering inside the function | 780 | # in run() — pickKey's unit test pins the ordering inside the function |
| 781 | # and is blind to the order they are handed to it. | 781 | # and is blind to the order they are handed to it. |
| 782 | env MUX_KEY_FILE="$QKEY.wrong" "$MUXD" run --sock "$SOCK10" --shell /bin/sh \ | 782 | env MUX_KEY_FILE="$QKEY.wrong" "$MUX" d run --sock "$SOCK10" --shell /bin/sh \ |
| 783 | --quic "127.0.0.1:$QPORT3" --key "$QKEY" --quic-idle-ms 3000 > "$OUT.d10.d" 2>&1 & | 783 | --quic "127.0.0.1:$QPORT3" --key "$QKEY" --quic-idle-ms 3000 > "$OUT.d10.d" 2>&1 & |
| 784 | D10PID=$! | 784 | D10PID=$! |
| 785 | defer_kill "$D10PID" | 785 | defer_kill "$D10PID" |
| @@ -800,14 +800,14 @@ ok "daemon honours MUX_KEY_FILE, and --key beats it" | |||
| 800 | rm_swept "$OUT.q" "$OUT.qc" "$OUT.qr" "$OUT.qa" "$OUT.qk" "$QKEY" "$QKEY.bad" "$QKEY.wrong" \ | 800 | rm_swept "$OUT.q" "$OUT.qc" "$OUT.qr" "$OUT.qa" "$OUT.qk" "$QKEY" "$QKEY.bad" "$QKEY.wrong" \ |
| 801 | "$OUT.qc.err" "$OUT.qr.err" "$OUT.qk.err" | 801 | "$OUT.qc.err" "$OUT.qr.err" "$OUT.qk.err" |
| 802 | 802 | ||
| 803 | # --- M10: muxd start — detached spawn, no-op rerun, race, pinned lines. | 803 | # --- M10: mux d start — detached spawn, no-op rerun, race, pinned lines. |
| 804 | # | 804 | # |
| 805 | # Every scenario below asserts a MARKER through the session, never just $?. | 805 | # Every scenario below asserts a MARKER through the session, never just $?. |
| 806 | # A client that fails to authenticate exits 0 by way of "aborted before | 806 | # A client that fails to authenticate exits 0 by way of "aborted before |
| 807 | # attaching", so an exit code cannot distinguish a working session from a | 807 | # attaching", so an exit code cannot distinguish a working session from a |
| 808 | # key or transport regression — only bytes coming back out of the shell can. | 808 | # key or transport regression — only bytes coming back out of the shell can. |
| 809 | "$MUXD" start --sock "$SOCK8" 2> "$OUT.start" | 809 | "$MUX" d start --sock "$SOCK8" 2> "$OUT.start" |
| 810 | grep -q '^muxd: starting' "$OUT.start" || { | 810 | grep -q '^mux d: starting' "$OUT.start" || { |
| 811 | echo "e2e FAIL: start printed no starting line"; cat "$OUT.start"; exit 1; } | 811 | echo "e2e FAIL: start printed no starting line"; cat "$OUT.start"; exit 1; } |
| 812 | grep -q '^up (' "$OUT.start" || { | 812 | grep -q '^up (' "$OUT.start" || { |
| 813 | echo "e2e FAIL: start printed no up line"; cat "$OUT.start"; exit 1; } | 813 | echo "e2e FAIL: start printed no up line"; cat "$OUT.start"; exit 1; } |
| @@ -829,7 +829,7 @@ pipe_mux "$OUT.s8" "$OUT.s8.err" "$MUX" --sock "$SOCK8" | |||
| 829 | pipe_send 'printf "start-%%s\\n" works\n' | 829 | pipe_send 'printf "start-%%s\\n" works\n' |
| 830 | await_out "$OUT.s8" "start-works" "start-works never reached the client" | 830 | await_out "$OUT.s8" "start-works" "start-works never reached the client" |
| 831 | pipe_detach | 831 | pipe_detach |
| 832 | "$MUXD" dump --sock "$SOCK8" | grep -q "start-works" || { | 832 | "$MUX" d dump --sock "$SOCK8" | grep -q "start-works" || { |
| 833 | echo "e2e FAIL: auto-started daemon lost the marker" | 833 | echo "e2e FAIL: auto-started daemon lost the marker" |
| 834 | cat "$OUT.s8" "$OUT.s8.err" 2>/dev/null; exit 1; } | 834 | cat "$OUT.s8" "$OUT.s8.err" 2>/dev/null; exit 1; } |
| 835 | assert_converged "$OUT.s8" "$SOCK8" "started daemon" | 835 | assert_converged "$OUT.s8" "$SOCK8" "started daemon" |
| @@ -842,10 +842,10 @@ LPERMS=$(stat -c %a "$MUXLOG") | |||
| 842 | [ "$LPERMS" = "600" ] || { echo "e2e FAIL: daemon log perms $LPERMS, want 600"; exit 1; } | 842 | [ "$LPERMS" = "600" ] || { echo "e2e FAIL: daemon log perms $LPERMS, want 600"; exit 1; } |
| 843 | 843 | ||
| 844 | # Rerun: silent no-op beyond the already-running line, exit 0, same daemon. | 844 | # Rerun: silent no-op beyond the already-running line, exit 0, same daemon. |
| 845 | "$MUXD" start --sock "$SOCK8" 2> "$OUT.start2" | 845 | "$MUX" d start --sock "$SOCK8" 2> "$OUT.start2" |
| 846 | grep -q "already running on $SOCK8 (stop it first with \`muxd stop --sock $SOCK8\`" "$OUT.start2" || { | 846 | grep -q "already running on $SOCK8 (stop it first with \`mux d stop --sock $SOCK8\`" "$OUT.start2" || { |
| 847 | echo "e2e FAIL: rerun did not say already running"; cat "$OUT.start2"; exit 1; } | 847 | echo "e2e FAIL: rerun did not say already running"; cat "$OUT.start2"; exit 1; } |
| 848 | "$MUXD" dump --sock "$SOCK8" | grep -q "start-works" || { | 848 | "$MUX" d dump --sock "$SOCK8" | grep -q "start-works" || { |
| 849 | echo "e2e FAIL: rerun replaced the daemon (marker gone)"; exit 1; } | 849 | echo "e2e FAIL: rerun replaced the daemon (marker gone)"; exit 1; } |
| 850 | 850 | ||
| 851 | # Truncated per spawn, not appended to: the log holds the CURRENT daemon's | 851 | # Truncated per spawn, not appended to: the log holds the CURRENT daemon's |
| @@ -853,7 +853,7 @@ grep -q "already running on $SOCK8 (stop it first with \`muxd stop --sock $SOCK8 | |||
| 853 | # restart. Padded, then a real spawn on a fresh path must shrink it. | 853 | # restart. Padded, then a real spawn on a fresh path must shrink it. |
| 854 | dd if=/dev/zero bs=1024 count=8 2>/dev/null >> "$MUXLOG" | 854 | dd if=/dev/zero bs=1024 count=8 2>/dev/null >> "$MUXLOG" |
| 855 | PADDED=$(stat -c %s "$MUXLOG") | 855 | PADDED=$(stat -c %s "$MUXLOG") |
| 856 | "$MUXD" start --sock "$SOCK8T" 2> "$OUT.start" | 856 | "$MUX" d start --sock "$SOCK8T" 2> "$OUT.start" |
| 857 | TPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.start") | 857 | TPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.start") |
| 858 | defer_kill "$TPID" | 858 | defer_kill "$TPID" |
| 859 | [ -n "$TPID" ] || { echo "e2e FAIL: truncation spawn reported no pid"; exit 1; } | 859 | [ -n "$TPID" ] || { echo "e2e FAIL: truncation spawn reported no pid"; exit 1; } |
| @@ -867,8 +867,8 @@ TPID="" | |||
| 867 | # survives — a second daemon on the path would have started a fresh shell). | 867 | # survives — a second daemon on the path would have started a fresh shell). |
| 868 | softkill "$SPID" && wait_gone "$SOCK8" | 868 | softkill "$SPID" && wait_gone "$SOCK8" |
| 869 | SPID="" | 869 | SPID="" |
| 870 | "$MUXD" start --sock "$SOCK8" 2> "$OUT.ra" & RA=$!; defer_kill "$RA" | 870 | "$MUX" d start --sock "$SOCK8" 2> "$OUT.ra" & RA=$!; defer_kill "$RA" |
| 871 | "$MUXD" start --sock "$SOCK8" 2> "$OUT.rb" & RB=$!; defer_kill "$RB" | 871 | "$MUX" d start --sock "$SOCK8" 2> "$OUT.rb" & RB=$!; defer_kill "$RB" |
| 872 | set +e | 872 | set +e |
| 873 | wait "$RA"; RCA=$? | 873 | wait "$RA"; RCA=$? |
| 874 | wait "$RB"; RCB=$? | 874 | wait "$RB"; RCB=$? |
| @@ -879,7 +879,7 @@ pipe_mux "$OUT.race" /dev/null "$MUX" --sock "$SOCK8" | |||
| 879 | pipe_send 'printf "race-%%s\\n" one\n' | 879 | pipe_send 'printf "race-%%s\\n" one\n' |
| 880 | await_out "$OUT.race" "race-one" "race-one never reached the client" | 880 | await_out "$OUT.race" "race-one" "race-one never reached the client" |
| 881 | pipe_detach | 881 | pipe_detach |
| 882 | "$MUXD" dump --sock "$SOCK8" | grep -q "race-one" || { | 882 | "$MUX" d dump --sock "$SOCK8" | grep -q "race-one" || { |
| 883 | echo "e2e FAIL: race: session unusable"; exit 1; } | 883 | echo "e2e FAIL: race: session unusable"; exit 1; } |
| 884 | SPID=$(cat "$OUT.ra" "$OUT.rb" | sed -n 's/.* pid=\([0-9]*\).*/\1/p' | while read -r p; do | 884 | SPID=$(cat "$OUT.ra" "$OUT.rb" | sed -n 's/.* pid=\([0-9]*\).*/\1/p' | while read -r p; do |
| 885 | kill -0 "$p" 2>/dev/null && echo "$p"; done | head -1) | 885 | kill -0 "$p" 2>/dev/null && echo "$p"; done | head -1) |
| @@ -889,29 +889,29 @@ defer_kill "$SPID" | |||
| 889 | # instead of unlinking the winner's socket and binding over it. The race | 889 | # instead of unlinking the winner's socket and binding over it. The race |
| 890 | # above cannot see that — both starts report success either way, and the | 890 | # above cannot see that — both starts report success either way, and the |
| 891 | # marker comes back from whichever daemon owns the path — so it is pinned | 891 | # marker comes back from whichever daemon owns the path — so it is pinned |
| 892 | # head-on: a second `muxd run` against a live socket must refuse. | 892 | # head-on: a second `mux d run` against a live socket must refuse. |
| 893 | set +e | 893 | set +e |
| 894 | timeout 10 "$MUXD" run --sock "$SOCK8" --shell /bin/sh > "$OUT.rb" 2>&1 | 894 | timeout 10 "$MUX" d run --sock "$SOCK8" --shell /bin/sh > "$OUT.rb" 2>&1 |
| 895 | RCS=$? | 895 | RCS=$? |
| 896 | set -e | 896 | set -e |
| 897 | [ "$RCS" -eq 1 ] || { | 897 | [ "$RCS" -eq 1 ] || { |
| 898 | echo "e2e FAIL: second muxd run on a live socket exited $RCS (want 1; 124 means it took the path)" | 898 | echo "e2e FAIL: second mux d run on a live socket exited $RCS (want 1; 124 means it took the path)" |
| 899 | cat "$OUT.rb"; exit 1; } | 899 | cat "$OUT.rb"; exit 1; } |
| 900 | grep -q "already running" "$OUT.rb" || { | 900 | grep -q "already running" "$OUT.rb" || { |
| 901 | echo "e2e FAIL: second muxd run refused, but not as already-running"; cat "$OUT.rb"; exit 1; } | 901 | echo "e2e FAIL: second mux d run refused, but not as already-running"; cat "$OUT.rb"; exit 1; } |
| 902 | # ...and the session the winner was serving is undisturbed. | 902 | # ...and the session the winner was serving is undisturbed. |
| 903 | "$MUXD" dump --sock "$SOCK8" | grep -q "race-one" || { | 903 | "$MUX" d dump --sock "$SOCK8" | grep -q "race-one" || { |
| 904 | echo "e2e FAIL: the refused daemon disturbed the live session"; exit 1; } | 904 | echo "e2e FAIL: the refused daemon disturbed the live session"; exit 1; } |
| 905 | # The race client's own capture, checked here rather than above: the refused | 905 | # The race client's own capture, checked here rather than above: the refused |
| 906 | # `muxd run` between the two touches no session, so the grid has not moved. | 906 | # `mux d run` between the two touches no session, so the grid has not moved. |
| 907 | assert_converged "$OUT.race" "$SOCK8" "start race" | 907 | assert_converged "$OUT.race" "$SOCK8" "start race" |
| 908 | 908 | ||
| 909 | softkill "$SPID" || true | 909 | softkill "$SPID" || true |
| 910 | SPID="" | 910 | SPID="" |
| 911 | ok "muxd start — spawn, no-op rerun, log truncation, race" | 911 | ok "mux d start — spawn, no-op rerun, log truncation, race" |
| 912 | 912 | ||
| 913 | # --- M10: a start whose daemon dies young REPORTS it. This is the first-run | 913 | # --- M10: a start whose daemon dies young REPORTS it. This is the first-run |
| 914 | # mistake the failure line exists for — `ssh HOST 'muxd start --quic 0.0.0.0'` | 914 | # mistake the failure line exists for — `ssh HOST 'mux d start --quic 0.0.0.0'` |
| 915 | # before the key was ever scp'd — so it must be a message, not a panic. The | 915 | # before the key was ever scp'd — so it must be a message, not a panic. The |
| 916 | # child exits on the missing key, the poll loop reaps it, and polling | 916 | # child exits on the missing key, the poll loop reaps it, and polling |
| 917 | # continues to the deadline; a second waitpid there gets ECHILD, which the | 917 | # continues to the deadline; a second waitpid there gets ECHILD, which the |
| @@ -920,7 +920,7 @@ ok "muxd start — spawn, no-op rerun, log truncation, race" | |||
| 920 | DEADCFG="${TMPDIR:-/tmp}/mux-e2e-deadchild-$$" | 920 | DEADCFG="${TMPDIR:-/tmp}/mux-e2e-deadchild-$$" |
| 921 | defer_rm "$DEADCFG" | 921 | defer_rm "$DEADCFG" |
| 922 | set +e | 922 | set +e |
| 923 | env XDG_CONFIG_HOME="$DEADCFG" timeout 30 "$MUXD" start --sock "$SOCK8T" \ | 923 | env XDG_CONFIG_HOME="$DEADCFG" timeout 30 "$MUX" d start --sock "$SOCK8T" \ |
| 924 | --quic "127.0.0.1:1" --key /nonexistent > "$OUT.dead" 2>&1 | 924 | --quic "127.0.0.1:1" --key /nonexistent > "$OUT.dead" 2>&1 |
| 925 | DRC=$? | 925 | DRC=$? |
| 926 | set -e | 926 | set -e |
| @@ -931,7 +931,7 @@ grep -q "did not answer" "$OUT.dead" || { | |||
| 931 | echo "e2e FAIL: doomed start printed no deadline line"; cat "$OUT.dead"; exit 1; } | 931 | echo "e2e FAIL: doomed start printed no deadline line"; cat "$OUT.dead"; exit 1; } |
| 932 | grep -q "log: .*muxd\.log" "$OUT.dead" || { | 932 | grep -q "log: .*muxd\.log" "$OUT.dead" || { |
| 933 | echo "e2e FAIL: deadline line does not name the log"; cat "$OUT.dead"; exit 1; } | 933 | echo "e2e FAIL: deadline line does not name the log"; cat "$OUT.dead"; exit 1; } |
| 934 | # The failure says "daemon did not answer", never "muxd did not answer": | 934 | # The failure says "daemon did not answer", never a mode word: |
| 935 | # the prefix is already the program's name, and the same line serves the | 935 | # the prefix is already the program's name, and the same line serves the |
| 936 | # mux-side caller when auto-start lands. | 936 | # mux-side caller when auto-start lands. |
| 937 | grep -q "daemon did not answer" "$OUT.dead" || { | 937 | grep -q "daemon did not answer" "$OUT.dead" || { |
| @@ -946,9 +946,9 @@ ok "a start whose daemon dies young says so, with the log path" | |||
| 946 | 946 | ||
| 947 | # --- M10: the goal commands, minus ssh: keygen'd default key on both ends, | 947 | # --- M10: the goal commands, minus ssh: keygen'd default key on both ends, |
| 948 | # explicit loopback port (4433 on the suite machine is somebody's daemon). | 948 | # explicit loopback port (4433 on the suite machine is somebody's daemon). |
| 949 | # No --key on either side — the key is the one `muxd keygen` wrote at the | 949 | # No --key on either side — the key is the one `mux d keygen` wrote at the |
| 950 | # hermetic default path near the top of this suite. | 950 | # hermetic default path near the top of this suite. |
| 951 | "$MUXD" start --sock "$SOCK11" --quic "127.0.0.1:$QPORT4" 2> "$OUT.goal" | 951 | "$MUX" d start --sock "$SOCK11" --quic "127.0.0.1:$QPORT4" 2> "$OUT.goal" |
| 952 | GPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.goal") | 952 | GPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.goal") |
| 953 | defer_kill "$GPID" | 953 | defer_kill "$GPID" |
| 954 | [ -n "$GPID" ] || { echo "e2e FAIL: goal start reported no pid"; cat "$OUT.goal"; exit 1; } | 954 | [ -n "$GPID" ] || { echo "e2e FAIL: goal start reported no pid"; cat "$OUT.goal"; exit 1; } |
| @@ -956,7 +956,7 @@ pipe_mux "$OUT.g9" "$OUT.g9.err" timeout 30 "$MUX" "quic://127.0.0.1:$QPORT4" | |||
| 956 | pipe_send 'printf "goal-%%s\\n" quic\n' | 956 | pipe_send 'printf "goal-%%s\\n" quic\n' |
| 957 | await_out "$OUT.g9" "goal-quic" "goal-quic never reached the client" | 957 | await_out "$OUT.g9" "goal-quic" "goal-quic never reached the client" |
| 958 | pipe_detach | 958 | pipe_detach |
| 959 | "$MUXD" dump --sock "$SOCK11" | grep -q "goal-quic" || { | 959 | "$MUX" d dump --sock "$SOCK11" | grep -q "goal-quic" || { |
| 960 | echo "e2e FAIL: no-key-flag QUIC attach did not reach the session" | 960 | echo "e2e FAIL: no-key-flag QUIC attach did not reach the session" |
| 961 | cat "$OUT.g9" "$OUT.g9.err" 2>/dev/null; exit 1; } | 961 | cat "$OUT.g9" "$OUT.g9.err" 2>/dev/null; exit 1; } |
| 962 | assert_converged "$OUT.g9" "$SOCK11" "goal commands" | 962 | assert_converged "$OUT.g9" "$SOCK11" "goal commands" |
| @@ -968,7 +968,7 @@ ok "keygen + start --quic + mux quic:// with no --key anywhere" | |||
| 968 | # A connection that writes the first byte of a frame and then holds still. | 968 | # A connection that writes the first byte of a frame and then holds still. |
| 969 | # Before this was fixed the daemon blocked in read() on that fd and every | 969 | # Before this was fixed the daemon blocked in read() on that fd and every |
| 970 | # other connection — stats included — waited behind it (2026-08-27, found | 970 | # other connection — stats included — waited behind it (2026-08-27, found |
| 971 | # on a live wall). The witness is `muxd stats` answering within its timeout | 971 | # on a live wall). The witness is `mux d stats` answering within its timeout |
| 972 | # WHILE the half frame is still outstanding; the peer is only released after. | 972 | # WHILE the half frame is still outstanding; the peer is only released after. |
| 973 | SOCKHF="${TMPDIR:-/tmp}/muxd-e2e-halfframe-$$.sock" | 973 | SOCKHF="${TMPDIR:-/tmp}/muxd-e2e-halfframe-$$.sock" |
| 974 | start_daemon "$SOCKHF" "$OUT.hf.d" "half-frame daemon never bound" --shell /bin/sh | 974 | start_daemon "$SOCKHF" "$OUT.hf.d" "half-frame daemon never bound" --shell /bin/sh |
| @@ -1013,7 +1013,7 @@ while [ "$(find "/proc/$HFDPID/fd" -mindepth 1 2>/dev/null | wc -l)" -le "$HFFD0 | |||
| 1013 | sleep 0.05 | 1013 | sleep 0.05 |
| 1014 | done | 1014 | done |
| 1015 | set +e | 1015 | set +e |
| 1016 | timeout $((5 * TIME_SCALE)) "$MUXD" stats --sock "$SOCKHF" > "$OUT.hf.st" 2>&1 | 1016 | timeout $((5 * TIME_SCALE)) "$MUX" d stats --sock "$SOCKHF" > "$OUT.hf.st" 2>&1 |
| 1017 | HFRC=$? | 1017 | HFRC=$? |
| 1018 | set -e | 1018 | set -e |
| 1019 | [ "$HFRC" -eq 0 ] || { | 1019 | [ "$HFRC" -eq 0 ] || { |
| @@ -1026,4 +1026,4 @@ softkill "$HFPID" || true | |||
| 1026 | HFPID="" | 1026 | HFPID="" |
| 1027 | softkill "$DHFPID" || true | 1027 | softkill "$DHFPID" || true |
| 1028 | DHFPID="" | 1028 | DHFPID="" |
| 1029 | ok "one peer holding half a frame does not stop muxd stats" | 1029 | ok "one peer holding half a frame does not stop mux d stats" |
test/e2e_02_predict.sh
| Old | New | ||
|---|---|---|---|
| @@ -66,7 +66,7 @@ D5PID=$DPID | |||
| 66 | set +e | 66 | set +e |
| 67 | { sleep 2; printf 'z'; sleep 4; printf '\034\034'; } | \ | 67 | { sleep 2; printf 'z'; sleep 4; printf '\034\034'; } | \ |
| 68 | DELAY_MS="$PDELAY" MUX_PREDICT_STATS=1 timeout 40 "$MUX" \ | 68 | DELAY_MS="$PDELAY" MUX_PREDICT_STATS=1 timeout 40 "$MUX" \ |
| 69 | --via "$DELAYPIPE | $MUXD proxy --sock $SOCK5 | $DELAYPIPE" \ | 69 | --via "$DELAYPIPE | $MUX d proxy --sock $SOCK5 | $DELAYPIPE" \ |
| 70 | > "$OUT.p1" 2> "$OUT.p1.err" & | 70 | > "$OUT.p1" 2> "$OUT.p1.err" & |
| 71 | P1PID=$! | 71 | P1PID=$! |
| 72 | defer_kill "$P1PID" | 72 | defer_kill "$P1PID" |
| @@ -105,7 +105,7 @@ CONF=$(predict_stat "$OUT.p1.err" confirmed) | |||
| 105 | echo "e2e FAIL: line mode: confirmed=$CONF, want >=1"; grep "^predict " "$OUT.p1.err"; exit 1; | 105 | echo "e2e FAIL: line mode: confirmed=$CONF, want >=1"; grep "^predict " "$OUT.p1.err"; exit 1; |
| 106 | } | 106 | } |
| 107 | # The session really received the keystroke: cat echoed it into the grid. | 107 | # The session really received the keystroke: cat echoed it into the grid. |
| 108 | "$MUXD" dump --sock "$SOCK5" | grep -q "z" || { | 108 | "$MUX" d dump --sock "$SOCK5" | grep -q "z" || { |
| 109 | echo "e2e FAIL: line mode: daemon grid never saw the keystroke"; exit 1; | 109 | echo "e2e FAIL: line mode: daemon grid never saw the keystroke"; exit 1; |
| 110 | } | 110 | } |
| 111 | assert_converged "$OUT.p1" "$SOCK5" "line-mode prediction" | 111 | assert_converged "$OUT.p1" "$SOCK5" "line-mode prediction" |
| @@ -119,7 +119,7 @@ set +e | |||
| 119 | { sleep 2; for c in b u r s t; do printf '%s' "$c"; sleep 0.25; done; \ | 119 | { sleep 2; for c in b u r s t; do printf '%s' "$c"; sleep 0.25; done; \ |
| 120 | sleep 4; printf '\034\034'; } | \ | 120 | sleep 4; printf '\034\034'; } | \ |
| 121 | DELAY_MS="$PDELAY" MUX_PREDICT_STATS=1 timeout 40 "$MUX" \ | 121 | DELAY_MS="$PDELAY" MUX_PREDICT_STATS=1 timeout 40 "$MUX" \ |
| 122 | --via "$DELAYPIPE | $MUXD proxy --sock $SOCK5 | $DELAYPIPE" \ | 122 | --via "$DELAYPIPE | $MUX d proxy --sock $SOCK5 | $DELAYPIPE" \ |
| 123 | > "$OUT.pb" 2> "$OUT.pb.err" | 123 | > "$OUT.pb" 2> "$OUT.pb.err" |
| 124 | RC=$? | 124 | RC=$? |
| 125 | set -e | 125 | set -e |
| @@ -164,7 +164,7 @@ want_stat_ge "$OUT.pb.err" confirmed 4 "burst" | |||
| 164 | # flake was first investigated as a reconcile bug. | 164 | # flake was first investigated as a reconcile bug. |
| 165 | want_stat "$OUT.pb.err" expired 0 "burst" | 165 | want_stat "$OUT.pb.err" expired 0 "burst" |
| 166 | want_stat "$OUT.pb.err" contradicted 0 "burst" | 166 | want_stat "$OUT.pb.err" contradicted 0 "burst" |
| 167 | "$MUXD" dump --sock "$SOCK5" | grep -q "burst" || { | 167 | "$MUX" d dump --sock "$SOCK5" | grep -q "burst" || { |
| 168 | echo "e2e FAIL: burst: daemon grid does not hold the typed text"; exit 1; | 168 | echo "e2e FAIL: burst: daemon grid does not hold the typed text"; exit 1; |
| 169 | } | 169 | } |
| 170 | assert_converged "$OUT.pb" "$SOCK5" "burst" | 170 | assert_converged "$OUT.pb" "$SOCK5" "burst" |
| @@ -221,9 +221,9 @@ fi | |||
| 221 | # ...and the run was not vacuous: the shell really received all seven | 221 | # ...and the run was not vacuous: the shell really received all seven |
| 222 | # characters, so the absence above is prediction declining rather than | 222 | # characters, so the absence above is prediction declining rather than |
| 223 | # nothing having been typed. | 223 | # nothing having been typed. |
| 224 | "$MUXD" dump --sock "$SOCK6" | grep -q "pw-len-7" || { | 224 | "$MUX" d dump --sock "$SOCK6" | grep -q "pw-len-7" || { |
| 225 | echo "e2e FAIL: the password never reached the shell; the absence above proves nothing" | 225 | echo "e2e FAIL: the password never reached the shell; the absence above proves nothing" |
| 226 | "$MUXD" dump --sock "$SOCK6"; exit 1; | 226 | "$MUX" d dump --sock "$SOCK6"; exit 1; |
| 227 | } | 227 | } |
| 228 | assert_converged "$OUT.pw" "$SOCK6" "password" | 228 | assert_converged "$OUT.pw" "$SOCK6" "password" |
| 229 | 229 | ||
| @@ -247,7 +247,7 @@ set +e | |||
| 247 | { sleep 2; for c in a b c; do printf '%s' "$c"; sleep 1; done; \ | 247 | { sleep 2; for c in a b c; do printf '%s' "$c"; sleep 1; done; \ |
| 248 | printf '\000'; sleep 1; printf 'j'; sleep 2.5; printf '\034\034'; } | \ | 248 | printf '\000'; sleep 1; printf 'j'; sleep 2.5; printf '\034\034'; } | \ |
| 249 | DELAY_MS="$PDELAY" MUX_PREDICT_STATS=1 timeout 40 "$MUX" \ | 249 | DELAY_MS="$PDELAY" MUX_PREDICT_STATS=1 timeout 40 "$MUX" \ |
| 250 | --via "$DELAYPIPE | $MUXD proxy --sock $SOCK7 | $DELAYPIPE" \ | 250 | --via "$DELAYPIPE | $MUX d proxy --sock $SOCK7 | $DELAYPIPE" \ |
| 251 | > "$OUT.rw" 2> "$OUT.rw.err" | 251 | > "$OUT.rw" 2> "$OUT.rw.err" |
| 252 | RC=$? | 252 | RC=$? |
| 253 | set -e | 253 | set -e |
| @@ -333,7 +333,7 @@ D5PID=$DPID | |||
| 333 | set +e | 333 | set +e |
| 334 | { sleep 2; printf 'p'; sleep 5; printf 'q'; sleep 4; printf '\034\034'; } | \ | 334 | { sleep 2; printf 'p'; sleep 5; printf 'q'; sleep 4; printf '\034\034'; } | \ |
| 335 | DELAY_MS="$PDELAY" MUX_PREDICT_STATS=1 timeout 60 "$MUX" \ | 335 | DELAY_MS="$PDELAY" MUX_PREDICT_STATS=1 timeout 60 "$MUX" \ |
| 336 | --via "$DELAYPIPE | $MUXD proxy --sock $SOCK5 | $DELAYPIPE" \ | 336 | --via "$DELAYPIPE | $MUX d proxy --sock $SOCK5 | $DELAYPIPE" \ |
| 337 | > "$OUT.pr" 2> "$OUT.pr.err" & | 337 | > "$OUT.pr" 2> "$OUT.pr.err" & |
| 338 | PRPID=$! | 338 | PRPID=$! |
| 339 | defer_kill "$PRPID" | 339 | defer_kill "$PRPID" |
| @@ -383,9 +383,9 @@ want_stat "$OUT.pr.err" expired 0 "reconnect" | |||
| 383 | } | 383 | } |
| 384 | # The session survived and the client resumed into it: the keystroke typed | 384 | # The session survived and the client resumed into it: the keystroke typed |
| 385 | # after the tear reached the shell. | 385 | # after the tear reached the shell. |
| 386 | "$MUXD" dump --sock "$SOCK5" | grep -q "q" || { | 386 | "$MUX" d dump --sock "$SOCK5" | grep -q "q" || { |
| 387 | echo "e2e FAIL: reconnect: the post-tear keystroke never reached the session" | 387 | echo "e2e FAIL: reconnect: the post-tear keystroke never reached the session" |
| 388 | "$MUXD" dump --sock "$SOCK5"; exit 1; | 388 | "$MUX" d dump --sock "$SOCK5"; exit 1; |
| 389 | } | 389 | } |
| 390 | assert_converged "$OUT.pr" "$SOCK5" "reconnect flush" | 390 | assert_converged "$OUT.pr" "$SOCK5" "reconnect flush" |
| 391 | 391 | ||
| @@ -648,7 +648,7 @@ D13PID=$DPID | |||
| 648 | # and page 1 is rows 54..77. Every needle below is chosen against those | 648 | # and page 1 is rows 54..77. Every needle below is chosen against those |
| 649 | # numbers, which is why this wait is part of the scenario and not a nicety. | 649 | # numbers, which is why this wait is part of the scenario and not a nicety. |
| 650 | i=0 | 650 | i=0 |
| 651 | until "$MUXD" dump --sock "$SOCK13" | grep -q "100"; do | 651 | until "$MUX" d dump --sock "$SOCK13" | grep -q "100"; do |
| 652 | i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: tp1 session never finished seq"; exit 1; } | 652 | i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: tp1 session never finished seq"; exit 1; } |
| 653 | sleep 0.1 | 653 | sleep 0.1 |
| 654 | done | 654 | done |
| @@ -681,7 +681,7 @@ done | |||
| 681 | hostroom predict1 | 681 | hostroom predict1 |
| 682 | XDG_STATE_HOME="$HOSTROOM" MUX_PREDICT_STATS=1 timeout 60 "$PTYCLIENT" --cols 80 --rows 24 \ | 682 | XDG_STATE_HOME="$HOSTROOM" MUX_PREDICT_STATS=1 timeout 60 "$PTYCLIENT" --cols 80 --rows 24 \ |
| 683 | --out "$OUT.tp1" --err "$OUT.tp1.err" -- \ | 683 | --out "$OUT.tp1" --err "$OUT.tp1.err" -- \ |
| 684 | "$MUX" --via "$MUXD proxy --sock $SOCK13" > "$OUT.tp1.log" 2>&1 <<'EOF' & | 684 | "$MUX" --via "$MUX d proxy --sock $SOCK13" > "$OUT.tp1.log" 2>&1 <<'EOF' & |
| 685 | expect \x1b[0m100 10000 | 685 | expect \x1b[0m100 10000 |
| 686 | send \x1b[5;2~ | 686 | send \x1b[5;2~ |
| 687 | expect 60 10000 | 687 | expect 60 10000 |
| @@ -740,7 +740,7 @@ kill -9 "$TP1PROXY" | |||
| 740 | # it is our own backgrounded child, so a dead one is a zombie until the | 740 | # it is our own backgrounded child, so a dead one is a zombie until the |
| 741 | # shell reaps it, and signalling a zombie succeeds. A dump answers only if | 741 | # shell reaps it, and signalling a zombie succeeds. A dump answers only if |
| 742 | # the process is alive and its accept loop is running. | 742 | # the process is alive and its accept loop is running. |
| 743 | "$MUXD" dump --sock "$SOCK13" >/dev/null 2>&1 || { | 743 | "$MUX" d dump --sock "$SOCK13" >/dev/null 2>&1 || { |
| 744 | echo "e2e FAIL: tp1 tear killed the daemon, not the proxy"; exit 1; } | 744 | echo "e2e FAIL: tp1 tear killed the daemon, not the proxy"; exit 1; } |
| 745 | 745 | ||
| 746 | set +e | 746 | set +e |
test/e2e_03_side.sh
| Old | New | ||
|---|---|---|---|
| @@ -5,10 +5,10 @@ | |||
| 5 | # tp1 runs its fixture in the BACKGROUND — the tear has to happen while the | 5 | # tp1 runs its fixture in the BACKGROUND — the tear has to happen while the |
| 6 | # script is mid-flight — so its pid is tracked and killed like a daemon's. | 6 | # script is mid-flight — so its pid is tracked and killed like a daemon's. |
| 7 | # M13 auto-start + stop. Two paths, and they no longer answer the same | 7 | # M13 auto-start + stop. Two paths, and they no longer answer the same |
| 8 | # way: the proxy arc's daemon is started BY HAND, because `muxd proxy` | 8 | # way: the proxy arc's daemon is started BY HAND, because `mux d proxy` |
| 9 | # reads a box and may not create a session on it, while the pty leg's is | 9 | # reads a box and may not create a session on it, while the pty leg's is |
| 10 | # still spawned by local mux under the M12 fixture. Both are torn down by | 10 | # still spawned by local mux under the M12 fixture. Both are torn down by |
| 11 | # `muxd stop` — the verb under test is also the cleanup, and the trap only | 11 | # `mux d stop` — the verb under test is also the cleanup, and the trap only |
| 12 | # backstops it. Neither daemon is a child of this shell, so each pid is | 12 | # backstops it. Neither daemon is a child of this shell, so each pid is |
| 13 | # read off the up-line its own spawner printed, which is the only handle | 13 | # read off the up-line its own spawner printed, which is the only handle |
| 14 | # that can never name a bystander. | 14 | # that can never name a bystander. |
| @@ -25,11 +25,11 @@ defer_sock "$SOCK15" | |||
| 25 | SOCK24="${TMPDIR:-/tmp}/muxd-e2e-bell-$$.sock" | 25 | SOCK24="${TMPDIR:-/tmp}/muxd-e2e-bell-$$.sock" |
| 26 | defer_sock "$SOCK24" | 26 | defer_sock "$SOCK24" |
| 27 | 27 | ||
| 28 | # --- M13: proxy reaches a daemon, `muxd start` makes one, muxd stop ---- | 28 | # --- M13: proxy reaches a daemon, `mux d start` makes one, mux d stop ---- |
| 29 | # | 29 | # |
| 30 | # Nothing is serving SOCK14, and `muxd proxy` no longer answers that by | 30 | # Nothing is serving SOCK14, and `mux d proxy` no longer answers that by |
| 31 | # starting a daemon: it is the verb a client reaches over ssh, and a wall | 31 | # starting a daemon: it is the verb a client reaches over ssh, and a wall |
| 32 | # runs its sibling `muxd endpoint` once a second per listed host — a read | 32 | # runs its sibling `mux d endpoint` once a second per listed host — a read |
| 33 | # that creates a session (and a shell) is the regression this refuses. | 33 | # that creates a session (and a shell) is the regression this refuses. |
| 34 | # So the cold attach FAILS, in proxy.zig's own words, and the daemon the | 34 | # So the cold attach FAILS, in proxy.zig's own words, and the daemon the |
| 35 | # rest of this leg attaches to is one the user started by hand, which is | 35 | # rest of this leg attaches to is one the user started by hand, which is |
| @@ -38,14 +38,14 @@ defer_sock "$SOCK24" | |||
| 38 | # SHELL pinned because the daemon gets no --shell flag of its own and | 38 | # SHELL pinned because the daemon gets no --shell flag of its own and |
| 39 | # resolves $SHELL — the suite must not inherit the developer's. | 39 | # resolves $SHELL — the suite must not inherit the developer's. |
| 40 | set +e | 40 | set +e |
| 41 | env SHELL=/bin/sh timeout 30 "$MUX" --via "$MUXD proxy --sock $SOCK14" \ | 41 | env SHELL=/bin/sh timeout 30 "$MUX" --via "$MUX d proxy --sock $SOCK14" \ |
| 42 | > "$OUT.ascold" 2>&1 < /dev/null | 42 | > "$OUT.ascold" 2>&1 < /dev/null |
| 43 | RC_COLD=$? | 43 | RC_COLD=$? |
| 44 | set -e | 44 | set -e |
| 45 | [ "$RC_COLD" -ne 0 ] || { | 45 | [ "$RC_COLD" -ne 0 ] || { |
| 46 | echo "e2e FAIL: a --via proxy against a socket nobody serves exited 0:" | 46 | echo "e2e FAIL: a --via proxy against a socket nobody serves exited 0:" |
| 47 | cat "$OUT.ascold"; exit 1; } | 47 | cat "$OUT.ascold"; exit 1; } |
| 48 | grep -q "muxd proxy: cannot connect to $SOCK14" "$OUT.ascold" || { | 48 | grep -q "mux d proxy: cannot connect to $SOCK14" "$OUT.ascold" || { |
| 49 | echo "e2e FAIL: the cold --via proxy did not name the socket it could not reach:" | 49 | echo "e2e FAIL: the cold --via proxy did not name the socket it could not reach:" |
| 50 | cat "$OUT.ascold"; exit 1; } | 50 | cat "$OUT.ascold"; exit 1; } |
| 51 | # Asked of the filesystem, not of the client: a refusal that had started a | 51 | # Asked of the filesystem, not of the client: a refusal that had started a |
| @@ -61,19 +61,19 @@ grep -q "muxd proxy: cannot connect to $SOCK14" "$OUT.ascold" || { | |||
| 61 | # zero. Unlike the M10 start block's version of this gap (:1150) it is | 61 | # zero. Unlike the M10 start block's version of this gap (:1150) it is |
| 62 | # covered anyway: the trap also stops SOCK14/SOCK15 by PATH, which needs | 62 | # covered anyway: the trap also stops SOCK14/SOCK15 by PATH, which needs |
| 63 | # no pid. | 63 | # no pid. |
| 64 | "$MUXD" start --sock "$SOCK14" --shell /bin/sh 2> "$OUT.as.start" || { | 64 | "$MUX" d start --sock "$SOCK14" --shell /bin/sh 2> "$OUT.as.start" || { |
| 65 | echo "e2e FAIL: muxd start refused $SOCK14:"; cat "$OUT.as.start"; exit 1; } | 65 | echo "e2e FAIL: mux d start refused $SOCK14:"; cat "$OUT.as.start"; exit 1; } |
| 66 | APID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.as.start" | head -1) | 66 | APID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.as.start" | head -1) |
| 67 | defer_kill "$APID" | 67 | defer_kill "$APID" |
| 68 | [ -n "$APID" ] || { | 68 | [ -n "$APID" ] || { |
| 69 | echo "e2e FAIL: muxd start's up-line carries no pid"; cat "$OUT.as.start"; exit 1; } | 69 | echo "e2e FAIL: mux d start's up-line carries no pid"; cat "$OUT.as.start"; exit 1; } |
| 70 | kill -0 "$APID" || { echo "e2e FAIL: the started daemon is not alive"; exit 1; } | 70 | kill -0 "$APID" || { echo "e2e FAIL: the started daemon is not alive"; exit 1; } |
| 71 | 71 | ||
| 72 | pipe_mux "$OUT.as" "$OUT.as.err" env SHELL=/bin/sh timeout 30 "$MUX" --via "$MUXD proxy --sock $SOCK14" | 72 | pipe_mux "$OUT.as" "$OUT.as.err" env SHELL=/bin/sh timeout 30 "$MUX" --via "$MUX d proxy --sock $SOCK14" |
| 73 | pipe_send 'printf "auto-%%s\\n" start\n' | 73 | pipe_send 'printf "auto-%%s\\n" start\n' |
| 74 | await_out "$OUT.as" "auto-start" "proxy attach: marker never reached the client" | 74 | await_out "$OUT.as" "auto-start" "proxy attach: marker never reached the client" |
| 75 | pipe_detach | 75 | pipe_detach |
| 76 | "$MUXD" dump --sock "$SOCK14" | grep -q "auto-start" || { | 76 | "$MUX" d dump --sock "$SOCK14" | grep -q "auto-start" || { |
| 77 | echo "e2e FAIL: proxy attach: the daemon lost the marker" | 77 | echo "e2e FAIL: proxy attach: the daemon lost the marker" |
| 78 | cat "$OUT.as" "$OUT.as.err" 2>/dev/null; exit 1; } | 78 | cat "$OUT.as" "$OUT.as.err" 2>/dev/null; exit 1; } |
| 79 | # Nothing on this path may print spawn progress any more — the daemon was | 79 | # Nothing on this path may print spawn progress any more — the daemon was |
| @@ -85,14 +85,14 @@ assert_converged "$OUT.as" "$SOCK14" "attach over proxy" | |||
| 85 | 85 | ||
| 86 | # Silence is now BOTH paths: nothing on this road starts a daemon, so no | 86 | # Silence is now BOTH paths: nothing on this road starts a daemon, so no |
| 87 | # attach to it may print spawn progress. | 87 | # attach to it may print spawn progress. |
| 88 | pipe_mux "$OUT.as2" "$OUT.as2.err" env SHELL=/bin/sh timeout 30 "$MUX" --via "$MUXD proxy --sock $SOCK14" | 88 | pipe_mux "$OUT.as2" "$OUT.as2.err" env SHELL=/bin/sh timeout 30 "$MUX" --via "$MUX d proxy --sock $SOCK14" |
| 89 | pipe_send 'printf "auto-%%s\\n" again\n' | 89 | pipe_send 'printf "auto-%%s\\n" again\n' |
| 90 | await_out "$OUT.as2" "auto-again" "warm attach: marker never reached the client" | 90 | await_out "$OUT.as2" "auto-again" "warm attach: marker never reached the client" |
| 91 | pipe_detach | 91 | pipe_detach |
| 92 | grep -q 'starting' "$OUT.as2.err" && { | 92 | grep -q 'starting' "$OUT.as2.err" && { |
| 93 | echo "e2e FAIL: warm attach printed spawn progress" | 93 | echo "e2e FAIL: warm attach printed spawn progress" |
| 94 | cat "$OUT.as2.err"; exit 1; } | 94 | cat "$OUT.as2.err"; exit 1; } |
| 95 | "$MUXD" dump --sock "$SOCK14" | grep -q "auto-again" || { | 95 | "$MUX" d dump --sock "$SOCK14" | grep -q "auto-again" || { |
| 96 | echo "e2e FAIL: warm attach did not reach the same daemon"; exit 1; } | 96 | echo "e2e FAIL: warm attach did not reach the same daemon"; exit 1; } |
| 97 | # The same daemon, asserted by pid rather than by the marker alone: a | 97 | # The same daemon, asserted by pid rather than by the marker alone: a |
| 98 | # second daemon on the path would have started a fresh shell, but a | 98 | # second daemon on the path would have started a fresh shell, but a |
| @@ -111,12 +111,12 @@ assert_converged "$OUT.as2" "$SOCK14" "second attach over proxy" | |||
| 111 | # own, and the trap would then rm the one file saying why. The regrade | 111 | # own, and the trap would then rm the one file saying why. The regrade |
| 112 | # resurrects exactly that failure, so it has to PRINT. | 112 | # resurrects exactly that failure, so it has to PRINT. |
| 113 | set +e | 113 | set +e |
| 114 | "$MUXD" stop --sock "$SOCK14" 2> "$OUT.stop" | 114 | "$MUX" d stop --sock "$SOCK14" 2> "$OUT.stop" |
| 115 | RC_STOP=$? | 115 | RC_STOP=$? |
| 116 | set -e | 116 | set -e |
| 117 | [ "$RC_STOP" = "0" ] || { | 117 | [ "$RC_STOP" = "0" ] || { |
| 118 | echo "e2e FAIL: stop exited $RC_STOP, want 0"; cat "$OUT.stop"; exit 1; } | 118 | echo "e2e FAIL: stop exited $RC_STOP, want 0"; cat "$OUT.stop"; exit 1; } |
| 119 | grep -q '^muxd: stopped' "$OUT.stop" || { | 119 | grep -q '^mux d: stopped' "$OUT.stop" || { |
| 120 | echo "e2e FAIL: stop did not report stopped"; cat "$OUT.stop"; exit 1; } | 120 | echo "e2e FAIL: stop did not report stopped"; cat "$OUT.stop"; exit 1; } |
| 121 | [ ! -S "$SOCK14" ] || { | 121 | [ ! -S "$SOCK14" ] || { |
| 122 | echo "e2e FAIL: stop left the socket behind"; ls -l "$SOCK14"; exit 1; } | 122 | echo "e2e FAIL: stop left the socket behind"; ls -l "$SOCK14"; exit 1; } |
| @@ -125,7 +125,7 @@ APID="" | |||
| 125 | 125 | ||
| 126 | # Idempotence control: stop with nothing there is exit 0 and says so. | 126 | # Idempotence control: stop with nothing there is exit 0 and says so. |
| 127 | set +e | 127 | set +e |
| 128 | "$MUXD" stop --sock "$SOCK14" 2> "$OUT.stop2" | 128 | "$MUX" d stop --sock "$SOCK14" 2> "$OUT.stop2" |
| 129 | RC_STOP=$? | 129 | RC_STOP=$? |
| 130 | set -e | 130 | set -e |
| 131 | [ "$RC_STOP" = "0" ] || { | 131 | [ "$RC_STOP" = "0" ] || { |
| @@ -134,13 +134,13 @@ grep -q "nothing listening on $SOCK14" "$OUT.stop2" || { | |||
| 134 | echo "e2e FAIL: stop-when-nothing said the wrong thing"; cat "$OUT.stop2"; exit 1; } | 134 | echo "e2e FAIL: stop-when-nothing said the wrong thing"; cat "$OUT.stop2"; exit 1; } |
| 135 | rm_swept "$OUT.ascold" "$OUT.as.start" "$OUT.as" "$OUT.as.err" "$OUT.as2" \ | 135 | rm_swept "$OUT.ascold" "$OUT.as.start" "$OUT.as" "$OUT.as.err" "$OUT.as2" \ |
| 136 | "$OUT.as2.err" "$OUT.stop" "$OUT.stop2" | 136 | "$OUT.as2.err" "$OUT.stop" "$OUT.stop2" |
| 137 | ok "a cold --via proxy refuses and starts nothing; muxd start makes the daemon and muxd stop tears it down" | 137 | ok "a cold --via proxy refuses and starts nothing; mux d start makes the daemon and mux d stop tears it down" |
| 138 | 138 | ||
| 139 | # --- M13: local mux auto-start, under the pty fixture ------------------ | 139 | # --- M13: local mux auto-start, under the pty fixture ------------------ |
| 140 | # | 140 | # |
| 141 | # A local attach is tty-gated territory, so it runs on the M12 fixture. | 141 | # A local attach is tty-gated territory, so it runs on the M12 fixture. |
| 142 | # PATH is prefixed with the build dir: findInPath must resolve exactly | 142 | # PATH is prefixed with the build dir: findInPath must resolve exactly |
| 143 | # the muxd under test, and the suite proves the resolution by the daemon | 143 | # the binary under test, and the suite proves the resolution by the daemon |
| 144 | # existing afterwards. | 144 | # existing afterwards. |
| 145 | # | 145 | # |
| 146 | # The script keeps the two M12 rules that every pty leg is written to: | 146 | # The script keeps the two M12 rules that every pty leg is written to: |
| @@ -155,7 +155,7 @@ ok "a cold --via proxy refuses and starts nothing; muxd start makes the daemon a | |||
| 155 | # holds, and the convergence check below would blame the product for it. | 155 | # holds, and the convergence check below would blame the product for it. |
| 156 | set +e | 156 | set +e |
| 157 | hostroom autostart | 157 | hostroom autostart |
| 158 | SHELL=/bin/sh PATH="$(dirname "$MUXD"):$PATH" XDG_STATE_HOME="$HOSTROOM" timeout 40 \ | 158 | SHELL=/bin/sh PATH="$(dirname "$MUX"):$PATH" XDG_STATE_HOME="$HOSTROOM" timeout 40 \ |
| 159 | "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.pa" --err "$OUT.pa.err" \ | 159 | "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.pa" --err "$OUT.pa.err" \ |
| 160 | -- "$MUX" --sock "$SOCK15" > "$OUT.pa.log" 2>&1 <<'EOF' | 160 | -- "$MUX" --sock "$SOCK15" > "$OUT.pa.log" 2>&1 <<'EOF' |
| 161 | expect \x1b[?1049h 15000 | 161 | expect \x1b[?1049h 15000 |
| @@ -170,7 +170,7 @@ set -e | |||
| 170 | [ "$RC" -eq 0 ] || { | 170 | [ "$RC" -eq 0 ] || { |
| 171 | echo "e2e FAIL: local auto-start ptyclient exited $RC:" | 171 | echo "e2e FAIL: local auto-start ptyclient exited $RC:" |
| 172 | cat "$OUT.pa.log"; cat -v "$OUT.pa.err" 2>/dev/null; exit 1; } | 172 | cat "$OUT.pa.log"; cat -v "$OUT.pa.err" 2>/dev/null; exit 1; } |
| 173 | "$MUXD" dump --sock "$SOCK15" | grep -q "pty-auto" || { | 173 | "$MUX" d dump --sock "$SOCK15" | grep -q "pty-auto" || { |
| 174 | echo "e2e FAIL: local auto-start lost the marker" | 174 | echo "e2e FAIL: local auto-start lost the marker" |
| 175 | cat "$OUT.pa.log" "$OUT.pa.err" 2>/dev/null; exit 1; } | 175 | cat "$OUT.pa.log" "$OUT.pa.err" 2>/dev/null; exit 1; } |
| 176 | # The fixture hands the client a FILE for stderr, so the progress lines | 176 | # The fixture hands the client a FILE for stderr, so the progress lines |
| @@ -182,18 +182,31 @@ grep -q '^mux: starting' "$OUT.pa.err" || { | |||
| 182 | PAPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.pa.err" | head -1) | 182 | PAPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.pa.err" | head -1) |
| 183 | defer_kill "$PAPID" | 183 | defer_kill "$PAPID" |
| 184 | [ -n "$PAPID" ] || { echo "e2e FAIL: mux up-line carries no pid"; cat "$OUT.pa.err"; exit 1; } | 184 | [ -n "$PAPID" ] || { echo "e2e FAIL: mux up-line carries no pid"; cat "$OUT.pa.err"; exit 1; } |
| 185 | # THE ambient-PATH pin, asked of the kernel and not of the daemon: the | ||
| 186 | # process an auto-start brought up is running the binary this suite was | ||
| 187 | # handed, byte for byte. `mux` used to resolve `muxd` against PATH, and a | ||
| 188 | # leg whose daemon had died reattached to an INSTALLED v0.0.1-10 with no | ||
| 189 | # agent code in it and passed. It now execs /proc/self/exe, so the link | ||
| 190 | # below can only ever be the running image — and this reads the link rather | ||
| 191 | # than the argv, because argv is what a wrong spawn would still get right. | ||
| 192 | PAEXE=$(readlink "/proc/$PAPID/exe") | ||
| 193 | PAWANT=$(cd "$(dirname "$MUX_ELF")" && pwd)/$(basename "$MUX_ELF") | ||
| 194 | [ "$PAEXE" = "$PAWANT" ] || { | ||
| 195 | echo "e2e FAIL: the auto-started daemon is running $PAEXE, not the build's $PAWANT" | ||
| 196 | echo " (an auto-start that resolves a NAME grades whatever is installed)" | ||
| 197 | exit 1; } | ||
| 185 | assert_converged "$OUT.pa" "$SOCK15" "local mux auto-start" | 198 | assert_converged "$OUT.pa" "$SOCK15" "local mux auto-start" |
| 186 | # Same teardown, same reasons — and the stderr is captured rather than | 199 | # Same teardown, same reasons — and the stderr is captured rather than |
| 187 | # discarded, so this leg pins the stopped line too. $OUT.stop is reused | 200 | # discarded, so this leg pins the stopped line too. $OUT.stop is reused |
| 188 | # deliberately: the proxy arc removed it above, and it is in the trap's | 201 | # deliberately: the proxy arc removed it above, and it is in the trap's |
| 189 | # rm list either way. | 202 | # rm list either way. |
| 190 | set +e | 203 | set +e |
| 191 | "$MUXD" stop --sock "$SOCK15" 2> "$OUT.stop" | 204 | "$MUX" d stop --sock "$SOCK15" 2> "$OUT.stop" |
| 192 | RC_STOP=$? | 205 | RC_STOP=$? |
| 193 | set -e | 206 | set -e |
| 194 | [ "$RC_STOP" = "0" ] || { | 207 | [ "$RC_STOP" = "0" ] || { |
| 195 | echo "e2e FAIL: pty leg stop exited $RC_STOP, want 0"; cat "$OUT.stop"; exit 1; } | 208 | echo "e2e FAIL: pty leg stop exited $RC_STOP, want 0"; cat "$OUT.stop"; exit 1; } |
| 196 | grep -q '^muxd: stopped' "$OUT.stop" || { | 209 | grep -q '^mux d: stopped' "$OUT.stop" || { |
| 197 | echo "e2e FAIL: pty leg stop did not report stopped"; cat "$OUT.stop"; exit 1; } | 210 | echo "e2e FAIL: pty leg stop did not report stopped"; cat "$OUT.stop"; exit 1; } |
| 198 | [ ! -S "$SOCK15" ] || { | 211 | [ ! -S "$SOCK15" ] || { |
| 199 | echo "e2e FAIL: stop left the pty leg's socket"; ls -l "$SOCK15"; exit 1; } | 212 | echo "e2e FAIL: stop left the pty leg's socket"; ls -l "$SOCK15"; exit 1; } |
| @@ -589,7 +602,7 @@ BELLN=$(occurrences "$OUT.bell" "$BELBYTE") | |||
| 589 | [ "$BELLN" -eq 1 ] || { | 602 | [ "$BELLN" -eq 1 ] || { |
| 590 | echo "e2e FAIL: $BELLN bells on the host tty for one burst of five, want exactly 1" | 603 | echo "e2e FAIL: $BELLN bells on the host tty for one burst of five, want exactly 1" |
| 591 | cat -v "$OUT.bell"; exit 1; } | 604 | cat -v "$OUT.bell"; exit 1; } |
| 592 | "$MUXD" stop --sock "$SOCK24" > /dev/null 2>&1 || true | 605 | "$MUX" d stop --sock "$SOCK24" > /dev/null 2>&1 || true |
| 593 | wait_pid_gone "$D21PID" "bell leg: stop reported stopped" | 606 | wait_pid_gone "$D21PID" "bell leg: stop reported stopped" |
| 594 | D21PID="" | 607 | D21PID="" |
| 595 | rm_swept "$OUT.bell" "$OUT.bell.err" "$OUT.bell.log" "$OUT.bell.sh" "$OUT.d21.d" | 608 | rm_swept "$OUT.bell" "$OUT.bell.err" "$OUT.bell.log" "$OUT.bell.sh" "$OUT.d21.d" |
test/e2e_04_handoff.sh
| Old | New | ||
|---|---|---|---|
| @@ -7,7 +7,7 @@ | |||
| 7 | # per daemon: the handoff dials the DEFAULT socket on the far side, and the | 7 | # per daemon: the handoff dials the DEFAULT socket on the far side, and the |
| 8 | # far side is this box, so each scenario's default socket has to be one of | 8 | # far side is this box, so each scenario's default socket has to be one of |
| 9 | # ours rather than the operator's. Two daemons, no more — the one a cold | 9 | # ours rather than the operator's. Two daemons, no more — the one a cold |
| 10 | # attach starts by running `muxd start` over the shim, and the key-mismatch | 10 | # attach starts by running `mux d start` over the shim, and the key-mismatch |
| 11 | # one that cannot be shared because it holds a key nothing else on this box | 11 | # one that cannot be shared because it holds a key nothing else on this box |
| 12 | # has. | 12 | # has. |
| 13 | SSHIM_DIR="${TMPDIR:-/tmp}/muxd-e2e-sshim-$$" | 13 | SSHIM_DIR="${TMPDIR:-/tmp}/muxd-e2e-sshim-$$" |
| @@ -23,7 +23,7 @@ SOCK17="$HRUN2/muxd.sock" | |||
| 23 | defer_sock "$SOCK16" "$SOCK17" | 23 | defer_sock "$SOCK16" "$SOCK17" |
| 24 | HKEY="${TMPDIR:-/tmp}/mux-e2e-hkey-$$" | 24 | HKEY="${TMPDIR:-/tmp}/mux-e2e-hkey-$$" |
| 25 | defer_rm "$HKEY" | 25 | defer_rm "$HKEY" |
| 26 | # A config home that is a FILE: `muxd endpoint` cannot create a key under | 26 | # A config home that is a FILE: `mux d endpoint` cannot create a key under |
| 27 | # it and cannot find one, which is the announce-none scenario's lever. | 27 | # it and cannot find one, which is the announce-none scenario's lever. |
| 28 | HCFGBAD="${TMPDIR:-/tmp}/mux-e2e-hnokey-$$" | 28 | HCFGBAD="${TMPDIR:-/tmp}/mux-e2e-hnokey-$$" |
| 29 | defer_rm "$HCFGBAD" | 29 | defer_rm "$HCFGBAD" |
| @@ -45,7 +45,7 @@ HDEADPORT=$(( 16000 + ($$ % 4000) )) | |||
| 45 | # a poisoned cache, a key mismatch, and a remote that can offer nothing. | 45 | # a poisoned cache, a key mismatch, and a remote that can offer nothing. |
| 46 | # | 46 | # |
| 47 | # The shim IS ssh as far as the client can tell: `ssh HOST CMD...` drops | 47 | # The shim IS ssh as far as the client can tell: `ssh HOST CMD...` drops |
| 48 | # HOST and execs CMD here, so `ssh whatever muxd endpoint` runs the real | 48 | # HOST and execs CMD here, so `ssh whatever mux d endpoint` runs the real |
| 49 | # binary against a socket of this suite's own making and every line of the | 49 | # binary against a socket of this suite's own making and every line of the |
| 50 | # client's handoff code runs for real, with no network anywhere. | 50 | # client's handoff code runs for real, with no network anywhere. |
| 51 | # | 51 | # |
| @@ -60,7 +60,7 @@ HDEADPORT=$(( 16000 + ($$ % 4000) )) | |||
| 60 | # remote command (the PATH-suffix form recipeFor builds) run here the | 60 | # remote command (the PATH-suffix form recipeFor builds) run here the |
| 61 | # same way sshd would run it. A bare `exec "$@"` was enough while the | 61 | # same way sshd would run it. A bare `exec "$@"` was enough while the |
| 62 | # command was plain argv; it execs a program literally named | 62 | # command was plain argv; it execs a program literally named |
| 63 | # `PATH=... muxd endpoint` now, and dies at 127 without an announce. | 63 | # `PATH=... mux d endpoint` now, and dies at 127 without an announce. |
| 64 | # | 64 | # |
| 65 | # The option loop and the arity guard come from `ssh_shim_head`, which | 65 | # The option loop and the arity guard come from `ssh_shim_head`, which |
| 66 | # both of this suite's fake sshs share: the recipes these scenarios drive | 66 | # both of this suite's fake sshs share: the recipes these scenarios drive |
| @@ -77,9 +77,9 @@ exec /bin/sh -c "$*" | |||
| 77 | SHIM | 77 | SHIM |
| 78 | chmod +x "$SSHIM_DIR/ssh" | 78 | chmod +x "$SSHIM_DIR/ssh" |
| 79 | # The PATH every scenario below runs the client under: the shim shadows any | 79 | # The PATH every scenario below runs the client under: the shim shadows any |
| 80 | # real ssh, and `muxd` resolves to the binary under test rather than to | 80 | # real ssh, and `mux d` resolves to the binary under test rather than to |
| 81 | # whatever is installed. | 81 | # whatever is installed. |
| 82 | HPATH="$SSHIM_DIR:$(dirname "$MUXD"):$PATH" | 82 | HPATH="$SSHIM_DIR:$(dirname "$MUX"):$PATH" |
| 83 | # `user@` on the host is not decoration. The QUIC dial strips it | 83 | # `user@` on the host is not decoration. The QUIC dial strips it |
| 84 | # (handoff.dialHost) and gets a loopback literal, which is what makes the | 84 | # (handoff.dialHost) and gets a loopback literal, which is what makes the |
| 85 | # dial reach the daemon at all; the CACHE keys on the whole word, which is | 85 | # dial reach the daemon at all; the CACHE keys on the whole word, which is |
| @@ -89,8 +89,8 @@ HPATH="$SSHIM_DIR:$(dirname "$MUXD"):$PATH" | |||
| 89 | HHOST="mux-e2e@127.0.0.1" | 89 | HHOST="mux-e2e@127.0.0.1" |
| 90 | HCACHE="$XDG_CACHE_HOME/mux/hosts/$HHOST" | 90 | HCACHE="$XDG_CACHE_HOME/mux/hosts/$HHOST" |
| 91 | # (a) COLD: no daemon, no cache. The attach has to produce the daemon — | 91 | # (a) COLD: no daemon, no cache. The attach has to produce the daemon — |
| 92 | # `muxd endpoint` refuses a box that has none, and the CLIENT answers that | 92 | # `mux d endpoint` refuses a box that has none, and the CLIENT answers that |
| 93 | # by running `muxd start` over the shim — fetch coordinates over the shim, | 93 | # by running `mux d start` over the shim — fetch coordinates over the shim, |
| 94 | # and END UP ON QUIC — and that last part is proven by observation rather than | 94 | # and END UP ON QUIC — and that last part is proven by observation rather than |
| 95 | # inference: the ssh the announce arrived on is killed by the client on | 95 | # inference: the ssh the announce arrived on is killed by the client on |
| 96 | # QUIC success, so once its pid is OBSERVED gone, a second marker still | 96 | # QUIC success, so once its pid is OBSERVED gone, a second marker still |
| @@ -108,14 +108,14 @@ wait_for "$OUT.h1" "cold-one" 25 || { | |||
| 108 | cat "$OUT.h1" "$OUT.h1.err" 2>/dev/null; exit 1; } | 108 | cat "$OUT.h1" "$OUT.h1.err" 2>/dev/null; exit 1; } |
| 109 | 109 | ||
| 110 | # Exactly three ssh runs, and the LAST one's pid, from the log rather than | 110 | # Exactly three ssh runs, and the LAST one's pid, from the log rather than |
| 111 | # from ps. Three is the cold shape and it is bounded: `muxd endpoint` on a | 111 | # from ps. Three is the cold shape and it is bounded: `mux d endpoint` on a |
| 112 | # box with no daemon, then `muxd start`, then `muxd endpoint` again. Never | 112 | # box with no daemon, then `mux d start`, then `mux d endpoint` again. Never |
| 113 | # a fourth — the ask buys one start and one retry — and the count is what | 113 | # a fourth — the ask buys one start and one retry — and the count is what |
| 114 | # says so: a client that looped would show four here, not a hang. | 114 | # says so: a client that looped would show four here, not a hang. |
| 115 | HSHIMS=$(wc -l < "$SSHIM_PIDLOG") | 115 | HSHIMS=$(wc -l < "$SSHIM_PIDLOG") |
| 116 | [ "$HSHIMS" -eq 3 ] || { | 116 | [ "$HSHIMS" -eq 3 ] || { |
| 117 | echo "e2e FAIL: cold handoff ran $HSHIMS ssh invocations, want exactly 3" | 117 | echo "e2e FAIL: cold handoff ran $HSHIMS ssh invocations, want exactly 3" |
| 118 | echo " (muxd endpoint on an empty box, muxd start, muxd endpoint)" | 118 | echo " (mux d endpoint on an empty box, mux d start, mux d endpoint)" |
| 119 | cat "$SSHIM_PIDLOG"; exit 1; } | 119 | cat "$SSHIM_PIDLOG"; exit 1; } |
| 120 | # The LAST, not the first: the first two are the refusal and the start, and | 120 | # The LAST, not the first: the first two are the refusal and the start, and |
| 121 | # both are already dead by construction. The ssh whose death proves QUIC | 121 | # both are already dead by construction. The ssh whose death proves QUIC |
| @@ -136,20 +136,20 @@ wait_for "$OUT.h1" "cold-two" 20 || { | |||
| 136 | cat "$OUT.h1" "$OUT.h1.err" 2>/dev/null; exit 1; } | 136 | cat "$OUT.h1" "$OUT.h1.err" 2>/dev/null; exit 1; } |
| 137 | pipe_detach "cold handoff client" | 137 | pipe_detach "cold handoff client" |
| 138 | 138 | ||
| 139 | # The daemon the CLIENT started, by the pid `muxd start`'s own up-line | 139 | # The daemon the CLIENT started, by the pid `mux d start`'s own up-line |
| 140 | # reported — the only handle this suite has on a process that is nobody's | 140 | # reported — the only handle this suite has on a process that is nobody's |
| 141 | # child. The line is `muxd start`'s now, not `muxd endpoint`'s, and it | 141 | # child. The line is `mux d start`'s now, not `mux d endpoint`'s, and it |
| 142 | # reaches this file the same way ssh's own stderr does: inherited. Same | 142 | # reaches this file the same way ssh's own stderr does: inherited. Same |
| 143 | # accepted gap as the M13 blocks: a failure above leaves it untracked, and | 143 | # accepted gap as the M13 blocks: a failure above leaves it untracked, and |
| 144 | # the trap's stop-by-socket is what covers that. | 144 | # the trap's stop-by-socket is what covers that. |
| 145 | grep -q '^muxd: starting' "$OUT.h1.err" || { | 145 | grep -q '^mux d: starting' "$OUT.h1.err" || { |
| 146 | echo "e2e FAIL: cold handoff printed no muxd start line — the client did not" | 146 | echo "e2e FAIL: cold handoff printed no mux d start line — the client did not" |
| 147 | echo " start the daemon that muxd endpoint refused to" | 147 | echo " start the daemon that mux d endpoint refused to" |
| 148 | cat "$OUT.h1.err"; exit 1; } | 148 | cat "$OUT.h1.err"; exit 1; } |
| 149 | HAPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.h1.err" | head -1) | 149 | HAPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.h1.err" | head -1) |
| 150 | defer_kill "$HAPID" | 150 | defer_kill "$HAPID" |
| 151 | [ -n "$HAPID" ] || { | 151 | [ -n "$HAPID" ] || { |
| 152 | echo "e2e FAIL: muxd start's up-line carries no pid"; cat "$OUT.h1.err"; exit 1; } | 152 | echo "e2e FAIL: mux d start's up-line carries no pid"; cat "$OUT.h1.err"; exit 1; } |
| 153 | kill -0 "$HAPID" 2>/dev/null || { | 153 | kill -0 "$HAPID" 2>/dev/null || { |
| 154 | echo "e2e FAIL: the handoff daemon (pid $HAPID) is not alive"; exit 1; } | 154 | echo "e2e FAIL: the handoff daemon (pid $HAPID) is not alive"; exit 1; } |
| 155 | 155 | ||
| @@ -194,7 +194,7 @@ grep -q "warm-ok" "$OUT.h2" || { | |||
| 194 | cat "$OUT.h2" "$OUT.h2.err" 2>/dev/null; exit 1; } | 194 | cat "$OUT.h2" "$OUT.h2.err" 2>/dev/null; exit 1; } |
| 195 | HSHIMS_B2=$(wc -l < "$SSHIM_PIDLOG") | 195 | HSHIMS_B2=$(wc -l < "$SSHIM_PIDLOG") |
| 196 | # This also carries M13's warm-attach silence contract, which is why there | 196 | # This also carries M13's warm-attach silence contract, which is why there |
| 197 | # is no separate grep for spawn progress here: `muxd endpoint` is the only | 197 | # is no separate grep for spawn progress here: `mux d endpoint` is the only |
| 198 | # thing that prints a starting line, it only runs under ssh, and ssh | 198 | # thing that prints a starting line, it only runs under ssh, and ssh |
| 199 | # provably did not run. A progress grep could therefore never fail on its | 199 | # provably did not run. A progress grep could therefore never fail on its |
| 200 | # own — it would be a check the line above has already made. | 200 | # own — it would be a check the line above has already made. |
| @@ -284,7 +284,7 @@ assert_converged "$OUT.h3" "$SOCK16" "stale-cache self-heal" | |||
| 284 | ok "a stale cache self-heals: one refetch (${HMS_C}ms, no budget spent), no fallback line, real port cached" | 284 | ok "a stale cache self-heals: one refetch (${HMS_C}ms, no budget spent), no fallback line, real port cached" |
| 285 | 285 | ||
| 286 | # (d) FALLBACK LINE, by key mismatch. A second daemon holding a key nothing | 286 | # (d) FALLBACK LINE, by key mismatch. A second daemon holding a key nothing |
| 287 | # else on this box has; `muxd endpoint` announces the DEFAULT key (that is | 287 | # else on this box has; `mux d endpoint` announces the DEFAULT key (that is |
| 288 | # what the endpoint process resolves) together with this daemon's real | 288 | # what the endpoint process resolves) together with this daemon's real |
| 289 | # port, so the coordinates are perfectly well-formed and the handshake can | 289 | # port, so the coordinates are perfectly well-formed and the handshake can |
| 290 | # never complete. PSK auth is mutual and a listener does not answer a peer | 290 | # never complete. PSK auth is mutual and a listener does not answer a peer |
| @@ -372,7 +372,7 @@ ok "a key mismatch falls back to the ssh pipe: one deadline (${HMS}ms), one line | |||
| 372 | 372 | ||
| 373 | # (e) ANNOUNCE-NONE: a remote that cannot produce coordinates at all. The | 373 | # (e) ANNOUNCE-NONE: a remote that cannot produce coordinates at all. The |
| 374 | # lever is a config home that is a FILE, so the default key can be neither | 374 | # lever is a config home that is a FILE, so the default key can be neither |
| 375 | # created nor found — `muxd endpoint` says so on stderr (ssh carries it to | 375 | # created nor found — `mux d endpoint` says so on stderr (ssh carries it to |
| 376 | # the user) and announces `endpoint none`. | 376 | # the user) and announces `endpoint none`. |
| 377 | # | 377 | # |
| 378 | # Against that, no deadline may be paid: no coordinates were ever in play, | 378 | # Against that, no deadline may be paid: no coordinates were ever in play, |
| @@ -393,7 +393,7 @@ HT3=$(date +%s%N) | |||
| 393 | HMS_E=$(( (HT3 - HT2) / 1000000 )) | 393 | HMS_E=$(( (HT3 - HT2) / 1000000 )) |
| 394 | pipe_detach "announce-none client" | 394 | pipe_detach "announce-none client" |
| 395 | # The remote said why, in one line, on the stderr ssh already carries. | 395 | # The remote said why, in one line, on the stderr ssh already carries. |
| 396 | grep -q '^muxd endpoint: .*staying on ssh' "$OUT.h5.err" || { | 396 | grep -q '^mux d endpoint: .*staying on ssh' "$OUT.h5.err" || { |
| 397 | echo "e2e FAIL: announce-none said nothing about why it stayed on ssh" | 397 | echo "e2e FAIL: announce-none said nothing about why it stayed on ssh" |
| 398 | cat "$OUT.h5.err"; exit 1; } | 398 | cat "$OUT.h5.err"; exit 1; } |
| 399 | # ...and said nothing about a fallback, because nothing fell back: the | 399 | # ...and said nothing about a fallback, because nothing fell back: the |
test/e2e_05_session.sh
| Old | New | ||
|---|---|---|---|
| @@ -87,7 +87,7 @@ fi | |||
| 87 | # stats names all three, and names them one segment each. `sessions=3` on | 87 | # stats names all three, and names them one segment each. `sessions=3` on |
| 88 | # its own would survive a daemon that counted right and routed wrong, so | 88 | # its own would survive a daemon that counted right and routed wrong, so |
| 89 | # the per-session segments are grepped by name too. | 89 | # the per-session segments are grepped by name too. |
| 90 | "$MUXD" stats --sock "$SOCK21" > "$OUT.m18stats" 2>&1 | 90 | "$MUX" d stats --sock "$SOCK21" > "$OUT.m18stats" 2>&1 |
| 91 | grep -q 'sessions=3' "$OUT.m18stats" || { | 91 | grep -q 'sessions=3' "$OUT.m18stats" || { |
| 92 | echo "e2e FAIL: M18: stats does not see three sessions:" | 92 | echo "e2e FAIL: M18: stats does not see three sessions:" |
| 93 | cat "$OUT.m18stats"; exit 1; } | 93 | cat "$OUT.m18stats"; exit 1; } |
| @@ -101,40 +101,40 @@ done | |||
| 101 | # hang and not a fabricated "connection lost" (decision 9). The exit code | 101 | # hang and not a fabricated "connection lost" (decision 9). The exit code |
| 102 | # is 0 because the daemon replied; the text is the whole point. | 102 | # is 0 because the daemon replied; the text is the whole point. |
| 103 | dump_session "$SOCK21" zz > "$OUT.m18zz" 2>&1 | 103 | dump_session "$SOCK21" zz > "$OUT.m18zz" 2>&1 |
| 104 | grep -q 'muxd: no such session: zz' "$OUT.m18zz" || { | 104 | grep -q 'mux d: no such session: zz' "$OUT.m18zz" || { |
| 105 | echo "e2e FAIL: M18: an unknown session did not say so; dump answered:" | 105 | echo "e2e FAIL: M18: an unknown session did not say so; dump answered:" |
| 106 | cat "$OUT.m18zz"; exit 1; } | 106 | cat "$OUT.m18zz"; exit 1; } |
| 107 | 107 | ||
| 108 | # muxa addresses a session by name on the verbs that never attach at all. | 108 | # mux a addresses a session by name on the verbs that never attach at all. |
| 109 | # status_req and debug_dump grew the same name tail attach did (decision | 109 | # status_req and debug_dump grew the same name tail attach did (decision |
| 110 | # 14), and that is what lets an agent ask about one session of several | 110 | # 14), and that is what lets an agent ask about one session of several |
| 111 | # without claiming a grid — muxa attaches at 0x0 or not at all, so without | 111 | # without claiming a grid — mux a attaches at 0x0 or not at all, so without |
| 112 | # the tail it could only ever have answered for the default session. | 112 | # the tail it could only ever have answered for the default session. |
| 113 | set +e | 113 | set +e |
| 114 | timeout 20 "$MUXA" status --sock "$SOCK21" --session b > "$OUT.m18mst" 2>&1 | 114 | timeout 20 "$MUX" a status --sock "$SOCK21" --session b > "$OUT.m18mst" 2>&1 |
| 115 | RC=$? | 115 | RC=$? |
| 116 | set -e | 116 | set -e |
| 117 | [ "$RC" -eq 0 ] || { | 117 | [ "$RC" -eq 0 ] || { |
| 118 | echo "e2e FAIL: M18: muxa status --session b exited $RC:" | 118 | echo "e2e FAIL: M18: mux a status --session b exited $RC:" |
| 119 | cat "$OUT.m18mst"; exit 1; } | 119 | cat "$OUT.m18mst"; exit 1; } |
| 120 | # Not the exit code alone: a silently empty object would pass that. `cols` | 120 | # Not the exit code alone: a silently empty object would pass that. `cols` |
| 121 | # is a field only a decoded StatusReply can put there (agent.sh's shape). | 121 | # is a field only a decoded StatusReply can put there (agent.sh's shape). |
| 122 | grep -q '"cols"' "$OUT.m18mst" || { | 122 | grep -q '"cols"' "$OUT.m18mst" || { |
| 123 | echo "e2e FAIL: M18: muxa status --session b answered without a status reply:" | 123 | echo "e2e FAIL: M18: mux a status --session b answered without a status reply:" |
| 124 | cat "$OUT.m18mst"; exit 1; } | 124 | cat "$OUT.m18mst"; exit 1; } |
| 125 | 125 | ||
| 126 | set +e | 126 | set +e |
| 127 | timeout 20 "$MUXA" capture --sock "$SOCK21" --session a > "$OUT.m18mcap" 2>&1 | 127 | timeout 20 "$MUX" a capture --sock "$SOCK21" --session a > "$OUT.m18mcap" 2>&1 |
| 128 | RC=$? | 128 | RC=$? |
| 129 | set -e | 129 | set -e |
| 130 | [ "$RC" -eq 0 ] || { | 130 | [ "$RC" -eq 0 ] || { |
| 131 | echo "e2e FAIL: M18: muxa capture --session a exited $RC:" | 131 | echo "e2e FAIL: M18: mux a capture --session a exited $RC:" |
| 132 | cat "$OUT.m18mcap"; exit 1; } | 132 | cat "$OUT.m18mcap"; exit 1; } |
| 133 | grep -q 'm18a-pin' "$OUT.m18mcap" || { | 133 | grep -q 'm18a-pin' "$OUT.m18mcap" || { |
| 134 | echo "e2e FAIL: M18: muxa capture --session a did not return a's grid:" | 134 | echo "e2e FAIL: M18: mux a capture --session a did not return a's grid:" |
| 135 | cat "$OUT.m18mcap"; exit 1; } | 135 | cat "$OUT.m18mcap"; exit 1; } |
| 136 | if grep -q 'm18b-pin' "$OUT.m18mcap"; then | 136 | if grep -q 'm18b-pin' "$OUT.m18mcap"; then |
| 137 | echo "e2e FAIL: M18: muxa capture --session a returned b's grid too:" | 137 | echo "e2e FAIL: M18: mux a capture --session a returned b's grid too:" |
| 138 | cat "$OUT.m18mcap"; exit 1 | 138 | cat "$OUT.m18mcap"; exit 1 |
| 139 | fi | 139 | fi |
| 140 | 140 | ||
| @@ -188,7 +188,7 @@ wait_sessions "$SOCK21" 0 "M18: every one of the three shells has exited" | |||
| 188 | # than by the next scenario finding a dead socket. | 188 | # than by the next scenario finding a dead socket. |
| 189 | sleep 2 | 189 | sleep 2 |
| 190 | kill -0 "$D18PID" 2>/dev/null || { | 190 | kill -0 "$D18PID" 2>/dev/null || { |
| 191 | echo "e2e FAIL: M18: the daemon left with its last session — only muxd stop ends one" | 191 | echo "e2e FAIL: M18: the daemon left with its last session — only mux d stop ends one" |
| 192 | cat "$OUT.m18.d"; exit 1; } | 192 | cat "$OUT.m18.d"; exit 1; } |
| 193 | 193 | ||
| 194 | # And it can be born into again: a bare `mux --sock` names no session, so it | 194 | # And it can be born into again: a bare `mux --sock` names no session, so it |
| @@ -214,7 +214,7 @@ ok "a daemon outlives its last session and is born into again" | |||
| 214 | # proved with a CLI. | 214 | # proved 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 | "$MUXWEB" --sock "$SOCK22#a" --sock "$SOCK22#b" --port "$WPORT3" > "$OUT.m18wh" 2>&1 & | 217 | "$MUX" web --sock "$SOCK22#a" --sock "$SOCK22#b" --port "$WPORT3" > "$OUT.m18wh" 2>&1 & |
| 218 | W3PID=$! | 218 | W3PID=$! |
| 219 | defer_kill "$W3PID" | 219 | defer_kill "$W3PID" |
| 220 | wait_for "$OUT.m18wh" "serving" 10 || { | 220 | wait_for "$OUT.m18wh" "serving" 10 || { |
| @@ -297,7 +297,7 @@ chmod 600 "$M18KEY" | |||
| 297 | # This band sits INSIDE /proc/sys/net/ipv4/ip_local_port_range, and the | 297 | # This band sits INSIDE /proc/sys/net/ipv4/ip_local_port_range, and the |
| 298 | # scenarios above open plenty of outbound QUIC sockets — one of them can | 298 | # scenarios above open plenty of outbound QUIC sockets — one of them can |
| 299 | # already hold the candidate by the time this daemon asks for it. Soak | 299 | # already hold the candidate by the time this daemon asks for it. Soak |
| 300 | # caught exactly that, once in ten runs: `muxd: a daemon is already | 300 | # caught exactly that, once in ten runs: `mux d: a daemon is already |
| 301 | # listening on udp 127.0.0.1:57943`, from a client socket, not a daemon. | 301 | # listening on udp 127.0.0.1:57943`, from a client socket, not a daemon. |
| 302 | # | 302 | # |
| 303 | # So step on the daemon's own refusal until one binds, the way agent.sh's | 303 | # So step on the daemon's own refusal until one binds, the way agent.sh's |
| @@ -305,7 +305,7 @@ chmod 600 "$M18KEY" | |||
| 305 | # is reported with the daemon's own words; only the taken-port case walks. | 305 | # is reported with the daemon's own words; only the taken-port case walks. |
| 306 | M18_TRIES=0 | 306 | M18_TRIES=0 |
| 307 | while : ; do | 307 | while : ; do |
| 308 | "$MUXD" run --sock "$SOCK23" --quic "127.0.0.1:$QPORT5" --key "$M18KEY" \ | 308 | "$MUX" d run --sock "$SOCK23" --quic "127.0.0.1:$QPORT5" --key "$M18KEY" \ |
| 309 | --quic-idle-ms 15000 --shell /bin/sh > "$OUT.m18q.d" 2>&1 & | 309 | --quic-idle-ms 15000 --shell /bin/sh > "$OUT.m18q.d" 2>&1 & |
| 310 | D20PID=$! | 310 | D20PID=$! |
| 311 | defer_kill "$D20PID" | 311 | defer_kill "$D20PID" |
| @@ -348,9 +348,9 @@ if dump_session "$SOCK23" b | grep -q "q18a-pin"; then | |||
| 348 | echo "e2e FAIL: M18 quic: a's marker is on b's grid — one session, not two:" | 348 | echo "e2e FAIL: M18 quic: a's marker is on b's grid — one session, not two:" |
| 349 | dump_session "$SOCK23" b; exit 1 | 349 | dump_session "$SOCK23" b; exit 1 |
| 350 | fi | 350 | fi |
| 351 | "$MUXD" stats --sock "$SOCK23" | grep -q 'sessions=3' || { | 351 | "$MUX" d stats --sock "$SOCK23" | grep -q 'sessions=3' || { |
| 352 | echo "e2e FAIL: M18 quic: two dials did not make two sessions beside the default:" | 352 | echo "e2e FAIL: M18 quic: two dials did not make two sessions beside the default:" |
| 353 | "$MUXD" stats --sock "$SOCK23"; exit 1; } | 353 | "$MUX" d stats --sock "$SOCK23"; exit 1; } |
| 354 | 354 | ||
| 355 | # The cross-transport join. Same name, different door: this must land in | 355 | # The cross-transport join. Same name, different door: this must land in |
| 356 | # the shell the QUIC client left behind, not spawn a second one. | 356 | # the shell the QUIC client left behind, not spawn a second one. |
| @@ -367,9 +367,9 @@ dump_session "$SOCK23" a | grep -q "q18a-pin" || { | |||
| 367 | dump_session "$SOCK23" a; exit 1; } | 367 | dump_session "$SOCK23" a; exit 1; } |
| 368 | # ...and it JOINED rather than created: a fourth session here would mean | 368 | # ...and it JOINED rather than created: a fourth session here would mean |
| 369 | # the name resolved per-transport, which is the bug this pair exists for. | 369 | # the name resolved per-transport, which is the bug this pair exists for. |
| 370 | "$MUXD" stats --sock "$SOCK23" | grep -q 'sessions=3' || { | 370 | "$MUX" d stats --sock "$SOCK23" | grep -q 'sessions=3' || { |
| 371 | echo "e2e FAIL: M18 quic: a socket join of an existing name made a new session:" | 371 | echo "e2e FAIL: M18 quic: a socket join of an existing name made a new session:" |
| 372 | "$MUXD" stats --sock "$SOCK23"; exit 1; } | 372 | "$MUX" d stats --sock "$SOCK23"; exit 1; } |
| 373 | if dump_session "$SOCK23" b | grep -q "x18a-pin"; then | 373 | if dump_session "$SOCK23" b | grep -q "x18a-pin"; then |
| 374 | echo "e2e FAIL: M18 quic: the socket client's marker leaked into b:" | 374 | echo "e2e FAIL: M18 quic: the socket client's marker leaked into b:" |
| 375 | dump_session "$SOCK23" b; exit 1 | 375 | dump_session "$SOCK23" b; exit 1 |
test/e2e_06_web.sh
| Old | New | ||
|---|---|---|---|
| @@ -26,7 +26,7 @@ WPORT2=$(( 46000 + ($$ % 4000) )) | |||
| 26 | # artifact, and $XDG_STATE_HOME above is shared with every other scenario — | 26 | # artifact, and $XDG_STATE_HOME above is shared with every other scenario — |
| 27 | # the M-web and M18 hubs write their argv walls there, so a wall read out of | 27 | # the M-web and M18 hubs write their argv walls there, so a wall read out of |
| 28 | # it would be some other block's. Never the developer's ~/.local/state either; that is | 28 | # it would be some other block's. Never the developer's ~/.local/state either; that is |
| 29 | # why every muxweb here is spawned with the override in front of it. | 29 | # why every hub here is spawned with the override in front of it. |
| 30 | SOCK25="${TMPDIR:-/tmp}/muxd-e2e-dynwall-$$.sock" | 30 | SOCK25="${TMPDIR:-/tmp}/muxd-e2e-dynwall-$$.sock" |
| 31 | defer_sock "$SOCK25" | 31 | defer_sock "$SOCK25" |
| 32 | WPORT4=$(( 61000 + ($$ % 4000) )) | 32 | WPORT4=$(( 61000 + ($$ % 4000) )) |
| @@ -41,7 +41,7 @@ defer_rm "$DWSTATE" | |||
| 41 | # DAEMON-SIDE and hub-free on purpose: the mechanism is applySize's cols<2 | 41 | # DAEMON-SIDE and hub-free on purpose: the mechanism is applySize's cols<2 |
| 42 | # refusal plus claimGrid's 0x0-slot refusal (server.zig), and it must hold | 42 | # refusal plus claimGrid's 0x0-slot refusal (server.zig), and it must hold |
| 43 | # for any client that sends a size nobody can live at. The probe for "the grid did not move" is textual: a marker | 43 | # for any client that sends a size nobody can live at. The probe for "the grid did not move" is textual: a marker |
| 44 | # string typed at 80 wide can only appear CONTIGUOUS in `muxd dump` if | 44 | # string typed at 80 wide can only appear CONTIGUOUS in `mux d dump` if |
| 45 | # the grid is still 80 wide — a 1-column grid puts every glyph on its | 45 | # the grid is still 80 wide — a 1-column grid puts every glyph on its |
| 46 | # own row, so grep itself is the geometry assertion. | 46 | # own row, so grep itself is the geometry assertion. |
| 47 | start_daemon "$SOCK18" "$OUT.weba.d" "tiny attach daemon never bound" --shell /bin/sh | 47 | start_daemon "$SOCK18" "$OUT.weba.d" "tiny attach daemon never bound" --shell /bin/sh |
| @@ -87,12 +87,12 @@ set -e | |||
| 87 | 87 | ||
| 88 | # The geometry assertion: both markers contiguous AFTER the 1x1 client | 88 | # The geometry assertion: both markers contiguous AFTER the 1x1 client |
| 89 | # attached, typed, and left. A grid moved to 1 wide cannot hold either. | 89 | # attached, typed, and left. A grid moved to 1 wide cannot hold either. |
| 90 | "$MUXD" dump --sock "$SOCK18" | grep -q "wall-pin" || { | 90 | "$MUX" d dump --sock "$SOCK18" | grep -q "wall-pin" || { |
| 91 | echo "e2e FAIL: tiny attach: first marker lost after 1x1 attach:" | 91 | echo "e2e FAIL: tiny attach: first marker lost after 1x1 attach:" |
| 92 | "$MUXD" dump --sock "$SOCK18"; exit 1; } | 92 | "$MUX" d dump --sock "$SOCK18"; exit 1; } |
| 93 | "$MUXD" dump --sock "$SOCK18" | grep -q "zz-web" || { | 93 | "$MUX" d dump --sock "$SOCK18" | grep -q "zz-web" || { |
| 94 | echo "e2e FAIL: tiny attach: the 1x1 attacher moved the grid (marker not contiguous):" | 94 | echo "e2e FAIL: tiny attach: the 1x1 attacher moved the grid (marker not contiguous):" |
| 95 | "$MUXD" dump --sock "$SOCK18"; exit 1; } | 95 | "$MUX" d dump --sock "$SOCK18"; exit 1; } |
| 96 | 96 | ||
| 97 | await_out "$OUT.weba" "zz-web" "tiny attach: the second marker never reached the 80x24 client" | 97 | await_out "$OUT.weba" "zz-web" "tiny attach: the second marker never reached the 80x24 client" |
| 98 | pipe_detach "tiny attach: 80x24 client" | 98 | pipe_detach "tiny attach: 80x24 client" |
| @@ -105,7 +105,7 @@ ok "a 1x1 attach is refused the grid and can never claim it" | |||
| 105 | # --- M-web (b): the hub pumps a real session; wrong Origin refused. | 105 | # --- M-web (b): the hub pumps a real session; wrong Origin refused. |
| 106 | start_daemon "$SOCK19" "$OUT.webb.d" "web hub daemon never bound" --shell /bin/sh | 106 | start_daemon "$SOCK19" "$OUT.webb.d" "web hub daemon never bound" --shell /bin/sh |
| 107 | D15PID=$DPID | 107 | D15PID=$DPID |
| 108 | "$MUXWEB" --sock "$SOCK19" --port "$WPORT" > "$OUT.webh" 2>&1 & | 108 | "$MUX" web --sock "$SOCK19" --port "$WPORT" > "$OUT.webh" 2>&1 & |
| 109 | W1PID=$! | 109 | W1PID=$! |
| 110 | defer_kill "$W1PID" | 110 | defer_kill "$W1PID" |
| 111 | wait_for "$OUT.webh" "serving" 10 || { | 111 | wait_for "$OUT.webh" "serving" 10 || { |
| @@ -119,7 +119,7 @@ wait_grid "$SOCK19" "web-b1" "web hub: the typing client's marker" | |||
| 119 | 119 | ||
| 120 | # The browser stand-in: a passive 0x0 wall tile through the hub. Its | 120 | # The browser stand-in: a passive 0x0 wall tile through the hub. Its |
| 121 | # final grid is the daemon's replica as REPLAYED THROUGH the WebSocket | 121 | # final grid is the daemon's replica as REPLAYED THROUGH the WebSocket |
| 122 | # leg, dumped in muxd dump's own format. | 122 | # leg, dumped in mux d dump's own format. |
| 123 | set +e | 123 | set +e |
| 124 | timeout 40 "$WSCLIENT" --port "$WPORT" --tile 0 --out "$OUT.webws" --err "$OUT.webws.err" <<'EOF' | 124 | timeout 40 "$WSCLIENT" --port "$WPORT" --tile 0 --out "$OUT.webws" --err "$OUT.webws.err" <<'EOF' |
| 125 | attach 0 0 | 125 | attach 0 0 |
| @@ -138,7 +138,7 @@ assert_ws_converged "$OUT.webws" "$SOCK19" "web hub" | |||
| 138 | 138 | ||
| 139 | # Wrong Origin: refused at HTTP, before any upgrade — the wsclient sees | 139 | # Wrong Origin: refused at HTTP, before any upgrade — the wsclient sees |
| 140 | # a non-101 and exits 4 — and the daemon never sees a client for it. | 140 | # a non-101 and exits 4 — and the daemon never sees a client for it. |
| 141 | CLIENTS_BEFORE=$("$MUXD" stats --sock "$SOCK19" | sed -n 's/.*clients=\([0-9]*\).*/\1/p') | 141 | CLIENTS_BEFORE=$("$MUX" d stats --sock "$SOCK19" | sed -n 's/.*clients=\([0-9]*\).*/\1/p') |
| 142 | set +e | 142 | set +e |
| 143 | timeout 20 "$WSCLIENT" --port "$WPORT" --tile 0 --origin http://evil.example \ | 143 | timeout 20 "$WSCLIENT" --port "$WPORT" --tile 0 --origin http://evil.example \ |
| 144 | --out "$OUT.webevil" --err "$OUT.webevil.err" < /dev/null | 144 | --out "$OUT.webevil" --err "$OUT.webevil.err" < /dev/null |
| @@ -150,7 +150,7 @@ set -e | |||
| 150 | grep -q "upgrade refused.*403" "$OUT.webevil.err" || { | 150 | grep -q "upgrade refused.*403" "$OUT.webevil.err" || { |
| 151 | echo "e2e FAIL: web hub: refusal was not the 403 it must be:" | 151 | echo "e2e FAIL: web hub: refusal was not the 403 it must be:" |
| 152 | cat -v "$OUT.webevil.err"; exit 1; } | 152 | cat -v "$OUT.webevil.err"; exit 1; } |
| 153 | CLIENTS_AFTER=$("$MUXD" stats --sock "$SOCK19" | sed -n 's/.*clients=\([0-9]*\).*/\1/p') | 153 | CLIENTS_AFTER=$("$MUX" d stats --sock "$SOCK19" | sed -n 's/.*clients=\([0-9]*\).*/\1/p') |
| 154 | [ "$CLIENTS_BEFORE" = "$CLIENTS_AFTER" ] || { | 154 | [ "$CLIENTS_BEFORE" = "$CLIENTS_AFTER" ] || { |
| 155 | echo "e2e FAIL: web hub: evil origin reached the daemon (clients $CLIENTS_BEFORE -> $CLIENTS_AFTER)"; exit 1; } | 155 | echo "e2e FAIL: web hub: evil origin reached the daemon (clients $CLIENTS_BEFORE -> $CLIENTS_AFTER)"; exit 1; } |
| 156 | 156 | ||
| @@ -165,14 +165,14 @@ D15PID="" | |||
| 165 | ok "hub pumps a real session; wrong origin refused" | 165 | ok "hub pumps a real session; wrong origin refused" |
| 166 | 166 | ||
| 167 | # --- M-web (c): the hub narrates the tear; the replica re-attaches | 167 | # --- M-web (c): the hub narrates the tear; the replica re-attaches |
| 168 | # across an epoch. muxd stop kills the daemon under a live tile; the hub | 168 | # across an epoch. mux d stop kills the daemon under a live tile; the hub |
| 169 | # must say `reconnecting`, redial the restarted daemon (same socket, new | 169 | # must say `reconnecting`, redial the restarted daemon (same socket, new |
| 170 | # epoch), say `up`, and the stand-in's re-attach — quoting coordinates | 170 | # epoch), say `up`, and the stand-in's re-attach — quoting coordinates |
| 171 | # the NEW daemon has never issued — must be answered with a snapshot | 171 | # the NEW daemon has never issued — must be answered with a snapshot |
| 172 | # that converges on the new session's content. | 172 | # that converges on the new session's content. |
| 173 | start_daemon "$SOCK20" "$OUT.webc.d" "web tear daemon never bound" --shell /bin/sh | 173 | start_daemon "$SOCK20" "$OUT.webc.d" "web tear daemon never bound" --shell /bin/sh |
| 174 | D16PID=$DPID | 174 | D16PID=$DPID |
| 175 | "$MUXWEB" --sock "$SOCK20" --port "$WPORT2" > "$OUT.webh2" 2>&1 & | 175 | "$MUX" web --sock "$SOCK20" --port "$WPORT2" > "$OUT.webh2" 2>&1 & |
| 176 | W2PID=$! | 176 | W2PID=$! |
| 177 | defer_kill "$W2PID" | 177 | defer_kill "$W2PID" |
| 178 | wait_for "$OUT.webh2" "serving" 10 || { | 178 | wait_for "$OUT.webh2" "serving" 10 || { |
| @@ -235,13 +235,13 @@ ok "hub narrates the tear; the replica re-attaches across an epoch" | |||
| 235 | 235 | ||
| 236 | # --- M-wall: the wall is RUNTIME state. The page adds, removes and | 236 | # --- M-wall: the wall is RUNTIME state. The page adds, removes and |
| 237 | # reorders tiles over HTTP, and the file is what makes that survive a | 237 | # reorders tiles over HTTP, and the file is what makes that survive a |
| 238 | # restart. Every muxweb here runs with a state home of its own (see | 238 | # restart. Every mux web here runs with a state home of its own (see |
| 239 | # $DWSTATE), so what this leg reads back is the wall this leg wrote — and | 239 | # $DWSTATE), so what this leg reads back is the wall this leg wrote — and |
| 240 | # never the developer's real one. | 240 | # never the developer's real one. |
| 241 | start_daemon "$SOCK25" "$OUT.dw.d" "dyn wall daemon never bound" --shell /bin/sh | 241 | start_daemon "$SOCK25" "$OUT.dw.d" "dyn wall daemon never bound" --shell /bin/sh |
| 242 | D22PID=$DPID | 242 | D22PID=$DPID |
| 243 | 243 | ||
| 244 | XDG_STATE_HOME="$DWSTATE" "$MUXWEB" --port "$WPORT4" > "$OUT.dwh" 2>&1 & | 244 | XDG_STATE_HOME="$DWSTATE" "$MUX" web --port "$WPORT4" > "$OUT.dwh" 2>&1 & |
| 245 | W4PID=$! | 245 | W4PID=$! |
| 246 | defer_kill "$W4PID" | 246 | defer_kill "$W4PID" |
| 247 | wait_for "$OUT.dwh" "serving" 10 || { | 247 | wait_for "$OUT.dwh" "serving" 10 || { |
| @@ -339,15 +339,15 @@ curl -s -H "Origin: $DWORIG" -X PUT --data '0,7' "$DWORIG/tiles" | grep -q '^\[{ | |||
| 339 | # come out of the same sed: a stats line that renamed or dropped the field | 339 | # come out of the same sed: a stats line that renamed or dropped the field |
| 340 | # would make this `[ "" = "" ]` and pass forever, which is the one way this | 340 | # would make this `[ "" = "" ]` and pass forever, which is the one way this |
| 341 | # check could fail green (want_stat, above, guards its own the same way). | 341 | # check could fail green (want_stat, above, guards its own the same way). |
| 342 | SESS_BEFORE=$("$MUXD" stats --sock "$SOCK25" | sed -n 's/.*sessions=\([0-9]*\).*/\1/p') | 342 | SESS_BEFORE=$("$MUX" d stats --sock "$SOCK25" | sed -n 's/.*sessions=\([0-9]*\).*/\1/p') |
| 343 | [ -n "$SESS_BEFORE" ] || { | 343 | [ -n "$SESS_BEFORE" ] || { |
| 344 | echo "e2e FAIL: dyn wall: no sessions= in stats (the field moved?); it says:" | 344 | echo "e2e FAIL: dyn wall: no sessions= in stats (the field moved?); it says:" |
| 345 | "$MUXD" stats --sock "$SOCK25"; exit 1; } | 345 | "$MUX" d stats --sock "$SOCK25"; exit 1; } |
| 346 | RC=$(curl -s -o /dev/null -w '%{http_code}' -H "Origin: $DWORIG" -X DELETE "$DWORIG/tiles/0") | 346 | RC=$(curl -s -o /dev/null -w '%{http_code}' -H "Origin: $DWORIG" -X DELETE "$DWORIG/tiles/0") |
| 347 | [ "$RC" = "204" ] || { echo "e2e FAIL: dyn wall: delete got $RC, want 204"; exit 1; } | 347 | [ "$RC" = "204" ] || { echo "e2e FAIL: dyn wall: delete got $RC, want 204"; exit 1; } |
| 348 | RC=$(curl -s -o /dev/null -w '%{http_code}' -H "Origin: $DWORIG" -X DELETE "$DWORIG/tiles/0") | 348 | RC=$(curl -s -o /dev/null -w '%{http_code}' -H "Origin: $DWORIG" -X DELETE "$DWORIG/tiles/0") |
| 349 | [ "$RC" = "404" ] || { echo "e2e FAIL: dyn wall: second delete got $RC, want 404"; exit 1; } | 349 | [ "$RC" = "404" ] || { echo "e2e FAIL: dyn wall: second delete got $RC, want 404"; exit 1; } |
| 350 | SESS_AFTER=$("$MUXD" stats --sock "$SOCK25" | sed -n 's/.*sessions=\([0-9]*\).*/\1/p') | 350 | SESS_AFTER=$("$MUX" d stats --sock "$SOCK25" | sed -n 's/.*sessions=\([0-9]*\).*/\1/p') |
| 351 | [ "$SESS_BEFORE" = "$SESS_AFTER" ] || { | 351 | [ "$SESS_BEFORE" = "$SESS_AFTER" ] || { |
| 352 | echo "e2e FAIL: dyn wall: DELETE killed a session ($SESS_BEFORE -> $SESS_AFTER)"; exit 1; } | 352 | echo "e2e FAIL: dyn wall: DELETE killed a session ($SESS_BEFORE -> $SESS_AFTER)"; exit 1; } |
| 353 | 353 | ||
| @@ -380,7 +380,7 @@ grep -qxF -- "--sock $SOCK25#b" "$DWSTATE/mux/wall" || { | |||
| 380 | # hub numbers from 0 in wall order — and the SPELLING is what persisted. | 380 | # hub numbers from 0 in wall order — and the SPELLING is what persisted. |
| 381 | softkill "$W4PID" || true | 381 | softkill "$W4PID" || true |
| 382 | wait_pid_gone "$W4PID" "dyn wall: first hub killed by tracked pid" | 382 | wait_pid_gone "$W4PID" "dyn wall: first hub killed by tracked pid" |
| 383 | XDG_STATE_HOME="$DWSTATE" "$MUXWEB" --port "$WPORT4" > "$OUT.dwh2" 2>&1 & | 383 | XDG_STATE_HOME="$DWSTATE" "$MUX" web --port "$WPORT4" > "$OUT.dwh2" 2>&1 & |
| 384 | W4PID=$! | 384 | W4PID=$! |
| 385 | defer_kill "$W4PID" | 385 | defer_kill "$W4PID" |
| 386 | wait_for "$OUT.dwh2" "serving" 10 || { | 386 | wait_for "$OUT.dwh2" "serving" 10 || { |
| @@ -392,16 +392,16 @@ curl -s "$DWORIG/tiles" | grep -q "^\[{\"id\":0,\"label\":\"--sock $SOCK25#b\"" | |||
| 392 | # Restart WITH argv: the override is the VIEW's, not the file's. This | 392 | # Restart WITH argv: the override is the VIEW's, not the file's. This |
| 393 | # assertion INVERTED with the attach-history phase and did not merely move. | 393 | # assertion INVERTED with the attach-history phase and did not merely move. |
| 394 | # It used to read "argv appended to the wall instead of replacing it" — a | 394 | # It used to read "argv appended to the wall instead of replacing it" — a |
| 395 | # pin on muxweb overwriting the state file with whatever it was told to | 395 | # pin on mux web overwriting the state file with whatever it was told to |
| 396 | # show. That file is the user's attach history now, written by every `mux` | 396 | # show. That file is the user's attach history now, written by every `mux` |
| 397 | # attach, so one `muxweb HOST` overwriting it would silently erase the lot. | 397 | # attach, so one `mux web HOST` overwriting it would silently erase the lot. |
| 398 | # Argv tiles are ADDED (deduped by spelling) and nothing is removed; | 398 | # Argv tiles are ADDED (deduped by spelling) and nothing is removed; |
| 399 | # forgetting stays explicit — the page's `x`, the wall's `x`, `mux wall rm`. | 399 | # forgetting stays explicit — the page's `x`, the wall's `x`, `mux wall rm`. |
| 400 | # The tile is spelled as ONE quoted argument — the wall file's own | 400 | # The tile is spelled as ONE quoted argument — the wall file's own |
| 401 | # spelling, handed back to the binary that wrote it. | 401 | # spelling, handed back to the binary that wrote it. |
| 402 | softkill "$W4PID" || true | 402 | softkill "$W4PID" || true |
| 403 | wait_pid_gone "$W4PID" "dyn wall: restored hub killed by tracked pid" | 403 | wait_pid_gone "$W4PID" "dyn wall: restored hub killed by tracked pid" |
| 404 | XDG_STATE_HOME="$DWSTATE" "$MUXWEB" "--sock $SOCK25" --port "$WPORT4" > "$OUT.dwh3" 2>&1 & | 404 | XDG_STATE_HOME="$DWSTATE" "$MUX" web "--sock $SOCK25" --port "$WPORT4" > "$OUT.dwh3" 2>&1 & |
| 405 | W4PID=$! | 405 | W4PID=$! |
| 406 | defer_kill "$W4PID" | 406 | defer_kill "$W4PID" |
| 407 | wait_for "$OUT.dwh3" "serving" 10 || { | 407 | wait_for "$OUT.dwh3" "serving" 10 || { |
| @@ -414,7 +414,7 @@ grep -qxF -- "--sock $SOCK25" "$DWSTATE/mux/wall" || { | |||
| 414 | # on an overwrite, and a count alone would not say which lines are which. | 414 | # on an overwrite, and a count alone would not say which lines are which. |
| 415 | grep -qxF -- "--sock $SOCK25#b" "$DWSTATE/mux/wall" || { | 415 | grep -qxF -- "--sock $SOCK25#b" "$DWSTATE/mux/wall" || { |
| 416 | echo "e2e FAIL: dyn wall: argv ERASED the wall it was added to — an" | 416 | echo "e2e FAIL: dyn wall: argv ERASED the wall it was added to — an" |
| 417 | echo " attach history overwritten by one muxweb invocation:" | 417 | echo " attach history overwritten by one mux web invocation:" |
| 418 | cat "$DWSTATE/mux/wall"; exit 1; } | 418 | cat "$DWSTATE/mux/wall"; exit 1; } |
| 419 | [ "$(wc -l < "$DWSTATE/mux/wall")" = "2" ] || { | 419 | [ "$(wc -l < "$DWSTATE/mux/wall")" = "2" ] || { |
| 420 | echo "e2e FAIL: dyn wall: the wall file is not the two lines argv added to:" | 420 | echo "e2e FAIL: dyn wall: the wall file is not the two lines argv added to:" |
test/e2e_07_wallcli.sh
| Old | New | ||
|---|---|---|---|
| @@ -72,7 +72,7 @@ await_out "$OUT.cwb" "cwb-pin" "cwb-pin never reached the client" | |||
| 72 | pipe_detach | 72 | pipe_detach |
| 73 | wait_grid "$SOCK26" "cwb-pin" "CLI wall: session b's marker" b | 73 | wait_grid "$SOCK26" "cwb-pin" "CLI wall: session b's marker" b |
| 74 | 74 | ||
| 75 | ( sleep 5; "$MUXA" send 'printf "cwlive-%s\n" pin\n' \ | 75 | ( sleep 5; "$MUX" a send 'printf "cwlive-%s\n" pin\n' \ |
| 76 | --sock "$SOCK26" --session b > "$OUT.cwinj" 2>&1 ) & | 76 | --sock "$SOCK26" --session b > "$OUT.cwinj" 2>&1 ) & |
| 77 | CWINJPID=$! | 77 | CWINJPID=$! |
| 78 | defer_kill "$CWINJPID" | 78 | defer_kill "$CWINJPID" |
| @@ -154,7 +154,7 @@ grep -q "zprintf" "$OUT.pfx" && { | |||
| 154 | echo "e2e FAIL: prefix: the unknown command key reached the pty" | 154 | echo "e2e FAIL: prefix: the unknown command key reached the pty" |
| 155 | cat "$OUT.pfx"; exit 1; } | 155 | cat "$OUT.pfx"; exit 1; } |
| 156 | # Detached, not exited: the session outlives the client that left it. | 156 | # Detached, not exited: the session outlives the client that left it. |
| 157 | "$MUXA" status --sock "$SOCK27" > "$OUT.pfxst" 2>&1 || { | 157 | "$MUX" a status --sock "$SOCK27" > "$OUT.pfxst" 2>&1 || { |
| 158 | echo "e2e FAIL: prefix: the session did not survive the detach:" | 158 | echo "e2e FAIL: prefix: the session did not survive the detach:" |
| 159 | cat "$OUT.pfxst"; exit 1; } | 159 | cat "$OUT.pfxst"; exit 1; } |
| 160 | assert_stopped "$SOCK27" "$D24PID" "prefix" "$OUT.pfxstop" | 160 | assert_stopped "$SOCK27" "$D24PID" "prefix" "$OUT.pfxstop" |
| @@ -170,7 +170,7 @@ ok "Ctrl-\\ prefix: an unknown chord is swallowed, Ctrl-\\ d detaches" | |||
| 170 | # `\x1cc`, and runs a second marker. Both markers reach a shell — the | 170 | # `\x1cc`, and runs a second marker. Both markers reach a shell — the |
| 171 | # second one proves the client is attached to a session that works, not | 171 | # second one proves the client is attached to a session that works, not |
| 172 | # merely alive after the chord. | 172 | # merely alive after the chord. |
| 173 | # * `muxa status --session 1` proves the daemon really created the name | 173 | # * `mux a status --session 1` proves the daemon really created the name |
| 174 | # the client picked (lowest free integer beside the default "0"). | 174 | # the client picked (lowest free integer beside the default "0"). |
| 175 | # * the default session's capture still holds the FIRST marker and not the | 175 | # * the default session's capture still holds the FIRST marker and not the |
| 176 | # second. Without that pair the same log would be produced by a chord | 176 | # second. Without that pair the same log would be produced by a chord |
| @@ -219,19 +219,19 @@ set -e | |||
| 219 | [ "$RC" -eq 0 ] || { | 219 | [ "$RC" -eq 0 ] || { |
| 220 | echo "e2e FAIL: new session: ptyclient leg exited $RC (did \\x1cc make a tile?):" | 220 | echo "e2e FAIL: new session: ptyclient leg exited $RC (did \\x1cc make a tile?):" |
| 221 | cat "$OUT.nsw.log"; exit 1; } | 221 | cat "$OUT.nsw.log"; exit 1; } |
| 222 | "$MUXA" status --sock "$SOCK28" --session 1 > "$OUT.nswst" 2>&1 || { | 222 | "$MUX" a status --sock "$SOCK28" --session 1 > "$OUT.nswst" 2>&1 || { |
| 223 | echo "e2e FAIL: new session: the chord did not create session 1:" | 223 | echo "e2e FAIL: new session: the chord did not create session 1:" |
| 224 | cat "$OUT.nswst"; exit 1; } | 224 | cat "$OUT.nswst"; exit 1; } |
| 225 | # The session the client LEFT: still live, still holding what it was shown | 225 | # The session the client LEFT: still live, still holding what it was shown |
| 226 | # before the chord, and innocent of everything typed after it. | 226 | # before the chord, and innocent of everything typed after it. |
| 227 | "$MUXA" capture --sock "$SOCK28" > "$OUT.nswcap" 2>&1 | 227 | "$MUX" a capture --sock "$SOCK28" > "$OUT.nswcap" 2>&1 |
| 228 | grep -q "m2a-pin" "$OUT.nswcap" || { | 228 | grep -q "m2a-pin" "$OUT.nswcap" || { |
| 229 | echo "e2e FAIL: new session: the default session lost its own marker:" | 229 | echo "e2e FAIL: new session: the default session lost its own marker:" |
| 230 | cat "$OUT.nswcap"; exit 1; } | 230 | cat "$OUT.nswcap"; exit 1; } |
| 231 | grep -q "m2b-pin" "$OUT.nswcap" && { | 231 | grep -q "m2b-pin" "$OUT.nswcap" && { |
| 232 | echo "e2e FAIL: new session: the second marker ran in the OLD session" | 232 | echo "e2e FAIL: new session: the second marker ran in the OLD session" |
| 233 | cat "$OUT.nswcap"; exit 1; } | 233 | cat "$OUT.nswcap"; exit 1; } |
| 234 | "$MUXA" capture --sock "$SOCK28" --session 1 > "$OUT.nswcap1" 2>&1 | 234 | "$MUX" a capture --sock "$SOCK28" --session 1 > "$OUT.nswcap1" 2>&1 |
| 235 | grep -q "m2b-pin" "$OUT.nswcap1" || { | 235 | grep -q "m2b-pin" "$OUT.nswcap1" || { |
| 236 | echo "e2e FAIL: new session: the chord's own session never got the marker:" | 236 | echo "e2e FAIL: new session: the chord's own session never got the marker:" |
| 237 | cat "$OUT.nswcap1"; exit 1; } | 237 | cat "$OUT.nswcap1"; exit 1; } |
| @@ -287,14 +287,14 @@ grep -qF -- "[no room on the wall for another tile]" "$OUT.nsw2" || { | |||
| 287 | echo "e2e FAIL: new session: the refused chord said nothing — the notice" | 287 | echo "e2e FAIL: new session: the refused chord said nothing — the notice" |
| 288 | echo " never reached the terminal on the next claim:" | 288 | echo " never reached the terminal on the next claim:" |
| 289 | cat "$OUT.nsw2.log"; exit 1; } | 289 | cat "$OUT.nsw2.log"; exit 1; } |
| 290 | "$MUXA" capture --sock "$SOCK28" > "$OUT.nswcapd" 2>&1 | 290 | "$MUX" a capture --sock "$SOCK28" > "$OUT.nswcapd" 2>&1 |
| 291 | grep -q "m2d-pin" "$OUT.nswcapd" || { | 291 | grep -q "m2d-pin" "$OUT.nswcapd" || { |
| 292 | echo "e2e FAIL: new session: the refused chord did not come back to the focused session:" | 292 | echo "e2e FAIL: new session: the refused chord did not come back to the focused session:" |
| 293 | cat "$OUT.nswcapd"; exit 1; } | 293 | cat "$OUT.nswcapd"; exit 1; } |
| 294 | # * and the name it was reaching for was never created. Without this the | 294 | # * and the name it was reaching for was never created. Without this the |
| 295 | # leg would pass on a wall that refused the tile and made the session | 295 | # leg would pass on a wall that refused the tile and made the session |
| 296 | # anyway — a session nobody can see is the worse of the two failures. | 296 | # anyway — a session nobody can see is the worse of the two failures. |
| 297 | "$MUXA" status --sock "$SOCK28" --session 2 > "$OUT.nswst2" 2>&1 && { | 297 | "$MUX" a status --sock "$SOCK28" --session 2 > "$OUT.nswst2" 2>&1 && { |
| 298 | echo "e2e FAIL: new session: a refused tile left a session behind:" | 298 | echo "e2e FAIL: new session: a refused tile left a session behind:" |
| 299 | cat "$OUT.nswst2"; exit 1; } | 299 | cat "$OUT.nswst2"; exit 1; } |
| 300 | assert_stopped "$SOCK28" "$D25PID" "new session" "$OUT.nswstop" | 300 | assert_stopped "$SOCK28" "$D25PID" "new session" "$OUT.nswstop" |
| @@ -376,7 +376,7 @@ set -e | |||
| 376 | cat "$OUT.ring.log"; exit 1; } | 376 | cat "$OUT.ring.log"; exit 1; } |
| 377 | for s in 0:m3a 1:m3b 2:m3c; do | 377 | for s in 0:m3a 1:m3b 2:m3c; do |
| 378 | _sess="${s%%:*}"; _mark="${s##*:}-pin" | 378 | _sess="${s%%:*}"; _mark="${s##*:}-pin" |
| 379 | timeout 20 "$MUXA" capture --sock "$SOCK29" --session "$_sess" > "$OUT.ringcap" 2>&1 | 379 | timeout 20 "$MUX" a capture --sock "$SOCK29" --session "$_sess" > "$OUT.ringcap" 2>&1 |
| 380 | grep -q "$_mark" "$OUT.ringcap" || { | 380 | grep -q "$_mark" "$OUT.ringcap" || { |
| 381 | echo "e2e FAIL: session ring: session $_sess does not hold $_mark:" | 381 | echo "e2e FAIL: session ring: session $_sess does not hold $_mark:" |
| 382 | cat "$OUT.ringcap"; exit 1; } | 382 | cat "$OUT.ringcap"; exit 1; } |
| @@ -414,7 +414,7 @@ ok "Ctrl-\\ n / Ctrl-\\ p: the ring steps both ways and wraps at both ends" | |||
| 414 | # | 414 | # |
| 415 | # Every marker is assembled by printf from pieces and never typed whole, so | 415 | # Every marker is assembled by printf from pieces and never typed whole, so |
| 416 | # the grid's echo of the command line can never satisfy the grep. `; echo` | 416 | # the grid's echo of the command line can never satisfy the grep. `; echo` |
| 417 | # rather than a `\n` inside printf's format: muxa's send turns every `\n` | 417 | # rather than a `\n` inside printf's format: mux a's send turns every `\n` |
| 418 | # into a real newline, so a format string carrying one would put a shell | 418 | # into a real newline, so a format string carrying one would put a shell |
| 419 | # continuation prompt in the middle of the assertion. | 419 | # continuation prompt in the middle of the assertion. |
| 420 | # | 420 | # |
| @@ -431,22 +431,22 @@ MUXABS=$(cd "$(dirname "$MUX")" && pwd)/$(basename "$MUX") | |||
| 431 | start_daemon "$SOCK31" "$OUT.sa.d" "self-attach daemon never bound" --shell /bin/sh --cols 200 | 431 | start_daemon "$SOCK31" "$OUT.sa.d" "self-attach daemon never bound" --shell /bin/sh --cols 200 |
| 432 | D28PID=$DPID | 432 | D28PID=$DPID |
| 433 | 433 | ||
| 434 | timeout 20 "$MUXA" send \ | 434 | timeout 20 "$MUX" a send \ |
| 435 | "[ \"\$MUX_SOCK\" = \"$SOCK31\" ] && printf 'ENV%s-%s' OK \"\$MUX_SESSION\"; echo\n" \ | 435 | "[ \"\$MUX_SOCK\" = \"$SOCK31\" ] && printf 'ENV%s-%s' OK \"\$MUX_SESSION\"; echo\n" \ |
| 436 | --sock "$SOCK31" --session 0 > "$OUT.saenv" 2>&1 | 436 | --sock "$SOCK31" --session 0 > "$OUT.saenv" 2>&1 |
| 437 | sleep 1 | 437 | sleep 1 |
| 438 | timeout 20 "$MUXA" capture --sock "$SOCK31" --session 0 > "$OUT.sacap1" 2>&1 | 438 | timeout 20 "$MUX" a capture --sock "$SOCK31" --session 0 > "$OUT.sacap1" 2>&1 |
| 439 | # ENVOK-0 is both halves at once: the socket matched, and the name the shell | 439 | # ENVOK-0 is both halves at once: the socket matched, and the name the shell |
| 440 | # was told is the RESOLVED default, not the empty spelling the wire uses. | 440 | # was told is the RESOLVED default, not the empty spelling the wire uses. |
| 441 | grep -q "ENVOK-0" "$OUT.sacap1" || { | 441 | grep -q "ENVOK-0" "$OUT.sacap1" || { |
| 442 | echo "e2e FAIL: self-attach: the session shell was not told MUX_SOCK=$SOCK31 and MUX_SESSION=0:" | 442 | echo "e2e FAIL: self-attach: the session shell was not told MUX_SOCK=$SOCK31 and MUX_SESSION=0:" |
| 443 | cat "$OUT.saenv"; cat "$OUT.sacap1"; exit 1; } | 443 | cat "$OUT.saenv"; cat "$OUT.sacap1"; exit 1; } |
| 444 | 444 | ||
| 445 | timeout 20 "$MUXA" send \ | 445 | timeout 20 "$MUX" a send \ |
| 446 | "$MUXABS --sock $SOCK31; printf 'SELF%s-%s' rc \$?; echo\n" \ | 446 | "$MUXABS --sock $SOCK31; printf 'SELF%s-%s' rc \$?; echo\n" \ |
| 447 | --sock "$SOCK31" --session 0 > "$OUT.saself" 2>&1 | 447 | --sock "$SOCK31" --session 0 > "$OUT.saself" 2>&1 |
| 448 | sleep 2 | 448 | sleep 2 |
| 449 | timeout 20 "$MUXA" capture --sock "$SOCK31" --session 0 > "$OUT.sacap2" 2>&1 | 449 | timeout 20 "$MUX" a capture --sock "$SOCK31" --session 0 > "$OUT.sacap2" 2>&1 |
| 450 | grep -q "unset MUX_SESSION to override" "$OUT.sacap2" || { | 450 | grep -q "unset MUX_SESSION to override" "$OUT.sacap2" || { |
| 451 | echo "e2e FAIL: self-attach: mux did not refuse the session it was running in:" | 451 | echo "e2e FAIL: self-attach: mux did not refuse the session it was running in:" |
| 452 | cat "$OUT.saself"; cat "$OUT.sacap2"; exit 1; } | 452 | cat "$OUT.saself"; cat "$OUT.sacap2"; exit 1; } |
| @@ -455,7 +455,7 @@ grep -q "unset MUX_SESSION to override" "$OUT.sacap2" || { | |||
| 455 | grep -q "SELFrc-2" "$OUT.sacap2" || { | 455 | grep -q "SELFrc-2" "$OUT.sacap2" || { |
| 456 | echo "e2e FAIL: self-attach: the refusal did not exit 2 (or did not exit):" | 456 | echo "e2e FAIL: self-attach: the refusal did not exit 2 (or did not exit):" |
| 457 | cat "$OUT.sacap2"; exit 1; } | 457 | cat "$OUT.sacap2"; exit 1; } |
| 458 | timeout 20 "$MUXA" status --sock "$SOCK31" --session 0 > "$OUT.sast" 2>&1 | 458 | timeout 20 "$MUX" a status --sock "$SOCK31" --session 0 > "$OUT.sast" 2>&1 |
| 459 | grep -q '"alt_screen":false' "$OUT.sast" || { | 459 | grep -q '"alt_screen":false' "$OUT.sast" || { |
| 460 | echo "e2e FAIL: self-attach: an inner client took the alternate screen — the loop ran:" | 460 | echo "e2e FAIL: self-attach: an inner client took the alternate screen — the loop ran:" |
| 461 | cat "$OUT.sast"; exit 1; } | 461 | cat "$OUT.sast"; exit 1; } |
| @@ -465,11 +465,11 @@ grep -q '"alt_screen":false' "$OUT.sast" || { | |||
| 465 | # inside cannot be spelled at all — the wall simply never births a tile for | 465 | # inside cannot be spelled at all — the wall simply never births a tile for |
| 466 | # it (`planHostDiff`'s `self_name`). A `#` spelling is refused by the | 466 | # it (`planHostDiff`'s `self_name`). A `#` spelling is refused by the |
| 467 | # grammar before any of that, which is scenario 09's leg. | 467 | # grammar before any of that, which is scenario 09's leg. |
| 468 | timeout 20 "$MUXA" send \ | 468 | timeout 20 "$MUX" a send \ |
| 469 | "$MUXABS hosts add '--sock $SOCK31#0'; printf 'WALL%s-%s' rc \$?; echo\n" \ | 469 | "$MUXABS hosts add '--sock $SOCK31#0'; printf 'WALL%s-%s' rc \$?; echo\n" \ |
| 470 | --sock "$SOCK31" --session 0 > "$OUT.sawall" 2>&1 | 470 | --sock "$SOCK31" --session 0 > "$OUT.sawall" 2>&1 |
| 471 | sleep 2 | 471 | sleep 2 |
| 472 | timeout 20 "$MUXA" capture --sock "$SOCK31" --session 0 > "$OUT.sacap3" 2>&1 | 472 | timeout 20 "$MUX" a capture --sock "$SOCK31" --session 0 > "$OUT.sacap3" 2>&1 |
| 473 | grep -q -- "names a session after" "$OUT.sacap3" || { | 473 | grep -q -- "names a session after" "$OUT.sacap3" || { |
| 474 | echo "e2e FAIL: self-attach: 'mux hosts add' took a session spelling:" | 474 | echo "e2e FAIL: self-attach: 'mux hosts add' took a session spelling:" |
| 475 | cat "$OUT.sawall"; cat "$OUT.sacap3"; exit 1; } | 475 | cat "$OUT.sawall"; cat "$OUT.sacap3"; exit 1; } |
test/e2e_08_mouse.sh
| Old | New | ||
|---|---|---|---|
| @@ -88,7 +88,7 @@ D30PID=$DPID | |||
| 88 | # Attach only once seq has finished, for tp1's reason: the page the wheel | 88 | # Attach only once seq has finished, for tp1's reason: the page the wheel |
| 89 | # fetches has to be content this client was never sent. | 89 | # fetches has to be content this client was never sent. |
| 90 | i=0 | 90 | i=0 |
| 91 | until "$MUXD" dump --sock "$SOCK33" | grep -q "100"; do | 91 | until "$MUX" d dump --sock "$SOCK33" | grep -q "100"; do |
| 92 | i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: wheel session never finished seq"; exit 1; } | 92 | i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: wheel session never finished seq"; exit 1; } |
| 93 | sleep 0.1 | 93 | sleep 0.1 |
| 94 | done | 94 | done |
| @@ -119,7 +119,7 @@ set -e | |||
| 119 | # The other half of "the wheel is the client's": the bytes are CONSUMED. | 119 | # The other half of "the wheel is the client's": the bytes are CONSUMED. |
| 120 | # Seventeen mouse reports were typed at a shell that would have echoed every | 120 | # Seventeen mouse reports were typed at a shell that would have echoed every |
| 121 | # one of them, and the session's grid must hold none. | 121 | # one of them, and the session's grid must hold none. |
| 122 | timeout 20 "$MUXA" capture --sock "$SOCK33" > "$OUT.whlcap" 2>&1 | 122 | timeout 20 "$MUX" a capture --sock "$SOCK33" > "$OUT.whlcap" 2>&1 |
| 123 | grep -qF -- "[<64;" "$OUT.whlcap" && { | 123 | grep -qF -- "[<64;" "$OUT.whlcap" && { |
| 124 | echo "e2e FAIL: wheel: a mouse report reached the pty as input:" | 124 | echo "e2e FAIL: wheel: a mouse report reached the pty as input:" |
| 125 | cat "$OUT.whlcap"; exit 1; } | 125 | cat "$OUT.whlcap"; exit 1; } |
| @@ -158,7 +158,7 @@ chmod +x "$MOUSESH" | |||
| 158 | start_daemon "$SOCK34" "$OUT.mse.d" "app-mouse daemon never bound" --shell "$MOUSESH" | 158 | start_daemon "$SOCK34" "$OUT.mse.d" "app-mouse daemon never bound" --shell "$MOUSESH" |
| 159 | D31PID=$DPID | 159 | D31PID=$DPID |
| 160 | i=0 | 160 | i=0 |
| 161 | until "$MUXD" dump --sock "$SOCK34" | grep -q "app-holds-the-mouse"; do | 161 | until "$MUX" d dump --sock "$SOCK34" | grep -q "app-holds-the-mouse"; do |
| 162 | i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: app-mouse session never armed"; exit 1; } | 162 | i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: app-mouse session never armed"; exit 1; } |
| 163 | sleep 0.1 | 163 | sleep 0.1 |
| 164 | done | 164 | done |
| @@ -183,7 +183,7 @@ set -e | |||
| 183 | cat "$OUT.mse.log"; exit 1; } | 183 | cat "$OUT.mse.log"; exit 1; } |
| 184 | # The session's own grid, not just the client's screen: the echo is the pty | 184 | # The session's own grid, not just the client's screen: the echo is the pty |
| 185 | # saying it received the bytes. | 185 | # saying it received the bytes. |
| 186 | timeout 20 "$MUXA" capture --sock "$SOCK34" > "$OUT.msecap" 2>&1 | 186 | timeout 20 "$MUX" a capture --sock "$SOCK34" > "$OUT.msecap" 2>&1 |
| 187 | grep -qF -- "[<64;10;5M" "$OUT.msecap" || { | 187 | grep -qF -- "[<64;10;5M" "$OUT.msecap" || { |
| 188 | echo "e2e FAIL: app mouse: the wheel never reached the application's pty:" | 188 | echo "e2e FAIL: app mouse: the wheel never reached the application's pty:" |
| 189 | cat "$OUT.msecap"; exit 1; } | 189 | cat "$OUT.msecap"; exit 1; } |
| @@ -227,10 +227,10 @@ chmod +x "$LESSSH" | |||
| 227 | start_daemon "$SOCK35" "$OUT.pgr.d" "pager daemon never bound" --shell "$LESSSH" | 227 | start_daemon "$SOCK35" "$OUT.pgr.d" "pager daemon never bound" --shell "$LESSSH" |
| 228 | D32PID=$DPID | 228 | D32PID=$DPID |
| 229 | i=0 | 229 | i=0 |
| 230 | until "$MUXD" dump --sock "$SOCK35" | grep -qx "200"; do | 230 | until "$MUX" d dump --sock "$SOCK35" | grep -qx "200"; do |
| 231 | i=$((i+1)); [ "$i" -lt 100 ] || { | 231 | i=$((i+1)); [ "$i" -lt 100 ] || { |
| 232 | echo "e2e FAIL: pager never reached the end of the file:" | 232 | echo "e2e FAIL: pager never reached the end of the file:" |
| 233 | "$MUXD" dump --sock "$SOCK35" | tail -3; exit 1; } | 233 | "$MUX" d dump --sock "$SOCK35" | tail -3; exit 1; } |
| 234 | sleep 0.1 | 234 | sleep 0.1 |
| 235 | done | 235 | done |
| 236 | set +e | 236 | set +e |
| @@ -247,12 +247,12 @@ RC=$? | |||
| 247 | set -e | 247 | set -e |
| 248 | [ "$RC" -eq 0 ] || { | 248 | [ "$RC" -eq 0 ] || { |
| 249 | echo "e2e FAIL: pager: ptyclient leg exited $RC:"; cat "$OUT.pgr.log"; exit 1; } | 249 | echo "e2e FAIL: pager: ptyclient leg exited $RC:"; cat "$OUT.pgr.log"; exit 1; } |
| 250 | # `muxd dump`, not `muxa capture`: the assertions below are WHOLE-LINE ones | 250 | # `mux d dump`, not `mux a capture`: the assertions below are WHOLE-LINE ones |
| 251 | # and capture answers in JSON, where the grid is one line with escaped | 251 | # and capture answers in JSON, where the grid is one line with escaped |
| 252 | # newlines in it — a `grep -x` against that can never match. (It did not, | 252 | # newlines in it — a `grep -x` against that can never match. (It did not, |
| 253 | # on the first run of this leg, while the grid underneath was exactly | 253 | # on the first run of this leg, while the grid underneath was exactly |
| 254 | # right.) dump prints the grid a row per line, which is what -x needs. | 254 | # right.) dump prints the grid a row per line, which is what -x needs. |
| 255 | "$MUXD" dump --sock "$SOCK35" > "$OUT.pgrcap" 2>&1 | 255 | "$MUX" d dump --sock "$SOCK35" > "$OUT.pgrcap" 2>&1 |
| 256 | # A whole line, because the pager's status line carries the file's PATH — | 256 | # A whole line, because the pager's status line carries the file's PATH — |
| 257 | # which holds this run's pid and could spell any short number. | 257 | # which holds this run's pid and could spell any short number. |
| 258 | grep -qx "154" "$OUT.pgrcap" || { | 258 | grep -qx "154" "$OUT.pgrcap" || { |
| @@ -295,13 +295,13 @@ RC=$? | |||
| 295 | [ "$RC" -eq 0 ] || { | 295 | [ "$RC" -eq 0 ] || { |
| 296 | echo "e2e FAIL: pipe stdin: the client exited $RC:"; cat "$OUT.pipe"; exit 1; } | 296 | echo "e2e FAIL: pipe stdin: the client exited $RC:"; cat "$OUT.pipe"; exit 1; } |
| 297 | i=0 | 297 | i=0 |
| 298 | until timeout 20 "$MUXA" capture --sock "$SOCK36" 2>&1 | grep -qF -- "world"; do | 298 | until timeout 20 "$MUX" a capture --sock "$SOCK36" 2>&1 | grep -qF -- "world"; do |
| 299 | i=$((i+1)); [ "$i" -lt 100 ] || { | 299 | i=$((i+1)); [ "$i" -lt 100 ] || { |
| 300 | echo "e2e FAIL: pipe stdin: the piped line never reached the pty:" | 300 | echo "e2e FAIL: pipe stdin: the piped line never reached the pty:" |
| 301 | timeout 20 "$MUXA" capture --sock "$SOCK36"; exit 1; } | 301 | timeout 20 "$MUX" a capture --sock "$SOCK36"; exit 1; } |
| 302 | sleep 0.1 | 302 | sleep 0.1 |
| 303 | done | 303 | done |
| 304 | timeout 20 "$MUXA" capture --sock "$SOCK36" > "$OUT.pipecap" 2>&1 | 304 | timeout 20 "$MUX" a capture --sock "$SOCK36" > "$OUT.pipecap" 2>&1 |
| 305 | # The whole line, escape included. `cat` echoes control bytes as `^[`, so | 305 | # The whole line, escape included. `cat` echoes control bytes as `^[`, so |
| 306 | # what a grid can show of `\x1b[<64;10;5M` is `[<64;10;5M` — and a client | 306 | # what a grid can show of `\x1b[<64;10;5M` is `[<64;10;5M` — and a client |
| 307 | # that ate the report leaves `hello world` with the middle missing. | 307 | # that ate the report leaves `hello world` with the middle missing. |
| @@ -393,10 +393,10 @@ ZSATT_AFTER=$(attaches_now "$SOCK37") | |||
| 393 | [ "$RC" -eq 0 ] || { | 393 | [ "$RC" -eq 0 ] || { |
| 394 | echo "e2e FAIL: focus skip: ptyclient leg exited $RC (did \\x1cn and \\x1c1 move the focus?):" | 394 | echo "e2e FAIL: focus skip: ptyclient leg exited $RC (did \\x1cn and \\x1c1 move the focus?):" |
| 395 | cat "$OUT.zspc"; exit 1; } | 395 | cat "$OUT.zspc"; exit 1; } |
| 396 | # Where each marker landed. `muxa capture` reads the session's own grid, so | 396 | # Where each marker landed. `mux a capture` reads the session's own grid, so |
| 397 | # none of this can be an echo of what this script typed at a terminal. | 397 | # none of this can be an echo of what this script typed at a terminal. |
| 398 | timeout 20 "$MUXA" capture --sock "$SOCK37" > "$OUT.zsfa" 2>&1 | 398 | timeout 20 "$MUX" a capture --sock "$SOCK37" > "$OUT.zsfa" 2>&1 |
| 399 | timeout 20 "$MUXA" capture --sock "$SOCK37" --session b > "$OUT.zsfb" 2>&1 | 399 | timeout 20 "$MUX" a capture --sock "$SOCK37" --session b > "$OUT.zsfb" 2>&1 |
| 400 | # | 400 | # |
| 401 | # The markers are numbered rather than named after their sessions, and | 401 | # The markers are numbered rather than named after their sessions, and |
| 402 | # deliberately so: `zs-b` and `zs-back` were the first spelling, and `zs-b` | 402 | # deliberately so: `zs-b` and `zs-back` were the first spelling, and `zs-b` |
| @@ -429,7 +429,7 @@ assert_never_two_clients "$OUT.zswatch" b "focus skip" | |||
| 429 | # rows each (30 terminal rows cut into two stripes of 15, minus one label | 429 | # rows each (30 terminal rows cut into two stripes of 15, minus one label |
| 430 | # bar each). A focus move resizes nothing. | 430 | # bar each). A focus move resizes nothing. |
| 431 | for _s in 0 b; do | 431 | for _s in 0 b; do |
| 432 | timeout 20 "$MUXA" status --sock "$SOCK37" --session "$_s" > "$OUT.zsst$_s" 2>&1 | 432 | timeout 20 "$MUX" a status --sock "$SOCK37" --session "$_s" > "$OUT.zsst$_s" 2>&1 |
| 433 | grep -q '"cols":40' "$OUT.zsst$_s" || { | 433 | grep -q '"cols":40' "$OUT.zsst$_s" || { |
| 434 | echo "e2e FAIL: focus skip: session $_s is not at the width its tile claimed:" | 434 | echo "e2e FAIL: focus skip: session $_s is not at the width its tile claimed:" |
| 435 | cat "$OUT.zsst$_s"; exit 1; } | 435 | cat "$OUT.zsst$_s"; exit 1; } |
| @@ -472,7 +472,7 @@ D38PID=$DPID | |||
| 472 | # The wall attaches only once seq has finished, scenario 42's reason: the | 472 | # The wall attaches only once seq has finished, scenario 42's reason: the |
| 473 | # page the wheel fetches has to be content this tile was never sent. | 473 | # page the wheel fetches has to be content this tile was never sent. |
| 474 | i=0 | 474 | i=0 |
| 475 | until "$MUXD" dump --sock "$SOCK41" | grep -q "wln100"; do | 475 | until "$MUX" d dump --sock "$SOCK41" | grep -q "wln100"; do |
| 476 | i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: wheel: session never finished seq"; exit 1; } | 476 | i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: wheel: session never finished seq"; exit 1; } |
| 477 | sleep 0.1 | 477 | sleep 0.1 |
| 478 | done | 478 | done |
| @@ -502,7 +502,7 @@ set -e | |||
| 502 | # The anchor first, and it is a POSITIVE: a keystroke typed at the focused | 502 | # The anchor first, and it is a POSITIVE: a keystroke typed at the focused |
| 503 | # tile reached this session's pty. The silent half below is measured against | 503 | # tile reached this session's pty. The silent half below is measured against |
| 504 | # it, so a leg that fell over early can never pass by having done nothing. | 504 | # it, so a leg that fell over early can never pass by having done nothing. |
| 505 | timeout 20 "$MUXA" capture --sock "$SOCK41" > "$OUT.zwcapg" 2>&1 | 505 | timeout 20 "$MUX" a capture --sock "$SOCK41" > "$OUT.zwcapg" 2>&1 |
| 506 | grep -q "zwheelpin" "$OUT.zwcapg" || { | 506 | grep -q "zwheelpin" "$OUT.zwcapg" || { |
| 507 | echo "e2e FAIL: wheel: the focus never typed into the session, so the" | 507 | echo "e2e FAIL: wheel: the focus never typed into the session, so the" |
| 508 | echo " silent half below proves nothing:" | 508 | echo " silent half below proves nothing:" |
| @@ -542,7 +542,7 @@ chmod +x "$ZMOUSESH" | |||
| 542 | start_daemon "$SOCK42" "$OUT.zm2.d" "app-mouse daemon never bound" --shell "$ZMOUSESH" | 542 | start_daemon "$SOCK42" "$OUT.zm2.d" "app-mouse daemon never bound" --shell "$ZMOUSESH" |
| 543 | D39PID=$DPID | 543 | D39PID=$DPID |
| 544 | i=0 | 544 | i=0 |
| 545 | until "$MUXD" dump --sock "$SOCK42" | grep -q "mapp-holds-the-mouse"; do | 545 | until "$MUX" d dump --sock "$SOCK42" | grep -q "mapp-holds-the-mouse"; do |
| 546 | i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: app mouse: session never armed"; exit 1; } | 546 | i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: app mouse: session never armed"; exit 1; } |
| 547 | sleep 0.1 | 547 | sleep 0.1 |
| 548 | done | 548 | done |
| @@ -570,7 +570,7 @@ set -e | |||
| 570 | cat "$OUT.zm2pc"; exit 1; } | 570 | cat "$OUT.zm2pc"; exit 1; } |
| 571 | # The session's own grid, not just this terminal: the echo is the pty saying | 571 | # The session's own grid, not just this terminal: the echo is the pty saying |
| 572 | # it received the bytes. | 572 | # it received the bytes. |
| 573 | timeout 20 "$MUXA" capture --sock "$SOCK42" > "$OUT.zm2capg" 2>&1 | 573 | timeout 20 "$MUX" a capture --sock "$SOCK42" > "$OUT.zm2capg" 2>&1 |
| 574 | grep -qF -- "[<64;10;5M" "$OUT.zm2capg" || { | 574 | grep -qF -- "[<64;10;5M" "$OUT.zm2capg" || { |
| 575 | echo "e2e FAIL: app mouse: the wheel never reached the application's pty:" | 575 | echo "e2e FAIL: app mouse: the wheel never reached the application's pty:" |
| 576 | cat "$OUT.zm2capg"; exit 1; } | 576 | cat "$OUT.zm2capg"; exit 1; } |
test/e2e_09_hosts.sh
| Old | New | ||
|---|---|---|---|
| @@ -76,7 +76,7 @@ hosts_where() { | |||
| 76 | # with one line in front of it. | 76 | # with one line in front of it. |
| 77 | # | 77 | # |
| 78 | # The auto-start half is asked of the OS, not of the wall: the marker is | 78 | # The auto-start half is asked of the OS, not of the wall: the marker is |
| 79 | # the SHELL's own `$$`, and `muxd stats` on the default path is a second | 79 | # the SHELL's own `$$`, and `mux d stats` on the default path is a second |
| 80 | # process finding a daemon that was not there a moment ago. | 80 | # process finding a daemon that was not there a moment ago. |
| 81 | pipe_mux "$OUT.hw1" "$OUT.hw1.err" env XDG_STATE_HOME="$HSTATE" timeout 40 "$MUX" --sock "$SOCKH1" | 81 | pipe_mux "$OUT.hw1" "$OUT.hw1.err" env XDG_STATE_HOME="$HSTATE" timeout 40 "$MUX" --sock "$SOCKH1" |
| 82 | pipe_send 'printf "hw1-%%s\\n" pin\n' | 82 | pipe_send 'printf "hw1-%%s\\n" pin\n' |
| @@ -98,10 +98,10 @@ mkdir -p "$HASTATE/mux" "$HARUN" | |||
| 98 | printf -- '--sock %s\n' "$HASOCK" > "$HASTATE/mux/hosts" | 98 | printf -- '--sock %s\n' "$HASOCK" > "$HASTATE/mux/hosts" |
| 99 | [ ! -S "$HASOCK" ] || { echo "e2e FAIL: hosts: the auto-start path is already served"; exit 1; } | 99 | [ ! -S "$HASOCK" ] || { echo "e2e FAIL: hosts: the auto-start path is already served"; exit 1; } |
| 100 | set +e | 100 | set +e |
| 101 | # PATH so `findInPath` resolves exactly the muxd under test, and | 101 | # PATH so the ssh recipes resolve exactly the binary under test, and |
| 102 | # XDG_RUNTIME_DIR so `--sock <default>` is this leg's own path; the wall | 102 | # XDG_RUNTIME_DIR so `--sock <default>` is this leg's own path; the wall |
| 103 | # needs a terminal, so this is the pty fixture and not a pipe. | 103 | # needs a terminal, so this is the pty fixture and not a pipe. |
| 104 | SHELL=/bin/sh PATH="$(dirname "$MUXD"):$PATH" XDG_RUNTIME_DIR="$HARUN" \ | 104 | SHELL=/bin/sh PATH="$(dirname "$MUX"):$PATH" XDG_RUNTIME_DIR="$HARUN" \ |
| 105 | XDG_STATE_HOME="$HASTATE" timeout 60 "$PTYCLIENT" --cols 80 --rows 24 \ | 105 | XDG_STATE_HOME="$HASTATE" timeout 60 "$PTYCLIENT" --cols 80 --rows 24 \ |
| 106 | --out "$OUT.hauto" --err "$OUT.hauto.err" -- "$MUX" > "$OUT.hauto.log" 2>&1 <<'EOF' | 106 | --out "$OUT.hauto" --err "$OUT.hauto.err" -- "$MUX" > "$OUT.hauto.log" 2>&1 <<'EOF' |
| 107 | expect \x1b[?1049h 20000 | 107 | expect \x1b[?1049h 20000 |
| @@ -124,10 +124,10 @@ defer_kill "$HAPID" | |||
| 124 | cat "$OUT.hauto.err"; exit 1; } | 124 | cat "$OUT.hauto.err"; exit 1; } |
| 125 | # The daemon exists and holds the shell that answered — asked of the daemon | 125 | # The daemon exists and holds the shell that answered — asked of the daemon |
| 126 | # by a process that had nothing to do with starting it. | 126 | # by a process that had nothing to do with starting it. |
| 127 | HASH_PID=$("$MUXD" dump --sock "$HASOCK" | sed -n 's/.*hapid=\([0-9]*\).*/\1/p' | tail -1) | 127 | HASH_PID=$("$MUX" d dump --sock "$HASOCK" | sed -n 's/.*hapid=\([0-9]*\).*/\1/p' | tail -1) |
| 128 | [ -n "$HASH_PID" ] || { | 128 | [ -n "$HASH_PID" ] || { |
| 129 | echo "e2e FAIL: hosts: the auto-started daemon holds no shell pid:" | 129 | echo "e2e FAIL: hosts: the auto-started daemon holds no shell pid:" |
| 130 | "$MUXD" dump --sock "$HASOCK"; exit 1; } | 130 | "$MUX" d dump --sock "$HASOCK"; exit 1; } |
| 131 | # ...and the pid on the grid is a process the OS agrees exists, in the | 131 | # ...and the pid on the grid is a process the OS agrees exists, in the |
| 132 | # daemon's own tree. A daemon reporting on itself cannot catch itself | 132 | # daemon's own tree. A daemon reporting on itself cannot catch itself |
| 133 | # being wrong. | 133 | # being wrong. |
| @@ -149,7 +149,7 @@ mkdir -p "$HERUN" | |||
| 149 | [ ! -e "$HESTATE" ] || { echo "e2e FAIL: hosts: the empty-file leg's state home already exists"; exit 1; } | 149 | [ ! -e "$HESTATE" ] || { echo "e2e FAIL: hosts: the empty-file leg's state home already exists"; exit 1; } |
| 150 | [ ! -S "$HESOCK" ] || { echo "e2e FAIL: hosts: the empty-file leg's socket is already served"; exit 1; } | 150 | [ ! -S "$HESOCK" ] || { echo "e2e FAIL: hosts: the empty-file leg's socket is already served"; exit 1; } |
| 151 | set +e | 151 | set +e |
| 152 | SHELL=/bin/sh PATH="$(dirname "$MUXD"):$PATH" XDG_RUNTIME_DIR="$HERUN" \ | 152 | SHELL=/bin/sh PATH="$(dirname "$MUX"):$PATH" XDG_RUNTIME_DIR="$HERUN" \ |
| 153 | XDG_STATE_HOME="$HESTATE" timeout 60 "$PTYCLIENT" --cols 80 --rows 24 \ | 153 | XDG_STATE_HOME="$HESTATE" timeout 60 "$PTYCLIENT" --cols 80 --rows 24 \ |
| 154 | --out "$OUT.hempty" --err "$OUT.hempty.err" -- "$MUX" > "$OUT.hempty.log" 2>&1 <<'EOF' | 154 | --out "$OUT.hempty" --err "$OUT.hempty.err" -- "$MUX" > "$OUT.hempty.log" 2>&1 <<'EOF' |
| 155 | expect \x1b[?1049h 20000 | 155 | expect \x1b[?1049h 20000 |
| @@ -183,10 +183,10 @@ grep -qxF -- "--sock $HESOCK" "$HESTATE/mux/hosts" || { | |||
| 183 | # ...and the session behind that line is a real shell, asked of the OS: the | 183 | # ...and the session behind that line is a real shell, asked of the OS: the |
| 184 | # pid the grid shows is a live process whose parent is the daemon `mux` | 184 | # pid the grid shows is a live process whose parent is the daemon `mux` |
| 185 | # started. A daemon reporting on itself cannot catch itself being wrong. | 185 | # started. A daemon reporting on itself cannot catch itself being wrong. |
| 186 | HESH_PID=$("$MUXD" dump --sock "$HESOCK" | sed -n 's/.*hepid=\([0-9]*\).*/\1/p' | tail -1) | 186 | HESH_PID=$("$MUX" d dump --sock "$HESOCK" | sed -n 's/.*hepid=\([0-9]*\).*/\1/p' | tail -1) |
| 187 | [ -n "$HESH_PID" ] || { | 187 | [ -n "$HESH_PID" ] || { |
| 188 | echo "e2e FAIL: hosts: the empty-file leg's grid shows no shell pid:" | 188 | echo "e2e FAIL: hosts: the empty-file leg's grid shows no shell pid:" |
| 189 | "$MUXD" dump --sock "$HESOCK"; exit 1; } | 189 | "$MUX" d dump --sock "$HESOCK"; exit 1; } |
| 190 | [ "$(ps -o ppid= -p "$HESH_PID" 2>/dev/null | tr -d ' ')" = "$(real_pid "$HEPID")" ] || { | 190 | [ "$(ps -o ppid= -p "$HESH_PID" 2>/dev/null | tr -d ' ')" = "$(real_pid "$HEPID")" ] || { |
| 191 | echo "e2e FAIL: hosts: the shell on the grid is not the auto-started daemon's child" | 191 | echo "e2e FAIL: hosts: the shell on the grid is not the auto-started daemon's child" |
| 192 | ps -o pid,ppid,args -p "$HESH_PID" 2>&1; exit 1; } | 192 | ps -o pid,ppid,args -p "$HESH_PID" 2>&1; exit 1; } |
| @@ -289,11 +289,11 @@ HW2=$(hosts_where walk-two) | |||
| 289 | [ "$HFHASH" = "$(sha256sum "$HSTATE/mux/hosts" | cut -d' ' -f1)" ] || { | 289 | [ "$HFHASH" = "$(sha256sum "$HSTATE/mux/hosts" | cut -d' ' -f1)" ] || { |
| 290 | echo "e2e FAIL: hosts: Ctrl-\\ c edited the hosts file — a birth is the daemon's, not the file's:" | 290 | echo "e2e FAIL: hosts: Ctrl-\\ c edited the hosts file — a birth is the daemon's, not the file's:" |
| 291 | cat "$HSTATE/mux/hosts"; exit 1; } | 291 | cat "$HSTATE/mux/hosts"; exit 1; } |
| 292 | HBORN=$(( $("$MUXD" stats --sock "$SOCKH1" | sed -n 's/.*sessions=\([0-9]*\).*/\1/p') + | 292 | HBORN=$(( $("$MUX" d stats --sock "$SOCKH1" | sed -n 's/.*sessions=\([0-9]*\).*/\1/p') + |
| 293 | $("$MUXD" stats --sock "$SOCKH2" | sed -n 's/.*sessions=\([0-9]*\).*/\1/p') )) | 293 | $("$MUX" d stats --sock "$SOCKH2" | sed -n 's/.*sessions=\([0-9]*\).*/\1/p') )) |
| 294 | [ "$HBORN" -eq 5 ] || { | 294 | [ "$HBORN" -eq 5 ] || { |
| 295 | echo "e2e FAIL: hosts: Ctrl-\\ c left $HBORN sessions across the two daemons, want 5" | 295 | echo "e2e FAIL: hosts: Ctrl-\\ c left $HBORN sessions across the two daemons, want 5" |
| 296 | "$MUXD" stats --sock "$SOCKH1"; "$MUXD" stats --sock "$SOCKH2"; exit 1; } | 296 | "$MUX" d stats --sock "$SOCKH1"; "$MUX" d stats --sock "$SOCKH2"; exit 1; } |
| 297 | # ...and a SECOND wall finds it, with nothing having been written down. | 297 | # ...and a SECOND wall finds it, with nothing having been written down. |
| 298 | set +e | 298 | set +e |
| 299 | XDG_STATE_HOME="$HSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 44 \ | 299 | XDG_STATE_HOME="$HSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 44 \ |
| @@ -318,7 +318,7 @@ grep -q -- "#1 \[" "$OUT.hw2cap" || { | |||
| 318 | # on which polled first, and that is a race no leg here has any business | 318 | # on which polled first, and that is a race no leg here has any business |
| 319 | # pinning. | 319 | # pinning. |
| 320 | for _hs in "$SOCKH1" "$SOCKH2"; do | 320 | for _hs in "$SOCKH1" "$SOCKH2"; do |
| 321 | if "$MUXD" stats --sock "$_hs" | grep -q 'session 1 '; then | 321 | if "$MUX" d stats --sock "$_hs" | grep -q 'session 1 '; then |
| 322 | pipe_mux "$OUT.hkill" "$OUT.hkill.err" env XDG_STATE_HOME="$HXSTATE" timeout 40 \ | 322 | pipe_mux "$OUT.hkill" "$OUT.hkill.err" env XDG_STATE_HOME="$HXSTATE" timeout 40 \ |
| 323 | "$MUX" --sock "$_hs" --session 1 | 323 | "$MUX" --sock "$_hs" --session 1 |
| 324 | pipe_send 'exit 0\n' | 324 | pipe_send 'exit 0\n' |
| @@ -420,6 +420,13 @@ ok "x refuses while others are attached, then ends; the other client sees the ex | |||
| 420 | # neighbour, or the daemon's own default session passes a wall of one and | 420 | # neighbour, or the daemon's own default session passes a wall of one and |
| 421 | # dies here. | 421 | # dies here. |
| 422 | # | 422 | # |
| 423 | # The expect AFTER the chord is what makes the final grid an assertion | ||
| 424 | # rather than a hope: expect(1) semantics mean it matches only bytes that | ||
| 425 | # arrived since the previous match, so it waits for the relayout to REPAINT | ||
| 426 | # a surviving tile rather than for a settle to run out. The heredoc is | ||
| 427 | # unquoted for that one line's `$SOCKH2`; `\x1c` survives it, because a | ||
| 428 | # backslash in a heredoc is special only before $, ` and a newline. | ||
| 429 | # | ||
| 423 | # `xm` is born LAST on daemon 1, so the daemon reports it third and the | 430 | # `xm` is born LAST on daemon 1, so the daemon reports it third and the |
| 424 | # wall lays it out at digit 3 of 5 (both daemons' lists, in file order). | 431 | # wall lays it out at digit 3 of 5 (both daemons' lists, in file order). |
| 425 | # The digit is not ASSUMED: the focused bar names the session it landed | 432 | # The digit is not ASSUMED: the focused bar names the session it landed |
| @@ -441,12 +448,13 @@ set +e | |||
| 441 | # somebody else's ordering. | 448 | # somebody else's ordering. |
| 442 | no_saved_tree "$HSTATE" | 449 | no_saved_tree "$HSTATE" |
| 443 | XDG_STATE_HOME="$HSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 44 \ | 450 | XDG_STATE_HOME="$HSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 44 \ |
| 444 | --out "$OUT.hxmcap" --err "$OUT.hxmcap.err" -- "$MUX" > "$OUT.hxmpc" 2>&1 <<'EOF' | 451 | --out "$OUT.hxmcap" --err "$OUT.hxmcap.err" -- "$MUX" > "$OUT.hxmpc" 2>&1 <<EOF |
| 445 | expect xmpid= 25000 | 452 | expect xmpid= 25000 |
| 446 | settle 1500 25000 | 453 | settle 1500 25000 |
| 447 | send \x1c3 | 454 | send \x1c3 |
| 448 | expect 3> --sock 20000 | 455 | expect 3> --sock 20000 |
| 449 | send \x1cx | 456 | send \x1cx |
| 457 | expect --sock $SOCKH2#0 25000 | ||
| 450 | settle 2500 25000 | 458 | settle 2500 25000 |
| 451 | send \x1cd | 459 | send \x1cd |
| 452 | waitexit 15000 | 460 | waitexit 15000 |
| @@ -501,7 +509,7 @@ ok "x on a wall ends the focused tile's session and no other, and that tile leav | |||
| 501 | # and its tiles are what the leg waits on, so "the poll has not landed | 509 | # and its tiles are what the leg waits on, so "the poll has not landed |
| 502 | # yet" and "daemon 2 has no bar" cannot be confused. | 510 | # yet" and "daemon 2 has no bar" cannot be confused. |
| 503 | # * back: its own default session and nothing else. `c`, which was live | 511 | # * back: its own default session and nothing else. `c`, which was live |
| 504 | # when the daemon died, is not there — asked of `muxd stats` on the | 512 | # when the daemon died, is not there — asked of `mux d stats` on the |
| 505 | # real daemon, not of the wall that would be reporting on its own | 513 | # real daemon, not of the wall that would be reporting on its own |
| 506 | # resurrection. | 514 | # resurrection. |
| 507 | assert_stopped "$SOCKH2" "$DH2PID" "hosts restart" "$OUT.h2stop" | 515 | assert_stopped "$SOCKH2" "$DH2PID" "hosts restart" "$OUT.h2stop" |
| @@ -555,9 +563,9 @@ if grep -q -- "--sock $SOCKH2#c" "$OUT.hrcap"; then | |||
| 555 | fi | 563 | fi |
| 556 | # The daemon's own account of it, which is the one that matters: a session | 564 | # The daemon's own account of it, which is the one that matters: a session |
| 557 | # the wall re-created would be here whether or not it ever got a tile. | 565 | # the wall re-created would be here whether or not it ever got a tile. |
| 558 | [ "$("$MUXD" stats --sock "$SOCKH2" | sed -n 's/.*sessions=\([0-9]*\).*/\1/p')" = "1" ] || { | 566 | [ "$("$MUX" d stats --sock "$SOCKH2" | sed -n 's/.*sessions=\([0-9]*\).*/\1/p')" = "1" ] || { |
| 559 | echo "e2e FAIL: hosts restart: the reborn daemon holds more than its own default session:" | 567 | echo "e2e FAIL: hosts restart: the reborn daemon holds more than its own default session:" |
| 560 | "$MUXD" stats --sock "$SOCKH2"; exit 1; } | 568 | "$MUX" d stats --sock "$SOCKH2"; exit 1; } |
| 561 | ok "a daemon that goes down leaves no bar behind, and comes back re-creating nothing" | 569 | ok "a daemon that goes down leaves no bar behind, and comes back re-creating nothing" |
| 562 | 570 | ||
| 563 | # ---- hosts rm takes a daemon off the wall and ends nothing -------------- | 571 | # ---- hosts rm takes a daemon off the wall and ends nothing -------------- |
| @@ -699,7 +707,7 @@ EMPTYSOCK="${TMPDIR:-/tmp}/muxd-e2e-neverbound-$$.sock" | |||
| 699 | # does. The regression it guards against is a wall auto-starting a daemon | 707 | # does. The regression it guards against is a wall auto-starting a daemon |
| 700 | # on a listed local path, and that failure leaves a live daemon on exactly | 708 | # on a listed local path, and that failure leaves a live daemon on exactly |
| 701 | # this socket: unregistered, the trap's residue guard unlinks the path from | 709 | # this socket: unregistered, the trap's residue guard unlinks the path from |
| 702 | # under it instead of asking `muxd stop` first. | 710 | # under it instead of asking `mux d stop` first. |
| 703 | defer_sock "$EMPTYSOCK" | 711 | defer_sock "$EMPTYSOCK" |
| 704 | mkdir -p "$HOSTROOM/mux" | 712 | mkdir -p "$HOSTROOM/mux" |
| 705 | printf -- '--sock %s\n' "$EMPTYSOCK" > "$HOSTROOM/mux/hosts" | 713 | printf -- '--sock %s\n' "$EMPTYSOCK" > "$HOSTROOM/mux/hosts" |
| @@ -748,7 +756,7 @@ ok "an empty wall opens the picker, Esc leaves the one line, and Ctrl-\\ d leave | |||
| 748 | # and each against an oracle outside the terminal: | 756 | # and each against an oracle outside the terminal: |
| 749 | # | 757 | # |
| 750 | # * Enter births on the SELECTED host — the daemon's own session count, | 758 | # * Enter births on the SELECTED host — the daemon's own session count, |
| 751 | # read back with `muxd stats`, says which machine took it. A birth | 759 | # read back with `mux d stats`, says which machine took it. A birth |
| 752 | # driven off a tile could only ever have landed on the focus's daemon. | 760 | # driven off a tile could only ever have landed on the focus's daemon. |
| 753 | # * `x` forgets a host: the file loses the line and the host's SHELL is | 761 | # * `x` forgets a host: the file loses the line and the host's SHELL is |
| 754 | # still there, asked of the OS with `kill -0` on the pid the shell | 762 | # still there, asked of the OS with `kill -0` on the pid the shell |
| @@ -801,7 +809,7 @@ mkdir -p "$PKSTATE/mux" | |||
| 801 | printf -- '--sock %s\n--sock %s\n' "$PKSOCK1" "$PKSOCK2" > "$PKSTATE/mux/hosts" | 809 | printf -- '--sock %s\n--sock %s\n' "$PKSOCK1" "$PKSOCK2" > "$PKSTATE/mux/hosts" |
| 802 | no_saved_tree "$PKSTATE" | 810 | no_saved_tree "$PKSTATE" |
| 803 | 811 | ||
| 804 | PKBEFORE=$("$MUXD" stats --sock "$PKSOCK2" | tr ' ' '\n' | grep -c '^session$') | 812 | PKBEFORE=$("$MUX" d stats --sock "$PKSOCK2" | tr ' ' '\n' | grep -c '^session$') |
| 805 | set +e | 813 | set +e |
| 806 | XDG_STATE_HOME="$PKSTATE" timeout 90 "$PTYCLIENT" --cols 100 --rows 30 \ | 814 | XDG_STATE_HOME="$PKSTATE" timeout 90 "$PTYCLIENT" --cols 100 --rows 30 \ |
| 807 | --out "$OUT.pkcap" --err "$OUT.pkcap.err" -- \ | 815 | --out "$OUT.pkcap" --err "$OUT.pkcap.err" -- \ |
| @@ -850,10 +858,10 @@ grep -qa -- "$PKSOCK2" "$OUT.pkcap" || { | |||
| 850 | 858 | ||
| 851 | # The birth's oracle is the DAEMON's count, not the screen: row 2 was | 859 | # The birth's oracle is the DAEMON's count, not the screen: row 2 was |
| 852 | # selected, so daemon 2 is the one that gained a session. | 860 | # selected, so daemon 2 is the one that gained a session. |
| 853 | PKAFTER=$("$MUXD" stats --sock "$PKSOCK2" | tr ' ' '\n' | grep -c '^session$') | 861 | PKAFTER=$("$MUX" d stats --sock "$PKSOCK2" | tr ' ' '\n' | grep -c '^session$') |
| 854 | [ "$PKAFTER" -eq "$((PKBEFORE + 1))" ] || { | 862 | [ "$PKAFTER" -eq "$((PKBEFORE + 1))" ] || { |
| 855 | echo "e2e FAIL: picker: daemon 2 holds $PKAFTER sessions, want $((PKBEFORE + 1))" | 863 | echo "e2e FAIL: picker: daemon 2 holds $PKAFTER sessions, want $((PKBEFORE + 1))" |
| 856 | "$MUXD" stats --sock "$PKSOCK2"; "$MUXD" stats --sock "$PKSOCK1"; exit 1; } | 864 | "$MUX" d stats --sock "$PKSOCK2"; "$MUX" d stats --sock "$PKSOCK1"; exit 1; } |
| 857 | 865 | ||
| 858 | # The forget: the line left the file and came back, and the SESSION never | 866 | # The forget: the line left the file and came back, and the SESSION never |
| 859 | # noticed either. `kill -0` on the shell's own pid, never the daemon's word | 867 | # noticed either. `kill -0` on the shell's own pid, never the daemon's word |
| @@ -890,23 +898,23 @@ ok "the picker births on the host a digit names, forgets a host without ending i | |||
| 890 | 898 | ||
| 891 | # ---- reading a host never starts a daemon; asking does ------------------ | 899 | # ---- reading a host never starts a daemon; asking does ------------------ |
| 892 | # | 900 | # |
| 893 | # `muxd endpoint` used to auto-start a daemon before answering, and the | 901 | # `mux d endpoint` used to auto-start a daemon before answering, and the |
| 894 | # wall runs it over ssh once a second per listed host. A listed box with | 902 | # wall runs it over ssh once a second per listed host. A listed box with |
| 895 | # nothing on it therefore got a daemon — and a shell in session 0 — from a | 903 | # nothing on it therefore got a daemon — and a shell in session 0 — from a |
| 896 | # READ, and a `muxd stop` typed on that box was undone a second later. | 904 | # READ, and a `mux d stop` typed on that box was undone a second later. |
| 897 | # | 905 | # |
| 898 | # The rule now: a daemon starts when somebody asks. `mux HOST` and the | 906 | # The rule now: a daemon starts when somebody asks. `mux HOST` and the |
| 899 | # picker's Enter ask, and answer a refusal by running `muxd start` over the | 907 | # picker's Enter ask, and answer a refusal by running `mux d start` over the |
| 900 | # same ssh; the poller, `mux hosts` and every redial report `unreachable` | 908 | # same ssh; the poller, `mux hosts` and every redial report `unreachable` |
| 901 | # and leave the box alone. | 909 | # and leave the box alone. |
| 902 | # | 910 | # |
| 903 | # The oracle is the OS, never the daemon: the socket INODE on the far side, | 911 | # The oracle is the OS, never the daemon: the socket INODE on the far side, |
| 904 | # and a walk of /proc for a `muxd run` born under that box's runtime dir. A | 912 | # and a walk of /proc for a `mux d run` born under that box's runtime dir. A |
| 905 | # daemon asked whether it exists cannot answer no. | 913 | # daemon asked whether it exists cannot answer no. |
| 906 | # | 914 | # |
| 907 | # Two fake boxes, because one cannot show that a birth landed on the row | 915 | # Two fake boxes, because one cannot show that a birth landed on the row |
| 908 | # the user chose: the shim reads the host word and hands the remote command | 916 | # the user chose: the shim reads the host word and hands the remote command |
| 909 | # a runtime dir of that box's own, so `muxd`'s default socket resolves to a | 917 | # a runtime dir of that box's own, so `mux d`'s default socket resolves to a |
| 910 | # different path per host — which is exactly what two machines are. | 918 | # different path per host — which is exactly what two machines are. |
| 911 | NSHIM="${TMPDIR:-/tmp}/mux-e2e-nostart-shim-$$" | 919 | NSHIM="${TMPDIR:-/tmp}/mux-e2e-nostart-shim-$$" |
| 912 | defer_rm "$NSHIM" | 920 | defer_rm "$NSHIM" |
| @@ -925,7 +933,7 @@ mkdir -p "$NSHIM" "$NRUNA" "$NRUNB" "$NSTATE/mux" | |||
| 925 | : > "$NSHIMLOG" | 933 | : > "$NSHIMLOG" |
| 926 | # The option loop and the arity guard are `ssh_shim_head`'s; what is this | 934 | # The option loop and the arity guard are `ssh_shim_head`'s; what is this |
| 927 | # leg's own is the runtime dir per host word, which is what makes two fake | 935 | # leg's own is the runtime dir per host word, which is what makes two fake |
| 928 | # boxes two machines — `muxd`'s default socket resolves to a different path | 936 | # boxes two machines — `mux d`'s default socket resolves to a different path |
| 929 | # on each. | 937 | # on each. |
| 930 | ssh_shim_head "$NSHIM/ssh" | 938 | ssh_shim_head "$NSHIM/ssh" |
| 931 | cat >> "$NSHIM/ssh" <<'SHIM' | 939 | cat >> "$NSHIM/ssh" <<'SHIM' |
| @@ -940,13 +948,15 @@ shift | |||
| 940 | exec /bin/sh -c "$*" | 948 | exec /bin/sh -c "$*" |
| 941 | SHIM | 949 | SHIM |
| 942 | chmod +x "$NSHIM/ssh" | 950 | chmod +x "$NSHIM/ssh" |
| 943 | NPATH="$NSHIM:$(cd "$(dirname "$MUXD")" && pwd):$PATH" | 951 | NPATH="$NSHIM:$(cd "$(dirname "$MUX")" && pwd):$PATH" |
| 944 | printf 'nostart-a@127.0.0.1\nnostart-b@127.0.0.1\n' > "$NSTATE/mux/hosts" | 952 | printf 'nostart-a@127.0.0.1\nnostart-b@127.0.0.1\n' > "$NSTATE/mux/hosts" |
| 945 | 953 | ||
| 946 | # The /proc walk. `muxd run` and not `muxd` — the transient `muxd endpoint` | 954 | # The /proc walk. `mux d run` and not any `mux` — the transient `mux d |
| 947 | # and `muxd start` processes share the runtime dir and are not daemons — | 955 | # endpoint` and `mux d start` processes share the runtime dir and are not |
| 948 | # and `environ` rather than a name, because a daemon `muxd start` spawns | 956 | # daemons — and `environ` rather than a name, because a daemon `mux d start` |
| 949 | # carries no --sock to grep for: it took the default from this variable. | 957 | # spawns carries no --sock to grep for: it took the default from this |
| 958 | # variable. The daemon spells `d run` because that is the argv spawn.zig | ||
| 959 | # execs it with, which is also what makes `ps` legible. | ||
| 950 | nostart_daemons() { | 960 | nostart_daemons() { |
| 951 | _nd=0 | 961 | _nd=0 |
| 952 | for _ne in /proc/[0-9]*/environ; do | 962 | for _ne in /proc/[0-9]*/environ; do |
| @@ -956,7 +966,7 @@ nostart_daemons() { | |||
| 956 | # on one that exits mid-walk), so the silencing has to wrap the | 966 | # on one that exits mid-walk), so the silencing has to wrap the |
| 957 | # whole command and not just `tr`. | 967 | # whole command and not just `tr`. |
| 958 | { tr '\0' '\n' < "$_ne"; } 2>/dev/null | grep -qxF "XDG_RUNTIME_DIR=$1" || continue | 968 | { tr '\0' '\n' < "$_ne"; } 2>/dev/null | grep -qxF "XDG_RUNTIME_DIR=$1" || continue |
| 959 | { tr '\0' ' ' < "/proc/$_np/cmdline"; } 2>/dev/null | grep -q 'muxd run' || continue | 969 | { tr '\0' ' ' < "/proc/$_np/cmdline"; } 2>/dev/null | grep -q 'mux d run' || continue |
| 960 | _nd=$((_nd + 1)) | 970 | _nd=$((_nd + 1)) |
| 961 | done | 971 | done |
| 962 | echo "$_nd" | 972 | echo "$_nd" |
| @@ -965,7 +975,7 @@ nostart_daemons() { | |||
| 965 | nostart_empty() { # $1 = runtime dir, $2 = socket, $3 = what was just done | 975 | nostart_empty() { # $1 = runtime dir, $2 = socket, $3 = what was just done |
| 966 | _nc=$(nostart_daemons "$1") | 976 | _nc=$(nostart_daemons "$1") |
| 967 | [ "$_nc" -eq 0 ] || { | 977 | [ "$_nc" -eq 0 ] || { |
| 968 | echo "e2e FAIL: no-start: $3 left $_nc muxd run under $1, want 0"; exit 1; } | 978 | echo "e2e FAIL: no-start: $3 left $_nc mux d run under $1, want 0"; exit 1; } |
| 969 | [ ! -e "$2" ] || { | 979 | [ ! -e "$2" ] || { |
| 970 | echo "e2e FAIL: no-start: $3 bound $2 — a read started a daemon"; exit 1; } | 980 | echo "e2e FAIL: no-start: $3 bound $2 — a read started a daemon"; exit 1; } |
| 971 | } | 981 | } |
| @@ -1015,17 +1025,17 @@ nostart_empty "$NRUNA" "$NSOCKA" "a wall polling it" | |||
| 1015 | # every poll cycle, would show as several. | 1025 | # every poll cycle, would show as several. |
| 1016 | NCB=$(nostart_daemons "$NRUNB") | 1026 | NCB=$(nostart_daemons "$NRUNB") |
| 1017 | [ "$NCB" -eq 1 ] || { | 1027 | [ "$NCB" -eq 1 ] || { |
| 1018 | echo "e2e FAIL: no-start: picker Enter left $NCB muxd run under $NRUNB, want 1"; exit 1; } | 1028 | echo "e2e FAIL: no-start: picker Enter left $NCB mux d run under $NRUNB, want 1"; exit 1; } |
| 1019 | [ -S "$NSOCKB" ] || { | 1029 | [ -S "$NSOCKB" ] || { |
| 1020 | echo "e2e FAIL: no-start: picker Enter started nothing on box B ($NSOCKB)"; exit 1; } | 1030 | echo "e2e FAIL: no-start: picker Enter started nothing on box B ($NSOCKB)"; exit 1; } |
| 1021 | # ...and it is a real session, named `0` off the daemon's own empty list. | 1031 | # ...and it is a real session, named `0` off the daemon's own empty list. |
| 1022 | dump_session "$NSOCKB" 0 | grep -q 'ns-born-ok' || { | 1032 | dump_session "$NSOCKB" 0 | grep -q 'ns-born-ok' || { |
| 1023 | echo "e2e FAIL: no-start: box B's session 0 never ran the marker:" | 1033 | echo "e2e FAIL: no-start: box B's session 0 never ran the marker:" |
| 1024 | "$MUXD" stats --sock "$NSOCKB"; exit 1; } | 1034 | "$MUX" d stats --sock "$NSOCKB"; exit 1; } |
| 1025 | 1035 | ||
| 1026 | # (3b) A picker-born tile RECONNECTS, and a reconnect is nobody's ask. The | 1036 | # (3b) A picker-born tile RECONNECTS, and a reconnect is nobody's ask. The |
| 1027 | # ask buys one dial; if it survived into the redial loop, the poll's bug | 1037 | # ask buys one dial; if it survived into the redial loop, the poll's bug |
| 1028 | # would simply have moved onto a tile — `muxd stop` typed on box B would be | 1038 | # would simply have moved onto a tile — `mux d stop` typed on box B would be |
| 1029 | # undone by the tile's next backoff, forever, and the fallback line would | 1039 | # undone by the tile's next backoff, forever, and the fallback line would |
| 1030 | # print onto the alternate screen once a cycle. | 1040 | # print onto the alternate screen once a cycle. |
| 1031 | # | 1041 | # |
| @@ -1034,7 +1044,7 @@ dump_session "$NSOCKB" 0 | grep -q 'ns-born-ok' || { | |||
| 1034 | # already has box B's session 0 as a tile, births a SECOND session there | 1044 | # already has box B's session 0 as a tile, births a SECOND session there |
| 1035 | # (so the tile under test is picker-born, not list-born — a list-born tile | 1045 | # (so the tile under test is picker-born, not list-born — a list-born tile |
| 1036 | # was never asked for and would pass this vacuously), and then stops the | 1046 | # was never asked for and would pass this vacuously), and then stops the |
| 1037 | # daemon from inside its own shell. `muxd stop` dies with the shell it | 1047 | # daemon from inside its own shell. `mux d stop` dies with the shell it |
| 1038 | # killed; that is fine, the request was already sent, and the assertion | 1048 | # killed; that is fine, the request was already sent, and the assertion |
| 1039 | # below is on the process table rather than on that command's word. | 1049 | # below is on the process table rather than on that command's word. |
| 1040 | set +e | 1050 | set +e |
| @@ -1050,7 +1060,7 @@ send \r | |||
| 1050 | settle 2000 30000 | 1060 | settle 2000 30000 |
| 1051 | send printf 'ns-two-%s\n' ok\n | 1061 | send printf 'ns-two-%s\n' ok\n |
| 1052 | expect ns-two-ok 25000 | 1062 | expect ns-two-ok 25000 |
| 1053 | send muxd stop --sock $NSOCKB\n | 1063 | send mux d stop --sock $NSOCKB\n |
| 1054 | settle 6000 30000 | 1064 | settle 6000 30000 |
| 1055 | send \x1cd | 1065 | send \x1cd |
| 1056 | waitexit 15000 | 1066 | waitexit 15000 |
| @@ -1060,10 +1070,10 @@ set -e | |||
| 1060 | [ "$RC" -eq 0 ] || { | 1070 | [ "$RC" -eq 0 ] || { |
| 1061 | echo "e2e FAIL: no-start: the reconnect leg exited $RC:" | 1071 | echo "e2e FAIL: no-start: the reconnect leg exited $RC:" |
| 1062 | cat "$OUT.nspc2"; echo "--- stderr ---"; cat "$OUT.nscap2.err"; exit 1; } | 1072 | cat "$OUT.nspc2"; echo "--- stderr ---"; cat "$OUT.nscap2.err"; exit 1; } |
| 1063 | nostart_empty "$NRUNB" "$NSOCKB" "a picker-born tile reconnecting after muxd stop" | 1073 | nostart_empty "$NRUNB" "$NSOCKB" "a picker-born tile reconnecting after mux d stop" |
| 1064 | 1074 | ||
| 1065 | # (4) A cold `mux HOST` entry, on the box the wall left alone. One daemon, | 1075 | # (4) A cold `mux HOST` entry, on the box the wall left alone. One daemon, |
| 1066 | # and the client said so in its own voice — `muxd start`'s progress reaches | 1076 | # and the client said so in its own voice — `mux d start`'s progress reaches |
| 1067 | # the user's terminal over the same inherited stderr ssh's own diagnostics | 1077 | # the user's terminal over the same inherited stderr ssh's own diagnostics |
| 1068 | # ride on. | 1078 | # ride on. |
| 1069 | pipe_mux "$OUT.nsa" "$OUT.nsa.err" env SHELL=/bin/sh XDG_STATE_HOME="$NSTATE" \ | 1079 | pipe_mux "$OUT.nsa" "$OUT.nsa.err" env SHELL=/bin/sh XDG_STATE_HOME="$NSTATE" \ |
| @@ -1073,21 +1083,21 @@ await_out "$OUT.nsa" "ns-cold-ok" "no-start: the cold entry never served a sessi | |||
| 1073 | pipe_detach "no-start cold entry client" | 1083 | pipe_detach "no-start cold entry client" |
| 1074 | NCA=$(nostart_daemons "$NRUNA") | 1084 | NCA=$(nostart_daemons "$NRUNA") |
| 1075 | [ "$NCA" -eq 1 ] || { | 1085 | [ "$NCA" -eq 1 ] || { |
| 1076 | echo "e2e FAIL: no-start: a cold mux HOST left $NCA muxd run under $NRUNA, want 1" | 1086 | echo "e2e FAIL: no-start: a cold mux HOST left $NCA mux d run under $NRUNA, want 1" |
| 1077 | cat "$OUT.nsa.err"; exit 1; } | 1087 | cat "$OUT.nsa.err"; exit 1; } |
| 1078 | [ -S "$NSOCKA" ] || { | 1088 | [ -S "$NSOCKA" ] || { |
| 1079 | echo "e2e FAIL: no-start: the cold entry started nothing on box A"; exit 1; } | 1089 | echo "e2e FAIL: no-start: the cold entry started nothing on box A"; exit 1; } |
| 1080 | [ "$(grep -c '^muxd: starting' "$OUT.nsa.err")" = "1" ] || { | 1090 | [ "$(grep -c '^mux d: starting' "$OUT.nsa.err")" = "1" ] || { |
| 1081 | echo "e2e FAIL: no-start: the cold entry did not run muxd start exactly once:" | 1091 | echo "e2e FAIL: no-start: the cold entry did not run mux d start exactly once:" |
| 1082 | cat "$OUT.nsa.err"; exit 1; } | 1092 | cat "$OUT.nsa.err"; exit 1; } |
| 1083 | # ...and box A goes down by the daemon's own verb, which makes the teardown | 1093 | # ...and box A goes down by the daemon's own verb, which makes the teardown |
| 1084 | # one more assertion rather than a daemon handed to the trap: what a `muxd | 1094 | # one more assertion rather than a daemon handed to the trap: what a `mux d |
| 1085 | # stop` owes on a box somebody started is exactly what an untouched box | 1095 | # stop` owes on a box somebody started is exactly what an untouched box |
| 1086 | # already shows — no `muxd run` under that runtime dir, and no socket. | 1096 | # already shows — no `mux d run` under that runtime dir, and no socket. |
| 1087 | # `assert_stopped` is not usable here: it wants a pid, and this daemon was | 1097 | # `assert_stopped` is not usable here: it wants a pid, and this daemon was |
| 1088 | # forked by an ssh shim on the far side of a client. | 1098 | # forked by an ssh shim on the far side of a client. |
| 1089 | "$MUXD" stop --sock "$NSOCKA" > "$OUT.nsastop" 2>&1 || { | 1099 | "$MUX" d stop --sock "$NSOCKA" > "$OUT.nsastop" 2>&1 || { |
| 1090 | echo "e2e FAIL: no-start: muxd stop on box A exited nonzero:" | 1100 | echo "e2e FAIL: no-start: mux d stop on box A exited nonzero:" |
| 1091 | cat "$OUT.nsastop"; exit 1; } | 1101 | cat "$OUT.nsastop"; exit 1; } |
| 1092 | nostart_empty "$NRUNA" "$NSOCKA" "muxd stop" | 1102 | nostart_empty "$NRUNA" "$NSOCKA" "mux d stop" |
| 1093 | ok "reading a host leaves it alone; a picker Enter and a cold mux HOST each start one daemon" | 1103 | ok "reading a host leaves it alone; a picker Enter and a cold mux HOST each start one daemon" |
test/e2e_10_agent.sh
| Old | New | ||
|---|---|---|---|
| @@ -8,7 +8,7 @@ | |||
| 8 | # down together. The legs whose daemon also holds its own default session | 8 | # down together. The legs whose daemon also holds its own default session |
| 9 | # leave by chord instead: there a tile is left standing, and the wall stays | 9 | # leave by chord instead: there a tile is left standing, and the wall stays |
| 10 | # up. The refusal leg below starts its own daemon on the freed path rather | 10 | # up. The refusal leg below starts its own daemon on the freed path rather |
| 11 | # than inheriting one that is already gone. Both are $MUXD; neither | 11 | # than inheriting one that is already gone. Both are $MUX; neither |
| 12 | # may be auto-started (see the $PATH note at the top of this file). | 12 | # may be auto-started (see the $PATH note at the top of this file). |
| 13 | # | 13 | # |
| 14 | # The agent lives beside the sockets rather than under $OUT: a unix path is | 14 | # The agent lives beside the sockets rather than under $OUT: a unix path is |
| @@ -188,7 +188,7 @@ ok "agent forwarding: -A with no agent is a usage error, not a silent no-op" | |||
| 188 | # | 188 | # |
| 189 | # A fresh daemon on the path, and both halves of that are load-bearing. The | 189 | # A fresh daemon on the path, and both halves of that are load-bearing. The |
| 190 | # leg above ended its last session but not its DAEMON — a daemon lives until | 190 | # leg above ended its last session but not its DAEMON — a daemon lives until |
| 191 | # `muxd stop` — so the old one is stopped by name here rather than waited | 191 | # `mux d stop` — so the old one is stopped by name here rather than waited |
| 192 | # out. And the new one is started before anything dials, because a client | 192 | # out. And the new one is started before anything dials, because a client |
| 193 | # dialling an unserved $SOCK48 would AUTO-START one off $PATH instead — a | 193 | # dialling an unserved $SOCK48 would AUTO-START one off $PATH instead — a |
| 194 | # daemon that need not be the one under test, and against a release with no | 194 | # daemon that need not be the one under test, and against a release with no |
| @@ -505,7 +505,7 @@ set -e | |||
| 505 | # answers by the time the session ends. | 505 | # answers by the time the session ends. |
| 506 | 506 | ||
| 507 | # A typed `exit` ended this daemon's only session and the daemon is still | 507 | # A typed `exit` ended this daemon's only session and the daemon is still |
| 508 | # here: emptiness is not an exit, and only `muxd stop` ends one. So this is | 508 | # here: emptiness is not an exit, and only `mux d stop` ends one. So this is |
| 509 | # assert_stopped like every other teardown — the kill -0 that says the | 509 | # assert_stopped like every other teardown — the kill -0 that says the |
| 510 | # process really went is inside it (wait_pid_gone), and the empty daemon | 510 | # process really went is inside it (wait_pid_gone), and the empty daemon |
| 511 | # had to answer the stop to get there. | 511 | # had to answer the stop to get there. |
test/e2e_11_select.sh
| Old | New | ||
|---|---|---|---|
| @@ -45,7 +45,7 @@ wait_grid "$SOCK50" "wmb-pin" "wall cluster: session b's marker" b | |||
| 45 | # inside session b's content and terminal row 2 is inside session a's. | 45 | # inside session b's content and terminal row 2 is inside session a's. |
| 46 | # | 46 | # |
| 47 | # Which tile the focus is ON is read off the DAEMON, never off the paint: a | 47 | # Which tile the focus is ON is read off the DAEMON, never off the paint: a |
| 48 | # marker typed after the click lands in the focused session and `muxa | 48 | # marker typed after the click lands in the focused session and `mux a |
| 49 | # capture` says which one that was. The report bytes themselves go through | 49 | # capture` says which one that was. The report bytes themselves go through |
| 50 | # to the focused tile's Core unchanged, so the Core that owns the drag sees | 50 | # to the focused tile's Core unchanged, so the Core that owns the drag sees |
| 51 | # them. | 51 | # them. |
| @@ -75,8 +75,8 @@ set -e | |||
| 75 | [ "$RC" -eq 0 ] || { | 75 | [ "$RC" -eq 0 ] || { |
| 76 | echo "e2e FAIL: wall click: ptyclient leg exited $RC (did the click focus b?):" | 76 | echo "e2e FAIL: wall click: ptyclient leg exited $RC (did the click focus b?):" |
| 77 | cat "$OUT.wmpc3"; exit 1; } | 77 | cat "$OUT.wmpc3"; exit 1; } |
| 78 | timeout 20 "$MUXA" capture --sock "$SOCK50" > "$OUT.wmfa" 2>&1 | 78 | timeout 20 "$MUX" a capture --sock "$SOCK50" > "$OUT.wmfa" 2>&1 |
| 79 | timeout 20 "$MUXA" capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1 | 79 | timeout 20 "$MUX" a capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1 |
| 80 | grep -q "wm-three" "$OUT.wmfb" || { | 80 | grep -q "wm-three" "$OUT.wmfb" || { |
| 81 | echo "e2e FAIL: wall click: a click in session b's stripe did not focus it:" | 81 | echo "e2e FAIL: wall click: a click in session b's stripe did not focus it:" |
| 82 | cat "$OUT.wmfb"; exit 1; } | 82 | cat "$OUT.wmfb"; exit 1; } |
| @@ -108,8 +108,8 @@ set -e | |||
| 108 | [ "$RC" -eq 0 ] || { | 108 | [ "$RC" -eq 0 ] || { |
| 109 | echo "e2e FAIL: wall click: label-bar leg exited $RC:" | 109 | echo "e2e FAIL: wall click: label-bar leg exited $RC:" |
| 110 | cat "$OUT.wmpc4"; exit 1; } | 110 | cat "$OUT.wmpc4"; exit 1; } |
| 111 | timeout 20 "$MUXA" capture --sock "$SOCK50" > "$OUT.wmfa" 2>&1 | 111 | timeout 20 "$MUX" a capture --sock "$SOCK50" > "$OUT.wmfa" 2>&1 |
| 112 | timeout 20 "$MUXA" capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1 | 112 | timeout 20 "$MUX" a capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1 |
| 113 | grep -q "wm-four" "$OUT.wmfa" || { | 113 | grep -q "wm-four" "$OUT.wmfa" || { |
| 114 | echo "e2e FAIL: wall click: a click in session a's stripe did not focus it:" | 114 | echo "e2e FAIL: wall click: a click in session a's stripe did not focus it:" |
| 115 | cat "$OUT.wmfa"; exit 1; } | 115 | cat "$OUT.wmfa"; exit 1; } |
| @@ -143,8 +143,8 @@ set -e | |||
| 143 | [ "$RC" -eq 0 ] || { | 143 | [ "$RC" -eq 0 ] || { |
| 144 | echo "e2e FAIL: wall click: beside leg exited $RC (did the column click focus b?):" | 144 | echo "e2e FAIL: wall click: beside leg exited $RC (did the column click focus b?):" |
| 145 | cat "$OUT.wmpc5"; exit 1; } | 145 | cat "$OUT.wmpc5"; exit 1; } |
| 146 | timeout 20 "$MUXA" capture --sock "$SOCK50" > "$OUT.wmfa" 2>&1 | 146 | timeout 20 "$MUX" a capture --sock "$SOCK50" > "$OUT.wmfa" 2>&1 |
| 147 | timeout 20 "$MUXA" capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1 | 147 | timeout 20 "$MUX" a capture --sock "$SOCK50" --session b > "$OUT.wmfb" 2>&1 |
| 148 | grep -q "wm-five" "$OUT.wmfb" || { | 148 | grep -q "wm-five" "$OUT.wmfb" || { |
| 149 | echo "e2e FAIL: wall click: a click in the right pane did not focus it:" | 149 | echo "e2e FAIL: wall click: a click in the right pane did not focus it:" |
| 150 | cat "$OUT.wmfb"; exit 1; } | 150 | cat "$OUT.wmfb"; exit 1; } |
| @@ -310,7 +310,7 @@ ok "a resize is reported in-band to an app that turned on mode 2048" | |||
| 310 | 310 | ||
| 311 | # --- The default socket path refuses to be guessed. Field incident: a tmux | 311 | # --- The default socket path refuses to be guessed. Field incident: a tmux |
| 312 | # server started before logind exported XDG_RUNTIME_DIR handed every pane an | 312 | # server started before logind exported XDG_RUNTIME_DIR handed every pane an |
| 313 | # environment without it, so a pane's `muxd stop` looked at the /tmp guess | 313 | # environment without it, so a pane's `mux d stop` looked at the /tmp guess |
| 314 | # while the daemon a pane had started owned $XDG_RUNTIME_DIR/muxd.sock — one | 314 | # while the daemon a pane had started owned $XDG_RUNTIME_DIR/muxd.sock — one |
| 315 | # uid, one box, two daemons. `env -u` rather than the harness environment: | 315 | # uid, one box, two daemons. `env -u` rather than the harness environment: |
| 316 | # on a box that HAS the variable the unset case is otherwise never taken. | 316 | # on a box that HAS the variable the unset case is otherwise never taken. |
| @@ -319,8 +319,8 @@ ok "a resize is reported in-band to an app that turned on mode 2048" | |||
| 319 | # path: bare `mux` reaches for the local daemon only when nothing is | 319 | # path: bare `mux` reaches for the local daemon only when nothing is |
| 320 | # listed, and the suite's shared file is every daemon it has attached to. | 320 | # listed, and the suite's shared file is every daemon it has attached to. |
| 321 | hostroom nort | 321 | hostroom nort |
| 322 | for BIN in "$MUXD stats" "$MUX"; do | 322 | for BIN in "$MUX d stats" "$MUX"; do |
| 323 | # shellcheck disable=SC2086 # the two-word muxd form is deliberate | 323 | # shellcheck disable=SC2086 # the two-word `mux d` form is deliberate |
| 324 | if env -u XDG_RUNTIME_DIR XDG_STATE_HOME="$HOSTROOM" $BIN > "$OUT.nort" 2>&1; then | 324 | if env -u XDG_RUNTIME_DIR XDG_STATE_HOME="$HOSTROOM" $BIN > "$OUT.nort" 2>&1; then |
| 325 | echo "e2e FAIL: '$BIN' with no XDG_RUNTIME_DIR did not refuse"; exit 1 | 325 | echo "e2e FAIL: '$BIN' with no XDG_RUNTIME_DIR did not refuse"; exit 1 |
| 326 | fi | 326 | fi |
| @@ -329,15 +329,15 @@ for BIN in "$MUXD stats" "$MUX"; do | |||
| 329 | grep -q -- '--sock' "$OUT.nort" || { | 329 | grep -q -- '--sock' "$OUT.nort" || { |
| 330 | echo "e2e FAIL: '$BIN' refusal does not say how to fix it:"; cat "$OUT.nort"; exit 1; } | 330 | echo "e2e FAIL: '$BIN' refusal does not say how to fix it:"; cat "$OUT.nort"; exit 1; } |
| 331 | done | 331 | done |
| 332 | # muxa answers agents in JSON, so its refusal has to arrive as a reply. | 332 | # mux a answers agents in JSON, so its refusal has to arrive as a reply. |
| 333 | env -u XDG_RUNTIME_DIR "$MUXA" status > "$OUT.nort.a" 2>&1 && { | 333 | env -u XDG_RUNTIME_DIR "$MUX" a status > "$OUT.nort.a" 2>&1 && { |
| 334 | echo "e2e FAIL: muxa with no XDG_RUNTIME_DIR did not refuse"; exit 1; } | 334 | echo "e2e FAIL: mux a with no XDG_RUNTIME_DIR did not refuse"; exit 1; } |
| 335 | grep -q '"error":"no default socket path"' "$OUT.nort.a" || { | 335 | grep -q '"error":"no default socket path"' "$OUT.nort.a" || { |
| 336 | echo "e2e FAIL: muxa refusal is not a JSON error:"; cat "$OUT.nort.a"; exit 1; } | 336 | echo "e2e FAIL: mux a refusal is not a JSON error:"; cat "$OUT.nort.a"; exit 1; } |
| 337 | # The socketless verbs must survive the same environment: --version is how an | 337 | # The socketless verbs must survive the same environment: --version is how an |
| 338 | # operator checks whether the scp landed, and it owes nothing to a socket. | 338 | # operator checks whether the scp landed, and it owes nothing to a socket. |
| 339 | env -u XDG_RUNTIME_DIR "$MUXD" --version | grep -q '^muxd 0\.' || { | 339 | env -u XDG_RUNTIME_DIR "$MUX" d --version | grep -q '^mux 0\.' || { |
| 340 | echo "e2e FAIL: muxd --version needs XDG_RUNTIME_DIR"; exit 1; } | 340 | echo "e2e FAIL: mux d --version needs XDG_RUNTIME_DIR"; exit 1; } |
| 341 | env -u XDG_RUNTIME_DIR "$MUX" --version | grep -q '^mux 0\.' || { | 341 | env -u XDG_RUNTIME_DIR "$MUX" --version | grep -q '^mux 0\.' || { |
| 342 | echo "e2e FAIL: mux --version needs XDG_RUNTIME_DIR"; exit 1; } | 342 | echo "e2e FAIL: mux --version needs XDG_RUNTIME_DIR"; exit 1; } |
| 343 | rm -f "$OUT.nort" "$OUT.nort.a" | 343 | rm -f "$OUT.nort" "$OUT.nort.a" |
test/e2e_12_panes.sh
| Old | New | ||
|---|---|---|---|
| @@ -207,10 +207,10 @@ ok "a focused tile's scrollback page owns only its sub-rect, and the neighbour's | |||
| 207 | # terminal, every other gets 0x0. A 0x0 rect claims nothing, so hidden | 207 | # terminal, every other gets 0x0. A 0x0 rect claims nothing, so hidden |
| 208 | # panes stay attached and paint nothing. Focus moves while fullscreened | 208 | # panes stay attached and paint nothing. Focus moves while fullscreened |
| 209 | # re-flatten, so Ctrl-\ 2 gives tile 2 the full rect. `f` again restores | 209 | # re-flatten, so Ctrl-\ 2 gives tile 2 the full rect. `f` again restores |
| 210 | # the real layout. This leg proves all three with `muxa capture` — the | 210 | # the real layout. This leg proves all three with `mux a capture` — the |
| 211 | # marker each session received names which tile owned the terminal. | 211 | # marker each session received names which tile owned the terminal. |
| 212 | # | 212 | # |
| 213 | # No convergence point: `muxa capture` reads the daemon's grid, not the | 213 | # No convergence point: `mux a capture` reads the daemon's grid, not the |
| 214 | # terminal's, and the daemon's grid is the side channel this asserts on. | 214 | # terminal's, and the daemon's grid is the side channel this asserts on. |
| 215 | start_daemon "$SOCK54" "$OUT.fs.d" "fullscreen daemon never bound" --shell /bin/sh | 215 | start_daemon "$SOCK54" "$OUT.fs.d" "fullscreen daemon never bound" --shell /bin/sh |
| 216 | D55PID=$DPID | 216 | D55PID=$DPID |
| @@ -255,8 +255,8 @@ set -e | |||
| 255 | cat "$OUT.fspc" "$OUT.fscap.err"; exit 1; } | 255 | cat "$OUT.fspc" "$OUT.fscap.err"; exit 1; } |
| 256 | # fs-one landed in session a (the tile that was fullscreened first); | 256 | # fs-one landed in session a (the tile that was fullscreened first); |
| 257 | # fs-two landed in session b (the tile Ctrl-\ 2 moved the full rect to). | 257 | # fs-two landed in session b (the tile Ctrl-\ 2 moved the full rect to). |
| 258 | timeout 20 "$MUXA" capture --sock "$SOCK54" > "$OUT.fsfa" 2>&1 | 258 | timeout 20 "$MUX" a capture --sock "$SOCK54" > "$OUT.fsfa" 2>&1 |
| 259 | timeout 20 "$MUXA" capture --sock "$SOCK54" --session b > "$OUT.fsfb" 2>&1 | 259 | timeout 20 "$MUX" a capture --sock "$SOCK54" --session b > "$OUT.fsfb" 2>&1 |
| 260 | grep -q "fs-one" "$OUT.fsfa" || { | 260 | grep -q "fs-one" "$OUT.fsfa" || { |
| 261 | echo "e2e FAIL: fullscreen: session a never got fs-one:" | 261 | echo "e2e FAIL: fullscreen: session a never got fs-one:" |
| 262 | cat "$OUT.fsfa"; exit 1; } | 262 | cat "$OUT.fsfa"; exit 1; } |
| @@ -356,8 +356,8 @@ _last_rail=$(rail_cols "$OUT.rszcap" | tail -1) | |||
| 356 | echo "e2e FAIL: resize: rail column is $_last_rail, want >=43 (40 + 3 grows):" | 356 | echo "e2e FAIL: resize: rail column is $_last_rail, want >=43 (40 + 3 grows):" |
| 357 | cat "$OUT.rszcap"; exit 1; } | 357 | cat "$OUT.rszcap"; exit 1; } |
| 358 | # Esc ended resize mode and typed prose reached the focused session. | 358 | # Esc ended resize mode and typed prose reached the focused session. |
| 359 | timeout 20 "$MUXA" capture --sock "$SOCK55" > "$OUT.rszsta" 2>&1 | 359 | timeout 20 "$MUX" a capture --sock "$SOCK55" > "$OUT.rszsta" 2>&1 |
| 360 | timeout 20 "$MUXA" capture --sock "$SOCK55" --session b > "$OUT.rszstb" 2>&1 | 360 | timeout 20 "$MUX" a capture --sock "$SOCK55" --session b > "$OUT.rszstb" 2>&1 |
| 361 | if ! grep -q "rsz-grown" "$OUT.rszsta" && ! grep -q "rsz-grown" "$OUT.rszstb"; then | 361 | if ! grep -q "rsz-grown" "$OUT.rszsta" && ! grep -q "rsz-grown" "$OUT.rszstb"; then |
| 362 | echo "e2e FAIL: resize: rsz-grown reached neither session after Esc:" | 362 | echo "e2e FAIL: resize: rsz-grown reached neither session after Esc:" |
| 363 | cat "$OUT.rszsta" "$OUT.rszstb"; exit 1 | 363 | cat "$OUT.rszsta" "$OUT.rszstb"; exit 1 |
| @@ -428,10 +428,10 @@ ok "a span-clear flood in the left pane cannot blank the right pane's grid" | |||
| 428 | # | 428 | # |
| 429 | # One tile, split right (births a second session), split below (births a | 429 | # One tile, split right (births a second session), split below (births a |
| 430 | # third). Then walk focus with hjkl and type a distinct marker after each | 430 | # third). Then walk focus with hjkl and type a distinct marker after each |
| 431 | # move; muxa capture names which session received it. Covers at least a | 431 | # move; mux a capture names which session received it. Covers at least a |
| 432 | # left move, a right move, and one vertical move. | 432 | # left move, a right move, and one vertical move. |
| 433 | # | 433 | # |
| 434 | # No convergence point: the assertion is per-session muxa capture, not a | 434 | # No convergence point: the assertion is per-session mux a capture, not a |
| 435 | # daemon-grid diff. | 435 | # daemon-grid diff. |
| 436 | start_daemon "$SOCK57" "$OUT.hjk.d" "hjkl daemon never bound" --shell /bin/sh | 436 | start_daemon "$SOCK57" "$OUT.hjk.d" "hjkl daemon never bound" --shell /bin/sh |
| 437 | D58PID=$DPID | 437 | D58PID=$DPID |
| @@ -479,9 +479,9 @@ set -e | |||
| 479 | cat "$OUT.hjkpc" "$OUT.hjkcap.err"; exit 1; } | 479 | cat "$OUT.hjkpc" "$OUT.hjkcap.err"; exit 1; } |
| 480 | # Three sessions exist now: a (the origin), and two birthed by splits. | 480 | # Three sessions exist now: a (the origin), and two birthed by splits. |
| 481 | # The daemon assigns numeric names to the new sessions; capture all three. | 481 | # The daemon assigns numeric names to the new sessions; capture all three. |
| 482 | timeout 20 "$MUXA" capture --sock "$SOCK57" > "$OUT.hjkfa" 2>&1 | 482 | timeout 20 "$MUX" a capture --sock "$SOCK57" > "$OUT.hjkfa" 2>&1 |
| 483 | timeout 20 "$MUXA" capture --sock "$SOCK57" --session 1 > "$OUT.hjkfb" 2>&1 | 483 | timeout 20 "$MUX" a capture --sock "$SOCK57" --session 1 > "$OUT.hjkfb" 2>&1 |
| 484 | timeout 20 "$MUXA" capture --sock "$SOCK57" --session 2 > "$OUT.hjkfc" 2>&1 | 484 | timeout 20 "$MUX" a capture --sock "$SOCK57" --session 2 > "$OUT.hjkfc" 2>&1 |
| 485 | # went-left: typed after \x1ch from the right pane → lands in session a. | 485 | # went-left: typed after \x1ch from the right pane → lands in session a. |
| 486 | grep -q "hjk-went-left" "$OUT.hjkfa" || { | 486 | grep -q "hjk-went-left" "$OUT.hjkfa" || { |
| 487 | echo "e2e FAIL: hjkl: hjk-went-left not in session a (left focus move):" | 487 | echo "e2e FAIL: hjkl: hjk-went-left not in session a (left focus move):" |
| @@ -667,11 +667,11 @@ _max_rail=$(rail_cols "$OUT.lprcap2" | tail -1) | |||
| 667 | cat "$OUT.lprcap2"; exit 1; } | 667 | cat "$OUT.lprcap2"; exit 1; } |
| 668 | # The marker typed into the restored wall landed in session b — the | 668 | # The marker typed into the restored wall landed in session b — the |
| 669 | # focus was saved on pane b and restore put it back there, not tile 0. | 669 | # focus was saved on pane b and restore put it back there, not tile 0. |
| 670 | timeout 20 "$MUXA" capture --sock "$SOCK59" --session b > "$OUT.lprfb" 2>&1 | 670 | timeout 20 "$MUX" a capture --sock "$SOCK59" --session b > "$OUT.lprfb" 2>&1 |
| 671 | grep -q "lpr-rt-marker" "$OUT.lprfb" || { | 671 | grep -q "lpr-rt-marker" "$OUT.lprfb" || { |
| 672 | echo "e2e FAIL: layout-restore: marker not in session b (focus did not survive):" | 672 | echo "e2e FAIL: layout-restore: marker not in session b (focus did not survive):" |
| 673 | cat "$OUT.lprfb"; exit 1; } | 673 | cat "$OUT.lprfb"; exit 1; } |
| 674 | timeout 20 "$MUXA" capture --sock "$SOCK59" > "$OUT.lprfa" 2>&1 | 674 | timeout 20 "$MUX" a capture --sock "$SOCK59" > "$OUT.lprfa" 2>&1 |
| 675 | ! grep -q "lpr-rt-marker" "$OUT.lprfa" || { | 675 | ! grep -q "lpr-rt-marker" "$OUT.lprfa" || { |
| 676 | echo "e2e FAIL: layout-restore: marker leaked into the default session (focus was tile 0, not b):" | 676 | echo "e2e FAIL: layout-restore: marker leaked into the default session (focus was tile 0, not b):" |
| 677 | cat "$OUT.lprfa"; exit 1; } | 677 | cat "$OUT.lprfa"; exit 1; } |
| @@ -845,7 +845,7 @@ _lph_r2=$(rail_cols "$OUT.lphcap2" | awk '$1 > 1' | tail -1) | |||
| 845 | grep -q -- "--sock $SOCK60#c \[up\]" "$OUT.lphcap2" || { | 845 | grep -q -- "--sock $SOCK60#c \[up\]" "$OUT.lphcap2" || { |
| 846 | echo "e2e FAIL: layout-heal: the newcomer never became a tile:" | 846 | echo "e2e FAIL: layout-heal: the newcomer never became a tile:" |
| 847 | cat "$OUT.lphpc2"; exit 1; } | 847 | cat "$OUT.lphpc2"; exit 1; } |
| 848 | timeout 20 "$MUXA" capture --sock "$SOCK60" > "$OUT.lphfa" 2>&1 | 848 | timeout 20 "$MUX" a capture --sock "$SOCK60" > "$OUT.lphfa" 2>&1 |
| 849 | grep -q "lph-heal-marker" "$OUT.lphfa" || { | 849 | grep -q "lph-heal-marker" "$OUT.lphfa" || { |
| 850 | echo "e2e FAIL: layout-heal: the survivor's marker is not in the session its leaf kept:" | 850 | echo "e2e FAIL: layout-heal: the survivor's marker is not in the session its leaf kept:" |
| 851 | cat "$OUT.lphfa"; exit 1; } | 851 | cat "$OUT.lphfa"; exit 1; } |
| @@ -898,7 +898,7 @@ set -e | |||
| 898 | echo "e2e FAIL: layout-degrade: ptyclient exited $RC:" | 898 | echo "e2e FAIL: layout-degrade: ptyclient exited $RC:" |
| 899 | cat "$OUT.lpdpc" "$OUT.lpdcap.err"; exit 1; } | 899 | cat "$OUT.lpdpc" "$OUT.lpdcap.err"; exit 1; } |
| 900 | # The wall came up: the marker reached the focused session. | 900 | # The wall came up: the marker reached the focused session. |
| 901 | timeout 20 "$MUXA" capture --sock "$SOCK61" > "$OUT.lpdfa" 2>&1 | 901 | timeout 20 "$MUX" a capture --sock "$SOCK61" > "$OUT.lpdfa" 2>&1 |
| 902 | grep -q "lpd-degrade-marker" "$OUT.lpdfa" || { | 902 | grep -q "lpd-degrade-marker" "$OUT.lpdfa" || { |
| 903 | echo "e2e FAIL: layout-degrade: marker not in session a (wall did not come up):" | 903 | echo "e2e FAIL: layout-degrade: marker not in session a (wall did not come up):" |
| 904 | cat "$OUT.lpdfa"; exit 1; } | 904 | cat "$OUT.lpdfa"; exit 1; } |
test/e2e_13_birth.sh
| Old | New | ||
|---|---|---|---|
| @@ -168,14 +168,14 @@ grep -aq ": --sock " "$OUT.prcap" || { | |||
| 168 | grep -qa "pr-bmark" "$OUT.prcap" || { | 168 | grep -qa "pr-bmark" "$OUT.prcap" || { |
| 169 | echo "e2e FAIL: prompt: the added host's session never painted on the wall:" | 169 | echo "e2e FAIL: prompt: the added host's session never painted on the wall:" |
| 170 | cat "$OUT.prpc"; exit 1; } | 170 | cat "$OUT.prpc"; exit 1; } |
| 171 | timeout 20 "$MUXA" capture --sock "$SOCK62" > "$OUT.prfa" 2>&1 | 171 | timeout 20 "$MUX" a capture --sock "$SOCK62" > "$OUT.prfa" 2>&1 |
| 172 | grep -q "pr-after-esc" "$OUT.prfa" || { | 172 | grep -q "pr-after-esc" "$OUT.prfa" || { |
| 173 | echo "e2e FAIL: prompt: keys after Esc never reached the focused session (prompt did not close):" | 173 | echo "e2e FAIL: prompt: keys after Esc never reached the focused session (prompt did not close):" |
| 174 | cat "$OUT.prfa"; exit 1; } | 174 | cat "$OUT.prfa"; exit 1; } |
| 175 | grep -q "zzz" "$OUT.prfa" && { | 175 | grep -q "zzz" "$OUT.prfa" && { |
| 176 | echo "e2e FAIL: prompt: Esc leaked the line into the shell:" | 176 | echo "e2e FAIL: prompt: Esc leaked the line into the shell:" |
| 177 | cat "$OUT.prfa"; exit 1; } | 177 | cat "$OUT.prfa"; exit 1; } |
| 178 | timeout 20 "$MUXA" capture --sock "$SOCK63" > "$OUT.prfb" 2>&1 | 178 | timeout 20 "$MUX" a capture --sock "$SOCK63" > "$OUT.prfb" 2>&1 |
| 179 | grep -q "pr-after-esc" "$OUT.prfb" && { | 179 | grep -q "pr-after-esc" "$OUT.prfb" && { |
| 180 | echo "e2e FAIL: prompt: adding a host moved the focus onto it:" | 180 | echo "e2e FAIL: prompt: adding a host moved the focus onto it:" |
| 181 | cat "$OUT.prfb"; exit 1; } | 181 | cat "$OUT.prfb"; exit 1; } |
| @@ -199,16 +199,16 @@ ok "the picker's a adds a host by spelling: its sessions become tiles, recorded, | |||
| 199 | # --------------------------------------------------------------------------- | 199 | # --------------------------------------------------------------------------- |
| 200 | # A refused attach is not a shell that exited. | 200 | # A refused attach is not a shell that exited. |
| 201 | # | 201 | # |
| 202 | # muxa attaches at 0x0, and a 0x0 attach is join-only: the daemon refuses a | 202 | # mux a attaches at 0x0, and a 0x0 attach is join-only: the daemon refuses a |
| 203 | # name it does not hold by sending exit_status 1 and closing — the same frame | 203 | # name it does not hold by sending exit_status 1 and closing — the same frame |
| 204 | # a real shell's exit sends. muxa used to report that as | 204 | # a real shell's exit sends. mux a used to report that as |
| 205 | # `{"reason":"session_ended","exit_code":1}` and `{"error":"session ended"}`, | 205 | # `{"reason":"session_ended","exit_code":1}` and `{"error":"session ended"}`, |
| 206 | # telling an agent a command had run and failed when no shell ever existed, | 206 | # telling an agent a command had run and failed when no shell ever existed, |
| 207 | # while `status` got no answer at all and blamed the daemon (`DaemonGone`). | 207 | # while `status` got no answer at all and blamed the daemon (`DaemonGone`). |
| 208 | # The snapshot tells the two apart — a served attach always sends one first | 208 | # The snapshot tells the two apart — a served attach always sends one first |
| 209 | # — and what this leg pins is the words an agent actually parses. | 209 | # — and what this leg pins is the words an agent actually parses. |
| 210 | mkdir -p "$REFSTATE" | 210 | mkdir -p "$REFSTATE" |
| 211 | XDG_STATE_HOME="$REFSTATE" "$MUXD" run --sock "$SOCK66" --shell /bin/sh > "$OUT.ref.d" 2>&1 & | 211 | XDG_STATE_HOME="$REFSTATE" "$MUX" d run --sock "$SOCK66" --shell /bin/sh > "$OUT.ref.d" 2>&1 & |
| 212 | D66PID=$! | 212 | D66PID=$! |
| 213 | defer_kill "$D66PID" | 213 | defer_kill "$D66PID" |
| 214 | wait_sock "$SOCK66" "$OUT.ref.d" "refused-attach daemon never bound" | 214 | wait_sock "$SOCK66" "$OUT.ref.d" "refused-attach daemon never bound" |
| @@ -221,40 +221,40 @@ refuse_leg() { | |||
| 221 | _kind="$2" | 221 | _kind="$2" |
| 222 | shift 2 | 222 | shift 2 |
| 223 | set +e | 223 | set +e |
| 224 | XDG_STATE_HOME="$REFSTATE" timeout 20 "$MUXA" "$@" --sock "$SOCK66" \ | 224 | XDG_STATE_HOME="$REFSTATE" timeout 20 "$MUX" a "$@" --sock "$SOCK66" \ |
| 225 | --session nosuch --timeout 5000 > "$_out" 2>&1 | 225 | --session nosuch --timeout 5000 > "$_out" 2>&1 |
| 226 | _rc=$? | 226 | _rc=$? |
| 227 | set -e | 227 | set -e |
| 228 | [ "$_rc" -eq 1 ] || { | 228 | [ "$_rc" -eq 1 ] || { |
| 229 | echo "e2e FAIL: refused-attach: muxa $1 on a missing session exited $_rc, want 1:" | 229 | echo "e2e FAIL: refused-attach: mux a $1 on a missing session exited $_rc, want 1:" |
| 230 | cat "$_out"; exit 1; } | 230 | cat "$_out"; exit 1; } |
| 231 | grep -qF '"error":"attach refused"' "$_out" || { | 231 | grep -qF '"error":"attach refused"' "$_out" || { |
| 232 | echo "e2e FAIL: refused-attach: muxa $1 did not name the refusal:" | 232 | echo "e2e FAIL: refused-attach: mux a $1 did not name the refusal:" |
| 233 | cat "$_out"; exit 1; } | 233 | cat "$_out"; exit 1; } |
| 234 | grep -qF 'so nosuch must already exist' "$_out" || { | 234 | grep -qF 'so nosuch must already exist' "$_out" || { |
| 235 | echo "e2e FAIL: refused-attach: muxa $1 did not name the session asked for:" | 235 | echo "e2e FAIL: refused-attach: mux a $1 did not name the session asked for:" |
| 236 | cat "$_out"; exit 1; } | 236 | cat "$_out"; exit 1; } |
| 237 | # Only the verbs that attach have two causes. status and capture never | 237 | # Only the verbs that attach have two causes. status and capture never |
| 238 | # attach, so no client slot was ever asked for and none can be blamed: | 238 | # attach, so no client slot was ever asked for and none can be blamed: |
| 239 | # an agent sent to read `muxd stats` over a name it mistyped burns a | 239 | # an agent sent to read `mux d stats` over a name it mistyped burns a |
| 240 | # round trip on a table that is not full. | 240 | # round trip on a table that is not full. |
| 241 | if [ "$_kind" = attach ]; then | 241 | if [ "$_kind" = attach ]; then |
| 242 | grep -qF 'room for one more client' "$_out" || { | 242 | grep -qF 'room for one more client' "$_out" || { |
| 243 | echo "e2e FAIL: refused-attach: muxa $1 blamed absence for a refusal that has two causes:" | 243 | echo "e2e FAIL: refused-attach: mux a $1 blamed absence for a refusal that has two causes:" |
| 244 | cat "$_out"; exit 1; } | 244 | cat "$_out"; exit 1; } |
| 245 | else | 245 | else |
| 246 | grep -qF 'max_clients' "$_out" && { | 246 | grep -qF 'max_clients' "$_out" && { |
| 247 | echo "e2e FAIL: refused-attach: muxa $1 never attached, so it must not blame the client table:" | 247 | echo "e2e FAIL: refused-attach: mux a $1 never attached, so it must not blame the client table:" |
| 248 | cat "$_out"; exit 1; } | 248 | cat "$_out"; exit 1; } |
| 249 | fi | 249 | fi |
| 250 | grep -qF 'session_ended' "$_out" && { | 250 | grep -qF 'session_ended' "$_out" && { |
| 251 | echo "e2e FAIL: refused-attach: muxa $1 still reports a session that ended:" | 251 | echo "e2e FAIL: refused-attach: mux a $1 still reports a session that ended:" |
| 252 | cat "$_out"; exit 1; } | 252 | cat "$_out"; exit 1; } |
| 253 | grep -qF 'session ended' "$_out" && { | 253 | grep -qF 'session ended' "$_out" && { |
| 254 | echo "e2e FAIL: refused-attach: muxa $1 still reports a session that ended:" | 254 | echo "e2e FAIL: refused-attach: mux a $1 still reports a session that ended:" |
| 255 | cat "$_out"; exit 1; } | 255 | cat "$_out"; exit 1; } |
| 256 | grep -qF 'DaemonGone' "$_out" && { | 256 | grep -qF 'DaemonGone' "$_out" && { |
| 257 | echo "e2e FAIL: refused-attach: muxa $1 blamed the daemon for a missing name:" | 257 | echo "e2e FAIL: refused-attach: mux a $1 blamed the daemon for a missing name:" |
| 258 | cat "$_out"; exit 1; } | 258 | cat "$_out"; exit 1; } |
| 259 | return 0 | 259 | return 0 |
| 260 | } | 260 | } |
| @@ -265,7 +265,7 @@ refuse_leg send attach send x | |||
| 265 | # ...and the daemon that said no three times is still serving. Refusing is | 265 | # ...and the daemon that said no three times is still serving. Refusing is |
| 266 | # an answer, not a fault: a daemon that died on a typed `--session` would | 266 | # an answer, not a fault: a daemon that died on a typed `--session` would |
| 267 | # turn one bad name into everyone else's outage. | 267 | # turn one bad name into everyone else's outage. |
| 268 | XDG_STATE_HOME="$REFSTATE" timeout 20 "$MUXA" status --sock "$SOCK66" \ | 268 | XDG_STATE_HOME="$REFSTATE" timeout 20 "$MUX" a status --sock "$SOCK66" \ |
| 269 | --session 0 > "$OUT.refalive" 2>&1 || { | 269 | --session 0 > "$OUT.refalive" 2>&1 || { |
| 270 | echo "e2e FAIL: refused-attach: the daemon stopped answering after refusing:" | 270 | echo "e2e FAIL: refused-attach: the daemon stopped answering after refusing:" |
| 271 | cat "$OUT.refalive"; exit 1; } | 271 | cat "$OUT.refalive"; exit 1; } |
| @@ -276,7 +276,7 @@ grep -qF '"cols":' "$OUT.refalive" || { | |||
| 276 | assert_stopped "$SOCK66" "$D66PID" "refused-attach" "$OUT.refstop" | 276 | assert_stopped "$SOCK66" "$D66PID" "refused-attach" "$OUT.refstop" |
| 277 | D66PID="" | 277 | D66PID="" |
| 278 | rm -rf "$REFSTATE" | 278 | rm -rf "$REFSTATE" |
| 279 | ok "muxa names a refused attach instead of a shell that never ran" | 279 | ok "mux a names a refused attach instead of a shell that never ran" |
| 280 | 280 | ||
| 281 | # --- the same ruling through the browser ------------------------------- | 281 | # --- the same ruling through the browser ------------------------------- |
| 282 | # | 282 | # |
| @@ -292,7 +292,7 @@ ok "muxa names a refused attach instead of a shell that never ran" | |||
| 292 | # same run, reached both ways, so the comparison is between two kinds of | 292 | # same run, reached both ways, so the comparison is between two kinds of |
| 293 | # line and not between two rigs. | 293 | # line and not between two rigs. |
| 294 | # | 294 | # |
| 295 | # Daemon truth answers "was it created" — `muxa status` on a name the | 295 | # Daemon truth answers "was it created" — `mux a status` on a name the |
| 296 | # daemon does not have exits non-zero, and the same probe runs BEFORE the | 296 | # daemon does not have exits non-zero, and the same probe runs BEFORE the |
| 297 | # hub so the leg cannot pass vacuously. `sessions=1` at the end is the | 297 | # hub so the leg cannot pass vacuously. `sessions=1` at the end is the |
| 298 | # other half: one birth, not two, and nothing born for the remote line. | 298 | # other half: one birth, not two, and nothing born for the remote line. |
| @@ -311,14 +311,14 @@ printf -- '--sock %s#wghost\nquic://127.0.0.1:%s#wghost2\n' \ | |||
| 311 | 311 | ||
| 312 | # Vacuity guard: neither exists yet. | 312 | # Vacuity guard: neither exists yet. |
| 313 | for _wg in wghost wghost2; do | 313 | for _wg in wghost wghost2; do |
| 314 | timeout 20 "$MUXA" status --sock "$SOCK67" --session "$_wg" > "$OUT.wgpre" 2>&1 && { | 314 | timeout 20 "$MUX" a status --sock "$SOCK67" --session "$_wg" > "$OUT.wgpre" 2>&1 && { |
| 315 | echo "e2e FAIL: web-restore: session $_wg existed before the hub ran:" | 315 | echo "e2e FAIL: web-restore: session $_wg existed before the hub ran:" |
| 316 | cat "$OUT.wgpre"; exit 1; } | 316 | cat "$OUT.wgpre"; exit 1; } |
| 317 | done | 317 | done |
| 318 | 318 | ||
| 319 | # No argv: the hub restores the wall from the last run, which is the file | 319 | # No argv: the hub restores the wall from the last run, which is the file |
| 320 | # written above. Its own state home, never the developer's. | 320 | # written above. Its own state home, never the developer's. |
| 321 | XDG_STATE_HOME="$WGSTATE" "$MUXWEB" --port "$WGPORT" --key "$WGKEY" \ | 321 | XDG_STATE_HOME="$WGSTATE" "$MUX" web --port "$WGPORT" --key "$WGKEY" \ |
| 322 | --quic-idle-ms 15000 > "$OUT.wgh" 2>&1 & | 322 | --quic-idle-ms 15000 > "$OUT.wgh" 2>&1 & |
| 323 | W5PID=$! | 323 | W5PID=$! |
| 324 | defer_kill "$W5PID" | 324 | defer_kill "$W5PID" |
| @@ -385,7 +385,7 @@ set -e | |||
| 385 | # Daemon truth: the local line's session exists, and at the size the birth | 385 | # Daemon truth: the local line's session exists, and at the size the birth |
| 386 | # claimed — a browser tile claims none, so a session born at the tile's 0x0 | 386 | # claimed — a browser tile claims none, so a session born at the tile's 0x0 |
| 387 | # would not have been born at all. | 387 | # would not have been born at all. |
| 388 | timeout 20 "$MUXA" status --sock "$SOCK67" --session wghost > "$OUT.wgsta" 2>&1 || { | 388 | timeout 20 "$MUX" a status --sock "$SOCK67" --session wghost > "$OUT.wgsta" 2>&1 || { |
| 389 | echo "e2e FAIL: web-restore: the saved local line did not come back:" | 389 | echo "e2e FAIL: web-restore: the saved local line did not come back:" |
| 390 | cat "$OUT.wgsta"; cat "$OUT.wgh"; exit 1; } | 390 | cat "$OUT.wgsta"; cat "$OUT.wgh"; exit 1; } |
| 391 | 391 | ||
| @@ -395,18 +395,18 @@ timeout 20 "$MUXA" status --sock "$SOCK67" --session wghost > "$OUT.wgsta" 2>&1 | |||
| 395 | # `client.birth_rows` claim to mirror. Grepping for 80x24 would pass just | 395 | # `client.birth_rows` claim to mirror. Grepping for 80x24 would pass just |
| 396 | # as happily if both sides drifted together, which is the drift that | 396 | # as happily if both sides drifted together, which is the drift that |
| 397 | # matters — nothing else links the two files. | 397 | # matters — nothing else links the two files. |
| 398 | timeout 20 "$MUXA" status --sock "$SOCK67" --session 0 > "$OUT.wgsta0" 2>&1 || { | 398 | timeout 20 "$MUX" a status --sock "$SOCK67" --session 0 > "$OUT.wgsta0" 2>&1 || { |
| 399 | echo "e2e FAIL: web-restore: the daemon's own default session did not answer:" | 399 | echo "e2e FAIL: web-restore: the daemon's own default session did not answer:" |
| 400 | cat "$OUT.wgsta0"; exit 1; } | 400 | cat "$OUT.wgsta0"; exit 1; } |
| 401 | _wg_size() { sed -n 's/^{\("cols":[0-9]*,"rows":[0-9]*\).*/\1/p' "$1"; } | 401 | _wg_size() { sed -n 's/^{\("cols":[0-9]*,"rows":[0-9]*\).*/\1/p' "$1"; } |
| 402 | WG_BORN=$(_wg_size "$OUT.wgsta") | 402 | WG_BORN=$(_wg_size "$OUT.wgsta") |
| 403 | WG_DEFAULT=$(_wg_size "$OUT.wgsta0") | 403 | WG_DEFAULT=$(_wg_size "$OUT.wgsta0") |
| 404 | [ -n "$WG_BORN" ] && [ -n "$WG_DEFAULT" ] || { | 404 | [ -n "$WG_BORN" ] && [ -n "$WG_DEFAULT" ] || { |
| 405 | echo "e2e FAIL: web-restore: no size read out of muxa status" | 405 | echo "e2e FAIL: web-restore: no size read out of mux a status" |
| 406 | echo " born='$WG_BORN' default='$WG_DEFAULT' — the pin would be vacuous" | 406 | echo " born='$WG_BORN' default='$WG_DEFAULT' — the pin would be vacuous" |
| 407 | cat "$OUT.wgsta" "$OUT.wgsta0"; exit 1; } | 407 | cat "$OUT.wgsta" "$OUT.wgsta0"; exit 1; } |
| 408 | [ "$WG_BORN" = "$WG_DEFAULT" ] || { | 408 | [ "$WG_BORN" = "$WG_DEFAULT" ] || { |
| 409 | echo "e2e FAIL: web-restore: the birth size drifted from muxd's own default" | 409 | echo "e2e FAIL: web-restore: the birth size drifted from the daemon's own default" |
| 410 | echo " wghost $WG_BORN, session 0 $WG_DEFAULT" | 410 | echo " wghost $WG_BORN, session 0 $WG_DEFAULT" |
| 411 | echo " — client.birth_cols/birth_rows must mirror main.Opts" | 411 | echo " — client.birth_cols/birth_rows must mirror main.Opts" |
| 412 | exit 1; } | 412 | exit 1; } |
| @@ -418,12 +418,12 @@ grep -qF "wg-born" "$OUT.wgws" || { | |||
| 418 | cat "$OUT.wgws"; exit 1; } | 418 | cat "$OUT.wgws"; exit 1; } |
| 419 | 419 | ||
| 420 | # The remote line created NOTHING. Same daemon, same file, same run. | 420 | # The remote line created NOTHING. Same daemon, same file, same run. |
| 421 | timeout 20 "$MUXA" status --sock "$SOCK67" --session wghost2 > "$OUT.wgghost" 2>&1 && { | 421 | timeout 20 "$MUX" a status --sock "$SOCK67" --session wghost2 > "$OUT.wgghost" 2>&1 && { |
| 422 | echo "e2e FAIL: web-restore: a quic:// wall line created a session:" | 422 | echo "e2e FAIL: web-restore: a quic:// wall line created a session:" |
| 423 | cat "$OUT.wgghost"; exit 1; } | 423 | cat "$OUT.wgghost"; exit 1; } |
| 424 | 424 | ||
| 425 | # ...and nothing else was created behind either tile. Two, not one — | 425 | # ...and nothing else was created behind either tile. Two, not one — |
| 426 | # `muxd run` makes the default session at startup, so the birth is the | 426 | # `mux d run` makes the default session at startup, so the birth is the |
| 427 | # SECOND. How MANY births happened is the attach delta's claim above; this | 427 | # SECOND. How MANY births happened is the attach delta's claim above; this |
| 428 | # one is that the table holds exactly the expected set. | 428 | # one is that the table holds exactly the expected set. |
| 429 | wait_sessions "$SOCK67" 2 "web-restore: the table holds the default session and wghost, nothing else" | 429 | wait_sessions "$SOCK67" 2 "web-restore: the table holds the default session and wghost, nothing else" |
| @@ -458,7 +458,7 @@ set -e | |||
| 458 | 458 | ||
| 459 | # Daemon truth for the same claim: the session the user ended is gone and | 459 | # Daemon truth for the same claim: the session the user ended is gone and |
| 460 | # stayed gone, and the daemon is back to the one session it started with. | 460 | # stayed gone, and the daemon is back to the one session it started with. |
| 461 | timeout 20 "$MUXA" status --sock "$SOCK67" --session wghost > "$OUT.wgexit" 2>&1 && { | 461 | timeout 20 "$MUX" a status --sock "$SOCK67" --session wghost > "$OUT.wgexit" 2>&1 && { |
| 462 | echo "e2e FAIL: web-restore: typing exit got the user a new shell:" | 462 | echo "e2e FAIL: web-restore: typing exit got the user a new shell:" |
| 463 | cat "$OUT.wgexit"; exit 1; } | 463 | cat "$OUT.wgexit"; exit 1; } |
| 464 | wait_sessions "$SOCK67" 1 "web-restore: the ended session was not resurrected" | 464 | wait_sessions "$SOCK67" 1 "web-restore: the ended session was not resurrected" |
| @@ -500,7 +500,7 @@ cat > "$SPDIR/ssh" <<SPSHIM | |||
| 500 | #!/bin/sh | 500 | #!/bin/sh |
| 501 | echo \$\$ >> "$SPINLOG" | 501 | echo \$\$ >> "$SPINLOG" |
| 502 | printf 'endpoint none\n' | 502 | printf 'endpoint none\n' |
| 503 | exec "$MUXD" proxy --sock "$SOCK68" | 503 | exec "$MUX" d proxy --sock "$SOCK68" |
| 504 | SPSHIM | 504 | SPSHIM |
| 505 | chmod +x "$SPDIR/ssh" | 505 | chmod +x "$SPDIR/ssh" |
| 506 | 506 | ||
| @@ -511,7 +511,7 @@ mkdir -p "$SPSTATE/mux" | |||
| 511 | printf 'mux-spin@127.0.0.1#spinghost\n' > "$SPSTATE/mux/wall" | 511 | printf 'mux-spin@127.0.0.1#spinghost\n' > "$SPSTATE/mux/wall" |
| 512 | 512 | ||
| 513 | XDG_STATE_HOME="$SPSTATE" XDG_CACHE_HOME="$SPSTATE/cache" PATH="$SPDIR:$PATH" \ | 513 | XDG_STATE_HOME="$SPSTATE" XDG_CACHE_HOME="$SPSTATE/cache" PATH="$SPDIR:$PATH" \ |
| 514 | "$MUXWEB" --port "$SPPORT" > "$OUT.sph" 2>&1 & | 514 | "$MUX" web --port "$SPPORT" > "$OUT.sph" 2>&1 & |
| 515 | W6PID=$! | 515 | W6PID=$! |
| 516 | defer_kill "$W6PID" | 516 | defer_kill "$W6PID" |
| 517 | wait_for "$OUT.sph" "serving" 10 || { | 517 | wait_for "$OUT.sph" "serving" 10 || { |
| @@ -550,7 +550,7 @@ SPDIALS=$(wc -l < "$SPINLOG") | |||
| 550 | 550 | ||
| 551 | # ...and it stayed refused: nothing was born behind the hub's back, which | 551 | # ...and it stayed refused: nothing was born behind the hub's back, which |
| 552 | # is what makes the count above a count of REFUSED dials. | 552 | # is what makes the count above a count of REFUSED dials. |
| 553 | timeout 20 "$MUXA" status --sock "$SOCK68" --session spinghost > "$OUT.spghost" 2>&1 && { | 553 | timeout 20 "$MUX" a status --sock "$SOCK68" --session spinghost > "$OUT.spghost" 2>&1 && { |
| 554 | echo "e2e FAIL: refusal-spin: a remote wall line created a session:" | 554 | echo "e2e FAIL: refusal-spin: a remote wall line created a session:" |
| 555 | cat "$OUT.spghost"; exit 1; } | 555 | cat "$OUT.spghost"; exit 1; } |
| 556 | 556 | ||
| @@ -660,7 +660,7 @@ wait_sessions "$SOCK74" 3 "lowest-free-digit: the daemon should hold 0, 1 and 2" | |||
| 660 | # The digit and the name agree, asked of the DAEMON rather than of the wall | 660 | # The digit and the name agree, asked of the DAEMON rather than of the wall |
| 661 | # that would be reporting on its own arithmetic: session 1 is back, and it | 661 | # that would be reporting on its own arithmetic: session 1 is back, and it |
| 662 | # is a NEW one — the ended session's output is not in it. | 662 | # is a NEW one — the ended session's output is not in it. |
| 663 | timeout 20 "$MUXA" capture --sock "$SOCK74" --session 1 > "$OUT.lfdsess1" 2>&1 | 663 | timeout 20 "$MUX" a capture --sock "$SOCK74" --session 1 > "$OUT.lfdsess1" 2>&1 |
| 664 | grep -q "lfd-back" "$OUT.lfdsess1" || { | 664 | grep -q "lfd-back" "$OUT.lfdsess1" || { |
| 665 | echo "e2e FAIL: lowest-free-digit: session 1 does not hold what was typed at" | 665 | echo "e2e FAIL: lowest-free-digit: session 1 does not hold what was typed at" |
| 666 | echo " the reborn tile:" | 666 | echo " the reborn tile:" |
test/e2e_14_upgrade.sh
| Old | New | ||
|---|---|---|---|
| @@ -2,7 +2,7 @@ | |||
| 2 | # e2e_14_upgrade.sh — sourced by test/e2e.sh after e2e_lib.sh. Scenarios run in | 2 | # e2e_14_upgrade.sh — sourced by test/e2e.sh after e2e_lib.sh. Scenarios run in |
| 3 | # the order they stand in; see the lib's header for what this file may | 3 | # the order they stand in; see the lib's header for what this file may |
| 4 | # assume and what it must register. | 4 | # assume and what it must register. |
| 5 | # `muxd upgrade` — four daemons, because each one is a different daemon | 5 | # `mux d upgrade` — four daemons, because each one is a different daemon |
| 6 | # LIFECYCLE and no two can share a process: the same-binary leg's daemon | 6 | # LIFECYCLE and no two can share a process: the same-binary leg's daemon |
| 7 | # ends up running a second image, the rollback leg's is born with the abort | 7 | # ends up running a second image, the rollback leg's is born with the abort |
| 8 | # armed in its environment, the agent leg's holds an `-A` client, and the | 8 | # armed in its environment, the agent leg's holds an `-A` client, and the |
| @@ -41,9 +41,9 @@ defer_rm "$UPAGENT" | |||
| 41 | UPAGKEY="${TMPDIR:-/tmp}/mux-e2e-upagkey-$$" | 41 | UPAGKEY="${TMPDIR:-/tmp}/mux-e2e-upagkey-$$" |
| 42 | defer_rm "$UPAGKEY" "$UPAGKEY.pub" | 42 | defer_rm "$UPAGKEY" "$UPAGKEY.pub" |
| 43 | 43 | ||
| 44 | # --- muxd upgrade: the daemon becomes the new binary, holding everything --- | 44 | # --- mux d upgrade: the daemon becomes the new binary, holding everything --- |
| 45 | # | 45 | # |
| 46 | # `muxd upgrade` execs the candidate OVER the running daemon: same pid, same | 46 | # `mux d upgrade` execs the candidate OVER the running daemon: same pid, same |
| 47 | # children, same descriptors. Nothing on the wire can carry the proof — a | 47 | # children, same descriptors. Nothing on the wire can carry the proof — a |
| 48 | # client reconnecting to a freshly started daemon sees exactly the same | 48 | # client reconnecting to a freshly started daemon sees exactly the same |
| 49 | # snapshot — so the witness is the session shell's own pid, put on the grid | 49 | # snapshot — so the witness is the session shell's own pid, put on the grid |
| @@ -55,20 +55,20 @@ defer_rm "$UPAGKEY" "$UPAGKEY.pub" | |||
| 55 | # then every one of those things again on the far side. | 55 | # then every one of those things again on the far side. |
| 56 | # | 56 | # |
| 57 | # bash with marks on, because two of the claims are the mechanism's: | 57 | # bash with marks on, because two of the claims are the mechanism's: |
| 58 | # `muxa run` answers under `marks` before the exec and must answer under | 58 | # `mux a run` answers under `marks` before the exec and must answer under |
| 59 | # `marks` after it. The FIRST one after is the assertion that matters — | 59 | # `marks` after it. The FIRST one after is the assertion that matters — |
| 60 | # the manifest carries a return watermark, and one carried into the new | 60 | # the manifest carries a return watermark, and one carried into the new |
| 61 | # seq space is a watermark from the future that no return can pass, which | 61 | # seq space is a watermark from the future that no return can pass, which |
| 62 | # measured as the first await after every upgrade timing out at 30s while | 62 | # measured as the first await after every upgrade timing out at 30s while |
| 63 | # the shell had already answered. | 63 | # the shell had already answered. |
| 64 | UPVER=$("$MUXD" --version | awk '{print $2}') | 64 | UPVER=$("$MUX" d --version | awk '{print $2}') |
| 65 | case "$UPVER" in | 65 | case "$UPVER" in |
| 66 | ?*.?*) ;; | 66 | ?*.?*) ;; |
| 67 | *) echo "e2e FAIL: upgrade: muxd --version gave no version to refuse with: [$UPVER]" | 67 | *) echo "e2e FAIL: upgrade: mux d --version gave no version to refuse with: [$UPVER]" |
| 68 | exit 1;; | 68 | exit 1;; |
| 69 | esac | 69 | esac |
| 70 | mkdir -p "$UPHOME" | 70 | mkdir -p "$UPHOME" |
| 71 | MUX_SHELL_INTEGRATION=1 HOME="$UPHOME" "$MUXD" run --sock "$SOCK69" \ | 71 | MUX_SHELL_INTEGRATION=1 HOME="$UPHOME" "$MUX" d run --sock "$SOCK69" \ |
| 72 | --shell /bin/bash > "$OUT.up.d" 2>&1 & | 72 | --shell /bin/bash > "$OUT.up.d" 2>&1 & |
| 73 | D69PID=$! | 73 | D69PID=$! |
| 74 | defer_kill "$D69PID" | 74 | defer_kill "$D69PID" |
| @@ -76,8 +76,8 @@ wait_sock "$SOCK69" "$OUT.up.d" "upgrade daemon never bound" | |||
| 76 | 76 | ||
| 77 | # Marks BEFORE the exec, so "marks after" is a comparison rather than a | 77 | # Marks BEFORE the exec, so "marks after" is a comparison rather than a |
| 78 | # hope: a daemon that never had them would fail this line first. | 78 | # hope: a daemon that never had them would fail this line first. |
| 79 | timeout 20 "$MUXA" run --sock "$SOCK69" --timeout 8000 'echo up-marks-pre' > "$OUT.upm1" 2>&1 || { | 79 | timeout 20 "$MUX" a run --sock "$SOCK69" --timeout 8000 'echo up-marks-pre' > "$OUT.upm1" 2>&1 || { |
| 80 | echo "e2e FAIL: upgrade: muxa run failed before the upgrade even started:" | 80 | echo "e2e FAIL: upgrade: mux a run failed before the upgrade even started:" |
| 81 | cat "$OUT.upm1"; exit 1; } | 81 | cat "$OUT.upm1"; exit 1; } |
| 82 | grep -qF '"mechanism":"marks"' "$OUT.upm1" || { | 82 | grep -qF '"mechanism":"marks"' "$OUT.upm1" || { |
| 83 | echo "e2e FAIL: upgrade: the session had no marks to lose:" | 83 | echo "e2e FAIL: upgrade: the session had no marks to lose:" |
| @@ -85,7 +85,7 @@ grep -qF '"mechanism":"marks"' "$OUT.upm1" || { | |||
| 85 | 85 | ||
| 86 | # The attached client, on a real pty, held across the exec. Its script | 86 | # The attached client, on a real pty, held across the exec. Its script |
| 87 | # stops on `expect up-resumed`, which nothing in the script produces — the | 87 | # stops on `expect up-resumed`, which nothing in the script produces — the |
| 88 | # suite's own post-upgrade `muxa run` does, below. That is the rendezvous: | 88 | # suite's own post-upgrade `mux a run` does, below. That is the rendezvous: |
| 89 | # the fixture reads the master the whole time it waits, so the capture is | 89 | # the fixture reads the master the whole time it waits, so the capture is |
| 90 | # continuous across the tear, and the needle can only arrive through the | 90 | # continuous across the tear, and the needle can only arrive through the |
| 91 | # connection the client re-dialled. | 91 | # connection the client re-dialled. |
| @@ -131,7 +131,7 @@ UPSHPID=$(dump_session "$SOCK69" | sed -n 's/.*shpid=\([0-9][0-9]*\).*/\1/p' | h | |||
| 131 | echo "e2e FAIL: upgrade: no shell pid read off the grid — the witness would be vacuous" | 131 | echo "e2e FAIL: upgrade: no shell pid read off the grid — the witness would be vacuous" |
| 132 | dump_session "$SOCK69"; exit 1; } | 132 | dump_session "$SOCK69"; exit 1; } |
| 133 | 133 | ||
| 134 | "$MUXD" stats --sock "$SOCK69" > "$OUT.upst1" 2>&1 || { | 134 | "$MUX" d stats --sock "$SOCK69" > "$OUT.upst1" 2>&1 || { |
| 135 | echo "e2e FAIL: upgrade: stats did not answer before the upgrade"; exit 1; } | 135 | echo "e2e FAIL: upgrade: stats did not answer before the upgrade"; exit 1; } |
| 136 | UPATT1=$(sed -n 's/.*attaches=\([0-9]*\).*/\1/p' "$OUT.upst1") | 136 | UPATT1=$(sed -n 's/.*attaches=\([0-9]*\).*/\1/p' "$OUT.upst1") |
| 137 | UPSNAP1=$(sed -n 's/^snapshots=\([0-9]*\).*/\1/p' "$OUT.upst1") | 137 | UPSNAP1=$(sed -n 's/^snapshots=\([0-9]*\).*/\1/p' "$OUT.upst1") |
| @@ -142,7 +142,7 @@ UPSNAP1=$(sed -n 's/^snapshots=\([0-9]*\).*/\1/p' "$OUT.upst1") | |||
| 142 | # 1. Refused: same version, no flag. The reason names BOTH versions, which | 142 | # 1. Refused: same version, no flag. The reason names BOTH versions, which |
| 143 | # is the difference between a policy an operator can act on and a "no". | 143 | # is the difference between a policy an operator can act on and a "no". |
| 144 | set +e | 144 | set +e |
| 145 | "$MUXD" upgrade --sock "$SOCK69" > "$OUT.upref1" 2>&1 | 145 | "$MUX" d upgrade --sock "$SOCK69" > "$OUT.upref1" 2>&1 |
| 146 | UPRC=$? | 146 | UPRC=$? |
| 147 | set -e | 147 | set -e |
| 148 | [ "$UPRC" -eq 1 ] || { | 148 | [ "$UPRC" -eq 1 ] || { |
| @@ -152,7 +152,7 @@ grep -qF "refused: version: $UPVER is not newer than $UPVER" "$OUT.upref1" || { | |||
| 152 | echo "e2e FAIL: upgrade: the refusal did not name both versions:" | 152 | echo "e2e FAIL: upgrade: the refusal did not name both versions:" |
| 153 | cat "$OUT.upref1"; exit 1; } | 153 | cat "$OUT.upref1"; exit 1; } |
| 154 | 154 | ||
| 155 | # 2. Refused: a candidate that is not executable. `muxd upgrade` offers its | 155 | # 2. Refused: a candidate that is not executable. `mux d upgrade` offers its |
| 156 | # OWN path (/proc/self/exe), so the only way to spell this refusal is to | 156 | # OWN path (/proc/self/exe), so the only way to spell this refusal is to |
| 157 | # take the exec bit off a copy AFTER it has started — and the ordering is | 157 | # take the exec bit off a copy AFTER it has started — and the ordering is |
| 158 | # made deterministic rather than hoped for. The daemon is held under | 158 | # made deterministic rather than hoped for. The daemon is held under |
| @@ -160,10 +160,10 @@ grep -qF "refused: version: $UPVER is not newer than $UPVER" "$OUT.upref1" || { | |||
| 160 | # `readlink /proc/PID/exe` is the witness that the copy has already | 160 | # `readlink /proc/PID/exe` is the witness that the copy has already |
| 161 | # exec'd, so the chmod cannot beat it and turn the leg into an exec | 161 | # exec'd, so the chmod cannot beat it and turn the leg into an exec |
| 162 | # failure that asserts nothing. | 162 | # failure that asserts nothing. |
| 163 | cp "$MUXD_ELF" "$UPBIN" | 163 | cp "$MUX_ELF" "$UPBIN" |
| 164 | chmod 755 "$UPBIN" | 164 | chmod 755 "$UPBIN" |
| 165 | kill -STOP "$D69PID" | 165 | kill -STOP "$D69PID" |
| 166 | "$UPBIN" upgrade --sock "$SOCK69" --allow-same-version > "$OUT.upref2" 2>&1 & | 166 | "$UPBIN" d upgrade --sock "$SOCK69" --allow-same-version > "$OUT.upref2" 2>&1 & |
| 167 | UPXPID=$! | 167 | UPXPID=$! |
| 168 | defer_kill "$UPXPID" | 168 | defer_kill "$UPXPID" |
| 169 | _i=0 | 169 | _i=0 |
| @@ -193,20 +193,20 @@ grep -qF "refused: path: not executable" "$OUT.upref2" || { | |||
| 193 | # ...and two refusals changed NOTHING. A refusal that had torn a client | 193 | # ...and two refusals changed NOTHING. A refusal that had torn a client |
| 194 | # down, or written the manifest and left the fd table half open, would | 194 | # down, or written the manifest and left the fd table half open, would |
| 195 | # still have printed the words above. | 195 | # still have printed the words above. |
| 196 | timeout 20 "$MUXA" status --sock "$SOCK69" > "$OUT.upsta" 2>&1 || { | 196 | timeout 20 "$MUX" a status --sock "$SOCK69" > "$OUT.upsta" 2>&1 || { |
| 197 | echo "e2e FAIL: upgrade: the daemon stopped answering after refusing:" | 197 | echo "e2e FAIL: upgrade: the daemon stopped answering after refusing:" |
| 198 | cat "$OUT.upsta"; exit 1; } | 198 | cat "$OUT.upsta"; exit 1; } |
| 199 | wait_grid "$SOCK69" "shpid=$UPSHPID" "upgrade: a refusal cost the session its shell" | 199 | wait_grid "$SOCK69" "shpid=$UPSHPID" "upgrade: a refusal cost the session its shell" |
| 200 | 200 | ||
| 201 | # 3. The exec itself. | 201 | # 3. The exec itself. |
| 202 | set +e | 202 | set +e |
| 203 | "$MUXD" upgrade --sock "$SOCK69" --allow-same-version > "$OUT.upok" 2>&1 | 203 | "$MUX" d upgrade --sock "$SOCK69" --allow-same-version > "$OUT.upok" 2>&1 |
| 204 | UPRC=$? | 204 | UPRC=$? |
| 205 | set -e | 205 | set -e |
| 206 | [ "$UPRC" -eq 0 ] || { | 206 | [ "$UPRC" -eq 0 ] || { |
| 207 | echo "e2e FAIL: upgrade: the upgrade exited $UPRC, want 0:" | 207 | echo "e2e FAIL: upgrade: the upgrade exited $UPRC, want 0:" |
| 208 | cat "$OUT.upok" "$OUT.up.d"; exit 1; } | 208 | cat "$OUT.upok" "$OUT.up.d"; exit 1; } |
| 209 | grep -qF "muxd: upgraded to $UPVER" "$OUT.upok" || { | 209 | grep -qF "mux d: upgraded to $UPVER" "$OUT.upok" || { |
| 210 | echo "e2e FAIL: upgrade: no verdict naming the version it landed on:" | 210 | echo "e2e FAIL: upgrade: no verdict naming the version it landed on:" |
| 211 | cat "$OUT.upok"; exit 1; } | 211 | cat "$OUT.upok"; exit 1; } |
| 212 | 212 | ||
| @@ -227,7 +227,7 @@ grep -qF -- "--resume-fd" "$OUT.upst2" || { | |||
| 227 | # counters fails THAT poll — reported as a client that never came back, | 227 | # counters fails THAT poll — reported as a client that never came back, |
| 228 | # which is the wrong diagnosis of the wrong bug. `-ge`, because the | 228 | # which is the wrong diagnosis of the wrong bug. `-ge`, because the |
| 229 | # re-attach this reading races has not necessarily landed yet. | 229 | # re-attach this reading races has not necessarily landed yet. |
| 230 | "$MUXD" stats --sock "$SOCK69" > "$OUT.upst2" 2>&1 || { | 230 | "$MUX" d stats --sock "$SOCK69" > "$OUT.upst2" 2>&1 || { |
| 231 | echo "e2e FAIL: upgrade: stats did not answer after the exec"; cat "$OUT.up.d"; exit 1; } | 231 | echo "e2e FAIL: upgrade: stats did not answer after the exec"; cat "$OUT.up.d"; exit 1; } |
| 232 | UPATT2=$(sed -n 's/.*attaches=\([0-9]*\).*/\1/p' "$OUT.upst2") | 232 | UPATT2=$(sed -n 's/.*attaches=\([0-9]*\).*/\1/p' "$OUT.upst2") |
| 233 | UPSNAP2=$(sed -n 's/^snapshots=\([0-9]*\).*/\1/p' "$OUT.upst2") | 233 | UPSNAP2=$(sed -n 's/^snapshots=\([0-9]*\).*/\1/p' "$OUT.upst2") |
| @@ -250,7 +250,7 @@ UPSNAP2=$(sed -n 's/^snapshots=\([0-9]*\).*/\1/p' "$OUT.upst2") | |||
| 250 | # took before the tear and would pass on a daemon that never dropped anyone. | 250 | # took before the tear and would pass on a daemon that never dropped anyone. |
| 251 | _i=0 | 251 | _i=0 |
| 252 | while : ; do | 252 | while : ; do |
| 253 | _upst=$("$MUXD" stats --sock "$SOCK69" 2>/dev/null || true) | 253 | _upst=$("$MUX" d stats --sock "$SOCK69" 2>/dev/null || true) |
| 254 | _upatt=$(printf '%s' "$_upst" | sed -n 's/.*attaches=\([0-9]*\).*/\1/p') | 254 | _upatt=$(printf '%s' "$_upst" | sed -n 's/.*attaches=\([0-9]*\).*/\1/p') |
| 255 | case "$_upst" in | 255 | case "$_upst" in |
| 256 | *"session 0 clients=1"*) | 256 | *"session 0 clients=1"*) |
| @@ -269,14 +269,14 @@ done | |||
| 269 | wait_grid "$SOCK69" "up-pre" "upgrade: the pre-upgrade marker did not survive the exec" | 269 | wait_grid "$SOCK69" "up-pre" "upgrade: the pre-upgrade marker did not survive the exec" |
| 270 | wait_grid "$SOCK69" "shpid=$UPSHPID" "upgrade: the session is a NEW shell, not the one that crossed" | 270 | wait_grid "$SOCK69" "shpid=$UPSHPID" "upgrade: the session is a NEW shell, not the one that crossed" |
| 271 | 271 | ||
| 272 | # The FIRST muxa run after the exec, and it is also what releases the | 272 | # The FIRST mux a run after the exec, and it is also what releases the |
| 273 | # attached client's `expect up-resumed` above — one command, two claims. | 273 | # attached client's `expect up-resumed` above — one command, two claims. |
| 274 | set +e | 274 | set +e |
| 275 | timeout 40 "$MUXA" run --sock "$SOCK69" --timeout 20000 'echo up-resumed' > "$OUT.upm2" 2>&1 | 275 | timeout 40 "$MUX" a run --sock "$SOCK69" --timeout 20000 'echo up-resumed' > "$OUT.upm2" 2>&1 |
| 276 | UPRC=$? | 276 | UPRC=$? |
| 277 | set -e | 277 | set -e |
| 278 | [ "$UPRC" -eq 0 ] || { | 278 | [ "$UPRC" -eq 0 ] || { |
| 279 | echo "e2e FAIL: upgrade: the FIRST muxa run after the exec exited $UPRC — a carried" | 279 | echo "e2e FAIL: upgrade: the FIRST mux a run after the exec exited $UPRC — a carried" |
| 280 | echo " return watermark is a watermark from the future and no return passes it:" | 280 | echo " return watermark is a watermark from the future and no return passes it:" |
| 281 | cat "$OUT.upm2"; exit 1; } | 281 | cat "$OUT.upm2"; exit 1; } |
| 282 | grep -qF '"mechanism":"marks"' "$OUT.upm2" || { | 282 | grep -qF '"mechanism":"marks"' "$OUT.upm2" || { |
| @@ -306,7 +306,7 @@ UPPCPID="" | |||
| 306 | # a subshell sets `_mux_ran` in the subshell, so shellint's `precmd_fn` | 306 | # a subshell sets `_mux_ran` in the subshell, so shellint's `precmd_fn` |
| 307 | # sees an untouched prompt in the parent and emits no `D;` mark at all | 307 | # sees an untouched prompt in the parent and emits no `D;` mark at all |
| 308 | # (measured: the await ran its whole budget out). | 308 | # (measured: the await ran its whole budget out). |
| 309 | timeout 40 "$MUXA" run --sock "$SOCK69" --timeout 20000 'sh -c "exit 7"' > "$OUT.upm3" 2>&1 || true | 309 | timeout 40 "$MUX" a run --sock "$SOCK69" --timeout 20000 'sh -c "exit 7"' > "$OUT.upm3" 2>&1 || true |
| 310 | grep -qF '"exit_code":7' "$OUT.upm3" || { | 310 | grep -qF '"exit_code":7' "$OUT.upm3" || { |
| 311 | echo "e2e FAIL: upgrade: the shell's own exit code did not survive the exec:" | 311 | echo "e2e FAIL: upgrade: the shell's own exit code did not survive the exec:" |
| 312 | cat "$OUT.upm3"; exit 1; } | 312 | cat "$OUT.upm3"; exit 1; } |
| @@ -314,7 +314,7 @@ grep -qF '"exit_code":7' "$OUT.upm3" || { | |||
| 314 | # ...and the re-attach was COUNTED onto them rather than replacing them. | 314 | # ...and the re-attach was COUNTED onto them rather than replacing them. |
| 315 | # The carry was asserted above with `-ge`; this is the other half, taken | 315 | # The carry was asserted above with `-ge`; this is the other half, taken |
| 316 | # once the client's own snapshot has certainly been served. | 316 | # once the client's own snapshot has certainly been served. |
| 317 | "$MUXD" stats --sock "$SOCK69" > "$OUT.upst2" 2>&1 | 317 | "$MUX" d stats --sock "$SOCK69" > "$OUT.upst2" 2>&1 |
| 318 | UPATT2=$(sed -n 's/.*attaches=\([0-9]*\).*/\1/p' "$OUT.upst2") | 318 | UPATT2=$(sed -n 's/.*attaches=\([0-9]*\).*/\1/p' "$OUT.upst2") |
| 319 | UPSNAP2=$(sed -n 's/^snapshots=\([0-9]*\).*/\1/p' "$OUT.upst2") | 319 | UPSNAP2=$(sed -n 's/^snapshots=\([0-9]*\).*/\1/p' "$OUT.upst2") |
| 320 | [ -n "$UPATT2" ] && [ -n "$UPSNAP2" ] || { | 320 | [ -n "$UPATT2" ] && [ -n "$UPSNAP2" ] || { |
| @@ -333,7 +333,7 @@ assert_stopped "$SOCK69" "$D69PID" "upgrade" "$OUT.upstop" | |||
| 333 | D69PID="" | 333 | D69PID="" |
| 334 | rm -f "$UPBIN" | 334 | rm -f "$UPBIN" |
| 335 | rm -rf "$UPHOME" | 335 | rm -rf "$UPHOME" |
| 336 | ok "muxd upgrade keeps the shell, its pid, its title, its marks and its counters" | 336 | ok "mux d upgrade keeps the shell, its pid, its title, its marks and its counters" |
| 337 | 337 | ||
| 338 | # --- ...and a candidate that cannot adopt hands the daemon back ----------- | 338 | # --- ...and a candidate that cannot adopt hands the daemon back ----------- |
| 339 | # | 339 | # |
| @@ -346,7 +346,7 @@ ok "muxd upgrade keeps the shell, its pid, its title, its marks and its counters | |||
| 346 | # exec, so the abort is armed on the daemon before any upgrade asks. | 346 | # exec, so the abort is armed on the daemon before any upgrade asks. |
| 347 | # | 347 | # |
| 348 | # The daemon's own log is the only thing that can say a rollback HAPPENED — | 348 | # The daemon's own log is the only thing that can say a rollback HAPPENED — |
| 349 | # `muxd upgrade` prints its verdict the moment the daemon accepts, and the | 349 | # `mux d upgrade` prints its verdict the moment the daemon accepts, and the |
| 350 | # process that would have corrected it is gone. Without that line every | 350 | # process that would have corrected it is gone. Without that line every |
| 351 | # assertion below would pass just as well on an upgrade that simply worked. | 351 | # assertion below would pass just as well on an upgrade that simply worked. |
| 352 | mkdir -p "$UPHOME" | 352 | mkdir -p "$UPHOME" |
| @@ -354,8 +354,8 @@ MUX_SHELL_INTEGRATION=1 MUX_RESUME_FAIL_AT=session HOME="$UPHOME" \ | |||
| 354 | start_daemon "$SOCK70" "$OUT.url.d" "rollback daemon never bound" --shell /bin/bash | 354 | start_daemon "$SOCK70" "$OUT.url.d" "rollback daemon never bound" --shell /bin/bash |
| 355 | D70PID=$DPID | 355 | D70PID=$DPID |
| 356 | 356 | ||
| 357 | timeout 20 "$MUXA" run --sock "$SOCK70" --timeout 8000 'echo rollpid=$$' > "$OUT.urlm1" 2>&1 || { | 357 | timeout 20 "$MUX" a run --sock "$SOCK70" --timeout 8000 'echo rollpid=$$' > "$OUT.urlm1" 2>&1 || { |
| 358 | echo "e2e FAIL: rollback: muxa run failed before the upgrade:" | 358 | echo "e2e FAIL: rollback: mux a run failed before the upgrade:" |
| 359 | cat "$OUT.urlm1"; exit 1; } | 359 | cat "$OUT.urlm1"; exit 1; } |
| 360 | wait_grid "$SOCK70" "rollpid=[0-9]" "rollback: the session shell never printed its pid" | 360 | wait_grid "$SOCK70" "rollpid=[0-9]" "rollback: the session shell never printed its pid" |
| 361 | UPROLLPID=$(dump_session "$SOCK70" | sed -n 's/.*rollpid=\([0-9][0-9]*\).*/\1/p' | head -1) | 361 | UPROLLPID=$(dump_session "$SOCK70" | sed -n 's/.*rollpid=\([0-9][0-9]*\).*/\1/p' | head -1) |
| @@ -363,7 +363,7 @@ UPROLLPID=$(dump_session "$SOCK70" | sed -n 's/.*rollpid=\([0-9][0-9]*\).*/\1/p' | |||
| 363 | echo "e2e FAIL: rollback: no shell pid read off the grid"; dump_session "$SOCK70"; exit 1; } | 363 | echo "e2e FAIL: rollback: no shell pid read off the grid"; dump_session "$SOCK70"; exit 1; } |
| 364 | 364 | ||
| 365 | set +e | 365 | set +e |
| 366 | "$MUXD" upgrade --sock "$SOCK70" --allow-same-version > "$OUT.urlup" 2>&1 | 366 | "$MUX" d upgrade --sock "$SOCK70" --allow-same-version > "$OUT.urlup" 2>&1 |
| 367 | UPRC=$? | 367 | UPRC=$? |
| 368 | set -e | 368 | set -e |
| 369 | [ "$UPRC" -eq 0 ] || { | 369 | [ "$UPRC" -eq 0 ] || { |
| @@ -383,7 +383,7 @@ kill -0 "$D70PID" 2>/dev/null || { | |||
| 383 | echo " the sessions with it" | 383 | echo " the sessions with it" |
| 384 | cat "$OUT.url.d"; exit 1; } | 384 | cat "$OUT.url.d"; exit 1; } |
| 385 | wait_grid "$SOCK70" "rollpid=$UPROLLPID" "rollback: the session lost its shell to a failed adoption" | 385 | wait_grid "$SOCK70" "rollpid=$UPROLLPID" "rollback: the session lost its shell to a failed adoption" |
| 386 | timeout 40 "$MUXA" run --sock "$SOCK70" --timeout 20000 'echo roll-post' > "$OUT.urlm2" 2>&1 || { | 386 | timeout 40 "$MUX" a run --sock "$SOCK70" --timeout 20000 'echo roll-post' > "$OUT.urlm2" 2>&1 || { |
| 387 | echo "e2e FAIL: rollback: the daemon that came back cannot run a command:" | 387 | echo "e2e FAIL: rollback: the daemon that came back cannot run a command:" |
| 388 | cat "$OUT.urlm2" "$OUT.url.d"; exit 1; } | 388 | cat "$OUT.urlm2" "$OUT.url.d"; exit 1; } |
| 389 | grep -qF '"mechanism":"marks"' "$OUT.urlm2" || { | 389 | grep -qF '"mechanism":"marks"' "$OUT.urlm2" || { |
| @@ -427,7 +427,7 @@ esac | |||
| 427 | 427 | ||
| 428 | pipe_mux "$OUT.uag" "$OUT.uag.err" env SSH_AUTH_SOCK="$UPAGENT" timeout 90 \ | 428 | pipe_mux "$OUT.uag" "$OUT.uag.err" env SSH_AUTH_SOCK="$UPAGENT" timeout 90 \ |
| 429 | "$MUX" -A --sock "$SOCK71" | 429 | "$MUX" -A --sock "$SOCK71" |
| 430 | UPATT1=$("$MUXD" stats --sock "$SOCK71" | sed -n 's/.*attaches=\([0-9]*\).*/\1/p') | 430 | UPATT1=$("$MUX" d stats --sock "$SOCK71" | sed -n 's/.*attaches=\([0-9]*\).*/\1/p') |
| 431 | [ -n "$UPATT1" ] || { echo "e2e FAIL: agent-upgrade: no attaches counter to compare against"; exit 1; } | 431 | [ -n "$UPATT1" ] || { echo "e2e FAIL: agent-upgrade: no attaches counter to compare against"; exit 1; } |
| 432 | # `agpre=` rather than the fingerprint alone: the shell echoes what is | 432 | # `agpre=` rather than the fingerprint alone: the shell echoes what is |
| 433 | # typed, and `$?` is not expanded in the echo, so only ssh-add's own status | 433 | # typed, and `$?` is not expanded in the echo, so only ssh-add's own status |
| @@ -444,7 +444,7 @@ UPFP1=$(grep -aoF -- "$UPFP" "$OUT.uag" | wc -l) | |||
| 444 | echo " (wanted $UPFP)"; exit 1; } | 444 | echo " (wanted $UPFP)"; exit 1; } |
| 445 | 445 | ||
| 446 | set +e | 446 | set +e |
| 447 | "$MUXD" upgrade --sock "$SOCK71" --allow-same-version > "$OUT.uagup" 2>&1 | 447 | "$MUX" d upgrade --sock "$SOCK71" --allow-same-version > "$OUT.uagup" 2>&1 |
| 448 | UPRC=$? | 448 | UPRC=$? |
| 449 | set -e | 449 | set -e |
| 450 | [ "$UPRC" -eq 0 ] || { | 450 | [ "$UPRC" -eq 0 ] || { |
| @@ -452,7 +452,7 @@ set -e | |||
| 452 | cat "$OUT.uagup" "$OUT.uag.d"; exit 1; } | 452 | cat "$OUT.uagup" "$OUT.uag.d"; exit 1; } |
| 453 | _i=0 | 453 | _i=0 |
| 454 | while : ; do | 454 | while : ; do |
| 455 | _upst=$("$MUXD" stats --sock "$SOCK71" 2>/dev/null || true) | 455 | _upst=$("$MUX" d stats --sock "$SOCK71" 2>/dev/null || true) |
| 456 | _upatt=$(printf '%s' "$_upst" | sed -n 's/.*attaches=\([0-9]*\).*/\1/p') | 456 | _upatt=$(printf '%s' "$_upst" | sed -n 's/.*attaches=\([0-9]*\).*/\1/p') |
| 457 | case "$_upst" in | 457 | case "$_upst" in |
| 458 | *"session 0 clients=1"*) | 458 | *"session 0 clients=1"*) |
| @@ -530,11 +530,11 @@ pipe_mux "$OUT.uqc" "$OUT.uqc.err" timeout 90 "$MUX" "quic://127.0.0.1:$UPQPORT" | |||
| 530 | pipe_send 'printf "uq-%%s\\n" pre\n' | 530 | pipe_send 'printf "uq-%%s\\n" pre\n' |
| 531 | await_out "$OUT.uqc" "uq-pre" "quic-upgrade: the QUIC client never got its pre-upgrade marker" | 531 | await_out "$OUT.uqc" "uq-pre" "quic-upgrade: the QUIC client never got its pre-upgrade marker" |
| 532 | 532 | ||
| 533 | UPATT1=$("$MUXD" stats --sock "$SOCK72" | sed -n 's/.*attaches=\([0-9]*\).*/\1/p') | 533 | UPATT1=$("$MUX" d stats --sock "$SOCK72" | sed -n 's/.*attaches=\([0-9]*\).*/\1/p') |
| 534 | [ -n "$UPATT1" ] || { echo "e2e FAIL: quic-upgrade: no attaches counter to compare against"; exit 1; } | 534 | [ -n "$UPATT1" ] || { echo "e2e FAIL: quic-upgrade: no attaches counter to compare against"; exit 1; } |
| 535 | UPT0=$(date +%s%N) | 535 | UPT0=$(date +%s%N) |
| 536 | set +e | 536 | set +e |
| 537 | "$MUXD" upgrade --sock "$SOCK72" --allow-same-version > "$OUT.uqup" 2>&1 | 537 | "$MUX" d upgrade --sock "$SOCK72" --allow-same-version > "$OUT.uqup" 2>&1 |
| 538 | UPRC=$? | 538 | UPRC=$? |
| 539 | set -e | 539 | set -e |
| 540 | [ "$UPRC" -eq 0 ] || { | 540 | [ "$UPRC" -eq 0 ] || { |
| @@ -542,7 +542,7 @@ set -e | |||
| 542 | cat "$OUT.uqup" "$OUT.uqc.d"; exit 1; } | 542 | cat "$OUT.uqup" "$OUT.uqc.d"; exit 1; } |
| 543 | _i=0 | 543 | _i=0 |
| 544 | while : ; do | 544 | while : ; do |
| 545 | _upst=$("$MUXD" stats --sock "$SOCK72" 2>/dev/null || true) | 545 | _upst=$("$MUX" d stats --sock "$SOCK72" 2>/dev/null || true) |
| 546 | _upatt=$(printf '%s' "$_upst" | sed -n 's/.*attaches=\([0-9]*\).*/\1/p') | 546 | _upatt=$(printf '%s' "$_upst" | sed -n 's/.*attaches=\([0-9]*\).*/\1/p') |
| 547 | case "$_upst" in | 547 | case "$_upst" in |
| 548 | *"session 0 clients=1"*) | 548 | *"session 0 clients=1"*) |
| @@ -574,11 +574,11 @@ assert_stopped "$SOCK72" "$D72PID" "quic-upgrade" "$OUT.uqstop" | |||
| 574 | D72PID="" | 574 | D72PID="" |
| 575 | ok "a QUIC client is served again within a breath of the exec, not after the idle timeout" | 575 | ok "a QUIC client is served again within a breath of the exec, not after the idle timeout" |
| 576 | 576 | ||
| 577 | # --- muxd stop returns when the PROCESS is gone, not when the path is | 577 | # --- mux d stop returns when the PROCESS is gone, not when the path is |
| 578 | # | 578 | # |
| 579 | # The unlink is the first thing a stopping daemon does; reaping its shells | 579 | # The unlink is the first thing a stopping daemon does; reaping its shells |
| 580 | # and deleting its dirs come after. A stop that said "stopped" at the | 580 | # and deleting its dirs come after. A stop that said "stopped" at the |
| 581 | # unlink handed a scripted `muxd start`, or a supervisor's "is it down", | 581 | # unlink handed a scripted `mux d start`, or a supervisor's "is it down", |
| 582 | # a daemon still running — `wait_pid_gone` after every stop in this suite | 582 | # a daemon still running — `wait_pid_gone` after every stop in this suite |
| 583 | # was that gap, papered. Shells that ignore TERM make the window a real | 583 | # was that gap, papered. Shells that ignore TERM make the window a real |
| 584 | # grace rather than a race the assertion could win by luck, and two | 584 | # grace rather than a race the assertion could win by luck, and two |
| @@ -590,19 +590,19 @@ D73PID=$DPID | |||
| 590 | pipe_mux "$OUT.sg" "" timeout 60 "$MUX" --sock "$SOCK73" --session second | 590 | pipe_mux "$OUT.sg" "" timeout 60 "$MUX" --sock "$SOCK73" --session second |
| 591 | sleep 0.5 | 591 | sleep 0.5 |
| 592 | pipe_detach "stop-gone: the second session's client" | 592 | pipe_detach "stop-gone: the second session's client" |
| 593 | [ "$("$MUXD" stats --sock "$SOCK73" | sed -n 's/.*sessions=\([0-9]*\).*/\1/p')" = "2" ] || { | 593 | [ "$("$MUX" d stats --sock "$SOCK73" | sed -n 's/.*sessions=\([0-9]*\).*/\1/p')" = "2" ] || { |
| 594 | echo "e2e FAIL: stop-gone: wanted two sessions of stubborn shells"; exit 1; } | 594 | echo "e2e FAIL: stop-gone: wanted two sessions of stubborn shells"; exit 1; } |
| 595 | D73REAL=$(real_pid "$D73PID") | 595 | D73REAL=$(real_pid "$D73PID") |
| 596 | "$MUXD" stop --sock "$SOCK73" 2> "$OUT.sg.stop" || { | 596 | "$MUX" d stop --sock "$SOCK73" 2> "$OUT.sg.stop" || { |
| 597 | echo "e2e FAIL: stop-gone: stop failed"; cat "$OUT.sg.stop"; exit 1; } | 597 | echo "e2e FAIL: stop-gone: stop failed"; cat "$OUT.sg.stop"; exit 1; } |
| 598 | # Asked of the OS the instant stop returns — no wait, no retry. | 598 | # Asked of the OS the instant stop returns — no wait, no retry. |
| 599 | if kill -0 "$D73REAL" 2>/dev/null; then | 599 | if kill -0 "$D73REAL" 2>/dev/null; then |
| 600 | echo "e2e FAIL: stop said stopped, but pid $D73PID is still running"; cat "$OUT.sg.stop"; exit 1 | 600 | echo "e2e FAIL: stop said stopped, but pid $D73PID is still running"; cat "$OUT.sg.stop"; exit 1 |
| 601 | fi | 601 | fi |
| 602 | grep -q '^muxd: stopped' "$OUT.sg.stop" || { | 602 | grep -q '^mux d: stopped' "$OUT.sg.stop" || { |
| 603 | echo "e2e FAIL: stop-gone: stop did not report stopped"; cat "$OUT.sg.stop"; exit 1; } | 603 | echo "e2e FAIL: stop-gone: stop did not report stopped"; cat "$OUT.sg.stop"; exit 1; } |
| 604 | D73PID="" | 604 | D73PID="" |
| 605 | ok "muxd stop returns when the process is gone, not when the socket is" | 605 | ok "mux d stop returns when the process is gone, not when the socket is" |
| 606 | 606 | ||
| 607 | # The pins. Literals, not variables set from counting something else — | 607 | # The pins. Literals, not variables set from counting something else — |
| 608 | # "assert the literal, never the constant the code under test reads" | 608 | # "assert the literal, never the constant the code under test reads" |
test/e2e_lib.sh
| Old | New | ||
|---|---|---|---|
| @@ -66,8 +66,8 @@ $E2E_KILL" | |||
| 66 | } | 66 | } |
| 67 | 67 | ||
| 68 | # defer_sock PATH... — a path a daemon may be listening on. The trap asks | 68 | # defer_sock PATH... — a path a daemon may be listening on. The trap asks |
| 69 | # `muxd stop` there before it kills anything and long before it unlinks the | 69 | # `mux d stop` there before it kills anything and long before it unlinks the |
| 70 | # path: a daemon this shell never forked — one a proxy, `muxd start` or the | 70 | # path: a daemon this shell never forked — one a proxy, `mux d start` or the |
| 71 | # handoff spawned — has no pid here, and the socket is the only handle | 71 | # handoff spawned — has no pid here, and the socket is the only handle |
| 72 | # there is. | 72 | # there is. |
| 73 | defer_sock() { | 73 | defer_sock() { |
| @@ -152,7 +152,7 @@ export XDG_CONFIG_HOME XDG_STATE_HOME XDG_CACHE_HOME | |||
| 152 | # resolves $SHELL, so without this the suite runs the developer's login | 152 | # resolves $SHELL, so without this the suite runs the developer's login |
| 153 | # shell and its whole rc — arbitrary code, on the session under test. | 153 | # shell and its whole rc — arbitrary code, on the session under test. |
| 154 | # | 154 | # |
| 155 | # Found by soak, not by reasoning. The M10 `muxd start` block inherited zsh, | 155 | # Found by soak, not by reasoning. The M10 `mux d start` block inherited zsh, |
| 156 | # whose plugin manager roots itself at $XDG_CACHE_HOME; pointing that at a | 156 | # whose plugin manager roots itself at $XDG_CACHE_HOME; pointing that at a |
| 157 | # fresh directory (the line above) made every session re-clone its plugins | 157 | # fresh directory (the line above) made every session re-clone its plugins |
| 158 | # from the network before the shell would answer, and the scenario's marker | 158 | # from the network before the shell would answer, and the scenario's marker |
| @@ -162,7 +162,7 @@ export XDG_CONFIG_HOME XDG_STATE_HOME XDG_CACHE_HOME | |||
| 162 | SHELL=/bin/sh | 162 | SHELL=/bin/sh |
| 163 | export SHELL | 163 | export SHELL |
| 164 | # MUX_KEY_FILE belongs to the same family and is cleared here for the same | 164 | # MUX_KEY_FILE belongs to the same family and is cleared here for the same |
| 165 | # forgets-proofing reason: `muxd endpoint` reads it BEFORE the default key | 165 | # forgets-proofing reason: `mux d endpoint` reads it BEFORE the default key |
| 166 | # path, so an operator who happens to have one exported would silently | 166 | # path, so an operator who happens to have one exported would silently |
| 167 | # change which key the handoff announces — and the scenarios would still | 167 | # change which key the handoff announces — and the scenarios would still |
| 168 | # pass, against the wrong key, until one of them did not. | 168 | # pass, against the wrong key, until one of them did not. |
| @@ -318,14 +318,14 @@ softkill() { | |||
| 318 | # own liveness rather than a fixed sleep, same reasoning as wait_for. | 318 | # own liveness rather than a fixed sleep, same reasoning as wait_for. |
| 319 | wait_gone() { | 319 | wait_gone() { |
| 320 | _i=0 | 320 | _i=0 |
| 321 | while "$MUXD" dump --sock "$1" > /dev/null 2>&1; do | 321 | while "$MUX" d dump --sock "$1" > /dev/null 2>&1; do |
| 322 | _i=$((_i + 1)); [ "$_i" -lt $(( 40 * TIME_SCALE )) ] || { echo "e2e FAIL: daemon on $1 never died"; exit 1; } | 322 | _i=$((_i + 1)); [ "$_i" -lt $(( 40 * TIME_SCALE )) ] || { echo "e2e FAIL: daemon on $1 never died"; exit 1; } |
| 323 | sleep 0.05 | 323 | sleep 0.05 |
| 324 | done | 324 | done |
| 325 | } | 325 | } |
| 326 | 326 | ||
| 327 | # wait_sock PATH LOG LABEL — poll until a daemon has bound PATH (5s), then | 327 | # wait_sock PATH LOG LABEL — poll until a daemon has bound PATH (5s), then |
| 328 | # ASSERT it. Every `muxd run &` in this file needs this: the bind happens | 328 | # ASSERT it. Every `mux d run &` in this file needs this: the bind happens |
| 329 | # after the fork, so the very next command would otherwise race it. LOG is | 329 | # after the fork, so the very next command would otherwise race it. LOG is |
| 330 | # the spawn's own capture, printed on failure because a daemon that failed | 330 | # the spawn's own capture, printed on failure because a daemon that failed |
| 331 | # to bind almost always said why; pass "" for the spawns that have none. | 331 | # to bind almost always said why; pass "" for the spawns that have none. |
| @@ -341,14 +341,14 @@ wait_sock() { | |||
| 341 | } | 341 | } |
| 342 | } | 342 | } |
| 343 | 343 | ||
| 344 | # start_daemon SOCK LOG LABEL [muxd args...] — a daemon in the background, | 344 | # start_daemon SOCK LOG LABEL [run flags...] — a daemon in the background, |
| 345 | # registered before it is waited for. Sets $DPID, the pid to end it by. | 345 | # registered before it is waited for. Sets $DPID, the pid to end it by. |
| 346 | # $DPID is scratch: the NEXT start_daemon overwrites it, so a leg that | 346 | # $DPID is scratch: the NEXT start_daemon overwrites it, so a leg that |
| 347 | # still needs its daemon further down keeps the pid under a name of its | 347 | # still needs its daemon further down keeps the pid under a name of its |
| 348 | # own. The long-lived daemon is $D1PID for exactly that reason — it is | 348 | # own. The long-lived daemon is $D1PID for exactly that reason — it is |
| 349 | # read some 5,000 lines after it is started. | 349 | # read some 5,000 lines after it is started. |
| 350 | # | 350 | # |
| 351 | # The registration is the whole reason this exists. A `muxd run &` written | 351 | # The registration is the whole reason this exists. A `mux d run &` written |
| 352 | # out by hand is three statements — the spawn, the pid, the wait — plus a | 352 | # out by hand is three statements — the spawn, the pid, the wait — plus a |
| 353 | # fourth in cleanup() saying how to end it, and the fourth is the one that | 353 | # fourth in cleanup() saying how to end it, and the fourth is the one that |
| 354 | # got forgotten. Here the spawn IS the registration and there is no fourth | 354 | # got forgotten. Here the spawn IS the registration and there is no fourth |
| @@ -361,7 +361,7 @@ wait_sock() { | |||
| 361 | # scenario after it. | 361 | # scenario after it. |
| 362 | start_daemon() { | 362 | start_daemon() { |
| 363 | _sds="$1"; _sdl="$2"; _sdlab="$3"; shift 3 | 363 | _sds="$1"; _sdl="$2"; _sdlab="$3"; shift 3 |
| 364 | "$MUXD" run --sock "$_sds" "$@" > "$_sdl" 2>&1 & | 364 | "$MUX" d run --sock "$_sds" "$@" > "$_sdl" 2>&1 & |
| 365 | DPID=$! | 365 | DPID=$! |
| 366 | defer_kill "$DPID" | 366 | defer_kill "$DPID" |
| 367 | defer_sock "$_sds" | 367 | defer_sock "$_sds" |
| @@ -426,11 +426,11 @@ await_out() { | |||
| 426 | # observer slot and promotes it to a client only when its attach frame | 426 | # observer slot and promotes it to a client only when its attach frame |
| 427 | # arrives, so simultaneous dials contend for the four observer slots, and | 427 | # arrives, so simultaneous dials contend for the four observer slots, and |
| 428 | # the ones with nowhere to land are closed outright. Measured at batch 7 | 428 | # the ones with nowhere to land are closed outright. Measured at batch 7 |
| 429 | # on a 32-slot daemon: 11 of 31 fills died with "connection to muxd lost" | 429 | # on a 32-slot daemon: 11 of 31 fills died with "connection to the daemon lost" |
| 430 | # and the table never filled; at batch 3, 31 of 31 land. | 430 | # and the table never filled; at batch 3, 31 of 31 land. |
| 431 | # | 431 | # |
| 432 | # STATE is a scratch home so the fills never write a wall line the leg | 432 | # STATE is a scratch home so the fills never write a wall line the leg |
| 433 | # later counts. The last name is asked back through muxa so a fill that | 433 | # later counts. The last name is asked back through mux a so a fill that |
| 434 | # never landed fails here with its own log rather than as the refusal leg | 434 | # never landed fails here with its own log rather than as the refusal leg |
| 435 | # proving nothing. | 435 | # proving nothing. |
| 436 | fill_sessions() { | 436 | fill_sessions() { |
| @@ -448,7 +448,7 @@ fill_sessions() { | |||
| 448 | echo "e2e FAIL: fill_sessions $_fs_pfx: a fill attach on $_fs_sock exited nonzero:" | 448 | echo "e2e FAIL: fill_sessions $_fs_pfx: a fill attach on $_fs_sock exited nonzero:" |
| 449 | cat "$OUT".fill."$_fs_pfx"*; exit 1; }; done | 449 | cat "$OUT".fill."$_fs_pfx"*; exit 1; }; done |
| 450 | done | 450 | done |
| 451 | "$MUXA" status --sock "$_fs_sock" --session "$_fs_pfx$_fs_to" > "$OUT.fill.$_fs_pfx.st" 2>&1 || { | 451 | "$MUX" a status --sock "$_fs_sock" --session "$_fs_pfx$_fs_to" > "$OUT.fill.$_fs_pfx.st" 2>&1 || { |
| 452 | echo "e2e FAIL: fill_sessions: $_fs_pfx$_fs_to was never created, the daemon is not full:" | 452 | echo "e2e FAIL: fill_sessions: $_fs_pfx$_fs_to was never created, the daemon is not full:" |
| 453 | cat "$OUT.fill.$_fs_pfx.st"; exit 1; } | 453 | cat "$OUT.fill.$_fs_pfx.st"; exit 1; } |
| 454 | } | 454 | } |
| @@ -564,7 +564,7 @@ pipe_waitexit() { | |||
| 564 | } | 564 | } |
| 565 | } | 565 | } |
| 566 | 566 | ||
| 567 | # dump_session SOCK [NAME] — `muxd dump` against one session (M18). An | 567 | # dump_session SOCK [NAME] — `mux d dump` against one session (M18). An |
| 568 | # EMPTY or absent NAME passes no --session flag AT ALL rather than an empty | 568 | # EMPTY or absent NAME passes no --session flag AT ALL rather than an empty |
| 569 | # one, and that is the load-bearing part: no tail is the wire's own | 569 | # one, and that is the load-bearing part: no tail is the wire's own |
| 570 | # default-session spelling and the pre-M18 compat path (decision 3), so | 570 | # default-session spelling and the pre-M18 compat path (decision 3), so |
| @@ -573,9 +573,9 @@ pipe_waitexit() { | |||
| 573 | # session and one asking about the default cannot drift into two spellings. | 573 | # session and one asking about the default cannot drift into two spellings. |
| 574 | dump_session() { | 574 | dump_session() { |
| 575 | if [ -n "${2:-}" ]; then | 575 | if [ -n "${2:-}" ]; then |
| 576 | "$MUXD" dump --sock "$1" --session "$2" | 576 | "$MUX" d dump --sock "$1" --session "$2" |
| 577 | else | 577 | else |
| 578 | "$MUXD" dump --sock "$1" | 578 | "$MUX" d dump --sock "$1" |
| 579 | fi | 579 | fi |
| 580 | } | 580 | } |
| 581 | 581 | ||
| @@ -601,7 +601,7 @@ wait_grid() { | |||
| 601 | exit 1 | 601 | exit 1 |
| 602 | } | 602 | } |
| 603 | 603 | ||
| 604 | # wait_sessions SOCK N LABEL — poll until `muxd stats` reports N live | 604 | # wait_sessions SOCK N LABEL — poll until `mux d stats` reports N live |
| 605 | # sessions (10s) and ASSERT it. wait_grid's shape for wait_grid's reason: a | 605 | # sessions (10s) and ASSERT it. wait_grid's shape for wait_grid's reason: a |
| 606 | # session is freed asynchronously — its shell exits, the daemon reaps on a | 606 | # session is freed asynchronously — its shell exits, the daemon reaps on a |
| 607 | # later pump — so "it died" has to be waited FOR, and a wait that falls out | 607 | # later pump — so "it died" has to be waited FOR, and a wait that falls out |
| @@ -610,11 +610,11 @@ wait_grid() { | |||
| 610 | wait_sessions() { | 610 | wait_sessions() { |
| 611 | _i=0 | 611 | _i=0 |
| 612 | while [ "$_i" -lt $(( 100 * TIME_SCALE )) ]; do | 612 | while [ "$_i" -lt $(( 100 * TIME_SCALE )) ]; do |
| 613 | "$MUXD" stats --sock "$1" 2>/dev/null | grep -q "sessions=$2" && return 0 | 613 | "$MUX" d stats --sock "$1" 2>/dev/null | grep -q "sessions=$2" && return 0 |
| 614 | sleep 0.1; _i=$((_i+1)) | 614 | sleep 0.1; _i=$((_i+1)) |
| 615 | done | 615 | done |
| 616 | echo "e2e FAIL: $3: stats never reported sessions=$2; it holds:" | 616 | echo "e2e FAIL: $3: stats never reported sessions=$2; it holds:" |
| 617 | "$MUXD" stats --sock "$1" || echo "(nothing answers on $1)" | 617 | "$MUX" d stats --sock "$1" || echo "(nothing answers on $1)" |
| 618 | exit 1 | 618 | exit 1 |
| 619 | } | 619 | } |
| 620 | 620 | ||
| @@ -629,7 +629,7 @@ wait_sessions() { | |||
| 629 | # once after it exits, the delta is exactly how many times anything attached | 629 | # once after it exits, the delta is exactly how many times anything attached |
| 630 | # across the whole leg — which for a wall of N tiles must be N, one per tile | 630 | # across the whole leg — which for a wall of N tiles must be N, one per tile |
| 631 | # at startup, and never N+1 however far the focus moved. It is read from | 631 | # at startup, and never N+1 however far the focus moved. It is read from |
| 632 | # `muxd stats`, which is plain human text and connects as an OBSERVER: the | 632 | # `mux d stats`, which is plain human text and connects as an OBSERVER: the |
| 633 | # reading itself never attaches, so it cannot pollute what it measures. | 633 | # reading itself never attaches, so it cannot pollute what it measures. |
| 634 | # | 634 | # |
| 635 | # THE GAUGE is `session NAME clients=`, sampled every 200ms into a file and | 635 | # THE GAUGE is `session NAME clients=`, sampled every 200ms into a file and |
| @@ -639,13 +639,13 @@ wait_sessions() { | |||
| 639 | # as another opens never exceeds 1, and one that lives less than a sample | 639 | # as another opens never exceeds 1, and one that lives less than a sample |
| 640 | # interval is invisible to it. The counter cannot miss either. | 640 | # interval is invisible to it. The counter cannot miss either. |
| 641 | # | 641 | # |
| 642 | # Nothing else may attach while a leg is being measured (`muxa capture` and | 642 | # Nothing else may attach while a leg is being measured (`mux a capture` and |
| 643 | # `wait_grid` are clients too), so both readings bracket the ptyclient leg | 643 | # `wait_grid` are clients too), so both readings bracket the ptyclient leg |
| 644 | # and nothing more. | 644 | # and nothing more. |
| 645 | # | 645 | # |
| 646 | # attaches_now SOCK — the daemon's cumulative accepted-attach count. | 646 | # attaches_now SOCK — the daemon's cumulative accepted-attach count. |
| 647 | attaches_now() { | 647 | attaches_now() { |
| 648 | timeout 5 "$MUXD" stats --sock "$1" 2>/dev/null | | 648 | timeout 5 "$MUX" d stats --sock "$1" 2>/dev/null | |
| 649 | sed -n 's/.*[^_]attaches=\([0-9]*\).*/\1/p' | 649 | sed -n 's/.*[^_]attaches=\([0-9]*\).*/\1/p' |
| 650 | } | 650 | } |
| 651 | 651 | ||
| @@ -669,7 +669,7 @@ WATCH_PID="" | |||
| 669 | watch_clients() { | 669 | watch_clients() { |
| 670 | touch "$2.on" | 670 | touch "$2.on" |
| 671 | ( while [ -e "$2.on" ]; do | 671 | ( while [ -e "$2.on" ]; do |
| 672 | timeout 5 "$MUXD" stats --sock "$1" 2>/dev/null | 672 | timeout 5 "$MUX" d stats --sock "$1" 2>/dev/null |
| 673 | sleep 0.2 | 673 | sleep 0.2 |
| 674 | done ) > "$2" & | 674 | done ) > "$2" & |
| 675 | WATCH_PID=$! | 675 | WATCH_PID=$! |
| @@ -704,8 +704,8 @@ assert_never_two_clients() { | |||
| 704 | } | 704 | } |
| 705 | 705 | ||
| 706 | # wait_pid_gone PID LABEL — poll until a tracked pid is gone (2s, the same | 706 | # wait_pid_gone PID LABEL — poll until a tracked pid is gone (2s, the same |
| 707 | # budget `muxd stop` gives itself). Its own helper rather than wait_gone's | 707 | # budget `mux d stop` gives itself). Its own helper rather than wait_gone's |
| 708 | # socket probe, because the two answer different questions: `muxd: stopped` | 708 | # socket probe, because the two answer different questions: `mux d: stopped` |
| 709 | # is printed on the first probe that gets a REFUSAL, which is the socket | 709 | # is printed on the first probe that gets a REFUSAL, which is the socket |
| 710 | # being unlinked, and the process can still be a fraction behind that. Only | 710 | # being unlinked, and the process can still be a fraction behind that. Only |
| 711 | # a pid can say the daemon itself ended, which is the assertion the M13 | 711 | # a pid can say the daemon itself ended, which is the assertion the M13 |
| @@ -727,7 +727,7 @@ wait_pid_gone() { | |||
| 727 | 727 | ||
| 728 | # assert_stopped SOCK PID LABEL ERRFILE — teardown by the sanctioned verb, | 728 | # assert_stopped SOCK PID LABEL ERRFILE — teardown by the sanctioned verb, |
| 729 | # asserted four ways. An exit code says the command RETURNED, not that it | 729 | # asserted four ways. An exit code says the command RETURNED, not that it |
| 730 | # did the job: `muxd: stopped` is the daemon's own account of it, the absent | 730 | # did the job: `mux d: stopped` is the daemon's own account of it, the absent |
| 731 | # socket is the filesystem's, and only the pid can say the process itself | 731 | # socket is the filesystem's, and only the pid can say the process itself |
| 732 | # ended (see wait_pid_gone). A stop that exited 0 while leaving any of the | 732 | # ended (see wait_pid_gone). A stop that exited 0 while leaving any of the |
| 733 | # three behind is precisely the regression this shape exists to catch. | 733 | # three behind is precisely the regression this shape exists to catch. |
| @@ -740,24 +740,28 @@ wait_pid_gone() { | |||
| 740 | # how a failing run says which daemon it leaked. | 740 | # how a failing run says which daemon it leaked. |
| 741 | assert_stopped() { | 741 | assert_stopped() { |
| 742 | set +e | 742 | set +e |
| 743 | "$MUXD" stop --sock "$1" 2> "$4" | 743 | "$MUX" d stop --sock "$1" 2> "$4" |
| 744 | _rc=$? | 744 | _rc=$? |
| 745 | set -e | 745 | set -e |
| 746 | [ "$_rc" = "0" ] || { | 746 | [ "$_rc" = "0" ] || { |
| 747 | echo "e2e FAIL: $3: stop exited $_rc, want 0"; cat "$4"; exit 1; } | 747 | echo "e2e FAIL: $3: stop exited $_rc, want 0"; cat "$4"; exit 1; } |
| 748 | grep -q '^muxd: stopped' "$4" || { | 748 | grep -q '^mux d: stopped' "$4" || { |
| 749 | echo "e2e FAIL: $3: stop did not report stopped"; cat "$4"; exit 1; } | 749 | echo "e2e FAIL: $3: stop did not report stopped"; cat "$4"; exit 1; } |
| 750 | [ ! -S "$1" ] || { | 750 | [ ! -S "$1" ] || { |
| 751 | echo "e2e FAIL: $3: stop left the socket"; ls -l "$1"; exit 1; } | 751 | echo "e2e FAIL: $3: stop left the socket"; ls -l "$1"; exit 1; } |
| 752 | wait_pid_gone "$2" "$3: stop reported stopped" | 752 | wait_pid_gone "$2" "$3: stop reported stopped" |
| 753 | } | 753 | } |
| 754 | 754 | ||
| 755 | # The transport child for a given socket: a process named muxd running the | 755 | # The transport child for a given socket: a `mux` whose own first two words |
| 756 | # `proxy` subcommand on that path. NEVER `pkill -f proxy` — the client's own | 756 | # are `d proxy`, on that path. The word test is positional and not a match |
| 757 | # argv contains the --via command string, so a pattern kill takes out the | 757 | # anywhere in the line, because one binary means the CLIENT is a `mux` too |
| 758 | # very client under test. | 758 | # and its argv carries the whole `--via ... d proxy ...` string — a pattern |
| 759 | # match here takes out the very client under test, and so would `pkill -f | ||
| 760 | # proxy`. | ||
| 759 | proxy_pid() { | 761 | proxy_pid() { |
| 760 | ps -eo pid,comm,args | awk -v s="$1" '$2=="muxd" && /proxy/ && index($0,s) {print $1}' | head -1 | 762 | ps -eo pid,comm,args | |
| 763 | awk -v s="$1" '$2=="mux" && $4=="d" && $5=="proxy" && index($0,s) {print $1}' | | ||
| 764 | head -1 | ||
| 761 | } | 765 | } |
| 762 | 766 | ||
| 763 | # --- M11: render-vs-dump convergence ----------------------------------- | 767 | # --- M11: render-vs-dump convergence ----------------------------------- |
| @@ -784,7 +788,7 @@ converged_quiet() { | |||
| 784 | # $_sz is two flags or nothing, never data. | 788 | # $_sz is two flags or nothing, never data. |
| 785 | # shellcheck disable=SC2086 | 789 | # shellcheck disable=SC2086 |
| 786 | "$RENDER" $_sz < "$_co" > "$_co.render" || return 1 | 790 | "$RENDER" $_sz < "$_co" > "$_co.render" || return 1 |
| 787 | "$MUXD" dump --sock "$_cs" > "$_co.dump" || return 1 | 791 | "$MUX" d dump --sock "$_cs" > "$_co.dump" || return 1 |
| 788 | # Trailing whitespace is a formatting difference between two correct | 792 | # Trailing whitespace is a formatting difference between two correct |
| 789 | # grids (padded vs unpadded row ends), not a divergence. | 793 | # grids (padded vs unpadded row ends), not a divergence. |
| 790 | sed 's/[[:space:]]*$//' "$_co.render" > "$_co.render.n" | 794 | sed 's/[[:space:]]*$//' "$_co.render" > "$_co.render.n" |
| @@ -798,7 +802,7 @@ converged_quiet() { | |||
| 798 | # Same as above: flags or nothing. | 802 | # Same as above: flags or nothing. |
| 799 | # shellcheck disable=SC2086 | 803 | # shellcheck disable=SC2086 |
| 800 | "$RENDER" --vt $_sz < "$_co" > "$_co.rvt" || return 1 | 804 | "$RENDER" --vt $_sz < "$_co" > "$_co.rvt" || return 1 |
| 801 | "$MUXD" dump --vt --sock "$_cs" > "$_co.dvt" || return 1 | 805 | "$MUX" d dump --vt --sock "$_cs" > "$_co.dvt" || return 1 |
| 802 | cmp -s "$_co.dvt" "$_co.rvt" || return 1 | 806 | cmp -s "$_co.dvt" "$_co.rvt" || return 1 |
| 803 | rm -f "$_co.render" "$_co.dump" "$_co.render.n" "$_co.dump.n" \ | 807 | rm -f "$_co.render" "$_co.dump" "$_co.render.n" "$_co.dump.n" \ |
| 804 | "$_co.diff" "$_co.rvt" "$_co.dvt" | 808 | "$_co.diff" "$_co.rvt" "$_co.dvt" |
| @@ -834,7 +838,7 @@ assert_converged() { | |||
| 834 | 838 | ||
| 835 | # assert_ws_converged WSOUT SOCK LABEL [SESSION] — the WebSocket leg's | 839 | # assert_ws_converged WSOUT SOCK LABEL [SESSION] — the WebSocket leg's |
| 836 | # convergence check. WSOUT is the stand-in's `dumpexit` file: its replica's | 840 | # convergence check. WSOUT is the stand-in's `dumpexit` file: its replica's |
| 837 | # grid, in `muxd dump`'s own format, built from frames that crossed the hub. | 841 | # grid, in `mux d dump`'s own format, built from frames that crossed the hub. |
| 838 | # Diff it against the daemon's grid, then doctor a copy of that grid and | 842 | # Diff it against the daemon's grid, then doctor a copy of that grid and |
| 839 | # assert the SAME diff catches the doctored one — the wan.sh rule (M9): a | 843 | # assert the SAME diff catches the doctored one — the wan.sh rule (M9): a |
| 840 | # convergence check that cannot fail proves nothing. | 844 | # convergence check that cannot fail proves nothing. |
| @@ -1004,7 +1008,7 @@ leak_sweep() { | |||
| 1004 | cat "$LEAKBANK" | 1008 | cat "$LEAKBANK" |
| 1005 | _lsbad=1 | 1009 | _lsbad=1 |
| 1006 | fi | 1010 | fi |
| 1007 | # The detached (`muxd start`) daemons log via XDG_STATE_HOME — and every | 1011 | # The detached (`mux d start`) daemons log via XDG_STATE_HOME — and every |
| 1008 | # wall on a terminal now runs under a state home of its OWN (hostroom, | 1012 | # wall on a terminal now runs under a state home of its OWN (hostroom, |
| 1009 | # and the per-leg $*STATE dirs), so reading the suite's shared home alone | 1013 | # and the per-leg $*STATE dirs), so reading the suite's shared home alone |
| 1010 | # swept none of the daemons those legs start: the local auto-start, the | 1014 | # swept none of the daemons those legs start: the local auto-start, the |
| @@ -1075,15 +1079,15 @@ cleanup() { | |||
| 1075 | _oifs=$IFS | 1079 | _oifs=$IFS |
| 1076 | IFS=' | 1080 | IFS=' |
| 1077 | ' | 1081 | ' |
| 1078 | # Sockets first, and by the daemon's own verb. `muxd stop` on a path | 1082 | # Sockets first, and by the daemon's own verb. `mux d stop` on a path |
| 1079 | # nobody serves is a no-op that exits 0, and on a daemon this shell never | 1083 | # nobody serves is a no-op that exits 0, and on a daemon this shell never |
| 1080 | # forked — one a proxy, `muxd start` or the handoff spawned — it is the | 1084 | # forked — one a proxy, `mux d start` or the handoff spawned — it is the |
| 1081 | # only handle there is. Before the kills, and long before the unlink | 1085 | # only handle there is. Before the kills, and long before the unlink |
| 1082 | # below: unlinking first would leave a live daemon nothing could reach | 1086 | # below: unlinking first would leave a live daemon nothing could reach |
| 1083 | # by path. | 1087 | # by path. |
| 1084 | for _cs in $E2E_SOCK; do | 1088 | for _cs in $E2E_SOCK; do |
| 1085 | if [ -S "$_cs" ] && [ -n "${MUXD:-}" ]; then | 1089 | if [ -S "$_cs" ] && [ -n "${MUX:-}" ]; then |
| 1086 | "$MUXD" stop --sock "$_cs" > /dev/null 2>&1 || true | 1090 | "$MUX" d stop --sock "$_cs" > /dev/null 2>&1 || true |
| 1087 | fi | 1091 | fi |
| 1088 | done | 1092 | done |
| 1089 | # Then every registered process. softkill rather than a plain `kill`, | 1093 | # Then every registered process. softkill rather than a plain `kill`, |
test/render.zig
| Old | New | ||
|---|---|---|---|
| @@ -1,5 +1,5 @@ | |||
| 1 | //! e2e render helper: replays a captured mux-client stdout stream through | 1 | //! e2e render helper: replays a captured mux-client stdout stream through |
| 2 | //! its own ghostty-vt engine and prints the final grid in `muxd dump`'s | 2 | //! its own ghostty-vt engine and prints the final grid in `mux d dump`'s |
| 3 | //! text format, so the suite can diff what the client painted against | 3 | //! text format, so the suite can diff what the client painted against |
| 4 | //! what the daemon holds. Third helper beside rawmode/delaypipe. | 4 | //! what the daemon holds. Third helper beside rawmode/delaypipe. |
| 5 | const std = @import("std"); | 5 | const std = @import("std"); |
| @@ -85,7 +85,7 @@ pub fn main() !u8 { | |||
| 85 | try eng.dumpPlain(alloc); | 85 | try eng.dumpPlain(alloc); |
| 86 | defer alloc.free(out); | 86 | defer alloc.free(out); |
| 87 | try writeAll(std.posix.STDOUT_FILENO, out); | 87 | try writeAll(std.posix.STDOUT_FILENO, out); |
| 88 | // muxd dump appends one newline after the payload (main.zig dump()); | 88 | // mux d dump appends one newline after the payload (main.zig dump()); |
| 89 | // matching it exactly is what makes the two outputs diffable. | 89 | // matching it exactly is what makes the two outputs diffable. |
| 90 | try writeAll(std.posix.STDOUT_FILENO, "\n"); | 90 | try writeAll(std.posix.STDOUT_FILENO, "\n"); |
| 91 | return 0; | 91 | return 0; |
test/soak.sh
| Old | New | ||
|---|---|---|---|
| @@ -6,10 +6,10 @@ | |||
| 6 | # same temp-file patterns while it looks (a concurrently running suite | 6 | # same temp-file patterns while it looks (a concurrently running suite |
| 7 | # would read as a leak). | 7 | # would read as a leak). |
| 8 | set -u | 8 | set -u |
| 9 | MUXD="$1"; MUX="$2"; RAWMODE="$3"; DELAYPIPE="$4"; RENDER="$5"; PTYCLIENT="$6"; WSCLIENT="$7"; MUXWEB="$8" | 9 | # The same list e2e.sh takes, in the same order — see the note in build.zig; |
| 10 | # M18: e2e.sh takes muxa as $9. This list is that list — see the note in | 10 | # soak IS that suite run N times, so an argument added to one and not the |
| 11 | # build.zig; the two must be handed the same binaries in the same order. | 11 | # other makes every run abort on an unbound variable. |
| 12 | MUXA="$9" | 12 | MUX="$1"; RAWMODE="$2"; DELAYPIPE="$3"; RENDER="$4"; PTYCLIENT="$5"; WSCLIENT="$6" |
| 13 | E2E="$(dirname "$0")/e2e.sh" | 13 | E2E="$(dirname "$0")/e2e.sh" |
| 14 | N="${SOAK_N:-10}" | 14 | N="${SOAK_N:-10}" |
| 15 | TMP="${TMPDIR:-/tmp}" | 15 | TMP="${TMPDIR:-/tmp}" |
| @@ -33,7 +33,7 @@ BASE_STRAYS=$(find "$TMP" -maxdepth 1 \( -name 'muxd-e2e-*' -o -name 'mux-e2e-*' | |||
| 33 | FAILED=0 | 33 | FAILED=0 |
| 34 | i=1 | 34 | i=1 |
| 35 | while [ "$i" -le "$N" ]; do | 35 | while [ "$i" -le "$N" ]; do |
| 36 | if "$E2E" "$MUXD" "$MUX" "$RAWMODE" "$DELAYPIPE" "$RENDER" "$PTYCLIENT" "$WSCLIENT" "$MUXWEB" "$MUXA" > "$LOG" 2>&1; then | 36 | if "$E2E" "$MUX" d "$MUX" "$RAWMODE" "$DELAYPIPE" "$RENDER" "$PTYCLIENT" "$WSCLIENT" "$MUX" web "$MUX" a > "$LOG" 2>&1; then |
| 37 | echo "soak run $i/$N: PASS" | 37 | echo "soak run $i/$N: PASS" |
| 38 | else | 38 | else |
| 39 | FAILED=$((FAILED + 1)) | 39 | FAILED=$((FAILED + 1)) |
| @@ -75,7 +75,7 @@ CYCLES="${SOAK_CYCLES:-20}" | |||
| 75 | WARMUP=3 | 75 | WARMUP=3 |
| 76 | RSS_BOUND_KB=4096 | 76 | RSS_BOUND_KB=4096 |
| 77 | PERSIST_FAILED_BEFORE=$FAILED | 77 | PERSIST_FAILED_BEFORE=$FAILED |
| 78 | "$MUXD" run --sock "$PSOCK" --shell /bin/sh > "$PLOG" 2>&1 & | 78 | "$MUX" d run --sock "$PSOCK" --shell /bin/sh > "$PLOG" 2>&1 & |
| 79 | PDPID=$! | 79 | PDPID=$! |
| 80 | _i=0 | 80 | _i=0 |
| 81 | while [ ! -S "$PSOCK" ] && [ "$_i" -lt 100 ]; do sleep 0.05; _i=$((_i + 1)); done | 81 | while [ ! -S "$PSOCK" ] && [ "$_i" -lt 100 ]; do sleep 0.05; _i=$((_i + 1)); done |
test/throughput.sh
| Old | New | ||
|---|---|---|---|
| @@ -1,6 +1,6 @@ | |||
| 1 | #!/bin/sh | 1 | #!/bin/sh |
| 2 | # Output-throughput gate. Guards ONE bug class, the one that shipped: a | 2 | # Output-throughput gate. Guards ONE bug class, the one that shipped: a |
| 3 | # muxd whose VT engine is doing per-mutation work nobody asked for. Built | 3 | # a daemon whose VT engine is doing per-mutation work nobody asked for. Built |
| 4 | # Debug, ghostty runs its page-integrity check on every page mutation — | 4 | # Debug, ghostty runs its page-integrity check on every page mutation — |
| 5 | # a hash map over every cell plus a fresh DebugAllocator — and this exact | 5 | # a hash map over every cell plus a fresh DebugAllocator — and this exact |
| 6 | # workload went from 7ms to 3713ms. tmux does it in 6ms. That is the | 6 | # workload went from 7ms to 3713ms. tmux does it in 6ms. That is the |
| @@ -32,10 +32,8 @@ | |||
| 32 | # Only meaningful against a ReleaseSafe build — see the `throughput` | 32 | # Only meaningful against a ReleaseSafe build — see the `throughput` |
| 33 | # target in the Makefile, which is why this is not a build.zig step. | 33 | # target in the Makefile, which is why this is not a build.zig step. |
| 34 | set -eu | 34 | set -eu |
| 35 | MUXD="$1" | 35 | MUX="$1" |
| 36 | MUX="$2" | 36 | PTYCLIENT="$2" |
| 37 | MUXA="$3" | ||
| 38 | PTYCLIENT="$4" | ||
| 39 | 37 | ||
| 40 | # Lines of `yes`, and the wall-clock ceiling each leg must come in under. | 38 | # Lines of `yes`, and the wall-clock ceiling each leg must come in under. |
| 41 | # | 39 | # |
| @@ -74,7 +72,7 @@ REPS=${REPS:-5} | |||
| 74 | TMPD="$(mktemp -d "${TMPDIR:-/tmp}/muxd-throughput-XXXXXX")" | 72 | TMPD="$(mktemp -d "${TMPDIR:-/tmp}/muxd-throughput-XXXXXX")" |
| 75 | # Every attach records a wall tile in $XDG_STATE_HOME/mux/wall; without a | 73 | # Every attach records a wall tile in $XDG_STATE_HOME/mux/wall; without a |
| 76 | # hermetic home this suite's throwaway sockets pile up in the developer's | 74 | # hermetic home this suite's throwaway sockets pile up in the developer's |
| 77 | # real wall file and muxweb tries to open them all. | 75 | # real wall file and the hub tries to open them all. |
| 78 | XDG_STATE_HOME="$TMPD/state"; XDG_CONFIG_HOME="$TMPD/cfg"; XDG_CACHE_HOME="$TMPD/cache" | 76 | XDG_STATE_HOME="$TMPD/state"; XDG_CONFIG_HOME="$TMPD/cfg"; XDG_CACHE_HOME="$TMPD/cache" |
| 79 | export XDG_STATE_HOME XDG_CONFIG_HOME XDG_CACHE_HOME | 77 | export XDG_STATE_HOME XDG_CONFIG_HOME XDG_CACHE_HOME |
| 80 | SOCK="$TMPD/t.sock" | 78 | SOCK="$TMPD/t.sock" |
| @@ -90,7 +88,7 @@ cleanup() { | |||
| 90 | } | 88 | } |
| 91 | trap cleanup EXIT INT TERM | 89 | trap cleanup EXIT INT TERM |
| 92 | 90 | ||
| 93 | "$MUXD" run --sock "$SOCK" --shell /bin/sh --cols 80 --rows 24 & | 91 | "$MUX" d run --sock "$SOCK" --shell /bin/sh --cols 80 --rows 24 & |
| 94 | DPID=$! | 92 | DPID=$! |
| 95 | i=0 | 93 | i=0 |
| 96 | while [ ! -S "$SOCK" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done | 94 | while [ ! -S "$SOCK" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done |
| @@ -101,10 +99,10 @@ while [ ! -S "$SOCK" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done | |||
| 101 | # out reporting a throughput problem it never measured. | 99 | # out reporting a throughput problem it never measured. |
| 102 | # Split by quotes the session's shell strips: the needle appears in the | 100 | # Split by quotes the session's shell strips: the needle appears in the |
| 103 | # OUTPUT and never in the echoed command line, so this cannot match itself. | 101 | # OUTPUT and never in the echoed command line, so this cannot match itself. |
| 104 | "$MUXA" send --sock "$SOCK" 'echo REA""DY\n' > /dev/null | 102 | "$MUX" a send --sock "$SOCK" 'echo REA""DY\n' > /dev/null |
| 105 | i=0 | 103 | i=0 |
| 106 | while [ "$i" -lt 50 ]; do | 104 | while [ "$i" -lt 50 ]; do |
| 107 | "$MUXA" capture --sock "$SOCK" | grep -qF 'READY' && break | 105 | "$MUX" a capture --sock "$SOCK" | grep -qF 'READY' && break |
| 108 | sleep 0.1 | 106 | sleep 0.1 |
| 109 | i=$((i+1)) | 107 | i=$((i+1)) |
| 110 | done | 108 | done |
| @@ -116,7 +114,7 @@ done | |||
| 116 | # inside feed(). | 114 | # inside feed(). |
| 117 | # The `done` marker is a separate file on purpose: polling the timings file | 115 | # The `done` marker is a separate file on purpose: polling the timings file |
| 118 | # itself would read it after the first rep and call that the answer. | 116 | # itself would read it after the first rep and call that the answer. |
| 119 | "$MUXA" send --sock "$SOCK" \ | 117 | "$MUX" a send --sock "$SOCK" \ |
| 120 | "for k in \$(seq $REPS); do A=\$(date +%s%N); yes | head -n $SOLO_LINES; B=\$(date +%s%N); echo \$(( (B-A)/1000000 )) >> $TMPD/solo; done; echo ok > $TMPD/solo.done\n" \ | 118 | "for k in \$(seq $REPS); do A=\$(date +%s%N); yes | head -n $SOLO_LINES; B=\$(date +%s%N); echo \$(( (B-A)/1000000 )) >> $TMPD/solo; done; echo ok > $TMPD/solo.done\n" \ |
| 121 | > /dev/null | 119 | > /dev/null |
| 122 | i=0 | 120 | i=0 |
| @@ -131,9 +129,9 @@ SOLO_MS="$(sort -n "$TMPD/solo" | head -1)" | |||
| 131 | # A shell that died at startup writes no output and would sail past a | 129 | # A shell that died at startup writes no output and would sail past a |
| 132 | # timing bound it never exercised. The screen must actually be full of | 130 | # timing bound it never exercised. The screen must actually be full of |
| 133 | # the flood. Byte needle: the grid arrives JSON-escaped, newlines as \n. | 131 | # the flood. Byte needle: the grid arrives JSON-escaped, newlines as \n. |
| 134 | "$MUXA" capture --sock "$SOCK" | grep -qF 'y\ny\ny' || { | 132 | "$MUX" a capture --sock "$SOCK" | grep -qF 'y\ny\ny' || { |
| 135 | echo "throughput FAIL: the flood left no output on the grid" | 133 | echo "throughput FAIL: the flood left no output on the grid" |
| 136 | "$MUXA" capture --sock "$SOCK" | 134 | "$MUX" a capture --sock "$SOCK" |
| 137 | exit 1 | 135 | exit 1 |
| 138 | } | 136 | } |
| 139 | 137 | ||
| @@ -146,7 +144,7 @@ echo "solo: $SOLO_LINES lines, no client: ${SOLO_MS}ms (max ${SOLO_MAX_MS}ms) | |||
| 146 | 144 | ||
| 147 | # ---- leg 2: a real client on a real pty -------------------------------- | 145 | # ---- leg 2: a real client on a real pty -------------------------------- |
| 148 | # The full path — engine, delta, wire, replica, paint — because `mux` runs | 146 | # The full path — engine, delta, wire, replica, paint — because `mux` runs |
| 149 | # an engine of its own and can regress the same way muxd did. | 147 | # an engine of its own and can regress the same way the daemon did. |
| 150 | cat > "$TMPD/script" <<EOF | 148 | cat > "$TMPD/script" <<EOF |
| 151 | settle 500 8000 | 149 | settle 500 8000 |
| 152 | send for k in \$(seq $REPS); do A=\$(date +%s%N); yes | head -n $CLIENT_LINES; B=\$(date +%s%N); echo \$(( (B-A)/1000000 )) >> $TMPD/client; done; echo DON""E\r | 150 | send for k in \$(seq $REPS); do A=\$(date +%s%N); yes | head -n $CLIENT_LINES; B=\$(date +%s%N); echo \$(( (B-A)/1000000 )) >> $TMPD/client; done; echo DON""E\r |
| @@ -197,7 +195,7 @@ echo "client: $CLIENT_LINES lines, attached: ${CLIENT_MS}ms (target ${CLIENT_WA | |||
| 197 | # per-session count rather than the daemon-wide one. | 195 | # per-session count rather than the daemon-wide one. |
| 198 | i=0 | 196 | i=0 |
| 199 | while [ "$i" -lt 50 ]; do | 197 | while [ "$i" -lt 50 ]; do |
| 200 | if [ "$("$MUXD" stats --sock "$SOCK" | sed -n 's/.*clients=\([0-9]*\).*/\1/p')" = "0" ]; then | 198 | if [ "$("$MUX" d stats --sock "$SOCK" | sed -n 's/.*clients=\([0-9]*\).*/\1/p')" = "0" ]; then |
| 201 | break | 199 | break |
| 202 | fi | 200 | fi |
| 203 | sleep 0.1 | 201 | sleep 0.1 |
| @@ -206,7 +204,7 @@ done | |||
| 206 | [ "$i" -lt 50 ] || { | 204 | [ "$i" -lt 50 ] || { |
| 207 | echo "throughput FAIL: leg 2's client is still attached, so leg 3 would not" | 205 | echo "throughput FAIL: leg 2's client is still attached, so leg 3 would not" |
| 208 | echo " be measuring a detached session at all" | 206 | echo " be measuring a detached session at all" |
| 209 | "$MUXD" stats --sock "$SOCK" | 207 | "$MUX" d stats --sock "$SOCK" |
| 210 | exit 1 | 208 | exit 1 |
| 211 | } | 209 | } |
| 212 | # | 210 | # |
| @@ -233,7 +231,7 @@ done | |||
| 233 | REPAINT_BYTES=$(wc -c < "$TMPD/big") | 231 | REPAINT_BYTES=$(wc -c < "$TMPD/big") |
| 234 | 232 | ||
| 235 | rm -f "$TMPD/repaint" "$TMPD/repaint.done" | 233 | rm -f "$TMPD/repaint" "$TMPD/repaint.done" |
| 236 | "$MUXA" send --sock "$SOCK" \ | 234 | "$MUX" a send --sock "$SOCK" \ |
| 237 | "for k in \$(seq $REPS); do A=\$(date +%s%N); cat $TMPD/big; B=\$(date +%s%N); echo \$(( (B-A)/1000000 )) >> $TMPD/repaint; done; echo ok > $TMPD/repaint.done\n" \ | 235 | "for k in \$(seq $REPS); do A=\$(date +%s%N); cat $TMPD/big; B=\$(date +%s%N); echo \$(( (B-A)/1000000 )) >> $TMPD/repaint; done; echo ok > $TMPD/repaint.done\n" \ |
| 238 | > /dev/null | 236 | > /dev/null |
| 239 | i=0 | 237 | i=0 |
| @@ -256,9 +254,9 @@ REPAINT_MS="$(echo "$REPAINT_ALL" | sort -n | head -1)" | |||
| 256 | } | 254 | } |
| 257 | # The screen must actually be full of the frame, or a shell that never ran | 255 | # The screen must actually be full of the frame, or a shell that never ran |
| 258 | # the cat would score a perfect time for doing nothing. | 256 | # the cat would score a perfect time for doing nothing. |
| 259 | "$MUXA" capture --sock "$SOCK" | grep -qF 'xxxxxxxx' || { | 257 | "$MUX" a capture --sock "$SOCK" | grep -qF 'xxxxxxxx' || { |
| 260 | echo "throughput FAIL: the repaint left no output on the grid" | 258 | echo "throughput FAIL: the repaint left no output on the grid" |
| 261 | "$MUXA" capture --sock "$SOCK" | 259 | "$MUX" a capture --sock "$SOCK" |
| 262 | exit 1 | 260 | exit 1 |
| 263 | } | 261 | } |
| 264 | 262 | ||
test/wan.sh
| Old | New | ||
|---|---|---|---|
| @@ -1,8 +1,8 @@ | |||
| 1 | #!/usr/bin/env bash | 1 | #!/usr/bin/env bash |
| 2 | # M6 WAN deployment + measurement harness. | 2 | # M6 WAN deployment + measurement harness. |
| 3 | # | 3 | # |
| 4 | # Deploys a static musl muxd to a remote box, runs a session over it via | 4 | # Deploys a static musl `mux` to a remote box, runs a session over it via |
| 5 | # `mux --via "<ssh> muxd proxy"`, and measures the numbers the M6 kill | 5 | # `mux --via "<ssh> mux d proxy"`, and measures the numbers the M6 kill |
| 6 | # criterion is decided on: | 6 | # criterion is decided on: |
| 7 | # | 7 | # |
| 8 | # baseline raw byte round-trip through `<ssh> cat` — the floor mux is | 8 | # baseline raw byte round-trip through `<ssh> cat` — the floor mux is |
| @@ -28,7 +28,7 @@ | |||
| 28 | # | 28 | # |
| 29 | # NARROWED, deliberately and on the record: the plan's criterion had a | 29 | # NARROWED, deliberately and on the record: the plan's criterion had a |
| 30 | # fourth element — "replica converged after the last tear (client render | 30 | # fourth element — "replica converged after the last tear (client render |
| 31 | # matches `muxd dump`)" — and this harness does NOT check that. The client | 31 | # matches `mux d dump`)" — and this harness does NOT check that. The client |
| 32 | # emits a stream of paints, not a grid, so comparing it to a dump means | 32 | # emits a stream of paints, not a grid, so comparing it to a dump means |
| 33 | # parsing VT in the harness: a second terminal emulator, in Python, whose | 33 | # parsing VT in the harness: a second terminal emulator, in Python, whose |
| 34 | # own bugs would then be indistinguishable from the ones it is meant to | 34 | # own bugs would then be indistinguishable from the ones it is meant to |
| @@ -53,7 +53,7 @@ | |||
| 53 | # Ctrl-C inside a session used not to work at all when the daemon had been | 53 | # Ctrl-C inside a session used not to work at all when the daemon had been |
| 54 | # backgrounded by a non-interactive shell — which is how this script, e2e.sh | 54 | # backgrounded by a non-interactive shell — which is how this script, e2e.sh |
| 55 | # and any deploy script start it. POSIX has such a shell set SIGINT/SIGQUIT | 55 | # and any deploy script start it. POSIX has such a shell set SIGINT/SIGQUIT |
| 56 | # to SIG_IGN for an async child; SIG_IGN survives exec, so muxd passed it to | 56 | # to SIG_IGN for an async child; SIG_IGN survives exec, so the daemon passed it to |
| 57 | # the pty child, and a shell keeps signals ignored-on-entry ignored for the | 57 | # the pty child, and a shell keeps signals ignored-on-entry ignored for the |
| 58 | # jobs it spawns. This run is what found it. FIXED in src/server/pty.zig, which now | 58 | # jobs it spawns. This run is what found it. FIXED in src/server/pty.zig, which now |
| 59 | # resets those dispositions in the forkpty child — see the comment there. | 59 | # resets those dispositions in the forkpty child — see the comment there. |
| @@ -110,9 +110,9 @@ REPS_BURST="${MUX_WAN_REPS_BURST:-10}" | |||
| 110 | # Unique remote names: the box may be shared, and a crashed run must never | 110 | # Unique remote names: the box may be shared, and a crashed run must never |
| 111 | # leave a socket another run mistakes for its own. | 111 | # leave a socket another run mistakes for its own. |
| 112 | TAG="wan-$$-$(date +%s)" | 112 | TAG="wan-$$-$(date +%s)" |
| 113 | RBIN="/tmp/muxd-$TAG" | 113 | RBIN="/tmp/mux-$TAG" |
| 114 | RSOCK="/tmp/mux-$TAG.sock" | 114 | RSOCK="/tmp/mux-$TAG.sock" |
| 115 | RLOG="/tmp/muxd-$TAG.log" | 115 | RLOG="/tmp/mux-$TAG.log" |
| 116 | # QUIC needs a key on both ends and a port of its own. The port is derived | 116 | # QUIC needs a key on both ends and a port of its own. The port is derived |
| 117 | # from the pid so two runs on one box cannot collide, and stays well clear | 117 | # from the pid so two runs on one box cannot collide, and stays well clear |
| 118 | # of the ephemeral range. | 118 | # of the ephemeral range. |
| @@ -128,7 +128,7 @@ FW_LEFT=0 | |||
| 128 | WORK="$(mktemp -d "${TMPDIR:-/tmp}/mux-wan-XXXXXX")" | 128 | WORK="$(mktemp -d "${TMPDIR:-/tmp}/mux-wan-XXXXXX")" |
| 129 | # Every attach records a wall tile in $XDG_STATE_HOME/mux/wall; without a | 129 | # Every attach records a wall tile in $XDG_STATE_HOME/mux/wall; without a |
| 130 | # hermetic home this suite's throwaway sockets pile up in the developer's | 130 | # hermetic home this suite's throwaway sockets pile up in the developer's |
| 131 | # real wall file and muxweb tries to open them all. | 131 | # real wall file and the hub tries to open them all. |
| 132 | XDG_STATE_HOME="$WORK/state"; XDG_CACHE_HOME="$WORK/cache" | 132 | XDG_STATE_HOME="$WORK/state"; XDG_CACHE_HOME="$WORK/cache" |
| 133 | export XDG_STATE_HOME XDG_CACHE_HOME | 133 | export XDG_STATE_HOME XDG_CACHE_HOME |
| 134 | PY="$WORK/wan.py" | 134 | PY="$WORK/wan.py" |
| @@ -211,7 +211,7 @@ cleanup() { | |||
| 211 | if [ -n "$left" ]; then | 211 | if [ -n "$left" ]; then |
| 212 | echo " WARNING: remote processes survived: $left" | 212 | echo " WARNING: remote processes survived: $left" |
| 213 | else | 213 | else |
| 214 | echo " remote clean (no muxd-$TAG processes, files removed)" | 214 | echo " remote clean (no mux-$TAG processes, files removed)" |
| 215 | fi | 215 | fi |
| 216 | rm -rf "$WORK" | 216 | rm -rf "$WORK" |
| 217 | # A box left degraded is a failure of this script whatever the | 217 | # A box left degraded is a failure of this script whatever the |
| @@ -237,7 +237,7 @@ measure() { | |||
| 237 | # The daemon's own view, which is where a handshake failure that | 237 | # The daemon's own view, which is where a handshake failure that |
| 238 | # looks identical from outside (full session, wrong key, no | 238 | # looks identical from outside (full session, wrong key, no |
| 239 | # listener) actually distinguishes itself. | 239 | # listener) actually distinguishes itself. |
| 240 | echo "-- remote stats:"; timeout 30 $MUX_WAN_SSH "$RBIN stats --sock $RSOCK" 2>&1 | tail -2 || true | 240 | echo "-- remote stats:"; timeout 30 $MUX_WAN_SSH "$RBIN d stats --sock $RSOCK" 2>&1 | tail -2 || true |
| 241 | echo "-- remote daemon log:"; timeout 30 $MUX_WAN_SSH "tail -20 $RLOG" 2>&1 || true | 241 | echo "-- remote daemon log:"; timeout 30 $MUX_WAN_SSH "tail -20 $RLOG" 2>&1 || true |
| 242 | exit 1 | 242 | exit 1 |
| 243 | fi | 243 | fi |
| @@ -590,7 +590,7 @@ def transport_pid(client_pid): | |||
| 590 | 590 | ||
| 591 | def remote_dump(ssh_cmd, rbin, rsock): | 591 | def remote_dump(ssh_cmd, rbin, rsock): |
| 592 | """The daemon's authoritative grid, as it sees it.""" | 592 | """The daemon's authoritative grid, as it sees it.""" |
| 593 | return subprocess.run(shlex.split(ssh_cmd) + ["%s dump --sock %s" % (rbin, rsock)], | 593 | return subprocess.run(shlex.split(ssh_cmd) + ["%s d dump --sock %s" % (rbin, rsock)], |
| 594 | capture_output=True, text=True, timeout=60).stdout | 594 | capture_output=True, text=True, timeout=60).stdout |
| 595 | 595 | ||
| 596 | 596 | ||
| @@ -601,7 +601,7 @@ def remote_snapshots(ssh_cmd, rbin, rsock): | |||
| 601 | exactly like one served by delta, so the counter is the only witness to | 601 | exactly like one served by delta, so the counter is the only witness to |
| 602 | which of the two actually happened. | 602 | which of the two actually happened. |
| 603 | """ | 603 | """ |
| 604 | out = subprocess.run(shlex.split(ssh_cmd) + ["%s stats --sock %s" % (rbin, rsock)], | 604 | out = subprocess.run(shlex.split(ssh_cmd) + ["%s d stats --sock %s" % (rbin, rsock)], |
| 605 | capture_output=True, text=True, timeout=60).stdout | 605 | capture_output=True, text=True, timeout=60).stdout |
| 606 | m = re.search(r"\bsnapshots=(\d+)", out) | 606 | m = re.search(r"\bsnapshots=(\d+)", out) |
| 607 | if m is None: | 607 | if m is None: |
| @@ -619,7 +619,7 @@ def cmd_reconnect(argv): | |||
| 619 | into the gap would measure the policy rather than the reconnect. | 619 | into the gap would measure the policy rather than the reconnect. |
| 620 | 620 | ||
| 621 | On the convergence check: the plan's criterion asked for the client's | 621 | On the convergence check: the plan's criterion asked for the client's |
| 622 | render to match `muxd dump` after the last tear. That is NOT what this | 622 | render to match `mux d dump` after the last tear. That is NOT what this |
| 623 | measures — see the header note. What it does measure is that the FIRST | 623 | measures — see the header note. What it does measure is that the FIRST |
| 624 | tear's marker is still in the daemon's grid after the LAST one, which | 624 | tear's marker is still in the daemon's grid after the LAST one, which |
| 625 | is the accumulated-state half of that claim and costs one ssh call. | 625 | is the accumulated-state half of that claim and costs one ssh call. |
| @@ -639,7 +639,7 @@ def cmd_reconnect(argv): | |||
| 639 | # 24-row viewport (two rows each: the echoed command and its | 639 | # 24-row viewport (two rows each: the echoed command and its |
| 640 | # output). Without this the survival check below would be a coin | 640 | # output). Without this the survival check below would be a coin |
| 641 | # flip on whether the first marker had scrolled out of the grid — | 641 | # flip on whether the first marker had scrolled out of the grid — |
| 642 | # `muxd dump` shows the viewport, not the scrollback. | 642 | # `mux d dump` shows the viewport, not the scrollback. |
| 643 | c.send(b"\x15clear\n") | 643 | c.send(b"\x15clear\n") |
| 644 | wait_idle(c, "reconnect") | 644 | wait_idle(c, "reconnect") |
| 645 | # Taken after the session is up and quiet, so the first attach's own | 645 | # Taken after the session is up and quiet, so the first attach's own |
| @@ -699,7 +699,7 @@ def remote_clients(ssh_cmd, rbin, rsock): | |||
| 699 | never attaches occupies a slot until its idle timeout, which was | 699 | never attaches occupies a slot until its idle timeout, which was |
| 700 | previously unobservable from outside the process. | 700 | previously unobservable from outside the process. |
| 701 | """ | 701 | """ |
| 702 | out = subprocess.run(shlex.split(ssh_cmd) + ["%s stats --sock %s" % (rbin, rsock)], | 702 | out = subprocess.run(shlex.split(ssh_cmd) + ["%s d stats --sock %s" % (rbin, rsock)], |
| 703 | capture_output=True, text=True, timeout=60).stdout | 703 | capture_output=True, text=True, timeout=60).stdout |
| 704 | m = re.search(r"\bclients=(\d+)", out) | 704 | m = re.search(r"\bclients=(\d+)", out) |
| 705 | return int(m.group(1)) if m else -1 | 705 | return int(m.group(1)) if m else -1 |
| @@ -1103,15 +1103,15 @@ PYEOF | |||
| 1103 | say "warming the ssh control master" | 1103 | say "warming the ssh control master" |
| 1104 | $MUX_WAN_SSH true || { echo "wan FAIL: cannot reach the box with MUX_WAN_SSH" >&2; exit 1; } | 1104 | $MUX_WAN_SSH true || { echo "wan FAIL: cannot reach the box with MUX_WAN_SSH" >&2; exit 1; } |
| 1105 | 1105 | ||
| 1106 | say "deploying a static musl muxd to $MUX_WAN_HOST:$RBIN" | 1106 | say "deploying a static musl mux to $MUX_WAN_HOST:$RBIN" |
| 1107 | (cd "$ROOT" && "$ZIG" build -Dtarget=x86_64-linux-musl) | 1107 | (cd "$ROOT" && "$ZIG" build -Dtarget=x86_64-linux-musl) |
| 1108 | $MUX_WAN_SCP "$ROOT/zig-out/bin/muxd" "$MUX_WAN_HOST:$RBIN" | 1108 | $MUX_WAN_SCP "$ROOT/zig-out/bin/mux" "$MUX_WAN_HOST:$RBIN" |
| 1109 | $MUX_WAN_SSH "chmod +x $RBIN && file $RBIN 2>/dev/null | head -1" || true | 1109 | $MUX_WAN_SSH "chmod +x $RBIN && file $RBIN 2>/dev/null | head -1" || true |
| 1110 | # The local mux must be native again before anything measures it. | 1110 | # The local mux must be native again before anything measures it. |
| 1111 | make -C "$ROOT" build >/dev/null | 1111 | make -C "$ROOT" build >/dev/null |
| 1112 | [ -x "$MUX" ] || { echo "wan FAIL: no native mux at $MUX" >&2; exit 1; } | 1112 | [ -x "$MUX" ] || { echo "wan FAIL: no native mux at $MUX" >&2; exit 1; } |
| 1113 | 1113 | ||
| 1114 | VIA="$MUX_WAN_SSH $RBIN proxy --sock $RSOCK" | 1114 | VIA="$MUX_WAN_SSH $RBIN d proxy --sock $RSOCK" |
| 1115 | 1115 | ||
| 1116 | say "baseline: raw byte round-trip through \`ssh cat\` ($REPS_BASE reps)" | 1116 | say "baseline: raw byte round-trip through \`ssh cat\` ($REPS_BASE reps)" |
| 1117 | measure clean baseline "$MUX_WAN_SSH" "$REPS_BASE" | 1117 | measure clean baseline "$MUX_WAN_SSH" "$REPS_BASE" |
| @@ -1125,7 +1125,7 @@ if [ "${MUX_WAN_QUIC:-}" = "1" ]; then | |||
| 1125 | head -c 32 /dev/urandom > "$LKEY" | 1125 | head -c 32 /dev/urandom > "$LKEY" |
| 1126 | chmod 600 "$LKEY" | 1126 | chmod 600 "$LKEY" |
| 1127 | $MUX_WAN_SCP "$LKEY" "$MUX_WAN_HOST:$RKEY" | 1127 | $MUX_WAN_SCP "$LKEY" "$MUX_WAN_HOST:$RKEY" |
| 1128 | # muxd refuses a group- or world-readable key, exactly as ssh does, so | 1128 | # The daemon refuses a group- or world-readable key, exactly as ssh does, so |
| 1129 | # this is required rather than tidy. | 1129 | # this is required rather than tidy. |
| 1130 | $MUX_WAN_SSH "chmod 600 $RKEY" | 1130 | $MUX_WAN_SSH "chmod 600 $RKEY" |
| 1131 | QUIC_ARGS="--quic 0.0.0.0:$QPORT --key $RKEY --quic-idle-ms 15000" | 1131 | QUIC_ARGS="--quic 0.0.0.0:$QPORT --key $RKEY --quic-idle-ms 15000" |
| @@ -1133,7 +1133,7 @@ if [ "${MUX_WAN_QUIC:-}" = "1" ]; then | |||
| 1133 | fi | 1133 | fi |
| 1134 | 1134 | ||
| 1135 | say "starting the remote daemon" | 1135 | say "starting the remote daemon" |
| 1136 | $MUX_WAN_SSH "rm -f $RSOCK; nohup $RBIN run --sock $RSOCK --shell /bin/bash \ | 1136 | $MUX_WAN_SSH "rm -f $RSOCK; nohup $RBIN d run --sock $RSOCK --shell /bin/bash \ |
| 1137 | $QUIC_ARGS >$RLOG 2>&1 </dev/null & sleep 0.2" | 1137 | $QUIC_ARGS >$RLOG 2>&1 </dev/null & sleep 0.2" |
| 1138 | i=0 | 1138 | i=0 |
| 1139 | until $MUX_WAN_SSH "test -S $RSOCK" 2>/dev/null; do | 1139 | until $MUX_WAN_SSH "test -S $RSOCK" 2>/dev/null; do |
| @@ -1185,7 +1185,7 @@ if [ "${MUX_WAN_QUIC:-}" = "1" ]; then | |||
| 1185 | *) echo "wan FAIL: udp $QPORT is not held by our daemon (pid $RPID)" >&2 | 1185 | *) echo "wan FAIL: udp $QPORT is not held by our daemon (pid $RPID)" >&2 |
| 1186 | $MUX_WAN_SSH "cat $RLOG" || true; exit 1 ;; | 1186 | $MUX_WAN_SSH "cat $RLOG" || true; exit 1 ;; |
| 1187 | esac | 1187 | esac |
| 1188 | echo " remote stats before QUIC: $($MUX_WAN_SSH "$RBIN stats --sock $RSOCK")" | 1188 | echo " remote stats before QUIC: $($MUX_WAN_SSH "$RBIN d stats --sock $RSOCK")" |
| 1189 | 1189 | ||
| 1190 | say "QUIC attach latency (launch -> first painted byte, $REPS_ATTACH reps)" | 1190 | say "QUIC attach latency (launch -> first painted byte, $REPS_ATTACH reps)" |
| 1191 | measure quic attach "$MUX" "$QSPEC" "$REPS_ATTACH" "$ERRLOG" | 1191 | measure quic attach "$MUX" "$QSPEC" "$REPS_ATTACH" "$ERRLOG" |
| @@ -1339,9 +1339,9 @@ for S in a b; do | |||
| 1339 | done | 1339 | done |
| 1340 | 1340 | ||
| 1341 | if [ "$M18_OK" -eq 1 ]; then | 1341 | if [ "$M18_OK" -eq 1 ]; then |
| 1342 | A_GRID="$(timeout 60 $MUX_WAN_SSH "$RBIN dump --sock $RSOCK --session a" 2>/dev/null)" | 1342 | A_GRID="$(timeout 60 $MUX_WAN_SSH "$RBIN d dump --sock $RSOCK --session a" 2>/dev/null)" |
| 1343 | B_GRID="$(timeout 60 $MUX_WAN_SSH "$RBIN dump --sock $RSOCK --session b" 2>/dev/null)" | 1343 | B_GRID="$(timeout 60 $MUX_WAN_SSH "$RBIN d dump --sock $RSOCK --session b" 2>/dev/null)" |
| 1344 | D_GRID="$(timeout 60 $MUX_WAN_SSH "$RBIN dump --sock $RSOCK" 2>/dev/null)" | 1344 | D_GRID="$(timeout 60 $MUX_WAN_SSH "$RBIN d dump --sock $RSOCK" 2>/dev/null)" |
| 1345 | 1345 | ||
| 1346 | # Positive AND negative per session: "a holds a's marker" is satisfied | 1346 | # Positive AND negative per session: "a holds a's marker" is satisfied |
| 1347 | # just as well by one shared shell, so only the negative separates them. | 1347 | # just as well by one shared shell, so only the negative separates them. |
| @@ -1372,7 +1372,7 @@ if [ "$M18_OK" -eq 1 ]; then | |||
| 1372 | if [ "$rc" -ne 0 ]; then | 1372 | if [ "$rc" -ne 0 ]; then |
| 1373 | echo " FAIL: re-attach to session a exited $rc"; M18_OK=0 | 1373 | echo " FAIL: re-attach to session a exited $rc"; M18_OK=0 |
| 1374 | else | 1374 | else |
| 1375 | A2="$(timeout 60 $MUX_WAN_SSH "$RBIN dump --sock $RSOCK --session a" 2>/dev/null)" | 1375 | A2="$(timeout 60 $MUX_WAN_SSH "$RBIN d dump --sock $RSOCK --session a" 2>/dev/null)" |
| 1376 | case "$A2" in *"wanre-$M18_N"*) ;; *) | 1376 | case "$A2" in *"wanre-$M18_N"*) ;; *) |
| 1377 | echo " FAIL: the re-attach's own marker never reached session a"; M18_OK=0 ;; esac | 1377 | echo " FAIL: the re-attach's own marker never reached session a"; M18_OK=0 ;; esac |
| 1378 | case "$A2" in *"wansess-a-$M18_N"*) ;; *) | 1378 | case "$A2" in *"wansess-a-$M18_N"*) ;; *) |
| @@ -1380,7 +1380,7 @@ if [ "$M18_OK" -eq 1 ]; then | |||
| 1380 | M18_OK=0 ;; esac | 1380 | M18_OK=0 ;; esac |
| 1381 | # ...and it joined rather than created: a fourth session here would | 1381 | # ...and it joined rather than created: a fourth session here would |
| 1382 | # mean the name resolved per-connection. | 1382 | # mean the name resolved per-connection. |
| 1383 | M18_STATS="$(timeout 60 $MUX_WAN_SSH "$RBIN stats --sock $RSOCK" 2>/dev/null)" | 1383 | M18_STATS="$(timeout 60 $MUX_WAN_SSH "$RBIN d stats --sock $RSOCK" 2>/dev/null)" |
| 1384 | case "$M18_STATS" in *"sessions=3"*) ;; *) | 1384 | case "$M18_STATS" in *"sessions=3"*) ;; *) |
| 1385 | echo " FAIL: re-attach by name changed the session count: $M18_STATS" | 1385 | echo " FAIL: re-attach by name changed the session count: $M18_STATS" |
| 1386 | M18_OK=0 ;; esac | 1386 | M18_OK=0 ;; esac |
| @@ -1647,7 +1647,7 @@ if [ -n "$(val predict predict med)" ]; then | |||
| 1647 | echo " the burst's own typing span) against the overlay's 1000ms" | 1647 | echo " the burst's own typing span) against the overlay's 1000ms" |
| 1648 | echo " expiry bound — the margin a slower path spends first" | 1648 | echo " expiry bound — the margin a slower path spends first" |
| 1649 | # Leg 2, in this harness's substituted form. Comparing the client's | 1649 | # Leg 2, in this harness's substituted form. Comparing the client's |
| 1650 | # paint stream to `muxd dump` byte for byte needs a second terminal | 1650 | # paint stream to `mux d dump` byte for byte needs a second terminal |
| 1651 | # emulator in the harness — the same narrowing recorded for M7 at | 1651 | # emulator in the harness — the same narrowing recorded for M7 at |
| 1652 | # the top of this file — so convergence is asserted through the | 1652 | # the top of this file — so convergence is asserted through the |
| 1653 | # daemon's grid plus attribution: no prediction may outlive the run | 1653 | # daemon's grid plus attribution: no prediction may outlive the run |
test/wsclient.zig
| Old | New | ||
|---|---|---|---|
| @@ -2,7 +2,7 @@ | |||
| 2 | //! (M-web Task 9). Speaks the hub's wire (RFC 6455 client side, masked; | 2 | //! (M-web Task 9). Speaks the hub's wire (RFC 6455 client side, masked; |
| 3 | //! one envelope byte per message) and maintains a REAL replica from the | 3 | //! one envelope byte per message) and maintains a REAL replica from the |
| 4 | //! frames it receives — the same replica.zig the CLI and the wasm core | 4 | //! frames it receives — the same replica.zig the CLI and the wasm core |
| 5 | //! run — so its `dumpexit` grid diffs honestly against `muxd dump`. | 5 | //! run — so its `dumpexit` grid diffs honestly against `mux d dump`. |
| 6 | //! Driven by a line-oriented script on stdin, ptyclient's conventions: | 6 | //! Driven by a line-oriented script on stdin, ptyclient's conventions: |
| 7 | //! decodeEscapes, expect deadlines in ms, exit codes 2/3/4. | 7 | //! decodeEscapes, expect deadlines in ms, exit codes 2/3/4. |
| 8 | //! | 8 | //! |
| @@ -20,7 +20,7 @@ | |||
| 20 | //! reattach C R MS [NAME] mux.js's ENV_CONTROL handler for MS ms: attach | 20 | //! reattach C R MS [NAME] mux.js's ENV_CONTROL handler for MS ms: attach |
| 21 | //! once now and once per `up` that follows | 21 | //! once now and once per `up` that follows |
| 22 | //! settle QUIET MS drain frames until QUIET ms of silence | 22 | //! settle QUIET MS drain frames until QUIET ms of silence |
| 23 | //! dumpexit write the replica grid (muxd dump format) to | 23 | //! dumpexit write the replica grid (mux d dump format) to |
| 24 | //! --out and exit 0 | 24 | //! --out and exit 0 |
| 25 | //! | 25 | //! |
| 26 | //! Usage: wsclient --port N --tile IDX --out FILE --err FILE | 26 | //! Usage: wsclient --port N --tile IDX --out FILE --err FILE |
| @@ -576,7 +576,7 @@ pub fn main() !void { | |||
| 576 | const out = std.fs.cwd().createFile(op, .{}) catch fatal(EXIT_USAGE, "cannot open --out", .{}); | 576 | const out = std.fs.cwd().createFile(op, .{}) catch fatal(EXIT_USAGE, "cannot open --out", .{}); |
| 577 | defer out.close(); | 577 | defer out.close(); |
| 578 | out.writeAll(dump) catch fatal(EXIT_USAGE, "write --out failed", .{}); | 578 | out.writeAll(dump) catch fatal(EXIT_USAGE, "write --out failed", .{}); |
| 579 | // `muxd dump` ends its output with a newline; match it so the | 579 | // `mux d dump` ends its output with a newline; match it so the |
| 580 | // e2e diff compares grids, not file conventions. | 580 | // e2e diff compares grids, not file conventions. |
| 581 | if (dump.len == 0 or dump[dump.len - 1] != '\n') | 581 | if (dump.len == 0 or dump[dump.len - 1] != '\n') |
| 582 | out.writeAll("\n") catch fatal(EXIT_USAGE, "write --out failed", .{}); | 582 | out.writeAll("\n") catch fatal(EXIT_USAGE, "write --out failed", .{}); |
| @@ -741,7 +741,7 @@ test "the resync re-attach quotes the TILE's size, never the grid it learned" { | |||
| 741 | } | 741 | } |
| 742 | 742 | ||
| 743 | test "the dump this exits with is the daemon's own dump format" { | 743 | test "the dump this exits with is the daemon's own dump format" { |
| 744 | // dumpexit writes Engine.dumpPlain — the SAME function muxd dump | 744 | // dumpexit writes Engine.dumpPlain — the SAME function mux d dump |
| 745 | // prints through — so the e2e diff cannot fail on formatting. The | 745 | // prints through — so the e2e diff cannot fail on formatting. The |
| 746 | // pin: feed both a daemon-side engine and this fixture's replica the | 746 | // pin: feed both a daemon-side engine and this fixture's replica the |
| 747 | // same snapshot; byte-identical dumps. | 747 | // same snapshot; byte-identical dumps. |
test/xversion.sh
| Old | New | ||
|---|---|---|---|
| @@ -15,7 +15,7 @@ | |||
| 15 | # | 15 | # |
| 16 | # CONTAINERS, NOT VMs, AND THE LIMIT THAT COMES WITH IT: a container shares | 16 | # CONTAINERS, NOT VMs, AND THE LIMIT THAT COMES WITH IT: a container shares |
| 17 | # the host kernel, so nothing here validates pty, poll or fd semantics | 17 | # the host kernel, so nothing here validates pty, poll or fd semantics |
| 18 | # against a DIFFERENT kernel — and those are most of what muxd is. What it | 18 | # against a DIFFERENT kernel — and those are most of what the daemon is. What it |
| 19 | # does buy is a stock userland (the static musl binary has to actually run | 19 | # does buy is a stock userland (the static musl binary has to actually run |
| 20 | # somewhere that is not this box), a clean two-versions-at-once story, and | 20 | # somewhere that is not this box), a clean two-versions-at-once story, and |
| 21 | # a rig that does not depend on a remote machine being powered on. Kernel | 21 | # a rig that does not depend on a remote machine being powered on. Kernel |
| @@ -45,10 +45,14 @@ skip() { SKIPS=$((SKIPS + 1)); echo "xver SKIP: $1"; } | |||
| 45 | # nothing and exited 0 is the failure mode this whole file exists against. | 45 | # nothing and exited 0 is the failure mode this whole file exists against. |
| 46 | command -v "$ENGINE" >/dev/null 2>&1 || | 46 | command -v "$ENGINE" >/dev/null 2>&1 || |
| 47 | { echo "xver FAIL: no $ENGINE — this gate cannot run without a container engine"; exit 1; } | 47 | { echo "xver FAIL: no $ENGINE — this gate cannot run without a container engine"; exit 1; } |
| 48 | for _d in "$OLD_BIN" "$NEW_BIN"; do | 48 | # The old side is still four binaries and the new side is one, so each |
| 49 | for _b in muxd mux; do | 49 | # prefix is checked for what THAT side ships. A `muxd` demanded of the new |
| 50 | [ -x "$_d/$_b" ] || { | 50 | # prefix would refuse a tree that is doing exactly what this gate exists to |
| 51 | echo "xver FAIL: no $_b in $_d" | 51 | # prove is safe. |
| 52 | for _d in "$OLD_BIN:muxd mux" "$NEW_BIN:mux"; do | ||
| 53 | for _b in ${_d#*:}; do | ||
| 54 | [ -x "${_d%%:*}/$_b" ] || { | ||
| 55 | echo "xver FAIL: no $_b in ${_d%%:*}" | ||
| 52 | echo " build both sides static (they run in a container AND on this host):" | 56 | echo " build both sides static (they run in a container AND on this host):" |
| 53 | echo " zig build -Dtarget=x86_64-linux-musl -p $ROOT/.xversion/new" | 57 | echo " zig build -Dtarget=x86_64-linux-musl -p $ROOT/.xversion/new" |
| 54 | echo " git -C <a checkout of the older ref> ... -p $ROOT/.xversion/old" | 58 | echo " git -C <a checkout of the older ref> ... -p $ROOT/.xversion/old" |
| @@ -74,7 +78,7 @@ done | |||
| 74 | # channel only to a terminal it has taken over — see the legs' own header. | 78 | # channel only to a terminal it has taken over — see the legs' own header. |
| 75 | [ -x "$NEW_BIN/ptyclient" ] || { | 79 | [ -x "$NEW_BIN/ptyclient" ] || { |
| 76 | echo "xver FAIL: no ptyclient in $NEW_BIN — legs 7 and 8 need the pty fixture" | 80 | echo "xver FAIL: no ptyclient in $NEW_BIN — legs 7 and 8 need the pty fixture" |
| 77 | echo " it is built by this tree's build.zig into the same prefix as mux/muxd" | 81 | echo " it is built by this tree's build.zig into the same prefix as mux" |
| 78 | exit 1 | 82 | exit 1 |
| 79 | } | 83 | } |
| 80 | 84 | ||
| @@ -121,14 +125,18 @@ trap cleanup EXIT INT TERM | |||
| 121 | # --network host rather than a published port: a rootless bridge NATs the | 125 | # --network host rather than a published port: a rootless bridge NATs the |
| 122 | # client's source address, and QUIC validates the path it is answering on. | 126 | # client's source address, and QUIC validates the path it is answering on. |
| 123 | # Testing the transport through an address translation this product never | 127 | # Testing the transport through an address translation this product never |
| 124 | # runs behind would be testing pasta, not muxd. The container still has its | 128 | # runs behind would be testing pasta, not this program. The container still has its |
| 125 | # own filesystem and process table, which is what this gate is actually | 129 | # own filesystem and process table, which is what this gate is actually |
| 126 | # asking about. | 130 | # asking about. |
| 131 | # $5 is how THAT side spells its daemon — `muxd run` on the old prefix, | ||
| 132 | # `mux d run` on the new one. The old binary cannot answer `mux d`, and | ||
| 133 | # asking it to would test this gate's own wording instead of the wire. | ||
| 127 | start_daemon() { | 134 | start_daemon() { |
| 135 | # shellcheck disable=SC2086 # $5 is a command spelling of one or two words | ||
| 128 | "$ENGINE" run -d --name "$1" --network host \ | 136 | "$ENGINE" run -d --name "$1" --network host \ |
| 129 | -v "$2:/mux:ro" -v "$3:/run/mux" \ | 137 | -v "$2:/mux:ro" -v "$3:/run/mux" \ |
| 130 | "$IMAGE" \ | 138 | "$IMAGE" \ |
| 131 | /mux/muxd run --sock /run/mux/muxd.sock --quic "127.0.0.1:$4" \ | 139 | $5 --sock /run/mux/muxd.sock --quic "127.0.0.1:$4" \ |
| 132 | --key /run/mux/key --quic-idle-ms 15000 --shell /bin/sh \ | 140 | --key /run/mux/key --quic-idle-ms 15000 --shell /bin/sh \ |
| 133 | >/dev/null 2>&1 | 141 | >/dev/null 2>&1 |
| 134 | } | 142 | } |
| @@ -153,9 +161,9 @@ echo "xver: old=$OLD_LABEL" | |||
| 153 | echo "xver: new=$NEW_LABEL" | 161 | echo "xver: new=$NEW_LABEL" |
| 154 | echo "xver: image=$IMAGE engine=$ENGINE ports=$PORT_OLD/$PORT_NEW" | 162 | echo "xver: image=$IMAGE engine=$ENGINE ports=$PORT_OLD/$PORT_NEW" |
| 155 | 163 | ||
| 156 | start_daemon "$CN_OLD" "$OLD_BIN" "$RUN_OLD" "$PORT_OLD" || | 164 | start_daemon "$CN_OLD" "$OLD_BIN" "$RUN_OLD" "$PORT_OLD" "/mux/muxd run" || |
| 157 | { echo "xver FAIL: could not start the old daemon's container"; exit 1; } | 165 | { echo "xver FAIL: could not start the old daemon's container"; exit 1; } |
| 158 | start_daemon "$CN_NEW" "$NEW_BIN" "$RUN_NEW" "$PORT_NEW" || | 166 | start_daemon "$CN_NEW" "$NEW_BIN" "$RUN_NEW" "$PORT_NEW" "/mux/mux d run" || |
| 159 | { echo "xver FAIL: could not start the new daemon's container"; exit 1; } | 167 | { echo "xver FAIL: could not start the new daemon's container"; exit 1; } |
| 160 | 168 | ||
| 161 | SOCK_OLD="$RUN_OLD/muxd.sock" | 169 | SOCK_OLD="$RUN_OLD/muxd.sock" |
| @@ -221,10 +229,10 @@ fi | |||
| 221 | # transport's call overwrote the first's evidence. That was correct only | 229 | # transport's call overwrote the first's evidence. That was correct only |
| 222 | # because of the order the two legs happen to run in — one reorder and a | 230 | # because of the order the two legs happen to run in — one reorder and a |
| 223 | # leg asserts against the other leg's stats. | 231 | # leg asserts against the other leg's stats. |
| 224 | assert_name_not_honoured() { # LABEL MUXD SOCK RC ERRFILE | 232 | assert_name_not_honoured() { # LABEL DAEMON SOCK RC ERRFILE |
| 225 | _lbl="$1"; _muxd="$2"; _sock="$3"; _rc="$4"; _err="$5" | 233 | _lbl="$1"; _daemon="$2"; _sock="$3"; _rc="$4"; _err="$5" |
| 226 | _stats="$TMP/stats-$_lbl" | 234 | _stats="$TMP/stats-$_lbl" |
| 227 | "$_muxd" stats --sock "$_sock" > "$_stats" 2>&1 | 235 | "$_daemon" stats --sock "$_sock" > "$_stats" 2>&1 |
| 228 | if grep -q 'session zz' "$_stats"; then | 236 | if grep -q 'session zz' "$_stats"; then |
| 229 | fail "$_lbl: the daemon created session zz — this is not a pre-M18 daemon [$(cat "$_stats")]" | 237 | fail "$_lbl: the daemon created session zz — this is not a pre-M18 daemon [$(cat "$_stats")]" |
| 230 | return 1 | 238 | return 1 |
| @@ -270,8 +278,8 @@ RC=$? | |||
| 270 | if [ "$RC" -ne 0 ]; then | 278 | if [ "$RC" -ne 0 ]; then |
| 271 | fail "old client -> new daemon exited $RC [$(tr -d '\n' < "$TMP/err")]" | 279 | fail "old client -> new daemon exited $RC [$(tr -d '\n' < "$TMP/err")]" |
| 272 | else | 280 | else |
| 273 | "$NEW_BIN/muxd" stats --sock "$SOCK_NEW" > "$TMP/s4" 2>&1 | 281 | "$NEW_BIN/mux" d stats --sock "$SOCK_NEW" > "$TMP/s4" 2>&1 |
| 274 | if ! "$NEW_BIN/muxd" dump --sock "$SOCK_NEW" | grep -q 'xv4-pin'; then | 282 | if ! "$NEW_BIN/mux" d dump --sock "$SOCK_NEW" | grep -q 'xv4-pin'; then |
| 275 | fail "new daemon's default grid is missing the old client's output" | 283 | fail "new daemon's default grid is missing the old client's output" |
| 276 | elif ! grep -q 'sessions=1' "$TMP/s4"; then | 284 | elif ! grep -q 'sessions=1' "$TMP/s4"; then |
| 277 | fail "an old client's bare attach did not land in the default session [$(cat "$TMP/s4")]" | 285 | fail "an old client's bare attach did not land in the default session [$(cat "$TMP/s4")]" |
| @@ -310,7 +318,7 @@ fi | |||
| 310 | # is pinned here: a stats reformat that drops it would quietly retire the | 318 | # is pinned here: a stats reformat that drops it would quietly retire the |
| 311 | # mitigation banked in roadmap.md before it was ever built. | 319 | # mitigation banked in roadmap.md before it was ever built. |
| 312 | "$OLD_BIN/muxd" stats --sock "$SOCK_OLD" > "$TMP/s6o" 2>&1 | 320 | "$OLD_BIN/muxd" stats --sock "$SOCK_OLD" > "$TMP/s6o" 2>&1 |
| 313 | "$NEW_BIN/muxd" stats --sock "$SOCK_NEW" > "$TMP/s6n" 2>&1 | 321 | "$NEW_BIN/mux" d stats --sock "$SOCK_NEW" > "$TMP/s6n" 2>&1 |
| 314 | if grep -q 'sessions=' "$TMP/s6o"; then | 322 | if grep -q 'sessions=' "$TMP/s6o"; then |
| 315 | fail "the old daemon's stats already say 'sessions=' — it is not a version signal [$(cat "$TMP/s6o")]" | 323 | fail "the old daemon's stats already say 'sessions=' — it is not a version signal [$(cat "$TMP/s6o")]" |
| 316 | elif ! grep -q 'sessions=' "$TMP/s6n"; then | 324 | elif ! grep -q 'sessions=' "$TMP/s6n"; then |
| @@ -570,7 +578,7 @@ xver_side_channel quic "quic://127.0.0.1:$PORT_NEW" --key "$KEY" --quic-idle-ms | |||
| 570 | # `side_channel_session`. | 578 | # `side_channel_session`. |
| 571 | # | 579 | # |
| 572 | # The session must SURVIVE, and the witness is the shell's own work rather | 580 | # The session must SURVIVE, and the witness is the shell's own work rather |
| 573 | # than a session count: the old ref predates M18, so its `muxd stats` has no | 581 | # than a session count: the old ref predates M18, so its `mux d stats` has no |
| 574 | # `sessions=` field at all (leg 6 pins exactly that absence). A marker that | 582 | # `sessions=` field at all (leg 6 pins exactly that absence). A marker that |
| 575 | # the shell EXPANDED and that reaches the old daemon's grid AFTER the x is | 583 | # the shell EXPANDED and that reaches the old daemon's grid AFTER the x is |
| 576 | # the stronger fact anyway — it says the pty is still there and still | 584 | # the stronger fact anyway — it says the pty is still there and still |
| @@ -632,7 +640,7 @@ else | |||
| 632 | fi | 640 | fi |
| 633 | 641 | ||
| 634 | # ---- teardown, by the sanctioned verb, then OBSERVED ------------------ | 642 | # ---- teardown, by the sanctioned verb, then OBSERVED ------------------ |
| 635 | "$NEW_BIN/muxd" stop --sock "$SOCK_NEW" >/dev/null 2>&1 | 643 | "$NEW_BIN/mux" d stop --sock "$SOCK_NEW" >/dev/null 2>&1 |
| 636 | "$OLD_BIN/muxd" stop --sock "$SOCK_OLD" >/dev/null 2>&1 | 644 | "$OLD_BIN/muxd" stop --sock "$SOCK_OLD" >/dev/null 2>&1 |
| 637 | _i=0 | 645 | _i=0 |
| 638 | while [ "$_i" -lt 40 ]; do | 646 | while [ "$_i" -lt 40 ]; do |
| @@ -641,13 +649,13 @@ while [ "$_i" -lt 40 ]; do | |||
| 641 | sleep 0.05; _i=$((_i + 1)) | 649 | sleep 0.05; _i=$((_i + 1)) |
| 642 | done | 650 | done |
| 643 | # Reported as observed, never as the stop command's claim. A container | 651 | # Reported as observed, never as the stop command's claim. A container |
| 644 | # still up here means `muxd stop` did not end the process it was aimed at, | 652 | # still up here means `mux d stop` did not end the process it was aimed at, |
| 645 | # which is a finding and not a cleanup detail. | 653 | # which is a finding and not a cleanup detail. |
| 646 | _live=$("$ENGINE" ps --filter "name=$CN_OLD" --filter "name=$CN_NEW" -q 2>/dev/null | wc -l) | 654 | _live=$("$ENGINE" ps --filter "name=$CN_OLD" --filter "name=$CN_NEW" -q 2>/dev/null | wc -l) |
| 647 | if [ "$_live" -eq 0 ]; then | 655 | if [ "$_live" -eq 0 ]; then |
| 648 | pass "both daemons ended on muxd stop (observed: no container still running)" | 656 | pass "both daemons ended on mux d stop (observed: no container still running)" |
| 649 | else | 657 | else |
| 650 | fail "$_live container(s) still running after muxd stop" | 658 | fail "$_live container(s) still running after mux d stop" |
| 651 | fi | 659 | fi |
| 652 | 660 | ||
| 653 | # The count, pinned against a literal for e2e.sh's reason: a leg that | 661 | # The count, pinned against a literal for e2e.sh's reason: a leg that |