cbf1a48f
test: the e2e suite stops depending on a fast reap, a pager's height and a $TMPDIR without a trailing slash
a73x 2026-09-04 10:16
Commit message
test/e2e_01_boot.sh
| Old | New | ||
|---|---|---|---|
| @@ -417,6 +417,21 @@ AGENTLEFT=$(find "$(dirname "$SOCK3")" -maxdepth 1 -name "mux-agent-$D3DEAD-*" | | |||
| 417 | echo "e2e FAIL: a SIGKILLed daemon left $AGENTLEFT agent directories for pid $D3DEAD (want 1)"; exit 1; } | 417 | echo "e2e FAIL: a SIGKILLed daemon left $AGENTLEFT agent directories for pid $D3DEAD (want 1)"; exit 1; } |
| 418 | start_daemon "$SOCK3" "$OUT.d3b.d" "daemon did not rebind the stale socket" --shell /bin/sh | 418 | start_daemon "$SOCK3" "$OUT.d3b.d" "daemon did not rebind the stale socket" --shell /bin/sh |
| 419 | D3PID=$DPID | 419 | D3PID=$DPID |
| 420 | # The reap is not a boot step: it happens inside the successor's own | ||
| 421 | # `makeDir`, which runs when its FIRST session is born — here, when the | ||
| 422 | # stranded client above redials. A bound socket says nothing about that, | ||
| 423 | # so wait for the successor's own directory to appear and read the | ||
| 424 | # predecessor's only then. Asserting straight off start_daemon read a | ||
| 425 | # directory that was removed microseconds later, which is a race Linux | ||
| 426 | # won every time and a Mac lost (2026-09-04). | ||
| 427 | _i=0 | ||
| 428 | until [ -n "$(find "$(dirname "$SOCK3")" -maxdepth 1 -name "mux-agent-$D3PID-*")" ]; do | ||
| 429 | _i=$((_i + 1)); [ "$_i" -lt $(( 100 * TIME_SCALE )) ] || { | ||
| 430 | echo "e2e FAIL: the restarted daemon never made an agent directory of its own," | ||
| 431 | echo " so nothing here has reaped yet and the check below would lie:" | ||
| 432 | find "$(dirname "$SOCK3")" -maxdepth 1 -name 'mux-agent-*'; exit 1; } | ||
| 433 | sleep 0.1 | ||
| 434 | done | ||
| 420 | AGENTLEFT=$(find "$(dirname "$SOCK3")" -maxdepth 1 -name "mux-agent-$D3DEAD-*" | wc -l) | 435 | AGENTLEFT=$(find "$(dirname "$SOCK3")" -maxdepth 1 -name "mux-agent-$D3DEAD-*" | wc -l) |
| 421 | [ "$AGENTLEFT" -eq 0 ] || { | 436 | [ "$AGENTLEFT" -eq 0 ] || { |
| 422 | echo "e2e FAIL: the restarted daemon left its SIGKILLed predecessor's agent directory:" | 437 | echo "e2e FAIL: the restarted daemon left its SIGKILLed predecessor's agent directory:" |
test/e2e_08_mouse.sh
| Old | New | ||
|---|---|---|---|
| @@ -199,19 +199,19 @@ ok "an application that asked for the mouse gets the wheel, and the client does | |||
| 199 | # consumed by the mouse filter and then dropped. Measured on this very | 199 | # consumed by the mouse filter and then dropped. Measured on this very |
| 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 +178g` on 200 lines: line 178 is put at the TOP of the screen, so a |
| 203 | # direction with somewhere to go. Eight notches at less's default of three | 203 | # wheel-UP has 177 rows to go and the leg knows where it started. Eight |
| 204 | # rows a notch is 24 rows, one screenful, so the top row moves back by 24 | 204 | # notches at less's default of three rows a notch is 24 rows, one |
| 205 | # and the row that was at the top leaves the screen entirely — a needle and | 205 | # screenful, so the top moves to 154 and 178 leaves the screen entirely — |
| 206 | # its negative. | 206 | # a needle and its negative. |
| 207 | # | 207 | # |
| 208 | # The two row numbers are READ off the grid before the wheel rather than | 208 | # `+G` is what this leg used to spell, and it is the one thing here that |
| 209 | # written here, because how many of the 24 rows a pager spends on its own | 209 | # cannot be written down: it says "put the END of the file on screen", and |
| 210 | # status line is the pager's business and differs between builds: less 704 | 210 | # where the top of that view lands depends on how many of the 24 rows the |
| 211 | # (Debian) starts this file at 178 and less 668 (macOS 26) at 179, and a | 211 | # build spends on its own status line. less 704 (Debian) left the top at |
| 212 | # leg that spelled 178 and 154 failed on the Mac by exactly one row while | 212 | # 178 and less 668 (macOS 26) at 179, so a leg that spelled 178 and 154 |
| 213 | # the wheel worked perfectly. What this leg is about is the 24, not where | 213 | # failed on the Mac by exactly one row while the wheel worked perfectly. |
| 214 | # the pager chose to begin. | 214 | # `g` names the top row instead of deriving it, and both builds obey it. |
| 215 | # | 215 | # |
| 216 | # 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 |
| 217 | # `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, |
| @@ -222,34 +222,24 @@ cat > "$LESSSH" <<EOF | |||
| 222 | #!/bin/sh | 222 | #!/bin/sh |
| 223 | LESS= | 223 | LESS= |
| 224 | export LESS | 224 | export LESS |
| 225 | exec less +G $LESSDATA | 225 | exec less +178g $LESSDATA |
| 226 | EOF | 226 | EOF |
| 227 | chmod +x "$LESSSH" | 227 | chmod +x "$LESSSH" |
| 228 | start_daemon "$SOCK35" "$OUT.pgr.d" "pager daemon never bound" --shell "$LESSSH" | 228 | start_daemon "$SOCK35" "$OUT.pgr.d" "pager daemon never bound" --shell "$LESSSH" |
| 229 | D32PID=$DPID | 229 | D32PID=$DPID |
| 230 | # Not wait_grid: the needle is anchored (`-qx`, a row that is EXACTLY the | 230 | # Not wait_grid: the needle is anchored (`-qx`, a row that is EXACTLY one |
| 231 | # file's last line, so a half-painted `2` does not pass) and the dump is | 231 | # of the file's lines, so a half-painted `19` does not pass) and the dump |
| 232 | # trimmed to the last rows, because a pager's whole grid is 24 rows of | 232 | # is trimmed to the last rows, because a pager's whole grid is 24 rows of |
| 233 | # numbers that say nothing about why it stopped short. | 233 | # numbers that say nothing about why it stopped short. 190 rather than the |
| 234 | # top row 178: it is well inside the page either build draws, so the needle | ||
| 235 | # does not depend on the height question the comment above settles. | ||
| 234 | i=0 | 236 | i=0 |
| 235 | until "$MUX" d dump --sock "$SOCK35" | grep -qx "200"; do | 237 | until "$MUX" d dump --sock "$SOCK35" | grep -qx "190"; do |
| 236 | i=$((i+1)); [ "$i" -lt $(( 100 * TIME_SCALE )) ] || { | 238 | i=$((i+1)); [ "$i" -lt $(( 100 * TIME_SCALE )) ] || { |
| 237 | echo "e2e FAIL: pager never reached the end of the file:" | 239 | echo "e2e FAIL: pager never painted the page it was sent to:" |
| 238 | "$MUX" d dump --sock "$SOCK35" | tail -3; exit 1; } | 240 | "$MUX" d dump --sock "$SOCK35" | tail -3; exit 1; } |
| 239 | sleep 0.1 | 241 | sleep 0.1 |
| 240 | done | 242 | 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; } | ||
| 253 | set +e | 243 | set +e |
| 254 | hostroom pager | 244 | hostroom pager |
| 255 | XDG_STATE_HOME="$HOSTROOM" timeout 40 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.pgr" --err "$OUT.pgr.err" \ | 245 | XDG_STATE_HOME="$HOSTROOM" timeout 40 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.pgr" --err "$OUT.pgr.err" \ |
| @@ -271,13 +261,12 @@ rc0 "pager: ptyclient leg exited $RC:" "$OUT.pgr.log" | |||
| 271 | "$MUX" d dump --sock "$SOCK35" > "$OUT.pgrcap" 2>&1 | 261 | "$MUX" d dump --sock "$SOCK35" > "$OUT.pgrcap" 2>&1 |
| 272 | # A whole line, because the pager's status line carries the file's PATH — | 262 | # A whole line, because the pager's status line carries the file's PATH — |
| 273 | # which holds this run's pid and could spell any short number. | 263 | # which holds this run's pid and could spell any short number. |
| 274 | PGRWANT=$(( PGRTOP - 24 )) | 264 | grep -qx "154" "$OUT.pgrcap" || { |
| 275 | grep -qx "$PGRWANT" "$OUT.pgrcap" || { | 265 | echo "e2e FAIL: pager: the wheel did not move the alternate screen back a screenful" |
| 276 | echo "e2e FAIL: pager: the wheel did not move the alternate screen back 24 rows" | 266 | echo " (top was 178, so row 154 should be on screen):" |
| 277 | echo " (top was $PGRTOP, so row $PGRWANT should be on screen):" | ||
| 278 | cat "$OUT.pgrcap"; exit 1; } | 267 | cat "$OUT.pgrcap"; exit 1; } |
| 279 | grep -qx "$PGRTOP" "$OUT.pgrcap" && { | 268 | grep -qx "178" "$OUT.pgrcap" && { |
| 280 | echo "e2e FAIL: pager: the old top row $PGRTOP is still on screen, so the view did not move a screenful:" | 269 | echo "e2e FAIL: pager: the old top row is still on screen, so the view did not move a screenful:" |
| 281 | cat "$OUT.pgrcap"; exit 1; } | 270 | cat "$OUT.pgrcap"; exit 1; } |
| 282 | # The pager never asked for the mouse, so it must never have SEEN a mouse | 271 | # The pager never asked for the mouse, so it must never have SEEN a mouse |
| 283 | # report: what reached it was arrow keys, which leave no text behind. | 272 | # report: what reached it was arrow keys, which leave no text behind. |
test/e2e_lib.sh
| Old | New | ||
|---|---|---|---|
| @@ -12,6 +12,26 @@ | |||
| 12 | # What is NOT here: the scenario count. ok() counts, and the pin that | 12 | # What is NOT here: the scenario count. ok() counts, and the pin that |
| 13 | # gates the count is the runner's, at the bottom of test/e2e.sh — a group | 13 | # gates the count is the runner's, at the bottom of test/e2e.sh — a group |
| 14 | # file knows nothing about how many scenarios the suite has. | 14 | # file knows nothing about how many scenarios the suite has. |
| 15 | # ---- $TMPDIR, trailing slash removed ----------------------------------- | ||
| 16 | # Every temporary path in this suite is spelled `${TMPDIR:-/tmp}/name`, and | ||
| 17 | # a good few of them are then compared as STRINGS — a layout leaf against | ||
| 18 | # the socket the daemon bound, a hosts line against the daemon the client | ||
| 19 | # attached to. macOS sets $TMPDIR to a per-user directory ending in `/`, so | ||
| 20 | # those paths came out with `//` in the middle: mux records the spelling it | ||
| 21 | # was handed, the suite spells its expectation the same way, and the two | ||
| 22 | # still differ when only one of them went through a normalizing step. Two | ||
| 23 | # groups failed on exactly that. Stripped once here rather than at 191 call | ||
| 24 | # sites, and before the first of them. | ||
| 25 | while :; do | ||
| 26 | case "${TMPDIR:-}" in | ||
| 27 | */) TMPDIR="${TMPDIR%/}" ;; | ||
| 28 | *) break ;; | ||
| 29 | esac | ||
| 30 | done | ||
| 31 | # An empty $TMPDIR is not a directory; leave it unset so `${TMPDIR:-/tmp}` | ||
| 32 | # falls back the way every caller here expects. | ||
| 33 | [ -n "${TMPDIR:-}" ] && export TMPDIR || unset TMPDIR | ||
| 34 | |||
| 15 | # ---- the cleanup registry --------------------------------------------- | 35 | # ---- the cleanup registry --------------------------------------------- |
| 16 | # Every process and every artifact a leg creates is REGISTERED where it is | 36 | # Every process and every artifact a leg creates is REGISTERED where it is |
| 17 | # created, and the EXIT trap walks the registers. Before this, cleanup() | 37 | # created, and the EXIT trap walks the registers. Before this, cleanup() |