a3fd1a94
test: e2e — hub scenarios: passivity pin, origin gate, tear/re-attach
a73x 2026-08-13 11:31
Commit message
build.zig
| Old | New | ||
|---|---|---|---|
| @@ -550,6 +550,8 @@ pub fn build(b: *std.Build) void { | |||
| 550 | e2e.addArtifactArg(delaypipe_exe); | 550 | e2e.addArtifactArg(delaypipe_exe); |
| 551 | e2e.addArtifactArg(render_exe); | 551 | e2e.addArtifactArg(render_exe); |
| 552 | e2e.addArtifactArg(ptyclient_exe); | 552 | e2e.addArtifactArg(ptyclient_exe); |
| 553 | e2e.addArtifactArg(wsclient_exe); | ||
| 554 | e2e.addArtifactArg(webhub_exe); | ||
| 553 | const e2e_step = b.step("e2e", "Run end-to-end test"); | 555 | const e2e_step = b.step("e2e", "Run end-to-end test"); |
| 554 | e2e_step.dependOn(&e2e.step); | 556 | e2e_step.dependOn(&e2e.step); |
| 555 | 557 | ||
| @@ -560,6 +562,8 @@ pub fn build(b: *std.Build) void { | |||
| 560 | soak.addArtifactArg(delaypipe_exe); | 562 | soak.addArtifactArg(delaypipe_exe); |
| 561 | soak.addArtifactArg(render_exe); | 563 | soak.addArtifactArg(render_exe); |
| 562 | soak.addArtifactArg(ptyclient_exe); | 564 | soak.addArtifactArg(ptyclient_exe); |
| 565 | soak.addArtifactArg(wsclient_exe); | ||
| 566 | soak.addArtifactArg(webhub_exe); | ||
| 563 | const soak_step = b.step("soak", "Run the e2e suite SOAK_N times (default 10)"); | 567 | const soak_step = b.step("soak", "Run the e2e suite SOAK_N times (default 10)"); |
| 564 | soak_step.dependOn(&soak.step); | 568 | soak_step.dependOn(&soak.step); |
| 565 | 569 | ||
test/e2e.sh
| Old | New | ||
|---|---|---|---|
| @@ -12,6 +12,10 @@ DELAYPIPE="$4" | |||
| 12 | RENDER="$5" | 12 | RENDER="$5" |
| 13 | # M12 pty fixture: runs the client on a real pty (test/ptyclient.zig). | 13 | # M12 pty fixture: runs the client on a real pty (test/ptyclient.zig). |
| 14 | PTYCLIENT="$6" | 14 | PTYCLIENT="$6" |
| 15 | # M-web: the scripted WebSocket replica client and the hub it speaks to | ||
| 16 | # (test/wsclient.zig, src/webhub_main.zig). | ||
| 17 | WSCLIENT="$7" | ||
| 18 | MUXWEB="$8" | ||
| 15 | SOCK="${TMPDIR:-/tmp}/muxd-e2e-$$.sock" | 19 | SOCK="${TMPDIR:-/tmp}/muxd-e2e-$$.sock" |
| 16 | OUT="${TMPDIR:-/tmp}/mux-e2e-out-$$" | 20 | OUT="${TMPDIR:-/tmp}/mux-e2e-out-$$" |
| 17 | # M10: hermetic XDG homes. Key-default scenarios must see OUR key or none, | 21 | # M10: hermetic XDG homes. Key-default scenarios must see OUR key or none, |
| @@ -147,6 +151,23 @@ H1PID="" | |||
| 147 | H3PID="" | 151 | H3PID="" |
| 148 | H4PID="" | 152 | H4PID="" |
| 149 | H5PID="" | 153 | H5PID="" |
| 154 | # M-web. Three daemons (the passivity pin's, the hub-basic one, and the | ||
| 155 | # tear scenario's two incarnations share SOCK20), two hub processes, and | ||
| 156 | # a port band of its own following the 5000-spacing convention — INSIDE | ||
| 157 | # the ephemeral range, which is fine for ports we bind (only provably-DEAD | ||
| 158 | # ports must stay below 32768; see HDEADPORT above). | ||
| 159 | SOCK18="${TMPDIR:-/tmp}/muxd-e2e-web-a-$$.sock" | ||
| 160 | SOCK19="${TMPDIR:-/tmp}/muxd-e2e-web-b-$$.sock" | ||
| 161 | SOCK20="${TMPDIR:-/tmp}/muxd-e2e-web-c-$$.sock" | ||
| 162 | WPORT=$(( 41000 + ($$ % 4000) )) | ||
| 163 | WPORT2=$(( 46000 + ($$ % 4000) )) | ||
| 164 | D14PID="" | ||
| 165 | D15PID="" | ||
| 166 | D16PID="" | ||
| 167 | D17PID="" | ||
| 168 | W1PID="" | ||
| 169 | W2PID="" | ||
| 170 | WCLIPID="" | ||
| 150 | 171 | ||
| 151 | # One counter out of a MUX_PREDICT_STATS line. The client prints exactly one | 172 | # One counter out of a MUX_PREDICT_STATS line. The client prints exactly one |
| 152 | # such line on exit; every field is a key=value pair, so a rename or reorder | 173 | # such line on exit; every field is a key=value pair, so a rename or reorder |
| @@ -420,6 +441,30 @@ cleanup() { | |||
| 420 | "$OUT.h1" "$OUT.h1.err" "$OUT.h2" "$OUT.h2.err" "$OUT.h3" "$OUT.h3.err" \ | 441 | "$OUT.h1" "$OUT.h1.err" "$OUT.h2" "$OUT.h2.err" "$OUT.h3" "$OUT.h3.err" \ |
| 421 | "$OUT.h4" "$OUT.h4.err" "$OUT.h5" "$OUT.h5.err" | 442 | "$OUT.h4" "$OUT.h4.err" "$OUT.h5" "$OUT.h5.err" |
| 422 | rm -rf "$SSHIM_DIR" "$HRUN" "$HRUN2" | 443 | rm -rf "$SSHIM_DIR" "$HRUN" "$HRUN2" |
| 444 | # M-web: hubs and daemons by tracked pid, then stop-by-socket for any | ||
| 445 | # daemon that died between fork and up-line, then the captures. The | ||
| 446 | # stops must precede the socket rm, like SOCK14-17 above. | ||
| 447 | [ -n "$W1PID" ] && kill "$W1PID" 2>/dev/null || true | ||
| 448 | [ -n "$W2PID" ] && kill "$W2PID" 2>/dev/null || true | ||
| 449 | [ -n "$WCLIPID" ] && kill "$WCLIPID" 2>/dev/null || true | ||
| 450 | [ -n "$D14PID" ] && kill "$D14PID" 2>/dev/null || true | ||
| 451 | [ -n "$D15PID" ] && kill "$D15PID" 2>/dev/null || true | ||
| 452 | [ -n "$D16PID" ] && kill "$D16PID" 2>/dev/null || true | ||
| 453 | [ -n "$D17PID" ] && kill "$D17PID" 2>/dev/null || true | ||
| 454 | [ -S "$SOCK18" ] && "$MUXD" stop --sock "$SOCK18" 2>/dev/null || true | ||
| 455 | [ -S "$SOCK19" ] && "$MUXD" stop --sock "$SOCK19" 2>/dev/null || true | ||
| 456 | [ -S "$SOCK20" ] && "$MUXD" stop --sock "$SOCK20" 2>/dev/null || true | ||
| 457 | rm -f "$SOCK18" "$SOCK19" "$SOCK20" \ | ||
| 458 | "$OUT.weba" "$OUT.weba.err" "$OUT.weba.d" \ | ||
| 459 | "$OUT.web1x1" "$OUT.web1x1.err" "$OUT.web1x1.log" \ | ||
| 460 | "$OUT.webb" "$OUT.webb.err" "$OUT.webb.d" "$OUT.webh" \ | ||
| 461 | "$OUT.webws" "$OUT.webws.err" "$OUT.webws.n" "$OUT.webws.dump" \ | ||
| 462 | "$OUT.webws.dump.n" "$OUT.webws.diff" "$OUT.webws.doc" \ | ||
| 463 | "$OUT.webevil" "$OUT.webevil.err" \ | ||
| 464 | "$OUT.webc" "$OUT.webc.err" "$OUT.webc.d" "$OUT.webc2" "$OUT.webc2.err" \ | ||
| 465 | "$OUT.webc2.d" "$OUT.webh2" "$OUT.webws2" "$OUT.webws2.err" \ | ||
| 466 | "$OUT.webws2.n" "$OUT.webws2.dump" "$OUT.webws2.dump.n" \ | ||
| 467 | "$OUT.webws2.diff" "$OUT.webstop" "$OUT.webstop2" "$OUT.webstop3" | ||
| 423 | # The convergence files a FAILING assert_converged leaves behind | 468 | # The convergence files a FAILING assert_converged leaves behind |
| 424 | # (.render/.dump/.rvt/.dvt/.diff for that capture) are deliberately not | 469 | # (.render/.dump/.rvt/.dvt/.diff for that capture) are deliberately not |
| 425 | # chased here: on a failing run they are the evidence. | 470 | # chased here: on a failing run they are the evidence. |
| @@ -2817,17 +2862,273 @@ rm -f "$OUT.h1" "$OUT.h1.err" "$OUT.h2" "$OUT.h2.err" "$OUT.h3" "$OUT.h3.err" \ | |||
| 2817 | "$OUT.h4" "$OUT.h4.err" "$OUT.h5" "$OUT.h5.err" "$OUT.stop" "$HKEY" "$HCFGBAD" | 2862 | "$OUT.h4" "$OUT.h4.err" "$OUT.h5" "$OUT.h5.err" "$OUT.stop" "$HKEY" "$HCFGBAD" |
| 2818 | rm -rf "$SSHIM_DIR" "$HRUN" "$HRUN2" | 2863 | rm -rf "$SSHIM_DIR" "$HRUN" "$HRUN2" |
| 2819 | 2864 | ||
| 2865 | # --- M-web (a): a 1x1 attach is refused the grid and can never claim it. | ||
| 2866 | # The wall tile's passivity contract, pinned DAEMON-SIDE and hub-free on | ||
| 2867 | # purpose: the mechanism is applySize's cols<2 refusal plus claimGrid's | ||
| 2868 | # 0x0-slot refusal (server.zig), and it must hold whether or not muxweb | ||
| 2869 | # exists. The probe for "the grid did not move" is textual: a marker | ||
| 2870 | # string typed at 80 wide can only appear CONTIGUOUS in `muxd dump` if | ||
| 2871 | # the grid is still 80 wide — a 1-column grid puts every glyph on its | ||
| 2872 | # own row, so grep itself is the geometry assertion. | ||
| 2873 | "$MUXD" run --sock "$SOCK18" --shell /bin/sh > "$OUT.weba.d" 2>&1 & | ||
| 2874 | D14PID=$! | ||
| 2875 | i=0 | ||
| 2876 | while [ ! -S "$SOCK18" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done | ||
| 2877 | [ -S "$SOCK18" ] || { echo "e2e FAIL: web passivity daemon never bound"; cat "$OUT.weba.d"; exit 1; } | ||
| 2878 | |||
| 2879 | # The 80x24 client: types the first marker, holds the session open long | ||
| 2880 | # enough for the 1x1 attacher to come and go, types the second marker | ||
| 2881 | # (the geometry probe), detaches. | ||
| 2882 | { printf 'printf "wall-%%s\\n" pin\n'; sleep 4; printf 'printf "zz-%%s\\n" web\n'; sleep 2; printf '\034'; } | \ | ||
| 2883 | timeout 40 "$MUX" --sock "$SOCK18" > "$OUT.weba" 2> "$OUT.weba.err" & | ||
| 2884 | WCLIPID=$! | ||
| 2885 | i=0 | ||
| 2886 | while [ "$i" -lt 100 ]; do | ||
| 2887 | "$MUXD" dump --sock "$SOCK18" 2>/dev/null | grep -q "wall-pin" && break | ||
| 2888 | sleep 0.1; i=$((i+1)) | ||
| 2889 | done | ||
| 2890 | "$MUXD" dump --sock "$SOCK18" | grep -q "wall-pin" || { | ||
| 2891 | echo "e2e FAIL: web passivity: first marker never landed"; exit 1; } | ||
| 2892 | |||
| 2893 | # The 1x1 attacher, on a real 1x1 pty. Its attach is answered with a | ||
| 2894 | # unicast snapshot (the alt-screen enter proves the first frame came), | ||
| 2895 | # its keystroke is the claim attempt, and the 'z' it waits for is the | ||
| 2896 | # second marker echoing — proof it was STILL ATTACHED while the marker | ||
| 2897 | # landed, so the grid it could have wrecked was live the whole time. | ||
| 2898 | set +e | ||
| 2899 | timeout 40 "$PTYCLIENT" --cols 1 --rows 1 --out "$OUT.web1x1" --err "$OUT.web1x1.err" \ | ||
| 2900 | -- "$MUX" --sock "$SOCK18" > "$OUT.web1x1.log" 2>&1 <<'EOF' | ||
| 2901 | expect \x1b[?1049h 15000 | ||
| 2902 | send \x20 | ||
| 2903 | expect z 20000 | ||
| 2904 | settle 300 10000 | ||
| 2905 | send \x1c | ||
| 2906 | waitexit 10000 | ||
| 2907 | EOF | ||
| 2908 | RC=$? | ||
| 2909 | set -e | ||
| 2910 | [ "$RC" -eq 0 ] || { | ||
| 2911 | echo "e2e FAIL: web passivity: 1x1 client exited $RC" | ||
| 2912 | cat "$OUT.web1x1.log"; cat -v "$OUT.web1x1.err" 2>/dev/null; exit 1; } | ||
| 2913 | |||
| 2914 | # The geometry assertion: both markers contiguous AFTER the 1x1 client | ||
| 2915 | # attached, typed, and left. A grid moved to 1 wide cannot hold either. | ||
| 2916 | "$MUXD" dump --sock "$SOCK18" | grep -q "wall-pin" || { | ||
| 2917 | echo "e2e FAIL: web passivity: first marker lost after 1x1 attach:" | ||
| 2918 | "$MUXD" dump --sock "$SOCK18"; exit 1; } | ||
| 2919 | "$MUXD" dump --sock "$SOCK18" | grep -q "zz-web" || { | ||
| 2920 | echo "e2e FAIL: web passivity: the 1x1 attacher moved the grid (marker not contiguous):" | ||
| 2921 | "$MUXD" dump --sock "$SOCK18"; exit 1; } | ||
| 2922 | |||
| 2923 | set +e | ||
| 2924 | wait "$WCLIPID" | ||
| 2925 | RC=$? | ||
| 2926 | set -e | ||
| 2927 | WCLIPID="" | ||
| 2928 | [ "$RC" -eq 0 ] || { echo "e2e FAIL: web passivity: 80x24 client exited $RC"; cat "$OUT.weba.err"; exit 1; } | ||
| 2929 | assert_converged "$OUT.weba" "$SOCK18" "web passivity: the 80x24 client never glitched" | ||
| 2930 | |||
| 2931 | set +e | ||
| 2932 | "$MUXD" stop --sock "$SOCK18" 2> "$OUT.webstop" | ||
| 2933 | RC_STOP=$? | ||
| 2934 | set -e | ||
| 2935 | [ "$RC_STOP" = "0" ] || { echo "e2e FAIL: web passivity stop exited $RC_STOP"; cat "$OUT.webstop"; exit 1; } | ||
| 2936 | grep -q '^muxd: stopped' "$OUT.webstop" || { echo "e2e FAIL: web passivity stop did not report stopped"; cat "$OUT.webstop"; exit 1; } | ||
| 2937 | [ ! -S "$SOCK18" ] || { echo "e2e FAIL: web passivity stop left the socket"; ls -l "$SOCK18"; exit 1; } | ||
| 2938 | wait_pid_gone "$D14PID" "web passivity: stop reported stopped" | ||
| 2939 | D14PID="" | ||
| 2940 | ok "a 1x1 attach is refused the grid and can never claim it" | ||
| 2941 | |||
| 2942 | # --- M-web (b): the hub pumps a real session; wrong Origin refused. | ||
| 2943 | "$MUXD" run --sock "$SOCK19" --shell /bin/sh > "$OUT.webb.d" 2>&1 & | ||
| 2944 | D15PID=$! | ||
| 2945 | i=0 | ||
| 2946 | while [ ! -S "$SOCK19" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done | ||
| 2947 | [ -S "$SOCK19" ] || { echo "e2e FAIL: web hub daemon never bound"; cat "$OUT.webb.d"; exit 1; } | ||
| 2948 | "$MUXWEB" --sock "$SOCK19" --port "$WPORT" > "$OUT.webh" 2>&1 & | ||
| 2949 | W1PID=$! | ||
| 2950 | wait_for "$OUT.webh" "serving" 10 || { | ||
| 2951 | echo "e2e FAIL: hub never reported serving"; cat "$OUT.webh"; exit 1; } | ||
| 2952 | |||
| 2953 | { printf 'printf "web-%%s\\n" b1\n'; sleep 4; printf '\034'; } | \ | ||
| 2954 | timeout 40 "$MUX" --sock "$SOCK19" > "$OUT.webb" 2> "$OUT.webb.err" & | ||
| 2955 | WCLIPID=$! | ||
| 2956 | i=0 | ||
| 2957 | while [ "$i" -lt 100 ]; do | ||
| 2958 | "$MUXD" dump --sock "$SOCK19" 2>/dev/null | grep -q "web-b1" && break | ||
| 2959 | sleep 0.1; i=$((i+1)) | ||
| 2960 | done | ||
| 2961 | |||
| 2962 | # The browser stand-in: a passive 1x1 wall tile through the hub. Its | ||
| 2963 | # final grid is the daemon's replica as REPLAYED THROUGH the WebSocket | ||
| 2964 | # leg, dumped in muxd dump's own format. | ||
| 2965 | set +e | ||
| 2966 | timeout 40 "$WSCLIENT" --port "$WPORT" --tile 0 --out "$OUT.webws" --err "$OUT.webws.err" <<'EOF' | ||
| 2967 | attach 1 1 | ||
| 2968 | expectstate up 10000 | ||
| 2969 | expectgrid web-b1 15000 | ||
| 2970 | settle 500 10000 | ||
| 2971 | dumpexit | ||
| 2972 | EOF | ||
| 2973 | RC=$? | ||
| 2974 | set -e | ||
| 2975 | [ "$RC" -eq 0 ] || { | ||
| 2976 | echo "e2e FAIL: web hub: wsclient exited $RC" | ||
| 2977 | cat -v "$OUT.webws.err" 2>/dev/null; cat "$OUT.webh"; exit 1; } | ||
| 2978 | |||
| 2979 | "$MUXD" dump --sock "$SOCK19" > "$OUT.webws.dump" | ||
| 2980 | sed 's/[[:space:]]*$//' "$OUT.webws" > "$OUT.webws.n" | ||
| 2981 | sed 's/[[:space:]]*$//' "$OUT.webws.dump" > "$OUT.webws.dump.n" | ||
| 2982 | diff -u "$OUT.webws.dump.n" "$OUT.webws.n" > "$OUT.webws.diff" || { | ||
| 2983 | echo "e2e FAIL: web hub: wsclient replica diverges from daemon grid (-daemon +ws):" | ||
| 2984 | head -40 "$OUT.webws.diff"; exit 1; } | ||
| 2985 | |||
| 2986 | # The cannot-fail control (the wan.sh rule, M9): doctor a copy of the | ||
| 2987 | # daemon dump and assert the SAME diff catches it. A convergence check | ||
| 2988 | # that cannot fail proves nothing. | ||
| 2989 | cp "$OUT.webws.dump.n" "$OUT.webws.doc" | ||
| 2990 | printf 'doctored-row\n' >> "$OUT.webws.doc" | ||
| 2991 | if diff -u "$OUT.webws.doc" "$OUT.webws.n" > /dev/null 2>&1; then | ||
| 2992 | echo "e2e FAIL: web hub: the convergence diff cannot fail (doctored dump passed)"; exit 1 | ||
| 2993 | fi | ||
| 2994 | |||
| 2995 | # Wrong Origin: refused at HTTP, before any upgrade — the wsclient sees | ||
| 2996 | # a non-101 and exits 4 — and the daemon never sees a client for it. | ||
| 2997 | CLIENTS_BEFORE=$("$MUXD" stats --sock "$SOCK19" | sed -n 's/.*clients=\([0-9]*\).*/\1/p') | ||
| 2998 | set +e | ||
| 2999 | timeout 20 "$WSCLIENT" --port "$WPORT" --tile 0 --origin http://evil.example \ | ||
| 3000 | --out "$OUT.webevil" --err "$OUT.webevil.err" < /dev/null | ||
| 3001 | RC=$? | ||
| 3002 | set -e | ||
| 3003 | [ "$RC" -eq 4 ] || { | ||
| 3004 | echo "e2e FAIL: web hub: evil-origin wsclient exited $RC, want 4" | ||
| 3005 | cat -v "$OUT.webevil.err" 2>/dev/null; exit 1; } | ||
| 3006 | grep -q "upgrade refused.*403" "$OUT.webevil.err" || { | ||
| 3007 | echo "e2e FAIL: web hub: refusal was not the 403 it must be:" | ||
| 3008 | cat -v "$OUT.webevil.err"; exit 1; } | ||
| 3009 | CLIENTS_AFTER=$("$MUXD" stats --sock "$SOCK19" | sed -n 's/.*clients=\([0-9]*\).*/\1/p') | ||
| 3010 | [ "$CLIENTS_BEFORE" = "$CLIENTS_AFTER" ] || { | ||
| 3011 | echo "e2e FAIL: web hub: evil origin reached the daemon (clients $CLIENTS_BEFORE -> $CLIENTS_AFTER)"; exit 1; } | ||
| 3012 | |||
| 3013 | set +e | ||
| 3014 | wait "$WCLIPID" | ||
| 3015 | RC=$? | ||
| 3016 | set -e | ||
| 3017 | WCLIPID="" | ||
| 3018 | [ "$RC" -eq 0 ] || { echo "e2e FAIL: web hub: typing client exited $RC"; cat "$OUT.webb.err"; exit 1; } | ||
| 3019 | assert_converged "$OUT.webb" "$SOCK19" "web hub: the typing client" | ||
| 3020 | |||
| 3021 | kill "$W1PID" 2>/dev/null || true | ||
| 3022 | wait_pid_gone "$W1PID" "web hub: killed by tracked pid" | ||
| 3023 | W1PID="" | ||
| 3024 | set +e | ||
| 3025 | "$MUXD" stop --sock "$SOCK19" 2> "$OUT.webstop2" | ||
| 3026 | RC_STOP=$? | ||
| 3027 | set -e | ||
| 3028 | [ "$RC_STOP" = "0" ] || { echo "e2e FAIL: web hub stop exited $RC_STOP"; cat "$OUT.webstop2"; exit 1; } | ||
| 3029 | grep -q '^muxd: stopped' "$OUT.webstop2" || { echo "e2e FAIL: web hub stop did not report stopped"; cat "$OUT.webstop2"; exit 1; } | ||
| 3030 | [ ! -S "$SOCK19" ] || { echo "e2e FAIL: web hub stop left the socket"; ls -l "$SOCK19"; exit 1; } | ||
| 3031 | wait_pid_gone "$D15PID" "web hub: stop reported stopped" | ||
| 3032 | D15PID="" | ||
| 3033 | ok "hub pumps a real session; wrong origin refused" | ||
| 3034 | |||
| 3035 | # --- M-web (c): the hub narrates the tear; the replica re-attaches | ||
| 3036 | # across an epoch. muxd stop kills the daemon under a live tile; the hub | ||
| 3037 | # must say `reconnecting`, redial the restarted daemon (same socket, new | ||
| 3038 | # epoch), say `up`, and the stand-in's re-attach — quoting coordinates | ||
| 3039 | # the NEW daemon has never issued — must be answered with a snapshot | ||
| 3040 | # that converges on the new session's content. | ||
| 3041 | "$MUXD" run --sock "$SOCK20" --shell /bin/sh > "$OUT.webc.d" 2>&1 & | ||
| 3042 | D16PID=$! | ||
| 3043 | i=0 | ||
| 3044 | while [ ! -S "$SOCK20" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done | ||
| 3045 | [ -S "$SOCK20" ] || { echo "e2e FAIL: web tear daemon never bound"; cat "$OUT.webc.d"; exit 1; } | ||
| 3046 | "$MUXWEB" --sock "$SOCK20" --port "$WPORT2" > "$OUT.webh2" 2>&1 & | ||
| 3047 | W2PID=$! | ||
| 3048 | wait_for "$OUT.webh2" "serving" 10 || { | ||
| 3049 | echo "e2e FAIL: tear hub never reported serving"; cat "$OUT.webh2"; exit 1; } | ||
| 3050 | |||
| 3051 | { printf 'printf "web-%%s\\n" c1\n'; sleep 1.5; printf '\034'; } | \ | ||
| 3052 | timeout 30 "$MUX" --sock "$SOCK20" > "$OUT.webc" 2> "$OUT.webc.err" | ||
| 3053 | i=0 | ||
| 3054 | while [ "$i" -lt 100 ]; do | ||
| 3055 | "$MUXD" dump --sock "$SOCK20" 2>/dev/null | grep -q "web-c1" && break | ||
| 3056 | sleep 0.1; i=$((i+1)) | ||
| 3057 | done | ||
| 3058 | |||
| 3059 | timeout 90 "$WSCLIENT" --port "$WPORT2" --tile 0 --out "$OUT.webws2" --err "$OUT.webws2.err" <<'EOF' & | ||
| 3060 | attach 1 1 | ||
| 3061 | expectstate up 10000 | ||
| 3062 | expectgrid web-c1 15000 | ||
| 3063 | expectstate reconnecting 25000 | ||
| 3064 | expectstate up 25000 | ||
| 3065 | attach 1 1 | ||
| 3066 | expectgrid web-c2 30000 | ||
| 3067 | settle 500 10000 | ||
| 3068 | dumpexit | ||
| 3069 | EOF | ||
| 3070 | WCLIPID=$! | ||
| 3071 | # Let the stand-in reach its first expectgrid before the tear; the marker | ||
| 3072 | # it waits for is already on the grid, so one settle-length is plenty. | ||
| 3073 | sleep 1 | ||
| 3074 | |||
| 3075 | set +e | ||
| 3076 | "$MUXD" stop --sock "$SOCK20" 2> "$OUT.webstop3" | ||
| 3077 | RC_STOP=$? | ||
| 3078 | set -e | ||
| 3079 | [ "$RC_STOP" = "0" ] || { echo "e2e FAIL: web tear stop exited $RC_STOP"; cat "$OUT.webstop3"; exit 1; } | ||
| 3080 | wait_pid_gone "$D16PID" "web tear: first daemon stopped under the tile" | ||
| 3081 | D16PID="" | ||
| 3082 | |||
| 3083 | "$MUXD" run --sock "$SOCK20" --shell /bin/sh > "$OUT.webc2.d" 2>&1 & | ||
| 3084 | D17PID=$! | ||
| 3085 | i=0 | ||
| 3086 | while [ ! -S "$SOCK20" ] && [ "$i" -lt 50 ]; do sleep 0.1; i=$((i+1)); done | ||
| 3087 | [ -S "$SOCK20" ] || { echo "e2e FAIL: web tear restart never bound"; cat "$OUT.webc2.d"; exit 1; } | ||
| 3088 | |||
| 3089 | { printf 'printf "web-%%s\\n" c2\n'; sleep 1.5; printf '\034'; } | \ | ||
| 3090 | timeout 30 "$MUX" --sock "$SOCK20" > "$OUT.webc2" 2> "$OUT.webc2.err" | ||
| 3091 | |||
| 3092 | set +e | ||
| 3093 | wait "$WCLIPID" | ||
| 3094 | RC=$? | ||
| 3095 | set -e | ||
| 3096 | WCLIPID="" | ||
| 3097 | [ "$RC" -eq 0 ] || { | ||
| 3098 | echo "e2e FAIL: web tear: wsclient exited $RC" | ||
| 3099 | cat -v "$OUT.webws2.err" 2>/dev/null; cat "$OUT.webh2"; exit 1; } | ||
| 3100 | |||
| 3101 | "$MUXD" dump --sock "$SOCK20" > "$OUT.webws2.dump" | ||
| 3102 | sed 's/[[:space:]]*$//' "$OUT.webws2" > "$OUT.webws2.n" | ||
| 3103 | sed 's/[[:space:]]*$//' "$OUT.webws2.dump" > "$OUT.webws2.dump.n" | ||
| 3104 | diff -u "$OUT.webws2.dump.n" "$OUT.webws2.n" > "$OUT.webws2.diff" || { | ||
| 3105 | echo "e2e FAIL: web tear: replica diverges after the epoch crossing (-daemon +ws):" | ||
| 3106 | head -40 "$OUT.webws2.diff"; exit 1; } | ||
| 3107 | |||
| 3108 | kill "$W2PID" 2>/dev/null || true | ||
| 3109 | wait_pid_gone "$W2PID" "web tear: hub killed by tracked pid" | ||
| 3110 | W2PID="" | ||
| 3111 | set +e | ||
| 3112 | "$MUXD" stop --sock "$SOCK20" 2> "$OUT.webstop3" | ||
| 3113 | RC_STOP=$? | ||
| 3114 | set -e | ||
| 3115 | [ "$RC_STOP" = "0" ] || { echo "e2e FAIL: web tear final stop exited $RC_STOP"; cat "$OUT.webstop3"; exit 1; } | ||
| 3116 | [ ! -S "$SOCK20" ] || { echo "e2e FAIL: web tear final stop left the socket"; ls -l "$SOCK20"; exit 1; } | ||
| 3117 | wait_pid_gone "$D17PID" "web tear: restarted daemon stopped" | ||
| 3118 | D17PID="" | ||
| 3119 | ok "hub narrates the tear; the replica re-attaches across an epoch" | ||
| 3120 | |||
| 2820 | # The pins. Literals, not variables set from counting something else — | 3121 | # The pins. Literals, not variables set from counting something else — |
| 2821 | # "assert the literal, never the constant the code under test reads" | 3122 | # "assert the literal, never the constant the code under test reads" |
| 2822 | # (decisions.md, M10). 20 scenario checkpoints; 33 convergence points. | 3123 | # (decisions.md, M10). 20 scenario checkpoints; 33 convergence points. |
| 2823 | # Anyone adding a scenario updates these by hand, on purpose. | 3124 | # Anyone adding a scenario updates these by hand, on purpose. |
| 2824 | [ "$OK_COUNT" = "20" ] || { | 3125 | [ "$OK_COUNT" = "23" ] || { |
| 2825 | echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 20 —" | 3126 | echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 23 —" |
| 2826 | echo " a scenario was added (update the pin) or silently lost" | 3127 | echo " a scenario was added (update the pin) or silently lost" |
| 2827 | exit 1 | 3128 | exit 1 |
| 2828 | } | 3129 | } |
| 2829 | [ "$CONV_COUNT" = "33" ] || { | 3130 | [ "$CONV_COUNT" = "35" ] || { |
| 2830 | echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 33" | 3131 | echo "e2e FAIL: $CONV_COUNT convergence points ran, the pin says 35" |
| 2831 | exit 1 | 3132 | exit 1 |
| 2832 | } | 3133 | } |
| 2833 | echo "e2e OK (20 scenarios, 33 convergence points)" | 3134 | echo "e2e OK (23 scenarios, 35 convergence points)" |
test/soak.sh
| Old | New | ||
|---|---|---|---|
| @@ -6,7 +6,7 @@ | |||
| 6 | # same temp-file patterns while it looks (a concurrently running suite | 6 | # same temp-file patterns while it looks (a concurrently running suite |
| 7 | # would read as a leak). | 7 | # would read as a leak). |
| 8 | set -u | 8 | set -u |
| 9 | MUXD="$1"; MUX="$2"; RAWMODE="$3"; DELAYPIPE="$4"; RENDER="$5"; PTYCLIENT="$6" | 9 | MUXD="$1"; MUX="$2"; RAWMODE="$3"; DELAYPIPE="$4"; RENDER="$5"; PTYCLIENT="$6"; WSCLIENT="$7"; MUXWEB="$8" |
| 10 | E2E="$(dirname "$0")/e2e.sh" | 10 | E2E="$(dirname "$0")/e2e.sh" |
| 11 | N="${SOAK_N:-10}" | 11 | N="${SOAK_N:-10}" |
| 12 | TMP="${TMPDIR:-/tmp}" | 12 | TMP="${TMPDIR:-/tmp}" |
| @@ -30,7 +30,7 @@ BASE_STRAYS=$(find "$TMP" -maxdepth 1 \( -name 'muxd-e2e-*' -o -name 'mux-e2e-*' | |||
| 30 | FAILED=0 | 30 | FAILED=0 |
| 31 | i=1 | 31 | i=1 |
| 32 | while [ "$i" -le "$N" ]; do | 32 | while [ "$i" -le "$N" ]; do |
| 33 | if "$E2E" "$MUXD" "$MUX" "$RAWMODE" "$DELAYPIPE" "$RENDER" "$PTYCLIENT" > "$LOG" 2>&1; then | 33 | if "$E2E" "$MUXD" "$MUX" "$RAWMODE" "$DELAYPIPE" "$RENDER" "$PTYCLIENT" "$WSCLIENT" "$MUXWEB" > "$LOG" 2>&1; then |
| 34 | echo "soak run $i/$N: PASS" | 34 | echo "soak run $i/$N: PASS" |
| 35 | else | 35 | else |
| 36 | FAILED=$((FAILED + 1)) | 36 | FAILED=$((FAILED + 1)) |