ff60cc02
test: every e2e poll budget rides TIME_SCALE
a73x 2026-09-01 13:45
Commit message
test/e2e_01_boot.sh
| Old | New | ||
|---|---|---|---|
| @@ -735,12 +735,15 @@ D4PID=$DPID | |||
| 735 | # same match are two matches waiting to drift apart. | 735 | # same match are two matches waiting to drift apart. |
| 736 | udp_local_bound() { awk -v h="$1" '$2==h{f=1} END{exit !f}' /proc/net/udp; } | 736 | udp_local_bound() { awk -v h="$1" '$2==h{f=1} END{exit !f}' /proc/net/udp; } |
| 737 | i=0 | 737 | i=0 |
| 738 | # 50 x 0.1s, the same 5s every daemon wait in this file allows. | 738 | # 50 x 0.1s, the same 5s every daemon wait in this file allows — and scaled |
| 739 | while ! udp_local_bound "$QHEX" && [ "$i" -lt 50 ]; do | 739 | # by TIME_SCALE like every other budget, because under test/coverage.sh the |
| 740 | # traced daemon takes longer to reach its bind and a bare 5s reads the delay | ||
| 741 | # as "it never rebound". | ||
| 742 | while ! udp_local_bound "$QHEX" && [ "$i" -lt $(( 50 * TIME_SCALE )) ]; do | ||
| 740 | sleep 0.1; i=$((i+1)) | 743 | sleep 0.1; i=$((i+1)) |
| 741 | done | 744 | done |
| 742 | udp_local_bound "$QHEX" || { | 745 | udp_local_bound "$QHEX" || { |
| 743 | echo "e2e FAIL: restarted daemon did not rebind udp 127.0.0.1:$QPORT ($QHEX) in 5s" | 746 | echo "e2e FAIL: restarted daemon did not rebind udp 127.0.0.1:$QPORT ($QHEX) in $(( 5 * TIME_SCALE ))s" |
| 744 | echo " daemon pid $D4PID: $(ps -o stat=,comm= -p "$D4PID" 2>/dev/null || echo gone)" | 747 | echo " daemon pid $D4PID: $(ps -o stat=,comm= -p "$D4PID" 2>/dev/null || echo gone)" |
| 745 | echo " /proc/net/udp lines mentioning the port:" | 748 | echo " /proc/net/udp lines mentioning the port:" |
| 746 | grep -i " $QHEX " /proc/net/udp || echo " (none)" | 749 | grep -i " $QHEX " /proc/net/udp || echo " (none)" |
test/e2e_02_predict.sh
| Old | New | ||
|---|---|---|---|
| @@ -665,11 +665,7 @@ D13PID=$DPID | |||
| 665 | # the session goes quiet there (cat prints no prompt), history_rows is 77, | 665 | # the session goes quiet there (cat prints no prompt), history_rows is 77, |
| 666 | # and page 1 is rows 54..77. Every needle below is chosen against those | 666 | # and page 1 is rows 54..77. Every needle below is chosen against those |
| 667 | # numbers, which is why this wait is part of the scenario and not a nicety. | 667 | # numbers, which is why this wait is part of the scenario and not a nicety. |
| 668 | i=0 | 668 | wait_grid "$SOCK13" "100" "tp1 session never finished seq" |
| 669 | until "$MUX" d dump --sock "$SOCK13" | grep -q "100"; do | ||
| 670 | i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: tp1 session never finished seq"; exit 1; } | ||
| 671 | sleep 0.1 | ||
| 672 | done | ||
| 673 | 669 | ||
| 674 | # MUX_PREDICT_STATS is exported into the FIXTURE's environment, not spelled | 670 | # MUX_PREDICT_STATS is exported into the FIXTURE's environment, not spelled |
| 675 | # as a `VAR=x cmd` prefix on the client: the fixture execve()s its child | 671 | # as a `VAR=x cmd` prefix on the client: the fixture execve()s its child |
test/e2e_05_session.sh
| Old | New | ||
|---|---|---|---|
| @@ -333,7 +333,11 @@ while : ; do | |||
| 333 | D20PID=$! | 333 | D20PID=$! |
| 334 | defer_kill "$D20PID" | 334 | defer_kill "$D20PID" |
| 335 | _i=0 | 335 | _i=0 |
| 336 | while [ ! -S "$SOCK23" ] && kill -0 "$D20PID" 2>/dev/null && [ "$_i" -lt 60 ]; do | 336 | # Not wait_sock: this wait also watches the daemon's own pid, because a |
| 337 | # daemon that refused the udp port EXITS and the retry below wants to | ||
| 338 | # read its words now rather than after the full budget. The budget is | ||
| 339 | # scaled for wait_sock's reason all the same. | ||
| 340 | while [ ! -S "$SOCK23" ] && kill -0 "$D20PID" 2>/dev/null && [ "$_i" -lt $(( 60 * TIME_SCALE )) ]; do | ||
| 337 | sleep 0.1; _i=$((_i + 1)) | 341 | sleep 0.1; _i=$((_i + 1)) |
| 338 | done | 342 | done |
| 339 | [ -S "$SOCK23" ] && break | 343 | [ -S "$SOCK23" ] && break |
test/e2e_06_web.sh
| Old | New | ||
|---|---|---|---|
| @@ -411,14 +411,8 @@ SESS_BEFORE=$("$MUX" d stats --sock "$SOCK25" | sed -n 's/.*sessions=\([0-9]*\). | |||
| 411 | R=$(curl -s -H "Origin: $DWORIG" -X POST "$DWORIG/tiles/$DWID_A0") | 411 | R=$(curl -s -H "Origin: $DWORIG" -X POST "$DWORIG/tiles/$DWID_A0") |
| 412 | [ "$R" = '{"session":"1"}' ] || { | 412 | [ "$R" = '{"session":"1"}' ] || { |
| 413 | echo "e2e FAIL: host wall: spawn on A's tile returned $R, want session 1"; exit 1; } | 413 | echo "e2e FAIL: host wall: spawn on A's tile returned $R, want session 1"; exit 1; } |
| 414 | _i=0 | 414 | wait_until 60 "host wall: the born session never became a tile; the wall holds:" \ |
| 415 | while [ "$_i" -lt 60 ]; do | 415 | '[ -n "$(dw_id_of "--sock $SOCK25" 1)" ]' 'curl -s "$DWORIG/tiles"' |
| 416 | [ -n "$(dw_id_of "--sock $SOCK25" 1)" ] && break | ||
| 417 | sleep 0.1; _i=$((_i+1)) | ||
| 418 | done | ||
| 419 | [ "$_i" -lt 60 ] || { | ||
| 420 | echo "e2e FAIL: host wall: the born session never became a tile; the wall holds:" | ||
| 421 | curl -s "$DWORIG/tiles"; exit 1; } | ||
| 422 | # Asked of the DAEMON, not of the hub: a hub reporting on a birth it made | 416 | # Asked of the DAEMON, not of the hub: a hub reporting on a birth it made |
| 423 | # cannot catch itself being wrong about whether a shell exists. | 417 | # cannot catch itself being wrong about whether a shell exists. |
| 424 | SESS_AFTER=$("$MUX" d stats --sock "$SOCK25" | sed -n 's/.*sessions=\([0-9]*\).*/\1/p') | 418 | SESS_AFTER=$("$MUX" d stats --sock "$SOCK25" | sed -n 's/.*sessions=\([0-9]*\).*/\1/p') |
| @@ -438,25 +432,16 @@ DWID_A1=$(dw_id_of "--sock $SOCK25" 1) | |||
| 438 | # `$$` — because a daemon reporting on its own children cannot catch itself | 432 | # `$$` — because a daemon reporting on its own children cannot catch itself |
| 439 | # being wrong about whether one is still there. | 433 | # being wrong about whether one is still there. |
| 440 | timeout 20 "$MUX" a send 'printf "bornpid-%s\n" $$\n' --sock "$SOCK25" --session 1 > /dev/null 2>&1 | 434 | timeout 20 "$MUX" a send 'printf "bornpid-%s\n" $$\n' --sock "$SOCK25" --session 1 > /dev/null 2>&1 |
| 441 | _i=0 | 435 | wait_until 80 "host wall: the born session never said its own pid; it holds:" \ |
| 442 | while [ "$_i" -lt 80 ]; do | 436 | 'timeout 20 "$MUX" a capture --sock "$SOCK25" --session 1 > "$OUT.dwpid" 2>&1; grep -q "bornpid-[0-9]" "$OUT.dwpid"' \ |
| 443 | timeout 20 "$MUX" a capture --sock "$SOCK25" --session 1 > "$OUT.dwpid" 2>&1 | 437 | 'cat "$OUT.dwpid"' |
| 444 | grep -q "bornpid-[0-9]" "$OUT.dwpid" && break | ||
| 445 | sleep 0.1; _i=$((_i+1)) | ||
| 446 | done | ||
| 447 | DWSHELL=$(sed -n 's/.*bornpid-\([0-9][0-9]*\).*/\1/p' "$OUT.dwpid" | head -1) | 438 | DWSHELL=$(sed -n 's/.*bornpid-\([0-9][0-9]*\).*/\1/p' "$OUT.dwpid" | head -1) |
| 448 | [ -n "$DWSHELL" ] || { | 439 | [ -n "$DWSHELL" ] || { |
| 449 | echo "e2e FAIL: host wall: the born session never said its own pid; it holds:" | 440 | echo "e2e FAIL: host wall: the born session never said its own pid; it holds:" |
| 450 | cat "$OUT.dwpid"; exit 1; } | 441 | cat "$OUT.dwpid"; exit 1; } |
| 451 | timeout 20 "$MUX" a send 'exit\n' --sock "$SOCK25" --session 1 > /dev/null 2>&1 || true | 442 | timeout 20 "$MUX" a send 'exit\n' --sock "$SOCK25" --session 1 > /dev/null 2>&1 || true |
| 452 | _i=0 | 443 | wait_until 80 "host wall: the ended session's tile never left; the wall holds:" \ |
| 453 | while [ "$_i" -lt 80 ]; do | 444 | '! curl -s "$DWORIG/tiles" | grep -q "\"session\":\"1\""' 'curl -s "$DWORIG/tiles"' |
| 454 | curl -s "$DWORIG/tiles" | grep -q '"session":"1"' || break | ||
| 455 | sleep 0.1; _i=$((_i+1)) | ||
| 456 | done | ||
| 457 | [ "$_i" -lt 80 ] || { | ||
| 458 | echo "e2e FAIL: host wall: the ended session's tile never left; the wall holds:" | ||
| 459 | curl -s "$DWORIG/tiles"; exit 1; } | ||
| 460 | # Asked of the OS, not of the daemon. | 445 | # Asked of the OS, not of the daemon. |
| 461 | [ ! -e "/proc/$DWSHELL" ] || { | 446 | [ ! -e "/proc/$DWSHELL" ] || { |
| 462 | echo "e2e FAIL: host wall: the shell pid $DWSHELL outlived its session per /proc" | 447 | echo "e2e FAIL: host wall: the shell pid $DWSHELL outlived its session per /proc" |
| @@ -470,14 +455,8 @@ done | |||
| 470 | R=$(curl -s -H "Origin: $DWORIG" -X POST "$DWORIG/tiles/$DWID_A0") | 455 | R=$(curl -s -H "Origin: $DWORIG" -X POST "$DWORIG/tiles/$DWID_A0") |
| 471 | [ "$R" = '{"session":"1"}' ] || { | 456 | [ "$R" = '{"session":"1"}' ] || { |
| 472 | echo "e2e FAIL: host wall: the second spawn returned $R, want session 1 again"; exit 1; } | 457 | echo "e2e FAIL: host wall: the second spawn returned $R, want session 1 again"; exit 1; } |
| 473 | _i=0 | 458 | wait_until 60 "host wall: the second birth never became a tile; the wall holds:" \ |
| 474 | while [ "$_i" -lt 60 ]; do | 459 | '[ -n "$(dw_id_of "--sock $SOCK25" 1)" ]' 'curl -s "$DWORIG/tiles"' |
| 475 | [ -n "$(dw_id_of "--sock $SOCK25" 1)" ] && break | ||
| 476 | sleep 0.1; _i=$((_i+1)) | ||
| 477 | done | ||
| 478 | [ "$_i" -lt 60 ] || { | ||
| 479 | echo "e2e FAIL: host wall: the second birth never became a tile; the wall holds:" | ||
| 480 | curl -s "$DWORIG/tiles"; exit 1; } | ||
| 481 | [ "$(dw_id_of "--sock $SOCK25" 1)" != "$DWID_A1" ] || { | 460 | [ "$(dw_id_of "--sock $SOCK25" 1)" != "$DWID_A1" ] || { |
| 482 | echo "e2e FAIL: host wall: session 1 came back on the SAME id $DWID_A1" | 461 | echo "e2e FAIL: host wall: session 1 came back on the SAME id $DWID_A1" |
| 483 | curl -s "$DWORIG/tiles"; exit 1; } | 462 | curl -s "$DWORIG/tiles"; exit 1; } |
test/e2e_08_mouse.sh
| Old | New | ||
|---|---|---|---|
| @@ -87,11 +87,7 @@ start_daemon "$SOCK33" "$OUT.whl.d" "wheel daemon never bound" --shell "$WHEELSH | |||
| 87 | D30PID=$DPID | 87 | D30PID=$DPID |
| 88 | # Attach only once seq has finished, for tp1's reason: the page the wheel | 88 | # Attach only once seq has finished, for tp1's reason: the page the wheel |
| 89 | # fetches has to be content this client was never sent. | 89 | # fetches has to be content this client was never sent. |
| 90 | i=0 | 90 | wait_grid "$SOCK33" "100" "wheel session never finished seq" |
| 91 | until "$MUX" d dump --sock "$SOCK33" | grep -q "100"; do | ||
| 92 | i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: wheel session never finished seq"; exit 1; } | ||
| 93 | sleep 0.1 | ||
| 94 | done | ||
| 95 | # `\x1b[0m100`, not "100": the client asks its own terminal for mouse | 91 | # `\x1b[0m100`, not "100": the client asks its own terminal for mouse |
| 96 | # reporting with `\x1b[?1000h`, so a bare "100" is in this capture before | 92 | # reporting with `\x1b[?1000h`, so a bare "100" is in this capture before |
| 97 | # any row is. The SGR reset in front of it is the paint's, and nothing else | 93 | # any row is. The SGR reset in front of it is the paint's, and nothing else |
| @@ -157,11 +153,7 @@ EOF | |||
| 157 | chmod +x "$MOUSESH" | 153 | chmod +x "$MOUSESH" |
| 158 | start_daemon "$SOCK34" "$OUT.mse.d" "app-mouse daemon never bound" --shell "$MOUSESH" | 154 | start_daemon "$SOCK34" "$OUT.mse.d" "app-mouse daemon never bound" --shell "$MOUSESH" |
| 159 | D31PID=$DPID | 155 | D31PID=$DPID |
| 160 | i=0 | 156 | wait_grid "$SOCK34" "app-holds-the-mouse" "app-mouse session never armed" |
| 161 | until "$MUX" d dump --sock "$SOCK34" | grep -q "app-holds-the-mouse"; do | ||
| 162 | i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: app-mouse session never armed"; exit 1; } | ||
| 163 | sleep 0.1 | ||
| 164 | done | ||
| 165 | # The modes are already set when this client attaches, so they arrive in | 157 | # The modes are already set when this client attaches, so they arrive in |
| 166 | # the attach's own term_modes and there is no race to settle for. The echo | 158 | # the attach's own term_modes and there is no race to settle for. The echo |
| 167 | # is the assertion: cat is in canonical mode with ECHOCTL, so bytes that | 159 | # is the assertion: cat is in canonical mode with ECHOCTL, so bytes that |
| @@ -226,9 +218,13 @@ EOF | |||
| 226 | chmod +x "$LESSSH" | 218 | chmod +x "$LESSSH" |
| 227 | start_daemon "$SOCK35" "$OUT.pgr.d" "pager daemon never bound" --shell "$LESSSH" | 219 | start_daemon "$SOCK35" "$OUT.pgr.d" "pager daemon never bound" --shell "$LESSSH" |
| 228 | D32PID=$DPID | 220 | D32PID=$DPID |
| 221 | # Not wait_grid: the needle is anchored (`-qx`, a row that is EXACTLY the | ||
| 222 | # file's last line, so a half-painted `2` does not pass) and the dump is | ||
| 223 | # trimmed to the last rows, because a pager's whole grid is 24 rows of | ||
| 224 | # numbers that say nothing about why it stopped short. | ||
| 229 | i=0 | 225 | i=0 |
| 230 | until "$MUX" d dump --sock "$SOCK35" | grep -qx "200"; do | 226 | until "$MUX" d dump --sock "$SOCK35" | grep -qx "200"; do |
| 231 | i=$((i+1)); [ "$i" -lt 100 ] || { | 227 | i=$((i+1)); [ "$i" -lt $(( 100 * TIME_SCALE )) ] || { |
| 232 | echo "e2e FAIL: pager never reached the end of the file:" | 228 | echo "e2e FAIL: pager never reached the end of the file:" |
| 233 | "$MUX" d dump --sock "$SOCK35" | tail -3; exit 1; } | 229 | "$MUX" d dump --sock "$SOCK35" | tail -3; exit 1; } |
| 234 | sleep 0.1 | 230 | sleep 0.1 |
| @@ -294,9 +290,12 @@ D33PID=$DPID | |||
| 294 | RC=$? | 290 | RC=$? |
| 295 | [ "$RC" -eq 0 ] || { | 291 | [ "$RC" -eq 0 ] || { |
| 296 | echo "e2e FAIL: pipe stdin: the client exited $RC:"; cat "$OUT.pipe"; exit 1; } | 292 | echo "e2e FAIL: pipe stdin: the client exited $RC:"; cat "$OUT.pipe"; exit 1; } |
| 293 | # Not wait_grid: this asks `mux a capture`, the agent surface, not | ||
| 294 | # `mux d dump` — the piped client is the thing under test, so the reading | ||
| 295 | # has to come down the same road an agent would use. | ||
| 297 | i=0 | 296 | i=0 |
| 298 | until timeout 20 "$MUX" a capture --sock "$SOCK36" 2>&1 | grep -qF -- "world"; do | 297 | until timeout 20 "$MUX" a capture --sock "$SOCK36" 2>&1 | grep -qF -- "world"; do |
| 299 | i=$((i+1)); [ "$i" -lt 100 ] || { | 298 | i=$((i+1)); [ "$i" -lt $(( 100 * TIME_SCALE )) ] || { |
| 300 | echo "e2e FAIL: pipe stdin: the piped line never reached the pty:" | 299 | echo "e2e FAIL: pipe stdin: the piped line never reached the pty:" |
| 301 | timeout 20 "$MUX" a capture --sock "$SOCK36"; exit 1; } | 300 | timeout 20 "$MUX" a capture --sock "$SOCK36"; exit 1; } |
| 302 | sleep 0.1 | 301 | sleep 0.1 |
| @@ -471,11 +470,7 @@ start_daemon "$SOCK41" "$OUT.zw.d" "wheel daemon never bound" --shell "$ZWHEELSH | |||
| 471 | D38PID=$DPID | 470 | D38PID=$DPID |
| 472 | # The wall attaches only once seq has finished, scenario 42's reason: the | 471 | # The wall attaches only once seq has finished, scenario 42's reason: the |
| 473 | # page the wheel fetches has to be content this tile was never sent. | 472 | # page the wheel fetches has to be content this tile was never sent. |
| 474 | i=0 | 473 | wait_grid "$SOCK41" "wln100" "wheel: session never finished seq" |
| 475 | until "$MUX" d dump --sock "$SOCK41" | grep -q "wln100"; do | ||
| 476 | i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: wheel: session never finished seq"; exit 1; } | ||
| 477 | sleep 0.1 | ||
| 478 | done | ||
| 479 | set +e | 474 | set +e |
| 480 | no_saved_tree "$ZWSTATE" | 475 | no_saved_tree "$ZWSTATE" |
| 481 | XDG_STATE_HOME="$ZWSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ | 476 | XDG_STATE_HOME="$ZWSTATE" timeout 90 "$PTYCLIENT" --cols 80 --rows 24 \ |
| @@ -541,11 +536,7 @@ EOF | |||
| 541 | chmod +x "$ZMOUSESH" | 536 | chmod +x "$ZMOUSESH" |
| 542 | start_daemon "$SOCK42" "$OUT.zm2.d" "app-mouse daemon never bound" --shell "$ZMOUSESH" | 537 | start_daemon "$SOCK42" "$OUT.zm2.d" "app-mouse daemon never bound" --shell "$ZMOUSESH" |
| 543 | D39PID=$DPID | 538 | D39PID=$DPID |
| 544 | i=0 | 539 | wait_grid "$SOCK42" "mapp-holds-the-mouse" "app mouse: session never armed" |
| 545 | until "$MUX" d dump --sock "$SOCK42" | grep -q "mapp-holds-the-mouse"; do | ||
| 546 | i=$((i+1)); [ "$i" -lt 100 ] || { echo "e2e FAIL: app mouse: session never armed"; exit 1; } | ||
| 547 | sleep 0.1 | ||
| 548 | done | ||
| 549 | # The echo is the assertion: cat is in canonical mode with ECHOCTL, so bytes | 540 | # The echo is the assertion: cat is in canonical mode with ECHOCTL, so bytes |
| 550 | # that reach the pty come back as `^[[<64;10;5M` and bytes that do not, do | 541 | # that reach the pty come back as `^[[<64;10;5M` and bytes that do not, do |
| 551 | # not. The tile claims the terminal on its first pass, so the modes are | 542 | # not. The tile claims the terminal on its first pass, so the modes are |
test/e2e_lib.sh
| Old | New | ||
|---|---|---|---|
| @@ -618,6 +618,40 @@ wait_sessions() { | |||
| 618 | exit 1 | 618 | exit 1 |
| 619 | } | 619 | } |
| 620 | 620 | ||
| 621 | # wait_until TENTHS LABEL PREDICATE [DUMP] — poll a shell PREDICATE until it | ||
| 622 | # succeeds, for TENTHS tenths of a second scaled by TIME_SCALE, then ASSERT | ||
| 623 | # it and print DUMP on failure. wait_grid's discipline for the waits whose | ||
| 624 | # subject is not a daemon grid — an HTTP body from the browser hub, a | ||
| 625 | # fixture's capture file. The budget is spelled in tenths because that is | ||
| 626 | # what the hand-rolled loops it replaces counted, so a conversion is one | ||
| 627 | # number moved and nothing else. | ||
| 628 | # | ||
| 629 | # PREDICATE and DUMP are EVALUATED ON EVERY TICK, so pass them single-quoted | ||
| 630 | # and let the expansion happen here: a `$(curl ...)` that the call site | ||
| 631 | # expanded once would poll its own first answer forever and time out on a | ||
| 632 | # wall that changed a tick later. | ||
| 633 | # | ||
| 634 | # eval rather than running "$@" as argv: every predicate these waits need is | ||
| 635 | # a pipeline or a command substitution, and neither survives being carried | ||
| 636 | # as a command and its arguments. | ||
| 637 | wait_until() { | ||
| 638 | _i=0 | ||
| 639 | while [ "$_i" -lt $(( $1 * TIME_SCALE )) ]; do | ||
| 640 | # First command of an AND-OR list, which set -e exempts (wait_grid's | ||
| 641 | # shape): a predicate that is false on this tick is the normal case | ||
| 642 | # and must not abort the group. | ||
| 643 | eval "$3" && return 0 | ||
| 644 | sleep 0.1; _i=$((_i+1)) | ||
| 645 | done | ||
| 646 | echo "e2e FAIL: $2" | ||
| 647 | # `|| true` because the dump is read in the failure state that provoked | ||
| 648 | # it — a curl against a hub that died exits 7, and that status would | ||
| 649 | # become this function's under set -e, replacing the exit 1 the runner | ||
| 650 | # reads with a number that means something else. | ||
| 651 | if [ -n "${4:-}" ]; then eval "$4" || true; fi | ||
| 652 | exit 1 | ||
| 653 | } | ||
| 654 | |||
| 621 | # --- tiles attach once each: did the daemon see a second? ------------- | 655 | # --- tiles attach once each: did the daemon see a second? ------------- |
| 622 | # | 656 | # |
| 623 | # The focus model's whole claim is that a tile's connection is its own — | 657 | # The focus model's whole claim is that a tile's connection is its own — |