3dc3515c
test: the e2e harness reads the process table and its counters the way both OSes answer
a73x 2026-09-04 10:16
Commit message
test/e2e_01_boot.sh
| Old | New | ||
|---|---|---|---|
| @@ -908,7 +908,8 @@ SCOMM=$(pid_comm "$SPID") | |||
| 908 | echo "e2e FAIL: the daemon mux d start -d brought up has comm '$SCOMM', want 'mux'" | 908 | echo "e2e FAIL: the daemon mux d start -d brought up has comm '$SCOMM', want 'mux'" |
| 909 | exit 1; } | 909 | exit 1; } |
| 910 | # Non-tty stderr: exactly two lines, no dots. | 910 | # Non-tty stderr: exactly two lines, no dots. |
| 911 | [ "$(wc -l < "$OUT.start")" = "2" ] || { | 911 | # -eq and not =, here and below: BSD wc pads its count with spaces. |
| 912 | [ "$(wc -l < "$OUT.start")" -eq 2 ] || { | ||
| 912 | echo "e2e FAIL: non-tty start not exactly two lines:"; cat "$OUT.start"; exit 1; } | 913 | echo "e2e FAIL: non-tty start not exactly two lines:"; cat "$OUT.start"; exit 1; } |
| 913 | 914 | ||
| 914 | # The daemon it started serves a session (marker in, marker in dump). | 915 | # The daemon it started serves a session (marker in, marker in dump). |
| @@ -1111,7 +1112,7 @@ grep -q "daemon did not answer" "$OUT.dead" || { | |||
| 1111 | cat "$OUT.dead"; exit 1; } | 1112 | cat "$OUT.dead"; exit 1; } |
| 1112 | # Non-tty: exactly two lines, same as the success path. The newline that | 1113 | # Non-tty: exactly two lines, same as the success path. The newline that |
| 1113 | # terminates the dot line is tty-only, so nothing blank creeps in here. | 1114 | # terminates the dot line is tty-only, so nothing blank creeps in here. |
| 1114 | [ "$(wc -l < "$OUT.dead")" = "2" ] || { | 1115 | [ "$(wc -l < "$OUT.dead")" -eq 2 ] || { |
| 1115 | echo "e2e FAIL: non-tty failure not exactly two lines:"; cat "$OUT.dead"; exit 1; } | 1116 | echo "e2e FAIL: non-tty failure not exactly two lines:"; cat "$OUT.dead"; exit 1; } |
| 1116 | rm -rf "$DEADCFG" | 1117 | rm -rf "$DEADCFG" |
| 1117 | ok "a start whose daemon dies young says so, with the log path" | 1118 | ok "a start whose daemon dies young says so, with the log path" |
test/e2e_04_handoff.sh
| Old | New | ||
|---|---|---|---|
| @@ -89,10 +89,12 @@ ssh_shim_head "$SSHIM_DIR/ssh" | |||
| 89 | cat >> "$SSHIM_DIR/ssh" <<'SHIM' | 89 | cat >> "$SSHIM_DIR/ssh" <<'SHIM' |
| 90 | echo $$ >> "${SSHIM_PIDLOG:?}" | 90 | echo $$ >> "${SSHIM_PIDLOG:?}" |
| 91 | # ps rather than the lib's pid_comm: this shim is written to disk and run | 91 | # ps rather than the lib's pid_comm: this shim is written to disk and run |
| 92 | # as its own `sh` by the client under test, so nothing the suite sourced | 92 | # as its own `sh` by the client under test, so nothing the suite sourced is |
| 93 | # is in scope here. `ps -o comm=` is the same answer the oracle gives and | 93 | # in scope here. Basenames taken by hand, for pid_comm's own reason: BSD ps |
| 94 | # is spelled the same way on every OS the suite could run on. | 94 | # prints comm as the executable's full path where Linux prints the |
| 95 | printf '%s %s\n' "$(ps -o comm= -p $$)" "$(ps -o comm= -p $PPID)" >> "${SSHIM_COMMLOG:?}" | 95 | # basename. |
| 96 | _sc=$(ps -o comm= -p $$); _sp=$(ps -o comm= -p $PPID) | ||
| 97 | printf '%s %s\n' "${_sc##*/}" "${_sp##*/}" >> "${SSHIM_COMMLOG:?}" | ||
| 96 | shift | 98 | shift |
| 97 | printf '%s\n' "$*" >> "${SSHIM_ARGLOG:?}" | 99 | printf '%s\n' "$*" >> "${SSHIM_ARGLOG:?}" |
| 98 | exec /bin/sh -c "$*" | 100 | exec /bin/sh -c "$*" |
| @@ -152,13 +154,20 @@ HASKS=$(grep -c -- 'mux d endpoint --start$' "$SSHIM_ARGLOG" || true) | |||
| 152 | [ "$HASKS" -eq 1 ] || { | 154 | [ "$HASKS" -eq 1 ] || { |
| 153 | echo "e2e FAIL: cold handoff ran the asking word $HASKS times, want exactly 1" | 155 | echo "e2e FAIL: cold handoff ran the asking word $HASKS times, want exactly 1" |
| 154 | cat "$SSHIM_ARGLOG"; exit 1; } | 156 | cat "$SSHIM_ARGLOG"; exit 1; } |
| 155 | # Asked of /proc, not of the client: that child was `ssh` itself, sitting | 157 | # Asked of the OS, not of the client: that child sat directly under `mux`, |
| 156 | # directly under `mux`. What this does NOT catch is a | 158 | # with nothing interposed. Only the PARENT word is pinned. The child's own |
| 157 | # `/bin/sh -c` line put back — bash and dash both exec a single simple | 159 | # comm is not comparable across the two OSes — Linux names a shebang |
| 158 | # command in place, so the shell is gone by the time /proc is read. That | 160 | # script's process after the SCRIPT (`ssh`) and Darwin after the |
| 159 | # regression is build.zig folder rule 5's, measured there; this leg pins the | 161 | # INTERPRETER (`sh`), so both words are correct answers and neither says |
| 160 | # shape the rule cannot see, that the argv reaches a real ssh unmangled. | 162 | # anything about the product. It is still recorded, because a failure here |
| 161 | HBADCOMM=$(grep -cv '^ssh mux$' "$SSHIM_COMMLOG" || true) | 163 | # is read by eye. |
| 164 | # | ||
| 165 | # What this does NOT catch is a `/bin/sh -c` line put back — bash and dash | ||
| 166 | # both exec a single simple command in place, so the shell is gone by the | ||
| 167 | # time the process table is read. That regression is build.zig folder rule | ||
| 168 | # 5's, measured there; this leg pins the shape the rule cannot see, that | ||
| 169 | # the argv reaches a real ssh unmangled. | ||
| 170 | HBADCOMM=$(awk '$2 != "mux" {n++} END {print n+0}' "$SSHIM_COMMLOG") | ||
| 162 | [ "$HBADCOMM" -eq 0 ] || { | 171 | [ "$HBADCOMM" -eq 0 ] || { |
| 163 | echo "e2e FAIL: the handoff spawned something other than ssh straight off mux:" | 172 | echo "e2e FAIL: the handoff spawned something other than ssh straight off mux:" |
| 164 | cat "$SSHIM_COMMLOG"; exit 1; } | 173 | cat "$SSHIM_COMMLOG"; exit 1; } |
test/e2e_08_mouse.sh
| Old | New | ||
|---|---|---|---|
| @@ -200,9 +200,18 @@ ok "an application that asked for the mouse gets the wheel, and the client does | |||
| 200 | # scenario before the fix: four notches, grid unmoved. | 200 | # scenario before the fix: four notches, grid unmoved. |
| 201 | # | 201 | # |
| 202 | # `less +G` on 200 lines: the view starts at the END, so a wheel-UP is the | 202 | # `less +G` on 200 lines: the view starts at the END, so a wheel-UP is the |
| 203 | # direction with somewhere to go. Eight notches is 24 rows, one screenful, | 203 | # direction with somewhere to go. Eight notches at less's default of three |
| 204 | # which moves the top from 178 to 154 — and 178 leaves the screen entirely, | 204 | # rows a notch is 24 rows, one screenful, so the top row moves back by 24 |
| 205 | # so the assertion has both a needle and its negative. | 205 | # and the row that was at the top leaves the screen entirely — a needle and |
| 206 | # its negative. | ||
| 207 | # | ||
| 208 | # The two row numbers are READ off the grid before the wheel rather than | ||
| 209 | # written here, because how many of the 24 rows a pager spends on its own | ||
| 210 | # status line is the pager's business and differs between builds: less 704 | ||
| 211 | # (Debian) starts this file at 178 and less 668 (macOS 26) at 179, and a | ||
| 212 | # leg that spelled 178 and 154 failed on the Mac by exactly one row while | ||
| 213 | # the wheel worked perfectly. What this leg is about is the 24, not where | ||
| 214 | # the pager chose to begin. | ||
| 206 | # | 215 | # |
| 207 | # LESS is cleared in the script rather than trusted: an operator with | 216 | # LESS is cleared in the script rather than trusted: an operator with |
| 208 | # `LESS=--mouse` exported would have a pager that DOES ask for the mouse, | 217 | # `LESS=--mouse` exported would have a pager that DOES ask for the mouse, |
| @@ -229,6 +238,18 @@ until "$MUX" d dump --sock "$SOCK35" | grep -qx "200"; do | |||
| 229 | "$MUX" d dump --sock "$SOCK35" | tail -3; exit 1; } | 238 | "$MUX" d dump --sock "$SOCK35" | tail -3; exit 1; } |
| 230 | sleep 0.1 | 239 | sleep 0.1 |
| 231 | done | 240 | done |
| 241 | # The row the pager put at the top, read now and not written down: the | ||
| 242 | # daemon's session is 80x24 and the ptyclient below attaches at 80x24, so | ||
| 243 | # nothing between here and the wheel moves the view. | ||
| 244 | PGRTOP=$("$MUX" d dump --sock "$SOCK35" | sed -n '1p') | ||
| 245 | case "$PGRTOP" in | ||
| 246 | ''|*[!0-9]*) | ||
| 247 | echo "e2e FAIL: pager: the top row of the settled grid is not a line number: '$PGRTOP'" | ||
| 248 | "$MUX" d dump --sock "$SOCK35" | head -3; exit 1 ;; | ||
| 249 | esac | ||
| 250 | [ "$PGRTOP" -gt 24 ] || { | ||
| 251 | echo "e2e FAIL: pager: top row $PGRTOP leaves no room for a screenful of wheel" | ||
| 252 | "$MUX" d dump --sock "$SOCK35" | head -3; exit 1; } | ||
| 232 | set +e | 253 | set +e |
| 233 | hostroom pager | 254 | hostroom pager |
| 234 | XDG_STATE_HOME="$HOSTROOM" timeout 40 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.pgr" --err "$OUT.pgr.err" \ | 255 | XDG_STATE_HOME="$HOSTROOM" timeout 40 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.pgr" --err "$OUT.pgr.err" \ |
| @@ -250,11 +271,13 @@ rc0 "pager: ptyclient leg exited $RC:" "$OUT.pgr.log" | |||
| 250 | "$MUX" d dump --sock "$SOCK35" > "$OUT.pgrcap" 2>&1 | 271 | "$MUX" d dump --sock "$SOCK35" > "$OUT.pgrcap" 2>&1 |
| 251 | # A whole line, because the pager's status line carries the file's PATH — | 272 | # A whole line, because the pager's status line carries the file's PATH — |
| 252 | # which holds this run's pid and could spell any short number. | 273 | # which holds this run's pid and could spell any short number. |
| 253 | grep -qx "154" "$OUT.pgrcap" || { | 274 | PGRWANT=$(( PGRTOP - 24 )) |
| 254 | echo "e2e FAIL: pager: the wheel moved nothing on the alternate screen:" | 275 | grep -qx "$PGRWANT" "$OUT.pgrcap" || { |
| 276 | echo "e2e FAIL: pager: the wheel did not move the alternate screen back 24 rows" | ||
| 277 | echo " (top was $PGRTOP, so row $PGRWANT should be on screen):" | ||
| 255 | cat "$OUT.pgrcap"; exit 1; } | 278 | cat "$OUT.pgrcap"; exit 1; } |
| 256 | grep -qx "178" "$OUT.pgrcap" && { | 279 | grep -qx "$PGRTOP" "$OUT.pgrcap" && { |
| 257 | echo "e2e FAIL: pager: the old top row is still on screen, so the view did not move a screenful:" | 280 | echo "e2e FAIL: pager: the old top row $PGRTOP is still on screen, so the view did not move a screenful:" |
| 258 | cat "$OUT.pgrcap"; exit 1; } | 281 | cat "$OUT.pgrcap"; exit 1; } |
| 259 | # The pager never asked for the mouse, so it must never have SEEN a mouse | 282 | # The pager never asked for the mouse, so it must never have SEEN a mouse |
| 260 | # report: what reached it was arrow keys, which leave no text behind. | 283 | # report: what reached it was arrow keys, which leave no text behind. |
test/e2e_09_hosts.sh
| Old | New | ||
|---|---|---|---|
| @@ -186,7 +186,9 @@ defer_kill "$HEPID" | |||
| 186 | grep -qxF -- "--sock $HESOCK" "$HESTATE/mux/hosts" || { | 186 | grep -qxF -- "--sock $HESOCK" "$HESTATE/mux/hosts" || { |
| 187 | echo "e2e FAIL: hosts: the empty file did not gain the local daemon's line:" | 187 | echo "e2e FAIL: hosts: the empty file did not gain the local daemon's line:" |
| 188 | cat "$HESTATE/mux/hosts"; exit 1; } | 188 | cat "$HESTATE/mux/hosts"; exit 1; } |
| 189 | [ "$(wc -l < "$HESTATE/mux/hosts")" = "1" ] || { | 189 | # -eq and not =: BSD wc pads its count to a column width, so the string |
| 190 | # comparison read " 1" and failed on a file that was exactly right. | ||
| 191 | [ "$(wc -l < "$HESTATE/mux/hosts")" -eq 1 ] || { | ||
| 190 | echo "e2e FAIL: hosts: the first mux wrote more than the daemon it attached to:" | 192 | echo "e2e FAIL: hosts: the first mux wrote more than the daemon it attached to:" |
| 191 | cat "$HESTATE/mux/hosts"; exit 1; } | 193 | cat "$HESTATE/mux/hosts"; exit 1; } |
| 192 | # ...and the session behind that line is a real shell, asked of the OS: the | 194 | # ...and the session behind that line is a real shell, asked of the OS: the |
test/e2e_lib.sh
| Old | New | ||
|---|---|---|---|
| @@ -964,9 +964,17 @@ assert_stopped() { | |||
| 964 | # anywhere in the line, because one binary means the CLIENT is a `mux` too | 964 | # anywhere in the line, because one binary means the CLIENT is a `mux` too |
| 965 | # and the delayed-link scripts name the same socket — a pattern match here | 965 | # and the delayed-link scripts name the same socket — a pattern match here |
| 966 | # takes out the very client under test, and so would `pkill -f proxy`. | 966 | # takes out the very client under test, and so would `pkill -f proxy`. |
| 967 | # | ||
| 968 | # Read out of `args` alone, and the program name taken as a BASENAME of its | ||
| 969 | # first word. A `comm` column cannot carry this question across the two | ||
| 970 | # OSes: BSD ps prints comm as the executable's full path where Linux prints | ||
| 971 | # the basename, and in a multi-column format it truncates that path to the | ||
| 972 | # column width, so `$2=="mux"` matched nothing at all on macOS and every | ||
| 973 | # scenario that tears a transport failed as "could not find the proxy". | ||
| 967 | proxy_pid() { | 974 | proxy_pid() { |
| 968 | ps -eo pid,comm,args | | 975 | ps -eo pid,args | |
| 969 | awk -v s="$1" '$2=="mux" && $4=="d" && $5=="proxy" && index($0,s) {print $1}' | | 976 | awk -v s="$1" '{ n = split($2, _p, "/") } |
| 977 | _p[n]=="mux" && $3=="d" && $4=="proxy" && index($0,s) {print $1}' | | ||
| 970 | head -1 | 978 | head -1 |
| 971 | } | 979 | } |
| 972 | 980 | ||
| @@ -1131,13 +1139,15 @@ ok() { | |||
| 1131 | # test/coverage.sh maps each kcov database to the scenario that was | 1139 | # test/coverage.sh maps each kcov database to the scenario that was |
| 1132 | # running when the traced process wrote it, and the timestamps are also | 1140 | # running when the traced process wrote it, and the timestamps are also |
| 1133 | # the only per-scenario timing this suite has ever been able to report. | 1141 | # the only per-scenario timing this suite has ever been able to report. |
| 1142 | # Milliseconds through the oracle's now_ms, not `date +%s.%N`: BSD date | ||
| 1143 | # has no %N and printed a literal N into the column on a Mac. | ||
| 1134 | # | 1144 | # |
| 1135 | # An `if` rather than `[ -n ... ] && printf`, for wait_sock's reason: a | 1145 | # An `if` rather than `[ -n ... ] && printf`, for wait_sock's reason: a |
| 1136 | # false guard as the last command in a function becomes that function's | 1146 | # false guard as the last command in a function becomes that function's |
| 1137 | # exit status, and under `set -e` every unstamped run would abort at its | 1147 | # exit status, and under `set -e` every unstamped run would abort at its |
| 1138 | # first passing scenario. | 1148 | # first passing scenario. |
| 1139 | if [ -n "${E2E_OK_LOG:-}" ]; then | 1149 | if [ -n "${E2E_OK_LOG:-}" ]; then |
| 1140 | printf '%s\t%s\t%s\n' "$OK_COUNT" "$(date +%s.%N)" "$1" >> "$E2E_OK_LOG" | 1150 | printf '%s\t%s\t%s\n' "$OK_COUNT" "$(now_ms)" "$1" >> "$E2E_OK_LOG" |
| 1141 | fi | 1151 | fi |
| 1142 | # Prefix slicing. This suite is linear and stateful — the M13 scenarios | 1152 | # Prefix slicing. This suite is linear and stateful — the M13 scenarios |
| 1143 | # run inside sessions the M10 scenarios created — so a prefix is the only | 1153 | # run inside sessions the M10 scenarios created — so a prefix is the only |