d351788c
test: e2e asserts render-vs-dump convergence, with a control that fires
a73x 2026-08-09 17:20
Commit message
test/e2e.sh
| Old | New | ||
|---|---|---|---|
| @@ -8,6 +8,8 @@ MUX="$2" | |||
| 8 | # that makes a slow round trip without netem or root. | 8 | # that makes a slow round trip without netem or root. |
| 9 | RAWMODE="$3" | 9 | RAWMODE="$3" |
| 10 | DELAYPIPE="$4" | 10 | DELAYPIPE="$4" |
| 11 | # M11 convergence: replays a client capture into a grid (test/render.zig). | ||
| 12 | RENDER="$5" | ||
| 11 | SOCK="${TMPDIR:-/tmp}/muxd-e2e-$$.sock" | 13 | SOCK="${TMPDIR:-/tmp}/muxd-e2e-$$.sock" |
| 12 | OUT="${TMPDIR:-/tmp}/mux-e2e-out-$$" | 14 | OUT="${TMPDIR:-/tmp}/mux-e2e-out-$$" |
| 13 | # M10: hermetic XDG homes. Key-default scenarios must see OUR key or none, | 15 | # M10: hermetic XDG homes. Key-default scenarios must see OUR key or none, |
| @@ -129,6 +131,49 @@ proxy_pid() { | |||
| 129 | ps -eo pid,comm,args | awk -v s="$1" '$2=="muxd" && /proxy/ && index($0,s) {print $1}' | head -1 | 131 | ps -eo pid,comm,args | awk -v s="$1" '$2=="muxd" && /proxy/ && index($0,s) {print $1}' | head -1 |
| 130 | } | 132 | } |
| 131 | 133 | ||
| 134 | # --- M11: render-vs-dump convergence ----------------------------------- | ||
| 135 | # converged_quiet CLIENT_OUT SOCK — render the captured client stream and | ||
| 136 | # diff it against the daemon's grid, plain and styled. Nonzero on | ||
| 137 | # divergence, leaving CLIENT_OUT.{render,dump,diff,rvt,dvt} behind for | ||
| 138 | # inspection. CLIENT_OUT must be PURE client stdout: a capture taken with | ||
| 139 | # 2>&1 has exit messages and predict stats mixed into the escape stream. | ||
| 140 | converged_quiet() { | ||
| 141 | _co="$1"; _cs="$2" | ||
| 142 | "$RENDER" < "$_co" > "$_co.render" || return 1 | ||
| 143 | "$MUXD" dump --sock "$_cs" > "$_co.dump" || return 1 | ||
| 144 | # Trailing whitespace is a formatting difference between two correct | ||
| 145 | # grids (padded vs unpadded row ends), not a divergence. | ||
| 146 | sed 's/[[:space:]]*$//' "$_co.render" > "$_co.render.n" | ||
| 147 | sed 's/[[:space:]]*$//' "$_co.dump" > "$_co.dump.n" | ||
| 148 | diff -u "$_co.dump.n" "$_co.render.n" > "$_co.diff" || return 1 | ||
| 149 | # Styled, byte-for-byte: both sides come out of the same formatter, so | ||
| 150 | # equal grids are equal bytes. This is the half that sees a bled SGR | ||
| 151 | # or a leftover prediction underline — plain text dumps the same glyph | ||
| 152 | # either way, which is exactly why plain alone cannot carry M9's | ||
| 153 | # overlay-never-becomes-state invariant. | ||
| 154 | "$RENDER" --vt < "$_co" > "$_co.rvt" || return 1 | ||
| 155 | "$MUXD" dump --vt --sock "$_cs" > "$_co.dvt" || return 1 | ||
| 156 | cmp -s "$_co.dvt" "$_co.rvt" || return 1 | ||
| 157 | rm -f "$_co.render" "$_co.dump" "$_co.render.n" "$_co.dump.n" \ | ||
| 158 | "$_co.diff" "$_co.rvt" "$_co.dvt" | ||
| 159 | return 0 | ||
| 160 | } | ||
| 161 | |||
| 162 | # assert_converged CLIENT_OUT SOCK NAME — a scenario's LAST act, after | ||
| 163 | # quiesce and after the client detached: mid-scenario the dump is still | ||
| 164 | # moving, and another attach would claim the grid. | ||
| 165 | CONV_COUNT=0 | ||
| 166 | assert_converged() { | ||
| 167 | CONV_COUNT=$((CONV_COUNT + 1)) | ||
| 168 | converged_quiet "$1" "$2" || { | ||
| 169 | echo "e2e FAIL: $3: client render diverges from daemon grid (-daemon +client):" | ||
| 170 | head -40 "$1.diff" 2>/dev/null || \ | ||
| 171 | echo "(no plain diff: divergence is styled-only, or the helper failed)" | ||
| 172 | echo "grids left in $1.render / $1.dump / $1.rvt / $1.dvt" | ||
| 173 | exit 1 | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 132 | cleanup() { | 177 | cleanup() { |
| 133 | # `${DPID:-}` rather than `$DPID`: DPID is the one variable this trap | 178 | # `${DPID:-}` rather than `$DPID`: DPID is the one variable this trap |
| 134 | # reads that is assigned AFTER the trap is installed, and the scenarios | 179 | # reads that is assigned AFTER the trap is installed, and the scenarios |
| @@ -160,7 +205,13 @@ cleanup() { | |||
| 160 | "$OUT" "$OUT.kill" "$OUT.re" "$OUT.a" \ | 205 | "$OUT" "$OUT.kill" "$OUT.re" "$OUT.a" \ |
| 161 | "$OUT.b" "$OUT.via" "$OUT.dead" "$OUT.abort" "$OUT.m7" "$OUT.m7b" \ | 206 | "$OUT.b" "$OUT.via" "$OUT.dead" "$OUT.abort" "$OUT.m7" "$OUT.m7b" \ |
| 162 | "$OUT.q" "$OUT.qc" "$OUT.qr" "$OUT.qa" "$OUT.qk" "$QKEY.wrong" \ | 207 | "$OUT.q" "$OUT.qc" "$OUT.qr" "$OUT.qa" "$OUT.qk" "$QKEY.wrong" \ |
| 163 | "$OUT.nokey" "$SOCK4.nokey" | 208 | "$OUT.nokey" "$SOCK4.nokey" \ |
| 209 | "$OUT.doctored" "$OUT.doctored.render" "$OUT.doctored.dump" \ | ||
| 210 | "$OUT.doctored.render.n" "$OUT.doctored.dump.n" "$OUT.doctored.diff" \ | ||
| 211 | "$OUT.doctored.rvt" "$OUT.doctored.dvt" | ||
| 212 | # The convergence files a FAILING assert_converged leaves behind | ||
| 213 | # (.render/.dump/.rvt/.dvt/.diff for that capture) are deliberately not | ||
| 214 | # chased here: on a failing run they are the evidence. | ||
| 164 | rm -rf "$XDG_CONFIG_HOME" "$XDG_STATE_HOME" "${NOKEY_CFG:-}" | 215 | rm -rf "$XDG_CONFIG_HOME" "$XDG_STATE_HOME" "${NOKEY_CFG:-}" |
| 165 | } | 216 | } |
| 166 | trap cleanup EXIT INT TERM | 217 | trap cleanup EXIT INT TERM |
| @@ -215,6 +266,22 @@ grep -q "e2e-works" "$OUT" || { | |||
| 215 | # 3. Detach left the daemon running. | 266 | # 3. Detach left the daemon running. |
| 216 | kill -0 "$DPID" || { echo "e2e FAIL: daemon died on detach"; exit 1; } | 267 | kill -0 "$DPID" || { echo "e2e FAIL: daemon died on detach"; exit 1; } |
| 217 | 268 | ||
| 269 | # 4. The M11 claim itself: the screen the client painted equals the | ||
| 270 | # screen the daemon holds — same engine, same formatter, both formats. | ||
| 271 | assert_converged "$OUT" "$SOCK" "base attach" | ||
| 272 | |||
| 273 | # The control: a doctored stream must NOT converge. A convergence check | ||
| 274 | # that cannot fail proves nothing (the wan.sh rule, M9). | ||
| 275 | cp "$OUT" "$OUT.doctored" | ||
| 276 | printf '\033[12;1Hconvergence-control-glyphs' >> "$OUT.doctored" | ||
| 277 | if converged_quiet "$OUT.doctored" "$SOCK"; then | ||
| 278 | echo "e2e FAIL: convergence control did not fire on a doctored stream"; exit 1 | ||
| 279 | fi | ||
| 280 | rm -f "$OUT.doctored" "$OUT.doctored.render" "$OUT.doctored.dump" \ | ||
| 281 | "$OUT.doctored.render.n" "$OUT.doctored.dump.n" "$OUT.doctored.diff" \ | ||
| 282 | "$OUT.doctored.rvt" "$OUT.doctored.dvt" | ||
| 283 | echo "e2e OK: convergence control fires on a doctored stream" | ||
| 284 | |||
| 218 | # --- M3: kill a client mid-run; daemon survives; reattach lands correctly. | 285 | # --- M3: kill a client mid-run; daemon survives; reattach lands correctly. |
| 219 | { printf 'seq 1 60\n'; sleep 2; } | "$MUX" --sock "$SOCK" > "$OUT.kill" & | 286 | { printf 'seq 1 60\n'; sleep 2; } | "$MUX" --sock "$SOCK" > "$OUT.kill" & |
| 220 | CPID=$! | 287 | CPID=$! |