a73x

84a7a44f

test(e2e): say why the paste leg waits the way it does

a73x   2026-08-15 17:50

Commit message
test(e2e): say why the paste leg waits the way it does

The dependency check moves to the head of the file: a box without nvim
should hear about it before 25 scenarios and a QUIC leg, not after. The
decision to require rather than guard stays, and now retires M9's "rawmode,
not nvim, in CI" on both its halves — presence, by refusing to run, and
redraw nondeterminism, by waiting only on conditions and on silence.

Three things the file could not previously tell a future editor: that the
absent 2004h barrier is deliberate, because adding one converts a broken
mirror from a staircase in the file into a timeout that never reaches the
assertion; that the mode needle is -- INSERT -- because a bare INSERT
differs from the shell's echo of "startinsert" by capitalisation alone; and
that what makes the written file safe to read is nvim's ordering, with the
settle as slack on top of that guarantee rather than a measurement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

test/e2e.sh
Old New
@@ -20,6 +20,19 @@ MUXWEB="$8"
20 # address a session by name without attaching to it. Everything else muxa 20 # address a session by name without attaching to it. Everything else muxa
21 # does is test/agent.sh's business. 21 # does is test/agent.sh's business.
22 MUXA="$9" 22 MUXA="$9"
23
24 # The one thing this suite needs that it does not build: a real editor, for
25 # the paste scenario far below. Checked HERE rather than there because there
26 # is 25 scenarios, several daemons and a QUIC leg away — minutes of work to
27 # report a missing package. Required rather than guarded; that trade, and why
28 # a `command -v` skip would be worse than a hard failure, is argued at the
29 # scenario itself.
30 command -v nvim > /dev/null 2>&1 || {
31 echo "e2e FAIL: this suite needs nvim (the paste scenario asserts on a file"
32 echo " a real editor wrote); install it, or lose the only check that"
33 echo " proves bracketed paste works rather than merely arrives"
34 exit 1; }
35
23 SOCK="${TMPDIR:-/tmp}/muxd-e2e-$$.sock" 36 SOCK="${TMPDIR:-/tmp}/muxd-e2e-$$.sock"
24 OUT="${TMPDIR:-/tmp}/mux-e2e-out-$$" 37 OUT="${TMPDIR:-/tmp}/mux-e2e-out-$$"
25 # M10: hermetic XDG homes. Key-default scenarios must see OUR key or none, 38 # M10: hermetic XDG homes. Key-default scenarios must see OUR key or none,
@@ -2754,12 +2767,19 @@ ok "the session's OSC 52 reaches the host terminal"
2754 # nvim is REQUIRED, not guarded with `command -v`. A guard would let a box 2767 # nvim is REQUIRED, not guarded with `command -v`. A guard would let a box
2755 # without nvim skip this quietly, and a silently-skipped scenario is exactly 2768 # without nvim skip this quietly, and a silently-skipped scenario is exactly
2756 # what the OK_COUNT pin at the bottom exists to catch — so the dependency 2769 # what the OK_COUNT pin at the bottom exists to catch — so the dependency
2757 # fails loudly here instead of disappearing. 2770 # fails loudly. The check itself is HOISTED to the head of this file, next to
2758 command -v nvim > /dev/null 2>&1 || { 2771 # the argument binding: a box without nvim should hear about it before it
2759 echo "e2e FAIL: this suite needs nvim (the paste scenario asserts on a file" 2772 # spends minutes getting here.
2760 echo " a real editor wrote); install it, or lose the only check that" 2773 #
2761 echo " proves bracketed paste works rather than merely arrives" 2774 # That reverses M9's standing rule (2026-08-08-m9-prediction.md: "rawmode
2762 exit 1; } 2775 # helper, not nvim, in CI"), which gave two reasons. Its PRESENCE half is
2776 # answered by the check above — refusing to run beats running less. Its
2777 # NONDETERMINISM half — nvim's redraw timing — is answered by construction:
2778 # every wait below is on a CONDITION (a needle that must arrive) or on
2779 # SILENCE (settle), and neither is a bet on when a redraw happens. If this
2780 # scenario ever flakes, that is the property to check first; a wait added
2781 # here that keys off redraw timing would be the regression.
2782 #
2763 # Unquoted heredoc, like the clipboard leg above: the send verb carries 2783 # Unquoted heredoc, like the clipboard leg above: the send verb carries
2764 # $OUT's per-run path. `\x1b`, `\r`, `\n` and `\x1c` are the fixture's 2784 # $OUT's per-run path. `\x1b`, `\r`, `\n` and `\x1c` are the fixture's
2765 # escapes and the shell leaves every one of them alone. 2785 # escapes and the shell leaves every one of them alone.
@@ -2768,22 +2788,52 @@ command -v nvim > /dev/null 2>&1 || {
2768 # their config, so 'autoindent' is set here and nowhere else — the staircase 2788 # their config, so 'autoindent' is set here and nowhere else — the staircase
2769 # this measures has to come from the option this line spells. 2789 # this measures has to come from the option this line spells.
2770 # 2790 #
2771 # ESC and `:wq` are two sends with a settle between them: delivered in one 2791 # `timeout 60` is under the heredoc's own deadlines (120s if every one of
2772 # read, nvim's input parser is entitled to read `\x1b:` as Alt-: rather than 2792 # them ran long), and that is deliberate rather than copied from the leg
2773 # as leaving insert mode, and the file would never be written. 2793 # above: those deadlines are per-verb ceilings for a fixture that is still
2794 # talking, and a run that genuinely needs more than 60s wall has something
2795 # wrong with it that a bare `exited 124` describes about as well as a
2796 # fixture dump would. The cost is real — 124 loses the "last N bytes
2797 # received" line — so anything raised here should raise this too.
2774 set +e 2798 set +e
2775 timeout 60 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.paste" --err "$OUT.paste.err" \ 2799 timeout 60 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.paste" --err "$OUT.paste.err" \
2776 -- "$MUX" --sock "$SOCK" > "$OUT.paste.log" 2>&1 <<EOF 2800 -- "$MUX" --sock "$SOCK" > "$OUT.paste.log" 2>&1 <<EOF
2777 expect \x1b[?1049h 15000 2801 expect \x1b[?1049h 15000
2778 settle 400 15000 2802 settle 400 15000
2779 send nvim -u NONE -i NONE -c "set autoindent" -c startinsert $OUT.pasted.txt\n 2803 send nvim -u NONE -i NONE -c "set autoindent" -c startinsert $OUT.pasted.txt\n
2780 expect INSERT 20000 2804 # The mode indicator, whole. The line above echoes "startinsert", so a bare
2805 # INSERT needle would differ from the echo by capitalisation alone (the M12
2806 # tp2 rule passing on a technicality); the dashes are what that echo cannot
2807 # spell.
2808 #
2809 # And NOT an expect for the 2004h escape, deliberately, however much this
2810 # reads like the place for one. The fixture watches for that escape without
2811 # waiting on it: it brackets a paste when it has seen one and sends bare
2812 # bytes when it has not. A needle for it here would wait, and that changes
2813 # what a broken mirror looks like — instead of the file below holding the
2814 # staircase, the run would time out on the needle and never reach the
2815 # assertion at all. The escape ARRIVING is already pinned, in client.zig's
2816 # unit tests. What THIS leg measures is pasting working, and it can only
2817 # measure that if the escape's absence is allowed through to the editor.
2818 expect -- INSERT -- 20000
2781 settle 500 20000 2819 settle 500 20000
2782 paste if x:\r a = 1\r b = 2\r 2820 paste if x:\r a = 1\r b = 2\r
2783 settle 500 20000 2821 settle 500 20000
2822 # Two sends, not one. Delivered in the same read as the colon, nvim's input
2823 # parser is entitled to read the ESC as the Alt- prefix of Alt-colon rather
2824 # than as leaving insert mode, and the file would never be written. The
2825 # settle between them is a condition, not a timer: nvim repaints the mode
2826 # line on leaving insert, so the silence ending this verb IS that repaint
2827 # being over, and it outlasts by far the 50ms ttimeoutlen the ambiguity
2828 # actually turns on.
2784 send \x1b 2829 send \x1b
2785 settle 300 15000 2830 settle 300 15000
2786 send :wq\r 2831 send :wq\r
2832 # What makes the file safe to read after this is the ORDERING, not the
2833 # number: nvim writes the buffer before it exits, and its exit repaint is
2834 # the last traffic the master ever sees, so silence after that repaint
2835 # cannot precede the write. The 800 is slack on top of a guarantee, not a
2836 # measurement of how long nvim takes.
2787 settle 800 20000 2837 settle 800 20000
2788 send \x1c 2838 send \x1c
2789 waitexit 10000 2839 waitexit 10000