a73x

d4e465a0

test: the upgrade, hosts, web, push and soak pins read the OS through the oracle

a73x   2026-09-03 15:10

Commit message
test: the upgrade, hosts, web, push and soak pins read the OS through the oracle

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SakwJEwD9dXBoRP5kWbemW

test/e2e_06_web.sh
Old New
@@ -535,10 +535,12 @@ timeout 20 "$MUX" a send 'exit\n' --sock "$SOCK25" --session 1 > /dev/null 2>&1
535 wait_until 80 "host wall: the ended session's pane never wore gone; the wall holds:" \ 535 wait_until 80 "host wall: the ended session's pane never wore gone; the wall holds:" \
536 'curl -s "$DWORIG/tiles" | tr "{" "\n" | grep -q "\"session\":\"1\",\"state\":\"gone\""' \ 536 'curl -s "$DWORIG/tiles" | tr "{" "\n" | grep -q "\"session\":\"1\",\"state\":\"gone\""' \
537 'curl -s "$DWORIG/tiles"' 537 'curl -s "$DWORIG/tiles"'
538 # Asked of the OS, not of the daemon. 538 # Asked of the OS, not of the daemon — through the oracle helpers, so the
539 [ ! -e "/proc/$DWSHELL" ] || { 539 # spelling of "is this pid alive" belongs to e2e_lib.sh and this line asks
540 echo "e2e FAIL: host wall: the shell pid $DWSHELL outlived its session per /proc" 540 # the question rather than naming /proc.
541 cat "/proc/$DWSHELL/cmdline" 2>/dev/null; exit 1; } 541 ! pid_alive "$DWSHELL" || {
542 echo "e2e FAIL: host wall: the shell pid $DWSHELL outlived its session per the OS"
543 pid_args "$DWSHELL"; echo; exit 1; }
542 # The pane STAYS, on its own id and in its own place: five leaves, and the 544 # The pane STAYS, on its own id and in its own place: five leaves, and the
543 # ended one still among them. A hub that vanished a graded pane would take 545 # ended one still among them. A hub that vanished a graded pane would take
544 # a rect the user authored on the strength of one poll answer. 546 # a rect the user authored on the strength of one poll answer.
test/e2e_09_hosts.sh
Old New
@@ -273,7 +273,7 @@ wait_sessions "$SOCKH2" 2 "hosts: daemon 2 should hold 0 and c"
273 # a narrow bar drops. 273 # a narrow bar drops.
274 seed_layout "$HSTATE" stacked \ 274 seed_layout "$HSTATE" stacked \
275 "--sock $SOCKH1#0" "--sock $SOCKH1#b" "--sock $SOCKH2#0" 275 "--sock $SOCKH1#0" "--sock $SOCKH1#b" "--sock $SOCKH2#0"
276 HFHASH=$(sha256sum "$HSTATE/mux/hosts" | cut -d' ' -f1) 276 HFHASH=$(sha256_of "$HSTATE/mux/hosts")
277 set +e 277 set +e
278 XDG_STATE_HOME="$HSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 44 \ 278 XDG_STATE_HOME="$HSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 44 \
279 --out "$OUT.hwcap" --err "$OUT.hwcap.err" -- "$MUX" > "$OUT.hwpc" 2>&1 <<'EOF' 279 --out "$OUT.hwcap" --err "$OUT.hwcap.err" -- "$MUX" > "$OUT.hwpc" 2>&1 <<'EOF'
@@ -348,7 +348,7 @@ for _hw in "$HW1" "$HW2" "$HW3"; do
348 done 348 done
349 # The hosts file did not move: a wall is opened FROM these two files and 349 # The hosts file did not move: a wall is opened FROM these two files and
350 # writes back only to the layout. 350 # writes back only to the layout.
351 [ "$HFHASH" = "$(sha256sum "$HSTATE/mux/hosts" | cut -d' ' -f1)" ] || { 351 [ "$HFHASH" = "$(sha256_of "$HSTATE/mux/hosts")" ] || {
352 echo "e2e FAIL: hosts: the wall edited the hosts file:" 352 echo "e2e FAIL: hosts: the wall edited the hosts file:"
353 cat "$HSTATE/mux/hosts"; exit 1; } 353 cat "$HSTATE/mux/hosts"; exit 1; }
354 # ...and the layout still holds the three leaves it was opened on. This run 354 # ...and the layout still holds the three leaves it was opened on. This run
@@ -1290,7 +1290,8 @@ ok "two walls on the same daemons are two layouts; neither learns of the other's
1290 # the client does with the answer. 1290 # the client does with the answer.
1291 # 1291 #
1292 # The oracle is the OS, never the daemon: the socket INODE on the far side, 1292 # The oracle is the OS, never the daemon: the socket INODE on the far side,
1293 # and a walk of /proc for a `mux d start` born under that box's runtime dir. A 1293 # and a walk of every pid on the box for a `mux d start` whose argv names
1294 # that box's socket, which is to say born under its runtime dir. A
1294 # daemon asked whether it exists cannot answer no. The shim log is the 1295 # daemon asked whether it exists cannot answer no. The shim log is the
1295 # other half — it records the remote command, so "a read started nothing" 1296 # other half — it records the remote command, so "a read started nothing"
1296 # is checked against the word that ran and not only against the outcome. 1297 # is checked against the word that ran and not only against the outcome.
@@ -1334,24 +1335,29 @@ chmod +x "$NSHIM/ssh"
1334 NPATH="$NSHIM:$(cd "$(dirname "$MUX")" && pwd):$PATH" 1335 NPATH="$NSHIM:$(cd "$(dirname "$MUX")" && pwd):$PATH"
1335 printf 'nostart-a@127.0.0.1\nnostart-b@127.0.0.1\n' > "$NSTATE/mux/hosts" 1336 printf 'nostart-a@127.0.0.1\nnostart-b@127.0.0.1\n' > "$NSTATE/mux/hosts"
1336 1337
1337 # The /proc walk. `mux d start` and not any `mux` — the transient `mux d 1338 # The process walk: every pid on the box, and its ARGV asked through the
1338 # endpoint` processes share the runtime dir and are not daemons — and 1339 # oracle. `mux d start` and not any `mux` — the transient `mux d endpoint`
1339 # `environ` rather than the argv's `--sock`: the path the ensure forwards is 1340 # processes share the runtime dir and are not daemons — and the box is
1340 # whatever this variable resolved to, so the variable is the box's identity 1341 # identified by the socket path in that argv rather than by
1341 # and the argv is derived from it. The shim gives each fake host a runtime 1342 # `XDG_RUNTIME_DIR` in the environment, because a process's environment
1342 # dir of its own, which is the whole of what makes them two machines. The 1343 # block is not readable on every OS while its argv is. Nothing is lost:
1343 # daemon spells `d start` because that is the argv spawn.zig 1344 # every starter spells `mux d start --sock PATH` and PATH is the runtime
1344 # execs it with, which is also what makes `ps` legible. 1345 # dir this variable holds, resolved by the ensure that forwarded it. The
1346 # shim gives each fake host a runtime dir of its own, which is the whole of
1347 # what makes them two machines. The daemon spells `d start` because that is
1348 # the argv spawn.zig execs it with, which is also what makes `ps` legible.
1349 #
1350 # Matched with `case` rather than `grep`, so the path is a literal: a tmp
1351 # directory whose name held a regex character would otherwise match a box
1352 # it does not name.
1345 nostart_daemons() { 1353 nostart_daemons() {
1346 _nd=0 1354 _nd=0
1347 for _ne in /proc/[0-9]*/environ; do 1355 for _np in $(ps -Ao pid= | tr -d ' '); do
1348 _np=${_ne#/proc/} 1356 # A pid that exits mid-walk, or one this uid may not read, answers
1349 _np=${_np%/environ} 1357 # nothing — which is not a daemon under $1 either way.
1350 # The redirect itself is what fails on another user's process (and 1358 _na=$(pid_args "$_np" 2>/dev/null) || continue
1351 # on one that exits mid-walk), so the silencing has to wrap the 1359 case "$_na" in *"mux d start"*) ;; *) continue ;; esac
1352 # whole command and not just `tr`. 1360 case "$_na" in *"$1"/*) ;; *) continue ;; esac
1353 { tr '\0' '\n' < "$_ne"; } 2>/dev/null | grep -qxF "XDG_RUNTIME_DIR=$1" || continue
1354 { tr '\0' ' ' < "/proc/$_np/cmdline"; } 2>/dev/null | grep -q 'mux d start' || continue
1355 _nd=$((_nd + 1)) 1361 _nd=$((_nd + 1))
1356 done 1362 done
1357 echo "$_nd" 1363 echo "$_nd"
test/e2e_14_upgrade.sh
Old New
@@ -33,8 +33,12 @@ defer_rm "$UPKEY"
33 UPHOME="${TMPDIR:-/tmp}/mux-e2e-uphome-$$" 33 UPHOME="${TMPDIR:-/tmp}/mux-e2e-uphome-$$"
34 defer_rm "$UPHOME" 34 defer_rm "$UPHOME"
35 # The candidate binary the refusal leg offers: a copy, so its exec bit can 35 # The candidate binary the refusal leg offers: a copy, so its exec bit can
36 # be taken away without touching the one every other scenario runs. 36 # be taken away without touching the one every other scenario runs. Fully
37 UPBIN="${TMPDIR:-/tmp}/mux-e2e-upcand-$$" 37 # resolved, because the exec witness below compares it against pid_exe,
38 # which answers with the resolved image — a $TMPDIR reached through a
39 # symlink would never equal a logical path and the wait would time out on a
40 # copy that had in fact exec'd.
41 UPBIN=$(readlink -f "${TMPDIR:-/tmp}/mux-e2e-upcand-$$")
38 defer_rm "$UPBIN" 42 defer_rm "$UPBIN"
39 UPAGENT="${TMPDIR:-/tmp}/mux-e2e-upagent-$$.sock" 43 UPAGENT="${TMPDIR:-/tmp}/mux-e2e-upagent-$$.sock"
40 defer_rm "$UPAGENT" 44 defer_rm "$UPAGENT"
@@ -156,10 +160,10 @@ grep -qF "refused: version: $UPVER is not newer than $UPVER" "$OUT.upref1" || {
156 # OWN path (/proc/self/exe), so the only way to spell this refusal is to 160 # 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 161 # 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 162 # made deterministic rather than hoped for. The daemon is held under
159 # SIGSTOP so it cannot read the request until the chmod has landed; 163 # SIGSTOP so it cannot read the request until the chmod has landed; the
160 # `readlink /proc/PID/exe` is the witness that the copy has already 164 # image the OS says that pid is running is the witness that the copy has
161 # exec'd, so the chmod cannot beat it and turn the leg into an exec 165 # already exec'd, so the chmod cannot beat it and turn the leg into an
162 # failure that asserts nothing. 166 # exec failure that asserts nothing.
163 cp "$MUX_ELF" "$UPBIN" 167 cp "$MUX_ELF" "$UPBIN"
164 chmod 755 "$UPBIN" 168 chmod 755 "$UPBIN"
165 kill -STOP "$D69PID" 169 kill -STOP "$D69PID"
@@ -167,7 +171,7 @@ kill -STOP "$D69PID"
167 UPXPID=$! 171 UPXPID=$!
168 defer_kill "$UPXPID" 172 defer_kill "$UPXPID"
169 _i=0 173 _i=0
170 while [ "$(readlink "/proc/$UPXPID/exe" 2>/dev/null)" != "$UPBIN" ]; do 174 while [ "$(pid_exe "$UPXPID")" != "$UPBIN" ]; do
171 _i=$((_i + 1)) 175 _i=$((_i + 1))
172 [ "$_i" -lt $(( 100 * TIME_SCALE )) ] || { 176 [ "$_i" -lt $(( 100 * TIME_SCALE )) ] || {
173 kill -CONT "$D69PID" 2>/dev/null || true 177 kill -CONT "$D69PID" 2>/dev/null || true
@@ -211,12 +215,13 @@ grep -qF "mux d: upgraded to $UPVER" "$OUT.upok" || {
211 cat "$OUT.upok"; exit 1; } 215 cat "$OUT.upok"; exit 1; }
212 216
213 # The pid is the feature. Not "a daemon is answering" — the same process, 217 # The pid is the feature. Not "a daemon is answering" — the same process,
214 # now running a different image, which /proc/PID/cmdline says in one word: 218 # now running a different image, which the OS's account of its argv says in
215 # a daemon that had restarted would carry the original `run --sock` argv. 219 # one word: a daemon that had restarted would carry the original
220 # `run --sock` argv.
216 kill -0 "$D69PID" 2>/dev/null || { 221 kill -0 "$D69PID" 2>/dev/null || {
217 echo "e2e FAIL: upgrade: the daemon pid $D69PID is gone — that is a restart, not an upgrade" 222 echo "e2e FAIL: upgrade: the daemon pid $D69PID is gone — that is a restart, not an upgrade"
218 cat "$OUT.up.d"; exit 1; } 223 cat "$OUT.up.d"; exit 1; }
219 tr '\0' ' ' < "/proc/$(real_pid "$D69PID")/cmdline" > "$OUT.upst2" 2>/dev/null || true 224 pid_args "$(real_pid "$D69PID")" > "$OUT.upst2" 2>/dev/null || true
220 grep -qF -- "--resume-fd" "$OUT.upst2" || { 225 grep -qF -- "--resume-fd" "$OUT.upst2" || {
221 echo "e2e FAIL: upgrade: pid $D69PID is not running the resumed argv; it holds:" 226 echo "e2e FAIL: upgrade: pid $D69PID is not running the resumed argv; it holds:"
222 cat "$OUT.upst2"; exit 1; } 227 cat "$OUT.upst2"; exit 1; }
@@ -477,7 +482,7 @@ pipe_detach "agent-upgrade client"
477 482
478 # A shell born of the UPGRADED daemon: a fresh named session, so its shell 483 # A shell born of the UPGRADED daemon: a fresh named session, so its shell
479 # was forked by the exec'd image. Its fd table is the witness — asked of 484 # was forked by the exec'd image. Its fd table is the witness — asked of
480 # /proc, because a daemon cannot see its own leak. Before the fix every 485 # the OS, because a daemon cannot see its own leak. Before the fix every
481 # post-exec shell held the daemon's listener and agent sockets. 486 # post-exec shell held the daemon's listener and agent sockets.
482 pipe_mux "$OUT.uagn" "$OUT.uagn.err" timeout 60 "$MUX" --sock "$SOCK71" --session post 487 pipe_mux "$OUT.uagn" "$OUT.uagn.err" timeout 60 "$MUX" --sock "$SOCK71" --session post
483 pipe_send 'echo newsh=$$\n' 488 pipe_send 'echo newsh=$$\n'
@@ -485,24 +490,32 @@ wait_grid "$SOCK71" "newsh=[0-9]" "agent-upgrade: the post-exec session never pr
485 UPNEWSH=$(dump_session "$SOCK71" post | sed -n 's/.*newsh=\([0-9][0-9]*\).*/\1/p' | head -1) 490 UPNEWSH=$(dump_session "$SOCK71" post | sed -n 's/.*newsh=\([0-9][0-9]*\).*/\1/p' | head -1)
486 [ -n "$UPNEWSH" ] || { 491 [ -n "$UPNEWSH" ] || {
487 echo "e2e FAIL: agent-upgrade: no post-exec shell pid read off the grid"; dump_session "$SOCK71" post; exit 1; } 492 echo "e2e FAIL: agent-upgrade: no post-exec shell pid read off the grid"; dump_session "$SOCK71" post; exit 1; }
488 # `/dev/ptmx` is a class of its own: a shell's OWN terminal is the SLAVE 493 # The three fd KINDS a daemon holds and a session shell must not: a
494 # listening socket, the upgrade manifest's carrier, and a pty master. The
495 # master is a class of its own — a shell's OWN terminal is the SLAVE
489 # (`/dev/pts/N`, dup2'd onto 0/1/2), so a master in a shell's fd table can 496 # (`/dev/pts/N`, dup2'd onto 0/1/2), so a master in a shell's fd table can
490 # only be an adopted session's, inherited. `|| true` because grep -c prints 497 # only be an adopted session's, inherited. Asked through the oracle by KIND
491 # 0 AND exits 1 when nothing matches, and this suite runs under set -e. 498 # rather than by the strings Linux happens to print, so a second OS answers
492 UPLEAK=$(ls -l "/proc/$UPNEWSH/fd" 2>/dev/null | grep -c -E 'socket:|memfd:|/dev/ptmx' || true) 499 # the same three questions with whatever names it uses.
500 UPLEAK=$(( $(pid_holds_fd_kind "$UPNEWSH" socket) \
501 + $(pid_holds_fd_kind "$UPNEWSH" carrier) \
502 + $(pid_holds_fd_kind "$UPNEWSH" ptymaster) ))
493 [ "$UPLEAK" -eq 0 ] || { 503 [ "$UPLEAK" -eq 0 ] || {
494 echo "e2e FAIL: agent-upgrade: the post-exec shell holds $UPLEAK daemon fd(s) — listeners, pty" 504 echo "e2e FAIL: agent-upgrade: the post-exec shell holds $UPLEAK daemon fd(s) — listeners, pty"
495 echo " masters or the manifest memfd crossed the exec without FD_CLOEXEC and were inherited:" 505 echo " masters or the manifest carrier crossed the exec without FD_CLOEXEC and were inherited:"
496 ls -l "/proc/$UPNEWSH/fd"; exit 1; } 506 pid_fd_targets "$UPNEWSH"; exit 1; }
497 pipe_detach "post-exec session client" 507 pipe_detach "post-exec session client"
498 508
499 # The other half of the same claim, asked of the daemon's own fd table: the 509 # The other half of the same claim, asked of the daemon's own fd table: the
500 # manifest memfd is adoption scaffolding, so once adoption is final nothing 510 # manifest carrier is adoption scaffolding, so once adoption is final
501 # should still hold it open — a survivor accumulates one per upgrade. 511 # nothing should still hold it open — a survivor accumulates one per
502 UPDMEM=$(ls -l "/proc/$(real_pid "$D71PID")/fd" 2>/dev/null | grep -c 'memfd:mux-upgrade' || true) 512 # upgrade. `carrier` is the only KIND asked for, because the manifest is
513 # the only carrier this daemon ever opens.
514 UPDPID=$(real_pid "$D71PID")
515 UPDMEM=$(pid_holds_fd_kind "$UPDPID" carrier)
503 [ "$UPDMEM" -eq 0 ] || { 516 [ "$UPDMEM" -eq 0 ] || {
504 echo "e2e FAIL: agent-upgrade: the daemon still holds $UPDMEM manifest memfd(s) after the exec" 517 echo "e2e FAIL: agent-upgrade: the daemon still holds $UPDMEM manifest carrier(s) after the exec"
505 ls -l "/proc/$(real_pid "$D71PID")/fd"; exit 1; } 518 pid_fd_targets "$UPDPID"; exit 1; }
506 519
507 assert_stopped "$SOCK71" "$D71PID" "agent-upgrade" "$OUT.uagstop" 520 assert_stopped "$SOCK71" "$D71PID" "agent-upgrade" "$OUT.uagstop"
508 D71PID="" 521 D71PID=""
test/e2e_16_push.sh
Old New
@@ -23,6 +23,12 @@ PUSHINST="$PUSHROOT/.local/bin/mux"
23 23
24 mkdir -p "$PUSHROOT/.local/bin" "$PUSHROOT/run" "$PUSHROOT/state" \ 24 mkdir -p "$PUSHROOT/.local/bin" "$PUSHROOT/run" "$PUSHROOT/state" \
25 "$PUSHBARE" "$PUSHIM_DIR/fakebin" 25 "$PUSHBARE" "$PUSHIM_DIR/fakebin"
26 # The path the daemon's image is compared against, resolved ONCE and the
27 # same way the oracle resolves the link: pid_exe is `readlink -f`, so a
28 # $TMPDIR reached through a symlink would fail a correct push if only one
29 # side were canonical. After the mkdir, because `readlink -f` needs every
30 # component but the last to exist.
31 PUSHWANT=$(readlink -f "$PUSHINST")
26 32
27 # The shim IS ssh as far as the pusher can tell (e2e_04 explains that 33 # The shim IS ssh as far as the pusher can tell (e2e_04 explains that
28 # contract), and here it also IS the remote box: it swaps in the fake box's 34 # contract), and here it also IS the remote box: it swaps in the fake box's
@@ -150,15 +156,17 @@ grep -q "upgraded to" "$OUT.push_live" || {
150 cat "$OUT.push_live" 156 cat "$OUT.push_live"
151 exit 1 157 exit 1
152 } 158 }
153 # The OS's account, not the daemon's: same pid, and its exe link names the 159 # The OS's account, not the daemon's: same pid, and the image the OS says
154 # installed path with no ' (deleted)' tail — the exec adopted the NEW inode 160 # it is running is the installed path with no ' (deleted)' tail — the exec
155 # at that path, rather than the old one riding out a rename. 161 # adopted the NEW inode at that path, rather than the old one riding out a
162 # rename. Both sides are fully resolved (see $PUSHWANT above).
156 kill -0 "$PUSHDPID" 2>/dev/null || { 163 kill -0 "$PUSHDPID" 2>/dev/null || {
157 echo "e2e FAIL: the daemon pid died across the push upgrade" 164 echo "e2e FAIL: the daemon pid died across the push upgrade"
158 exit 1 165 exit 1
159 } 166 }
160 [ "$(readlink "/proc/$PUSHDPID/exe")" = "$PUSHINST" ] || { 167 PUSHEXE=$(pid_exe "$PUSHDPID")
161 echo "e2e FAIL: /proc/$PUSHDPID/exe is $(readlink "/proc/$PUSHDPID/exe"), not $PUSHINST" 168 [ "$PUSHEXE" = "$PUSHWANT" ] || {
169 echo "e2e FAIL: the pushed daemon's image is $PUSHEXE, not $PUSHWANT"
162 exit 1 170 exit 1
163 } 171 }
164 cmp -s "$PUSHINST" "$MUX_ELF" || { 172 cmp -s "$PUSHINST" "$MUX_ELF" || {
test/soak.sh
Old New
@@ -11,6 +11,12 @@ set -u
11 # other makes every run abort on an unbound variable. 11 # other makes every run abort on an unbound variable.
12 MUX="$1"; RAWMODE="$2"; DELAYPIPE="$3"; RENDER="$4"; PTYCLIENT="$5"; WSCLIENT="$6" 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 # pid_rss_kb and pid_fd_count, from the file that owns every OS-specific
15 # spelling in this suite. Sourced rather than copied: os_oracle.sh brings
16 # no trap and no cleanup registry, so it composes with the ones this script
17 # already has, and a second OS's arm is written once for both readers.
18 # shellcheck source=test/os_oracle.sh
19 . "$(dirname "$0")/os_oracle.sh"
14 N="${SOAK_N:-10}" 20 N="${SOAK_N:-10}"
15 TMP="${TMPDIR:-/tmp}" 21 TMP="${TMPDIR:-/tmp}"
16 FAILDIR="$TMP/mux-soak-$$-failures" 22 FAILDIR="$TMP/mux-soak-$$-failures"
@@ -100,14 +106,14 @@ else
100 ATTACH_FAILED=1 106 ATTACH_FAILED=1
101 break 107 break
102 fi 108 fi
103 RSS=$(awk '/VmRSS/{print $2}' "/proc/$PDPID/status" 2>/dev/null || echo 0) 109 RSS=$(pid_rss_kb "$PDPID")
104 # The daemon pid can be an unreaped zombie by now: /proc/$PDPID/status 110 # The daemon pid can be an unreaped zombie by now: the OS still has
105 # still exists but has no VmRSS line, awk exits 0 with empty output, 111 # the pid but reports no resident size for it, so pid_rss_kb prints
106 # and the `|| echo 0` fallback never fires (awk didn't fail). Guard 112 # an empty string rather than failing. Guard it directly so the
107 # the empty string directly so the died-mid-phase check below isn't 113 # died-mid-phase check below isn't skipped by `[ "" -eq 0 ]`
108 # skipped by `[ "" -eq 0 ]` erroring-as-false. 114 # erroring-as-false.
109 RSS=${RSS:-0} 115 RSS=${RSS:-0}
110 FD=$(ls "/proc/$PDPID/fd" 2>/dev/null | wc -l) 116 FD=$(pid_fd_count "$PDPID")
111 [ "$c" -eq "$WARMUP" ] && { BASE_RSS=$RSS; BASE_FD=$FD; } 117 [ "$c" -eq "$WARMUP" ] && { BASE_RSS=$RSS; BASE_FD=$FD; }
112 c=$((c + 1)) 118 c=$((c + 1))
113 done 119 done
@@ -128,7 +134,7 @@ else
128 # settle beat kills the flake without weakening the equality 134 # settle beat kills the flake without weakening the equality
129 # check itself — a real leak won't self-heal in a second. 135 # check itself — a real leak won't self-heal in a second.
130 sleep 1 136 sleep 1
131 FD=$(ls "/proc/$PDPID/fd" 2>/dev/null | wc -l) 137 FD=$(pid_fd_count "$PDPID")
132 fi 138 fi
133 if [ "$FD" -ne "$BASE_FD" ]; then 139 if [ "$FD" -ne "$BASE_FD" ]; then
134 echo "soak FAIL: persistence fd count $BASE_FD -> $FD across detached cycles" 140 echo "soak FAIL: persistence fd count $BASE_FD -> $FD across detached cycles"
test/vm.sh
Old New
@@ -143,6 +143,9 @@ RPID=$(vssh 'mux d dump' | sed -n 's/.*vmj2=\([0-9][0-9]*\).*/\1/p' | head -1)
143 echo "vm FAIL: entry dial: the marker never reached the REMOTE daemon's grid; it holds:" 143 echo "vm FAIL: entry dial: the marker never reached the REMOTE daemon's grid; it holds:"
144 vssh 'mux d dump' | sed 's/^/ /' 144 vssh 'mux d dump' | sed 's/^/ /'
145 exit 1; } 145 exit 1; }
146 # Read on the VM, and the VM is Linux — /proc is the right spelling THERE
147 # whatever this script is run from, so this one stays out of the e2e_lib.sh
148 # oracle (whose helpers answer about the local box).
146 vssh "test -d /proc/$RPID" || { 149 vssh "test -d /proc/$RPID" || {
147 echo "vm FAIL: entry dial: the grid names shell pid $RPID but the VM has no such process" 150 echo "vm FAIL: entry dial: the grid names shell pid $RPID but the VM has no such process"
148 exit 1; } 151 exit 1; }
@@ -154,6 +157,8 @@ NPROCS=$(vssh 'pgrep -cx mux')
154 echo "vm FAIL: entry dial: after detach the box runs $NPROCS mux processes, want 1 (the daemon):" 157 echo "vm FAIL: entry dial: after detach the box runs $NPROCS mux processes, want 1 (the daemon):"
155 vssh 'ps -o pid,args -C mux' | sed 's/^/ /' 158 vssh 'ps -o pid,args -C mux' | sed 's/^/ /'
156 exit 1; } 159 exit 1; }
160 # Also read on the VM, and for the same reason: the box under test is a
161 # Linux VM, so its own /proc is the oracle here.
157 EXE=$(vssh "readlink /proc/$DPID/exe") 162 EXE=$(vssh "readlink /proc/$DPID/exe")
158 [ "$EXE" = "/home/ubuntu/.local/bin/mux" ] || { 163 [ "$EXE" = "/home/ubuntu/.local/bin/mux" ] || {
159 echo "vm FAIL: entry dial: daemon $DPID execs '$EXE', not the installed image" 164 echo "vm FAIL: entry dial: daemon $DPID execs '$EXE', not the installed image"
test/wan.sh
Old New
@@ -582,6 +582,10 @@ def transport_pid(client_pid):
582 string, so a pattern kill takes out the very client under test. (That 582 string, so a pattern kill takes out the very client under test. (That
583 mistake has already been made once in this project's e2e work; it is a 583 mistake has already been made once in this project's e2e work; it is a
584 rule now, not a preference.) 584 rule now, not a preference.)
585
586 `ps --ppid` is a GNU procps flag, which is fine here: this rig runs on
587 a Linux dev box against Linux boxes, and it is not part of the e2e
588 suite the platform layer keeps portable.
585 """ 589 """
586 out = subprocess.run(["ps", "--ppid", str(client_pid), "-o", "pid="], 590 out = subprocess.run(["ps", "--ppid", str(client_pid), "-o", "pid="],
587 capture_output=True, text=True).stdout.split() 591 capture_output=True, text=True).stdout.split()
@@ -1171,9 +1175,11 @@ if [ "${MUX_WAN_QUIC:-}" = "1" ]; then
1171 QSPEC="quic://$QHOST:$QPORT --key $LKEY --quic-idle-ms $QIDLE" 1175 QSPEC="quic://$QHOST:$QPORT --key $LKEY --quic-idle-ms $QIDLE"
1172 1176
1173 # OUR daemon owns that port, not merely somebody's socket. Matching the 1177 # OUR daemon owns that port, not merely somebody's socket. Matching the
1174 # port alone in /proc/net/udp would pass just as happily on a stranger's 1178 # port alone in the remote box's /proc/net/udp would pass just as
1175 # listener, and then every QUIC number below would be measuring a 1179 # happily on a stranger's listener, and then every QUIC number below
1176 # handshake against something else entirely. 1180 # would be measuring a handshake against something else entirely. (The
1181 # read runs over ssh on a Linux box, so its own /proc and `ss` are the
1182 # oracle there; e2e_lib.sh's helpers answer about the local box.)
1177 say "QUIC: checking the daemon owns udp $QPORT" 1183 say "QUIC: checking the daemon owns udp $QPORT"
1178 # Matched on PID, not on name: ss truncates a process name to 15 1184 # Matched on PID, not on name: ss truncates a process name to 15
1179 # characters, and a tag this long is exactly the kind of thing that 1185 # characters, and a tag this long is exactly the kind of thing that