24ccce50
test(e2e): the gate fix, pinned by a test that fails without it
a73x 2026-08-15 20:22
Commit message
test/e2e.sh
| Old | New | ||
|---|---|---|---|
| @@ -791,6 +791,8 @@ cleanup() { | |||
| 791 | # so without this line the residue survives exactly the runs where the | 791 | # so without this line the residue survives exactly the runs where the |
| 792 | # leg failed and somebody is looking at it. | 792 | # leg failed and somebody is looking at it. |
| 793 | rm -f "$OUT.title" "$OUT.title.err" "$OUT.title.log" "$OUT.title.sh" | 793 | rm -f "$OUT.title" "$OUT.title.err" "$OUT.title.log" "$OUT.title.sh" |
| 794 | # ...and the non-tty capture that leg's session feeds. | ||
| 795 | rm -f "$OUT.nogate" | ||
| 794 | # ...and its other half: the paste capture and the file nvim wrote, which | 796 | # ...and its other half: the paste capture and the file nvim wrote, which |
| 795 | # IS that scenario's assertion rather than a log beside it. | 797 | # IS that scenario's assertion rather than a log beside it. |
| 796 | rm -f "$OUT.paste" "$OUT.paste.err" "$OUT.paste.log" "$OUT.pasted.txt" | 798 | rm -f "$OUT.paste" "$OUT.paste.err" "$OUT.paste.log" "$OUT.pasted.txt" |
| @@ -2777,8 +2779,15 @@ ok "the session's OSC 52 reaches the host terminal" | |||
| 2777 | # Emitted by a FILE, never typed, for the M12 reason spelled out on the | 2779 | # Emitted by a FILE, never typed, for the M12 reason spelled out on the |
| 2778 | # clipboard leg above: the shell echoes what is typed, so a needle that | 2780 | # clipboard leg above: the shell echoes what is typed, so a needle that |
| 2779 | # could arrive as an echo would pass on a client that forwards nothing. | 2781 | # could arrive as an echo would pass on a client that forwards nothing. |
| 2782 | # | ||
| 2783 | # The `?2004h` is not this leg's business — it is the NEXT leg's control. | ||
| 2784 | # It leaves the session armed for bracketed paste as well as titled, so | ||
| 2785 | # that when the non-tty client below attaches to this same daemon there are | ||
| 2786 | # two side channels the daemon is definitely emitting, and "neither | ||
| 2787 | # arrived" cannot be confused with "neither was sent". | ||
| 2780 | cat > "$OUT.title.sh" <<'TITLESH' | 2788 | cat > "$OUT.title.sh" <<'TITLESH' |
| 2781 | printf '\033]2;mux-e2e-title\007' | 2789 | printf '\033]2;mux-e2e-title\007' |
| 2790 | printf '\033[?2004h' | ||
| 2782 | printf 'TITLEDONE\n' | 2791 | printf 'TITLEDONE\n' |
| 2783 | TITLESH | 2792 | TITLESH |
| 2784 | set +e | 2793 | set +e |
| @@ -2842,9 +2851,53 @@ POP_N=$(occurrences "$OUT.title" "$POP_ESC") | |||
| 2842 | echo "e2e FAIL: $PUSH_N title pushes on one run, want exactly 1"; exit 1; } | 2851 | echo "e2e FAIL: $PUSH_N title pushes on one run, want exactly 1"; exit 1; } |
| 2843 | [ "$POP_N" -eq 1 ] || { | 2852 | [ "$POP_N" -eq 1 ] || { |
| 2844 | echo "e2e FAIL: $POP_N title pops on one run, want exactly 1"; exit 1; } | 2853 | echo "e2e FAIL: $POP_N title pops on one run, want exactly 1"; exit 1; } |
| 2854 | # The control for the leg that follows, asserted on THIS capture because | ||
| 2855 | # this is the run that had a pty: the daemon emits both side channels for | ||
| 2856 | # this session, right now. Without it, "neither reached the non-tty client" | ||
| 2857 | # would pass just as well on a daemon that had stopped sending either. | ||
| 2858 | grep -qaF "$(printf '\033[?2004h')" "$OUT.title" || { | ||
| 2859 | echo "e2e FAIL: the session armed bracketed paste but the tty client never heard" | ||
| 2860 | cat -v "$OUT.title"; exit 1; } | ||
| 2845 | rm_swept "$OUT.title" "$OUT.title.err" "$OUT.title.log" "$OUT.title.sh" | 2861 | rm_swept "$OUT.title" "$OUT.title.err" "$OUT.title.log" "$OUT.title.sh" |
| 2846 | ok "the window title reaches the host as OSC 0, pushed before and popped after" | 2862 | ok "the window title reaches the host as OSC 0, pushed before and popped after" |
| 2847 | 2863 | ||
| 2864 | # --- side channel: no terminal taken over, no terminal state changed ----- | ||
| 2865 | # | ||
| 2866 | # The bug this pins shipped and was fixed in c18c54b. `is_tty` is isatty of | ||
| 2867 | # STDIN — it gates raw mode and the alt-screen entry, both input concerns — | ||
| 2868 | # while the side-channel arms write to STDOUT. A client with stdin | ||
| 2869 | # redirected therefore never set `alt_screen`, never ran the teardown, and | ||
| 2870 | # yet happily wrote the session's title and `?2004h` to whatever stdout | ||
| 2871 | # was. Measured on the parent commit, against this same daemon: the capture | ||
| 2872 | # ended `...ESC[?2004h ESC]0;mux-e2e-title BEL` — the title set and the | ||
| 2873 | # paste mode set as the final bytes, and nothing anywhere to undo either. | ||
| 2874 | # | ||
| 2875 | # Same session as the leg above, which is what makes it meaningful: that | ||
| 2876 | # leg just proved with a pty that the daemon is sending both of these. | ||
| 2877 | # | ||
| 2878 | # stdin from /dev/null and stdout to a FILE, so mux takes nothing over. | ||
| 2879 | timeout 4 "$MUX" --sock "$SOCK" < /dev/null > "$OUT.nogate" 2>&1 || true | ||
| 2880 | # Positive control first: this capture must be of a client that ATTACHED | ||
| 2881 | # and painted, or every negative below it is vacuous. TITLEDONE is on that | ||
| 2882 | # session's screen from the leg above, so it arrives by the ordinary grid | ||
| 2883 | # path — the path that is supposed to keep working here. | ||
| 2884 | grep -qaF "TITLEDONE" "$OUT.nogate" || { | ||
| 2885 | echo "e2e FAIL: the non-tty client never painted the session (nothing to conclude)" | ||
| 2886 | cat -v "$OUT.nogate"; exit 1; } | ||
| 2887 | # -F on every needle, for the reason documented at the dead-transport leg: | ||
| 2888 | # without it these are malformed bracket expressions, grep exits 2, and | ||
| 2889 | # `grep && { fail }` reads an error as "not found". | ||
| 2890 | # | ||
| 2891 | # `ESC]0;` is the bare prefix rather than the full title: ANY title write is | ||
| 2892 | # the failure here, not just that one session's. | ||
| 2893 | for NEEDLE in "$(printf '\033]0;')" "$(printf '\033[?2004h')" "$(printf '\033[22;0t')"; do | ||
| 2894 | grep -qaF "$NEEDLE" "$OUT.nogate" && { | ||
| 2895 | echo "e2e FAIL: side channel written to a terminal mux never took over" | ||
| 2896 | cat -v "$OUT.nogate"; exit 1; } | ||
| 2897 | done | ||
| 2898 | rm_swept "$OUT.nogate" | ||
| 2899 | ok "a client that takes no terminal over changes no terminal state" | ||
| 2900 | |||
| 2848 | # --- side channel: a paste into a real editor keeps its indentation ------ | 2901 | # --- side channel: a paste into a real editor keeps its indentation ------ |
| 2849 | # | 2902 | # |
| 2850 | # The byte-level pin (?2004h in a host capture) proves the FRAME arrived; | 2903 | # The byte-level pin (?2004h in a host capture) proves the FRAME arrived; |
| @@ -3959,7 +4012,7 @@ DPID="" | |||
| 3959 | 4012 | ||
| 3960 | # The pins. Literals, not variables set from counting something else — | 4013 | # The pins. Literals, not variables set from counting something else — |
| 3961 | # "assert the literal, never the constant the code under test reads" | 4014 | # "assert the literal, never the constant the code under test reads" |
| 3962 | # (decisions.md, M10). 29 scenario checkpoints; 35 convergence points. | 4015 | # (decisions.md, M10). 30 scenario checkpoints; 35 convergence points. |
| 3963 | # Anyone adding a scenario updates these by hand, on purpose. | 4016 | # Anyone adding a scenario updates these by hand, on purpose. |
| 3964 | # | 4017 | # |
| 3965 | # M18 added three checkpoints and no convergence points: its wall block | 4018 | # M18 added three checkpoints and no convergence points: its wall block |
| @@ -3971,9 +4024,11 @@ DPID="" | |||
| 3971 | # paste leg added the 28th and no convergence point either: what it asserts | 4024 | # paste leg added the 28th and no convergence point either: what it asserts |
| 3972 | # on is a file an editor wrote, not a grid at all. The window-title leg | 4025 | # on is a file an editor wrote, not a grid at all. The window-title leg |
| 3973 | # added the 29th, and no convergence point for the side-channel reason | 4026 | # added the 29th, and no convergence point for the side-channel reason |
| 3974 | # again: it asserts on a capture, for bytes no grid carries. | 4027 | # again: it asserts on a capture, for bytes no grid carries. The 30th is |
| 3975 | [ "$OK_COUNT" = "29" ] || { | 4028 | # that leg's negative twin — the same bytes, asserted ABSENT from a client |
| 3976 | echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 29 —" | 4029 | # that took no terminal over — and carries no convergence point either. |
| 4030 | [ "$OK_COUNT" = "30" ] || { | ||
| 4031 | echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 30 —" | ||
| 3977 | echo " a scenario was added (update the pin) or silently lost" | 4032 | echo " a scenario was added (update the pin) or silently lost" |
| 3978 | exit 1 | 4033 | exit 1 |
| 3979 | } | 4034 | } |
| @@ -3981,4 +4036,4 @@ DPID="" | |||
| 3981 | echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 35" | 4036 | echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 35" |
| 3982 | exit 1 | 4037 | exit 1 |
| 3983 | } | 4038 | } |
| 3984 | echo "e2e OK (29 scenarios, 35 convergence points)" | 4039 | echo "e2e OK (30 scenarios, 35 convergence points)" |