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/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 |