97a6a21f
test: four more Linux-only assumptions in the e2e harness
a73x 2026-09-04 10:16
Commit message
test/e2e_03_side.sh
| Old | New | ||
|---|---|---|---|
| @@ -221,9 +221,9 @@ PACOMM=$(pid_comm "$PAPID") | |||
| 221 | # rule the fork obeys — the child is the foreground daemon, and a `-d` | 221 | # rule the fork obeys — the child is the foreground daemon, and a `-d` |
| 222 | # still in its line would fork again. | 222 | # still in its line would fork again. |
| 223 | PACMD=$(pid_args "$PAPID") | 223 | PACMD=$(pid_args "$PAPID") |
| 224 | [ "$PACMD" = "mux d start --sock $SOCK15 " ] || { | 224 | [ "$PACMD" = "mux d start --sock $SOCK15" ] || { |
| 225 | echo "e2e FAIL: the auto-started daemon's argv is [$PACMD]," | 225 | echo "e2e FAIL: the auto-started daemon's argv is [$PACMD]," |
| 226 | echo " want [mux d start --sock $SOCK15 ]" | 226 | echo " want [mux d start --sock $SOCK15]" |
| 227 | exit 1; } | 227 | exit 1; } |
| 228 | assert_converged_pty "$OUT.pa" "$SOCK15" "local mux auto-start" 80 24 | 228 | assert_converged_pty "$OUT.pa" "$SOCK15" "local mux auto-start" 80 24 |
| 229 | # Same teardown, same reasons — and the stderr is captured rather than | 229 | # Same teardown, same reasons — and the stderr is captured rather than |
test/e2e_08_mouse.sh
| Old | New | ||
|---|---|---|---|
| @@ -205,13 +205,15 @@ ok "an application that asked for the mouse gets the wheel, and the client does | |||
| 205 | # screenful, so the top moves to 154 and 178 leaves the screen entirely — | 205 | # screenful, so the top moves to 154 and 178 leaves the screen entirely — |
| 206 | # a needle and its negative. | 206 | # a needle and its negative. |
| 207 | # | 207 | # |
| 208 | # `+G` is what this leg used to spell, and it is the one thing here that | 208 | # The `178g` is typed INTO the pager after the attach, and it is what makes |
| 209 | # cannot be written down: it says "put the END of the file on screen", and | 209 | # 178 a fact rather than a hope. Attaching makes less repaint, and the |
| 210 | # where the top of that view lands depends on how many of the 24 rows the | 210 | # repaint does not always land where the flag put it: measured on this |
| 211 | # build spends on its own status line. less 704 (Debian) left the top at | 211 | # scenario, less 704 (Debian) held the top at 178 while less 668 (macOS 26) |
| 212 | # 178 and less 668 (macOS 26) at 179, so a leg that spelled 178 and 154 | 212 | # moved it to 179, so a leg that read the top before the attach, or wrote |
| 213 | # failed on the Mac by exactly one row while the wheel worked perfectly. | 213 | # it down, was off by one row on the Mac while the wheel worked perfectly. |
| 214 | # `g` names the top row instead of deriving it, and both builds obey it. | 214 | # Re-anchoring after the repaint leaves nothing for a build to differ |
| 215 | # about. The `+178g` flag is still there so the wait loop below has a page | ||
| 216 | # to look for. | ||
| 215 | # | 217 | # |
| 216 | # LESS is cleared in the script rather than trusted: an operator with | 218 | # LESS is cleared in the script rather than trusted: an operator with |
| 217 | # `LESS=--mouse` exported would have a pager that DOES ask for the mouse, | 219 | # `LESS=--mouse` exported would have a pager that DOES ask for the mouse, |
| @@ -245,6 +247,9 @@ hostroom pager | |||
| 245 | XDG_STATE_HOME="$HOSTROOM" timeout 40 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.pgr" --err "$OUT.pgr.err" \ | 247 | XDG_STATE_HOME="$HOSTROOM" timeout 40 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.pgr" --err "$OUT.pgr.err" \ |
| 246 | -- "$MUX" --sock "$SOCK35" > "$OUT.pgr.log" 2>&1 <<'EOF' | 248 | -- "$MUX" --sock "$SOCK35" > "$OUT.pgr.log" 2>&1 <<'EOF' |
| 247 | expect 199 15000 | 249 | expect 199 15000 |
| 250 | settle 400 15000 | ||
| 251 | send 178g | ||
| 252 | settle 600 15000 | ||
| 248 | send \x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M | 253 | send \x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M\x1b[<64;10;5M |
| 249 | settle 800 15000 | 254 | settle 800 15000 |
| 250 | send \x1cd | 255 | send \x1cd |
test/e2e_09_hosts.sh
| Old | New | ||
|---|---|---|---|
| @@ -268,16 +268,28 @@ wait_sessions "$SOCKH2" 2 "hosts: daemon 2 should hold 0 and c" | |||
| 268 | # every later assertion about it is about a session the wall could reach | 268 | # every later assertion about it is about a session the wall could reach |
| 269 | # and does not. | 269 | # and does not. |
| 270 | # | 270 | # |
| 271 | # Tall, and NOT wide: 80 columns is under the aspect rule's | 271 | # Tall, and NOT wide: the terminal stays under the aspect rule's |
| 272 | # `cols >= 2 * rows`, so a bar gets the full width. Beside, three stripes | 272 | # `cols >= 2 * rows`, so a bar gets the full width. Beside, three stripes |
| 273 | # would each be twenty-odd columns and `labelText` would spend them on the | 273 | # would each be twenty-odd columns and `labelText` would spend them on the |
| 274 | # state word — the socket path these assertions grep for is the first thing | 274 | # state word — the socket path these assertions grep for is the first thing |
| 275 | # a narrow bar drops. | 275 | # a narrow bar drops. |
| 276 | # | ||
| 277 | # Wide ENOUGH is the other half, and it cannot be a constant: the label is | ||
| 278 | # `--sock <socket> #<session> [up]` and $TMPDIR decides how long the socket | ||
| 279 | # is. It is under /tmp on Linux and 80 columns has always been plenty; | ||
| 280 | # macOS puts it in a per-user directory whose name alone is 45 characters, | ||
| 281 | # and the bar elided exactly the path being grepped for. So the width comes | ||
| 282 | # from the socket and the height follows it, and a box with a short $TMPDIR | ||
| 283 | # gets the 80x44 this has always used. | ||
| 284 | HWCOLS=$(( ${#SOCKH1} + 20 )) | ||
| 285 | [ "$HWCOLS" -ge 80 ] || HWCOLS=80 | ||
| 286 | HWROWS=$(( HWCOLS / 2 + 4 )) | ||
| 287 | [ "$HWROWS" -ge 44 ] || HWROWS=44 | ||
| 276 | seed_layout "$HSTATE" stacked \ | 288 | seed_layout "$HSTATE" stacked \ |
| 277 | "--sock $SOCKH1#0" "--sock $SOCKH1#b" "--sock $SOCKH2#0" | 289 | "--sock $SOCKH1#0" "--sock $SOCKH1#b" "--sock $SOCKH2#0" |
| 278 | HFHASH=$(sha256_of "$HSTATE/mux/hosts") | 290 | HFHASH=$(sha256_of "$HSTATE/mux/hosts") |
| 279 | set +e | 291 | set +e |
| 280 | XDG_STATE_HOME="$HSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 44 \ | 292 | XDG_STATE_HOME="$HSTATE" timeout 90 "$PTYCLIENT" --cols "$HWCOLS" --rows "$HWROWS" \ |
| 281 | --out "$OUT.hwcap" --err "$OUT.hwcap.err" -- "$MUX" > "$OUT.hwpc" 2>&1 <<'EOF' | 293 | --out "$OUT.hwcap" --err "$OUT.hwcap.err" -- "$MUX" > "$OUT.hwpc" 2>&1 <<'EOF' |
| 282 | expect hw1-pin 25000 | 294 | expect hw1-pin 25000 |
| 283 | settle 1500 25000 | 295 | settle 1500 25000 |
test/e2e_10_agent.sh
| Old | New | ||
|---|---|---|---|
| @@ -87,6 +87,15 @@ esac | |||
| 87 | # Unquoted heredoc, like the OSC 52 leg's and for the same reason: $FP48 has | 87 | # Unquoted heredoc, like the OSC 52 leg's and for the same reason: $FP48 has |
| 88 | # to interpolate. Nothing else in the script needs expanding — `\n` and | 88 | # to interpolate. Nothing else in the script needs expanding — `\n` and |
| 89 | # `\x1b` are not escapes the shell touches. | 89 | # `\x1b` are not escapes the shell touches. |
| 90 | # | ||
| 91 | # No BACKTICKS below this line, comments included, and that is a rule and | ||
| 92 | # not a preference: an unquoted heredoc runs `word` as a command | ||
| 93 | # substitution wherever it appears, and the comments here used to quote | ||
| 94 | # `x`, `d`, `expect` and `settle` the way the rest of this repo quotes a | ||
| 95 | # key or a verb. On Linux all four are "command not found" and the run | ||
| 96 | # carries on none the wiser. On macOS /usr/bin/expect exists, so the shell | ||
| 97 | # started it, it read the rest of the heredoc as its own script, and the | ||
| 98 | # group hung until its budget ran out (2026-09-04). | ||
| 90 | set +e | 99 | set +e |
| 91 | hostroom agt48 | 100 | hostroom agt48 |
| 92 | XDG_STATE_HOME="$HOSTROOM" SSH_AUTH_SOCK="$AGENT48" timeout 40 "$PTYCLIENT" --cols 100 --rows 30 \ | 101 | XDG_STATE_HOME="$HOSTROOM" SSH_AUTH_SOCK="$AGENT48" timeout 40 "$PTYCLIENT" --cols 100 --rows 30 \ |
| @@ -98,14 +107,15 @@ send ssh-add -l\n | |||
| 98 | expect $FP48 15000 | 107 | expect $FP48 15000 |
| 99 | settle 400 15000 | 108 | settle 400 15000 |
| 100 | send exit\n | 109 | send exit\n |
| 101 | # On a TERMINAL the last tile's exit leaves an EMPTY WALL — `x` ends a | 110 | # On a TERMINAL the last tile's exit leaves an EMPTY WALL — the x key ends |
| 102 | # session, never mux — and the picker opens over it. Esc closes the popup | 111 | # a session, never mux — and the picker opens over it. Esc closes the popup |
| 103 | # and `d` is what leaves. A piped mux still exits with the shell's code. | 112 | # and the d key is what leaves. A piped mux still exits with the shell's |
| 113 | # code. | ||
| 104 | # | 114 | # |
| 105 | # `expect`, not `settle`: the popup is the CLAIM, so waiting for its legend | 115 | # An expect verb here rather than a settle: the popup is the CLAIM, so |
| 106 | # is what makes this leg fail if the exit ever ends mux again instead. The | 116 | # waiting for its legend is what makes this leg fail if the exit ever ends |
| 107 | # empty-wall scenario asserts a picker on a wall that never had a tile; | 117 | # mux again instead. The empty-wall scenario asserts a picker on a wall |
| 108 | # only this one sees the TRANSITION. | 118 | # that never had a tile; only this one sees the TRANSITION. |
| 109 | expect c new session 15000 | 119 | expect c new session 15000 |
| 110 | send \x1b | 120 | send \x1b |
| 111 | settle 300 15000 | 121 | settle 300 15000 |
| @@ -467,10 +477,10 @@ send echo b-ready\n | |||
| 467 | expect flip-now 30000 | 477 | expect flip-now 30000 |
| 468 | send ssh-add -l\n | 478 | send ssh-add -l\n |
| 469 | expect $FPB 20000 | 479 | expect $FPB 20000 |
| 470 | # A's `exit` ends the session both clients hold, so B's tile goes too and | 480 | # A's exit command ends the session both clients hold, so B's tile goes too and |
| 471 | # B is left on the same empty wall with the same picker over it. The | 481 | # B is left on the same empty wall with the same picker over it. The |
| 472 | # legend and not a settle, doubly so here: B is a passive observer, so its | 482 | # legend and not a settle, doubly so here: B is a passive observer, so its |
| 473 | # screen is already quiet when A types `exit` and a duration could expire | 483 | # screen is already quiet when A types exit and a duration could expire |
| 474 | # before the transition this waits for has happened at all. | 484 | # before the transition this waits for has happened at all. |
| 475 | expect c new session 20000 | 485 | expect c new session 20000 |
| 476 | send \x1b | 486 | send \x1b |
test/oracle_selftest.sh
| Old | New | ||
|---|---|---|---|
| @@ -118,6 +118,14 @@ time.sleep(300)' \ | |||
| 118 | echo "e2e FAIL: oracle: pid_comm of an sh is '$(pid_comm "$_opid")'"; exit 1; } | 118 | echo "e2e FAIL: oracle: pid_comm of an sh is '$(pid_comm "$_opid")'"; exit 1; } |
| 119 | pid_args "$_opid" | grep -q 'sleep 300' || { | 119 | pid_args "$_opid" | grep -q 'sleep 300' || { |
| 120 | echo "e2e FAIL: oracle: pid_args lost the argv: [$(pid_args "$_opid")]"; exit 1; } | 120 | echo "e2e FAIL: oracle: pid_args lost the argv: [$(pid_args "$_opid")]"; exit 1; } |
| 121 | # The SHAPE too, because the one caller that compares a whole argv does | ||
| 122 | # it with `=`: one space between words and none at the end. Linux reads | ||
| 123 | # a NUL-terminated /proc entry and Darwin reads ps, and the two agreed | ||
| 124 | # on every word and disagreed on the last byte until this was pinned. | ||
| 125 | case "$(pid_args "$_opid")" in | ||
| 126 | *' ') echo "e2e FAIL: oracle: pid_args ends in a space: [$(pid_args "$_opid")]"; exit 1 ;; | ||
| 127 | *' '*) echo "e2e FAIL: oracle: pid_args doubles a space: [$(pid_args "$_opid")]"; exit 1 ;; | ||
| 128 | esac | ||
| 121 | [ "$(pid_fd_count "$_opid")" -ge 5 ] || { | 129 | [ "$(pid_fd_count "$_opid")" -ge 5 ] || { |
| 122 | echo "e2e FAIL: oracle: pid_fd_count is $(pid_fd_count "$_opid") for a shell" | 130 | echo "e2e FAIL: oracle: pid_fd_count is $(pid_fd_count "$_opid") for a shell" |
| 123 | echo " holding fd 5 and fd 6 on top of the usual three"; exit 1; } | 131 | echo " holding fd 5 and fd 6 on top of the usual three"; exit 1; } |
test/os_oracle.sh
| Old | New | ||
|---|---|---|---|
| @@ -228,7 +228,13 @@ print "$r\n";' "$1" | |||
| 228 | pid_alive() { kill -0 "$1" 2>/dev/null || [ -d "/proc/$1" ]; } | 228 | pid_alive() { kill -0 "$1" 2>/dev/null || [ -d "/proc/$1" ]; } |
| 229 | pid_exe() { readlink -f "/proc/$1/exe" 2>/dev/null; } | 229 | pid_exe() { readlink -f "/proc/$1/exe" 2>/dev/null; } |
| 230 | pid_comm() { cat "/proc/$1/comm" 2>/dev/null; } | 230 | pid_comm() { cat "/proc/$1/comm" 2>/dev/null; } |
| 231 | pid_args() { tr '\0' ' ' < "/proc/$1/cmdline" 2>/dev/null; } | 231 | # The words with ONE space between them and none at the end. Every |
| 232 | # entry in /proc/PID/cmdline is NUL-TERMINATED, so a straight | ||
| 233 | # translation leaves a trailing separator that the Darwin arm's | ||
| 234 | # `ps -o args=` does not, and the one caller that compares the whole | ||
| 235 | # argv as a string then has to spell a space it can only have learned | ||
| 236 | # from Linux. Trimmed here so both arms answer the same shape. | ||
| 237 | pid_args() { tr '\0' ' ' < "/proc/$1/cmdline" 2>/dev/null | sed 's/ *$//'; } | ||
| 232 | pid_children() { ps -o pid= --ppid "$1" 2>/dev/null | tr -d ' '; } | 238 | pid_children() { ps -o pid= --ppid "$1" 2>/dev/null | tr -d ' '; } |
| 233 | pid_fd_count() { find "/proc/$1/fd" -mindepth 1 2>/dev/null | wc -l | tr -d ' '; } | 239 | pid_fd_count() { find "/proc/$1/fd" -mindepth 1 2>/dev/null | wc -l | tr -d ' '; } |
| 234 | pid_fd_targets() { readlink "/proc/$1"/fd/* 2>/dev/null; } | 240 | pid_fd_targets() { readlink "/proc/$1"/fd/* 2>/dev/null; } |
| @@ -304,9 +310,16 @@ eval "$(now_ms_snippet)" | |||
| 304 | # answers 127 where GNU answers 126; a signal sent to the WRAPPER is not | 310 | # answers 127 where GNU answers 126; a signal sent to the WRAPPER is not |
| 305 | # relayed to the child, where GNU forwards TERM, INT and HUP; and the child | 311 | # relayed to the child, where GNU forwards TERM, INT and HUP; and the child |
| 306 | # is not put in a process group, so GNU's kill-the-group on expiry becomes | 312 | # is not put in a process group, so GNU's kill-the-group on expiry becomes |
| 307 | # kill-the-child and a grandchild outlives the budget. Every caller in this | 313 | # kill-the-child and a grandchild outlives the budget. |
| 308 | # tree runs `timeout <seconds> CMD` in the foreground over a single mux or | 314 | # |
| 309 | # ptyclient child, with no flags. | 315 | # Almost every caller in this tree runs `timeout <seconds> CMD` in the |
| 316 | # foreground over a single mux or ptyclient child, with no flags, and none | ||
| 317 | # of the three differences can reach one. The exceptions, and why they are | ||
| 318 | # still safe: e2e_01_boot.sh backgrounds one client and reaps it by pid | ||
| 319 | # rather than by signalling the wrapper; e2e_05_session.sh wraps a wsclient, | ||
| 320 | # which is one child like the others; and test/vm.sh, test/mac.sh and | ||
| 321 | # test/xos.sh wrap ssh, but those three drivers run on the Linux side of a | ||
| 322 | # gate, where `timeout` is the GNU binary and this arm is never defined. | ||
| 310 | if command -v timeout >/dev/null 2>&1; then | 323 | if command -v timeout >/dev/null 2>&1; then |
| 311 | : | 324 | : |
| 312 | elif command -v gtimeout >/dev/null 2>&1; then | 325 | elif command -v gtimeout >/dev/null 2>&1; then |