a73x

66e13fe3

test: one binary through the suites, and the daemon's exe asked of the kernel

a73x   2026-08-28 22:34

Commit message
test: one binary through the suites, and the daemon's exe asked of the kernel

Every script takes `$MUX` alone and spells the mode; the Makefile installs,
releases and tars one binary; xversion asks each side what THAT side ships
(`muxd run` in the old prefix's container, `mux d run` in the new one's —
the old binary cannot answer `mux d` and is not asked to).

Two assertions are new or moved:

  - e2e_03_side reads `/proc/PID/exe` of the daemon an auto-start brought
    up and demands the binary this suite was handed. That is THE
    ambient-PATH pin, and it reads the LINK rather than the argv because
    argv is what a wrong spawn still gets right.
  - e2e_lib's proxy_pid matches `d proxy` POSITIONALLY. One binary means the
    client is a `mux` too, and its argv carries the whole
    `--via '… d proxy …'` string — a pattern match takes out the client
    under test.

And one leg stopped waiting on luck: e2e_09_hosts's wall-x detached after a
settle and then judged the final grid, so whether a surviving tile had been
repainted was timing. It now waits for that repaint — expect(1) semantics,
bytes since the last match — before detaching.

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