a73x

a0524f88

test: e2e — the first mux on a machine writes the wall it just started

a73x   2026-08-28 19:53

Commit message
test: e2e — the first mux on a machine writes the wall it just started

The empty-file entry — the spec's first e2e bullet — was pinned nowhere:
e2e_09's auto-start leg pre-writes the line it checks, so a `recordHost`
that never ran on the no-file path would have left the suite green.

A leg with no hosts file at all, on a pty, with a runtime dir of its own:
bare `mux` starts the local daemon, and the file it leaves behind holds
exactly the line `--sock $XDG_RUNTIME_DIR/muxd.sock` — one line, since a
machine with no file is a wall of one. The shell on the grid is asked of
the OS: its pid's parent is the daemon `mux` started, not what the daemon
says about itself.

The suite's checkpoint pin goes 81 -> 82.

test/e2e.sh
Old New
@@ -177,8 +177,8 @@ done
177 # one of those and adds a convergence point would be pinning a fact every 177 # one of those and adds a convergence point would be pinning a fact every
178 # leg above already establishes. 178 # leg above already establishes.
179 179
180 [ "$OK_COUNT" = "81" ] || { 180 [ "$OK_COUNT" = "82" ] || {
181 echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 81 —" 181 echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 82 —"
182 echo " a scenario was added (update the pin) or silently lost" 182 echo " a scenario was added (update the pin) or silently lost"
183 exit 1 183 exit 1
184 } 184 }
test/e2e_09_hosts.sh
Old New
@@ -30,6 +30,15 @@ HARUN="${TMPDIR:-/tmp}/mux-e2e-hosts-run-$$"
30 defer_rm "$HARUN" 30 defer_rm "$HARUN"
31 HASOCK="$HARUN/muxd.sock" 31 HASOCK="$HARUN/muxd.sock"
32 defer_sock "$HASOCK" 32 defer_sock "$HASOCK"
33 # The empty-file leg: a state home with NO hosts file at all — a machine
34 # where `mux` has never run — and, like the auto-start leg, a runtime dir of
35 # its own so the daemon it starts is this leg's and not the developer's.
36 HESTATE="${TMPDIR:-/tmp}/mux-e2e-hosts-empty-$$"
37 defer_rm "$HESTATE"
38 HERUN="${TMPDIR:-/tmp}/mux-e2e-hosts-empty-run-$$"
39 defer_rm "$HERUN"
40 HESOCK="$HERUN/muxd.sock"
41 defer_sock "$HESOCK"
33 # The `x` leg builds and eats its own daemon's session, so it reads a wall 42 # The `x` leg builds and eats its own daemon's session, so it reads a wall
34 # of ONE host and gets a state home nothing else writes. 43 # of ONE host and gets a state home nothing else writes.
35 HXSTATE="${TMPDIR:-/tmp}/mux-e2e-hosts-x-state-$$" 44 HXSTATE="${TMPDIR:-/tmp}/mux-e2e-hosts-x-state-$$"
@@ -129,6 +138,62 @@ assert_stopped "$HASOCK" "$HAPID" "hosts auto-start" "$OUT.hastop"
129 HAPID="" 138 HAPID=""
130 ok "mux records the daemon on the wall, never a session, and starts a listed local one that is gone" 139 ok "mux records the daemon on the wall, never a session, and starts a listed local one that is gone"
131 140
141 # ---- the empty file: the first `mux` on a machine writes the wall --------
142 #
143 # The other end of the auto-start rule, and the only leg where the file
144 # does not exist when `mux` starts. Bare `mux` with nothing listed is the
145 # local daemon: it starts one, attaches, and the attach writes the line
146 # that makes every later `mux` a wall. Nothing here pre-writes anything —
147 # a leg that seeds the file cannot see a `recordHost` that never ran.
148 mkdir -p "$HERUN"
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; }
151 set +e
152 SHELL=/bin/sh PATH="$(dirname "$MUXD"):$PATH" XDG_RUNTIME_DIR="$HERUN" \
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'
155 expect \x1b[?1049h 20000
156 settle 600 20000
157 send echo hepid=$$\n
158 expect hepid= 20000
159 settle 600 20000
160 send \x1cd
161 waitexit 10000
162 EOF
163 RC=$?
164 set -e
165 [ "$RC" -eq 0 ] || {
166 echo "e2e FAIL: hosts: the empty-file leg exited $RC (did bare mux start a local daemon?):"
167 cat "$OUT.hempty.log"; cat "$OUT.hempty.err" 2>/dev/null; exit 1; }
168 HEPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.hempty.err" | head -1)
169 defer_kill "$HEPID"
170 [ -n "$HEPID" ] || {
171 echo "e2e FAIL: hosts: the empty-file leg printed no daemon pid to hold onto:"
172 cat "$OUT.hempty.err"; exit 1; }
173 # The file the attach wrote: the DAEMON it reached, and nothing else. One
174 # line, because a wall of one is what a machine with no file has.
175 [ -f "$HESTATE/mux/hosts" ] || {
176 echo "e2e FAIL: hosts: the first mux wrote no hosts file under $HESTATE"; exit 1; }
177 grep -qxF -- "--sock $HESOCK" "$HESTATE/mux/hosts" || {
178 echo "e2e FAIL: hosts: the empty file did not gain the local daemon's line:"
179 cat "$HESTATE/mux/hosts"; exit 1; }
180 [ "$(wc -l < "$HESTATE/mux/hosts")" = "1" ] || {
181 echo "e2e FAIL: hosts: the first mux wrote more than the daemon it attached to:"
182 cat "$HESTATE/mux/hosts"; exit 1; }
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`
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)
187 [ -n "$HESH_PID" ] || {
188 echo "e2e FAIL: hosts: the empty-file leg's grid shows no shell pid:"
189 "$MUXD" dump --sock "$HESOCK"; exit 1; }
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"
192 ps -o pid,ppid,args -p "$HESH_PID" 2>&1; exit 1; }
193 assert_stopped "$HESOCK" "$HEPID" "hosts empty file" "$OUT.hestop"
194 HEPID=""
195 ok "the first mux on a machine starts the local daemon and writes it down"
196
132 # ---- every live session of every listed daemon is a tile ---------------- 197 # ---- every live session of every listed daemon is a tile ----------------
133 # 198 #
134 # The headline. Two daemons on the wall, four sessions between them, and 199 # The headline. Two daemons on the wall, four sessions between them, and