c3830637
test: e2e gates the switch by what moved and what did not
a73x 2026-08-19 17:17
Commit message
test/e2e.sh
| Old | New | ||
|---|---|---|---|
| @@ -252,6 +252,12 @@ D23PID="" | |||
| 252 | SOCK27="${TMPDIR:-/tmp}/muxd-e2e-prefix-$$.sock" | 252 | SOCK27="${TMPDIR:-/tmp}/muxd-e2e-prefix-$$.sock" |
| 253 | D24PID="" | 253 | D24PID="" |
| 254 | 254 | ||
| 255 | # Ctrl-\ c (create a session and switch to it): its own daemon, because the | ||
| 256 | # assertion is about which sessions EXIST — another block's session on the | ||
| 257 | # same daemon would change the name the chord picks. | ||
| 258 | SOCK28="${TMPDIR:-/tmp}/muxd-e2e-newsess-$$.sock" | ||
| 259 | D25PID="" | ||
| 260 | |||
| 255 | # One counter out of a MUX_PREDICT_STATS line. The client prints exactly one | 261 | # One counter out of a MUX_PREDICT_STATS line. The client prints exactly one |
| 256 | # such line on exit; every field is a key=value pair, so a rename or reorder | 262 | # such line on exit; every field is a key=value pair, so a rename or reorder |
| 257 | # in the client shows up here as an empty read rather than a wrong number. | 263 | # in the client shows up here as an empty read rather than a wrong number. |
| @@ -773,6 +779,7 @@ cleanup() { | |||
| 773 | [ -n "$D22PID" ] && kill "$D22PID" 2>/dev/null || true | 779 | [ -n "$D22PID" ] && kill "$D22PID" 2>/dev/null || true |
| 774 | [ -n "$D23PID" ] && kill "$D23PID" 2>/dev/null || true | 780 | [ -n "$D23PID" ] && kill "$D23PID" 2>/dev/null || true |
| 775 | [ -n "$D24PID" ] && kill "$D24PID" 2>/dev/null || true | 781 | [ -n "$D24PID" ] && kill "$D24PID" 2>/dev/null || true |
| 782 | [ -n "$D25PID" ] && kill "$D25PID" 2>/dev/null || true | ||
| 776 | # The stops still precede the socket rm below, like SOCK14-17 above: | 783 | # The stops still precede the socket rm below, like SOCK14-17 above: |
| 777 | # unlinking a socket first would leave a live daemon nothing could reach | 784 | # unlinking a socket first would leave a live daemon nothing could reach |
| 778 | # by path. | 785 | # by path. |
| @@ -782,6 +789,7 @@ cleanup() { | |||
| 782 | [ -S "$SOCK25" ] && "$MUXD" stop --sock "$SOCK25" 2>/dev/null || true | 789 | [ -S "$SOCK25" ] && "$MUXD" stop --sock "$SOCK25" 2>/dev/null || true |
| 783 | [ -S "$SOCK26" ] && "$MUXD" stop --sock "$SOCK26" 2>/dev/null || true | 790 | [ -S "$SOCK26" ] && "$MUXD" stop --sock "$SOCK26" 2>/dev/null || true |
| 784 | [ -S "$SOCK27" ] && "$MUXD" stop --sock "$SOCK27" 2>/dev/null || true | 791 | [ -S "$SOCK27" ] && "$MUXD" stop --sock "$SOCK27" 2>/dev/null || true |
| 792 | [ -S "$SOCK28" ] && "$MUXD" stop --sock "$SOCK28" 2>/dev/null || true | ||
| 785 | 793 | ||
| 786 | # ---- the leak sweep (hygiene kit, 6a) ---- | 794 | # ---- the leak sweep (hygiene kit, 6a) ---- |
| 787 | # Here rather than at the bottom of the file, which `set -e` reaches only | 795 | # Here rather than at the bottom of the file, which `set -e` reaches only |
| @@ -793,7 +801,7 @@ cleanup() { | |||
| 793 | "$D7PID" "$D9PID" "$D10PID" "$D12PID" "$D13PID" "$SPID" "$TPID" \ | 801 | "$D7PID" "$D9PID" "$D10PID" "$D12PID" "$D13PID" "$SPID" "$TPID" \ |
| 794 | "$GPID" "$APID" "$PAPID" "$HAPID" "$HDPID" \ | 802 | "$GPID" "$APID" "$PAPID" "$HAPID" "$HDPID" \ |
| 795 | "$D14PID" "$D15PID" "$D16PID" "$D17PID" "$D18PID" "$D19PID" \ | 803 | "$D14PID" "$D15PID" "$D16PID" "$D17PID" "$D18PID" "$D19PID" \ |
| 796 | "$D20PID" "$D21PID" "$D22PID" "$D23PID" "$D24PID" | 804 | "$D20PID" "$D21PID" "$D22PID" "$D23PID" "$D24PID" "$D25PID" |
| 797 | _leak=0 | 805 | _leak=0 |
| 798 | leak_sweep "$_rc" || _leak=1 | 806 | leak_sweep "$_rc" || _leak=1 |
| 799 | 807 | ||
| @@ -4466,6 +4474,114 @@ assert_stopped "$SOCK27" "$D24PID" "prefix" "$OUT.pfxstop" | |||
| 4466 | D24PID="" | 4474 | D24PID="" |
| 4467 | ok "Ctrl-\\ prefix: an unknown chord is swallowed, Ctrl-\\ d detaches" | 4475 | ok "Ctrl-\\ prefix: an unknown chord is swallowed, Ctrl-\\ d detaches" |
| 4468 | 4476 | ||
| 4477 | # ---- M2: Ctrl-\ c creates a session and switches to it ------------------- | ||
| 4478 | # | ||
| 4479 | # The claim is a MOVE, which takes two witnesses: the client ends up | ||
| 4480 | # somewhere new, and the place it left is still there and unchanged. | ||
| 4481 | # | ||
| 4482 | # * one pty client attaches to the default session, runs a marker, types | ||
| 4483 | # `\x1cc`, and runs a second marker. Both markers reach a shell — the | ||
| 4484 | # second one proves the client is attached to a session that works, not | ||
| 4485 | # merely alive after the chord. | ||
| 4486 | # * `muxa status --session 1` proves the daemon really created the name | ||
| 4487 | # the client picked (lowest free integer beside the default "0"). | ||
| 4488 | # * the default session's capture still holds the FIRST marker and not the | ||
| 4489 | # second. Without that pair the same log would be produced by a chord | ||
| 4490 | # that did nothing at all. | ||
| 4491 | # | ||
| 4492 | # The leg then keeps pressing until the daemon runs out of sessions, because | ||
| 4493 | # the refusal is the branch with the interesting answer: `.refused` is the | ||
| 4494 | # one attach failure the client recovers from instead of exiting, and | ||
| 4495 | # nothing else in the suite reaches it. See the block after the leg for the | ||
| 4496 | # three things "recovered" has to mean. | ||
| 4497 | # | ||
| 4498 | # The re-attach is waited for by its alternate-screen entry rather than a | ||
| 4499 | # fixed sleep: the switch tears the terminal down and builds it back up, so | ||
| 4500 | # the second `?1049h` is the client saying it has a new session on screen. | ||
| 4501 | "$MUXD" run --sock "$SOCK28" --shell /bin/sh > "$OUT.nsw.d" 2>&1 & | ||
| 4502 | D25PID=$! | ||
| 4503 | wait_sock "$SOCK28" "$OUT.nsw.d" "new-session daemon never bound" | ||
| 4504 | |||
| 4505 | set +e | ||
| 4506 | timeout 40 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.nsw" --err "$OUT.nsw.err" \ | ||
| 4507 | -- "$MUX" --sock "$SOCK28" > "$OUT.nsw.log" 2>&1 <<'EOF' | ||
| 4508 | expect \x1b[?1049h 15000 | ||
| 4509 | settle 400 15000 | ||
| 4510 | send printf 'm2a-%s\\n' pin\n | ||
| 4511 | expect m2a-pin 15000 | ||
| 4512 | settle 400 15000 | ||
| 4513 | send \x1cc | ||
| 4514 | expect \x1b[?1049h 15000 | ||
| 4515 | settle 400 15000 | ||
| 4516 | send printf 'm2b-%s\\n' pin\n | ||
| 4517 | expect m2b-pin 15000 | ||
| 4518 | settle 400 15000 | ||
| 4519 | send \x1cc | ||
| 4520 | expect \x1b[?1049h 15000 | ||
| 4521 | settle 400 15000 | ||
| 4522 | send \x1cc | ||
| 4523 | expect \x1b[?1049h 15000 | ||
| 4524 | settle 400 15000 | ||
| 4525 | send printf 'm2c-%s\\n' pin\n | ||
| 4526 | expect m2c-pin 15000 | ||
| 4527 | settle 400 15000 | ||
| 4528 | send \x1cc | ||
| 4529 | expect \x1b[?1049h 15000 | ||
| 4530 | settle 400 15000 | ||
| 4531 | send printf 'm2d-%s\\n' pin\n | ||
| 4532 | expect m2d-pin 15000 | ||
| 4533 | settle 400 15000 | ||
| 4534 | send \x1c\x1c | ||
| 4535 | waitexit 10000 | ||
| 4536 | EOF | ||
| 4537 | RC=$? | ||
| 4538 | set -e | ||
| 4539 | [ "$RC" -eq 0 ] || { | ||
| 4540 | echo "e2e FAIL: new session: ptyclient leg exited $RC (did \\x1cc switch?):" | ||
| 4541 | cat "$OUT.nsw.log"; exit 1; } | ||
| 4542 | "$MUXA" status --sock "$SOCK28" --session 1 > "$OUT.nswst" 2>&1 || { | ||
| 4543 | echo "e2e FAIL: new session: the chord did not create session 1:" | ||
| 4544 | cat "$OUT.nswst"; exit 1; } | ||
| 4545 | # The session the client LEFT: still live, still holding what it was shown | ||
| 4546 | # before the chord, and innocent of everything typed after it. | ||
| 4547 | "$MUXA" capture --sock "$SOCK28" > "$OUT.nswcap" 2>&1 | ||
| 4548 | grep -q "m2a-pin" "$OUT.nswcap" || { | ||
| 4549 | echo "e2e FAIL: new session: the default session lost its own marker:" | ||
| 4550 | cat "$OUT.nswcap"; exit 1; } | ||
| 4551 | grep -q "m2b-pin" "$OUT.nswcap" && { | ||
| 4552 | echo "e2e FAIL: new session: the second marker ran in the OLD session" | ||
| 4553 | cat "$OUT.nswcap"; exit 1; } | ||
| 4554 | # The fourth chord had nowhere to go: max_sessions is 4 and the leg filled | ||
| 4555 | # the table (0, 1, 2, 3), so the daemon answered the attach with an | ||
| 4556 | # exit_status before a single frame of state. That refusal is NOT fatal — | ||
| 4557 | # the switch's arrival knows the name it left — and the three claims below | ||
| 4558 | # are what "fell back" means, none of which the log shows on its own. | ||
| 4559 | # | ||
| 4560 | # * the client said so, once, in its own words. Its stderr is ptyclient's | ||
| 4561 | # --err file, not the pty, so the message is read there. | ||
| 4562 | grep -q "cannot create a new session" "$OUT.nsw.err" || { | ||
| 4563 | echo "e2e FAIL: new session: the refused switch said nothing:" | ||
| 4564 | cat "$OUT.nsw.err"; exit 1; } | ||
| 4565 | # * it is back in session 3, the one it typed the chord in — proved | ||
| 4566 | # POSITIVELY, by a marker run AFTER the refusal landing in that | ||
| 4567 | # session's grid beside the one from before it. A capture that held | ||
| 4568 | # only m2c-pin would equally describe a client that died there. | ||
| 4569 | "$MUXA" capture --sock "$SOCK28" --session 3 > "$OUT.nswcap3" 2>&1 | ||
| 4570 | grep -q "m2c-pin" "$OUT.nswcap3" || { | ||
| 4571 | echo "e2e FAIL: new session: session 3 lost the marker from before the refusal:" | ||
| 4572 | cat "$OUT.nswcap3"; exit 1; } | ||
| 4573 | grep -q "m2d-pin" "$OUT.nswcap3" || { | ||
| 4574 | echo "e2e FAIL: new session: the refused switch did not come back to session 3:" | ||
| 4575 | cat "$OUT.nswcap3"; exit 1; } | ||
| 4576 | # * and the name it was reaching for was never created. Without this the | ||
| 4577 | # leg would pass on a daemon that quietly grew a fifth slot. | ||
| 4578 | "$MUXA" status --sock "$SOCK28" --session 4 > "$OUT.nswst4" 2>&1 && { | ||
| 4579 | echo "e2e FAIL: new session: the daemon created a fifth session:" | ||
| 4580 | cat "$OUT.nswst4"; exit 1; } | ||
| 4581 | assert_stopped "$SOCK28" "$D25PID" "new session" "$OUT.nswstop" | ||
| 4582 | D25PID="" | ||
| 4583 | ok "Ctrl-\\ c: a new session is created and switched to, the old one intact; a full daemon refuses and the client stays where it was" | ||
| 4584 | |||
| 4469 | # The long-lived daemon has served every scenario that wanted it; stop it | 4585 | # The long-lived daemon has served every scenario that wanted it; stop it |
| 4470 | # NOW so its allocator verdict is written while the suite is still running | 4586 | # NOW so its allocator verdict is written while the suite is still running |
| 4471 | # and can say so. SIGTERM runs the clean-shutdown path, so the defer chain | 4587 | # and can say so. SIGTERM runs the clean-shutdown path, so the defer chain |
| @@ -4481,7 +4597,7 @@ DPID="" | |||
| 4481 | 4597 | ||
| 4482 | # The pins. Literals, not variables set from counting something else — | 4598 | # The pins. Literals, not variables set from counting something else — |
| 4483 | # "assert the literal, never the constant the code under test reads" | 4599 | # "assert the literal, never the constant the code under test reads" |
| 4484 | # (decisions.md, M10). 33 scenario checkpoints; 35 convergence points. | 4600 | # (decisions.md, M10). 35 scenario checkpoints; 35 convergence points. |
| 4485 | # Anyone adding a scenario updates these by hand, on purpose. | 4601 | # Anyone adding a scenario updates these by hand, on purpose. |
| 4486 | # | 4602 | # |
| 4487 | # M18 added three checkpoints and no convergence points: its wall block | 4603 | # M18 added three checkpoints and no convergence points: its wall block |
| @@ -4507,9 +4623,12 @@ DPID="" | |||
| 4507 | # — bytes a wall painted to a terminal — which no grid dump can witness. | 4623 | # — bytes a wall painted to a terminal — which no grid dump can witness. |
| 4508 | # The 34th is the Ctrl-\ prefix, and no convergence point for that same | 4624 | # The 34th is the Ctrl-\ prefix, and no convergence point for that same |
| 4509 | # reason: its subject is what the client REFUSED to forward, which a grid | 4625 | # reason: its subject is what the client REFUSED to forward, which a grid |
| 4510 | # that never saw the bytes cannot testify to either way. | 4626 | # that never saw the bytes cannot testify to either way. The 35th is |
| 4511 | [ "$OK_COUNT" = "34" ] || { | 4627 | # Ctrl-\ c, and no convergence point either: what it compares is TWO |
| 4512 | echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 34 —" | 4628 | # sessions' grids against each other — one holding a marker, one refusing |
| 4629 | # to — and assert_converged speaks about one session at a time. | ||
| 4630 | [ "$OK_COUNT" = "35" ] || { | ||
| 4631 | echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 35 —" | ||
| 4513 | echo " a scenario was added (update the pin) or silently lost" | 4632 | echo " a scenario was added (update the pin) or silently lost" |
| 4514 | exit 1 | 4633 | exit 1 |
| 4515 | } | 4634 | } |
| @@ -4517,4 +4636,4 @@ DPID="" | |||
| 4517 | echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 35" | 4636 | echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 35" |
| 4518 | exit 1 | 4637 | exit 1 |
| 4519 | } | 4638 | } |
| 4520 | echo "e2e OK (34 scenarios, 35 convergence points)" | 4639 | echo "e2e OK (35 scenarios, 35 convergence points)" |