ee04b68c
test: legible stop failures — the regrade's predicted catch must print, not abort
a73x 2026-08-10 19:31
Commit message
test/e2e.sh
| Old | New | ||
|---|---|---|---|
| @@ -147,6 +147,22 @@ wait_gone() { | |||
| 147 | done | 147 | done |
| 148 | } | 148 | } |
| 149 | 149 | ||
| 150 | # wait_pid_gone PID LABEL — poll until a tracked pid is gone (2s, the same | ||
| 151 | # budget `muxd stop` gives itself). Its own helper rather than wait_gone's | ||
| 152 | # socket probe, because the two answer different questions: `muxd: stopped` | ||
| 153 | # is printed on the first probe that gets a REFUSAL, which is the socket | ||
| 154 | # being unlinked, and the process can still be a fraction behind that. Only | ||
| 155 | # a pid can say the daemon itself ended, which is the assertion the M13 | ||
| 156 | # teardowns owe — never the stop command's own claim. | ||
| 157 | wait_pid_gone() { | ||
| 158 | _i=0 | ||
| 159 | while kill -0 "$1" 2>/dev/null; do | ||
| 160 | _i=$((_i + 1)); [ "$_i" -lt 40 ] || { | ||
| 161 | echo "e2e FAIL: $2: stop reported stopped but pid $1 still runs"; exit 1; } | ||
| 162 | sleep 0.05 | ||
| 163 | done | ||
| 164 | } | ||
| 165 | |||
| 150 | # The transport child for a given socket: a process named muxd running the | 166 | # The transport child for a given socket: a process named muxd running the |
| 151 | # `proxy` subcommand on that path. NEVER `pkill -f proxy` — the client's own | 167 | # `proxy` subcommand on that path. NEVER `pkill -f proxy` — the client's own |
| 152 | # argv contains the --via command string, so a pattern kill takes out the | 168 | # argv contains the --via command string, so a pattern kill takes out the |
| @@ -2030,13 +2046,18 @@ ok "reconnect while scrolled: view restored, prediction resumed" | |||
| 2030 | SHELL=/bin/sh timeout 30 "$MUX" --via "$MUXD proxy --sock $SOCK14" \ | 2046 | SHELL=/bin/sh timeout 30 "$MUX" --via "$MUXD proxy --sock $SOCK14" \ |
| 2031 | > "$OUT.as" 2> "$OUT.as.err" || true | 2047 | > "$OUT.as" 2> "$OUT.as.err" || true |
| 2032 | "$MUXD" dump --sock "$SOCK14" | grep -q "auto-start" || { | 2048 | "$MUXD" dump --sock "$SOCK14" | grep -q "auto-start" || { |
| 2033 | echo "e2e FAIL: auto-started daemon lost the marker" | 2049 | echo "e2e FAIL: proxy auto-start: attached daemon lost the marker" |
| 2034 | cat "$OUT.as.err"; exit 1; } | 2050 | cat "$OUT.as" "$OUT.as.err" 2>/dev/null; exit 1; } |
| 2035 | grep -q '^muxd proxy: starting' "$OUT.as.err" || { | 2051 | grep -q '^muxd proxy: starting' "$OUT.as.err" || { |
| 2036 | echo "e2e FAIL: cold attach printed no starting line"; cat "$OUT.as.err"; exit 1; } | 2052 | echo "e2e FAIL: cold attach printed no starting line"; cat "$OUT.as.err"; exit 1; } |
| 2037 | # The pid is the whole point of the up-line: it is the only handle the | 2053 | # The pid is the whole point of the up-line: it is the only handle the |
| 2038 | # suite has on a daemon that is nobody's child, so it is read before | 2054 | # suite has on a daemon that is nobody's child. Known gap, accepted: the |
| 2039 | # anything else here can fail and leave one unreachable. | 2055 | # two assertions above exit before this capture runs, so a failure in |
| 2056 | # either leaves a daemon with no pid tracked — and hoisting the capture | ||
| 2057 | # would not close it, since the window from the spawn to this sed cannot | ||
| 2058 | # be made zero. Unlike the M10 start block's version of this gap (:1150) | ||
| 2059 | # it is covered anyway: the trap also stops SOCK14/SOCK15 by PATH, which | ||
| 2060 | # needs no pid. | ||
| 2040 | APID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.as.err" | head -1) | 2061 | APID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.as.err" | head -1) |
| 2041 | [ -n "$APID" ] || { echo "e2e FAIL: proxy up-line carries no pid"; cat "$OUT.as.err"; exit 1; } | 2062 | [ -n "$APID" ] || { echo "e2e FAIL: proxy up-line carries no pid"; cat "$OUT.as.err"; exit 1; } |
| 2042 | kill -0 "$APID" || { echo "e2e FAIL: auto-started daemon not alive"; exit 1; } | 2063 | kill -0 "$APID" || { echo "e2e FAIL: auto-started daemon not alive"; exit 1; } |
| @@ -2054,25 +2075,30 @@ grep -q 'starting' "$OUT.as2.err" && { | |||
| 2054 | # The same daemon, asserted by pid rather than by the marker alone: a | 2075 | # The same daemon, asserted by pid rather than by the marker alone: a |
| 2055 | # second daemon on the path would have started a fresh shell, but a | 2076 | # second daemon on the path would have started a fresh shell, but a |
| 2056 | # reader should not have to reason that out from a missing string. | 2077 | # reader should not have to reason that out from a missing string. |
| 2057 | kill -0 "$APID" || { echo "e2e FAIL: warm attach replaced the daemon"; exit 1; } | 2078 | kill -0 "$APID" || { |
| 2079 | echo "e2e FAIL: warm attach replaced the daemon (pid $APID is gone)" | ||
| 2080 | cat "$OUT.as2.err"; exit 1; } | ||
| 2058 | assert_converged "$OUT.as2" "$SOCK14" "auto-start warm attach" | 2081 | assert_converged "$OUT.as2" "$SOCK14" "auto-start warm attach" |
| 2059 | 2082 | ||
| 2060 | # The verb under test is the teardown: stopped line, exit 0, socket gone, | 2083 | # The verb under test is the teardown: exit 0, stopped line, socket gone, |
| 2061 | # and the daemon OBSERVED dead by pid — never the command's claim alone. | 2084 | # and the daemon OBSERVED dead by pid — never the command's claim alone. |
| 2085 | # | ||
| 2086 | # The exit code is checked through `set +e` rather than left to `set -e`, | ||
| 2087 | # and that is the difference between a caught regression and a mystery: a | ||
| 2088 | # stop that fails exits 1, which would abort the suite with no line of its | ||
| 2089 | # own, and the trap would then rm the one file saying why. The regrade | ||
| 2090 | # resurrects exactly that failure, so it has to PRINT. | ||
| 2091 | set +e | ||
| 2062 | "$MUXD" stop --sock "$SOCK14" 2> "$OUT.stop" | 2092 | "$MUXD" stop --sock "$SOCK14" 2> "$OUT.stop" |
| 2093 | RC_STOP=$? | ||
| 2094 | set -e | ||
| 2095 | [ "$RC_STOP" = "0" ] || { | ||
| 2096 | echo "e2e FAIL: stop exited $RC_STOP, want 0"; cat "$OUT.stop"; exit 1; } | ||
| 2063 | grep -q '^muxd: stopped' "$OUT.stop" || { | 2097 | grep -q '^muxd: stopped' "$OUT.stop" || { |
| 2064 | echo "e2e FAIL: stop did not report stopped"; cat "$OUT.stop"; exit 1; } | 2098 | echo "e2e FAIL: stop did not report stopped"; cat "$OUT.stop"; exit 1; } |
| 2065 | [ ! -S "$SOCK14" ] || { echo "e2e FAIL: stop left the socket behind"; exit 1; } | 2099 | [ ! -S "$SOCK14" ] || { |
| 2066 | # `muxd: stopped` is printed on the first probe that gets a refusal, which | 2100 | echo "e2e FAIL: stop left the socket behind"; ls -l "$SOCK14"; exit 1; } |
| 2067 | # is the socket being unlinked — the process itself can still be a | 2101 | wait_pid_gone "$APID" "proxy auto-start" |
| 2068 | # fraction behind it. Hence the poll: 2s, the same budget the stop verb | ||
| 2069 | # gives itself, and only then a failure. | ||
| 2070 | _i=0 | ||
| 2071 | while kill -0 "$APID" 2>/dev/null; do | ||
| 2072 | _i=$((_i + 1)); [ "$_i" -lt 40 ] || { | ||
| 2073 | echo "e2e FAIL: stop reported stopped but pid $APID still runs"; exit 1; } | ||
| 2074 | sleep 0.05 | ||
| 2075 | done | ||
| 2076 | APID="" | 2102 | APID="" |
| 2077 | 2103 | ||
| 2078 | # Idempotence control: stop with nothing there is exit 0 and says so. | 2104 | # Idempotence control: stop with nothing there is exit 0 and says so. |
| @@ -2132,16 +2158,23 @@ grep -q '^mux: starting' "$OUT.pa.err" || { | |||
| 2132 | PAPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.pa.err" | head -1) | 2158 | PAPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.pa.err" | head -1) |
| 2133 | [ -n "$PAPID" ] || { echo "e2e FAIL: mux up-line carries no pid"; cat "$OUT.pa.err"; exit 1; } | 2159 | [ -n "$PAPID" ] || { echo "e2e FAIL: mux up-line carries no pid"; cat "$OUT.pa.err"; exit 1; } |
| 2134 | assert_converged "$OUT.pa" "$SOCK15" "local mux auto-start" | 2160 | assert_converged "$OUT.pa" "$SOCK15" "local mux auto-start" |
| 2135 | "$MUXD" stop --sock "$SOCK15" 2>/dev/null | 2161 | # Same teardown, same reasons — and the stderr is captured rather than |
| 2136 | [ ! -S "$SOCK15" ] || { echo "e2e FAIL: stop left the pty leg's socket"; exit 1; } | 2162 | # discarded, so this leg pins the stopped line too. $OUT.stop is reused |
| 2137 | _i=0 | 2163 | # deliberately: the proxy arc removed it above, and it is in the trap's |
| 2138 | while kill -0 "$PAPID" 2>/dev/null; do | 2164 | # rm list either way. |
| 2139 | _i=$((_i + 1)); [ "$_i" -lt 40 ] || { | 2165 | set +e |
| 2140 | echo "e2e FAIL: stop reported stopped but pid $PAPID still runs"; exit 1; } | 2166 | "$MUXD" stop --sock "$SOCK15" 2> "$OUT.stop" |
| 2141 | sleep 0.05 | 2167 | RC_STOP=$? |
| 2142 | done | 2168 | set -e |
| 2169 | [ "$RC_STOP" = "0" ] || { | ||
| 2170 | echo "e2e FAIL: pty leg stop exited $RC_STOP, want 0"; cat "$OUT.stop"; exit 1; } | ||
| 2171 | grep -q '^muxd: stopped' "$OUT.stop" || { | ||
| 2172 | echo "e2e FAIL: pty leg stop did not report stopped"; cat "$OUT.stop"; exit 1; } | ||
| 2173 | [ ! -S "$SOCK15" ] || { | ||
| 2174 | echo "e2e FAIL: stop left the pty leg's socket"; ls -l "$SOCK15"; exit 1; } | ||
| 2175 | wait_pid_gone "$PAPID" "pty leg" | ||
| 2143 | PAPID="" | 2176 | PAPID="" |
| 2144 | rm -f "$OUT.pa" "$OUT.pa.err" "$OUT.pa.log" | 2177 | rm -f "$OUT.pa" "$OUT.pa.err" "$OUT.pa.log" "$OUT.stop" |
| 2145 | ok "local mux auto-start under the pty fixture" | 2178 | ok "local mux auto-start under the pty fixture" |
| 2146 | 2179 | ||
| 2147 | # The pins. Literals, not variables set from counting something else — | 2180 | # The pins. Literals, not variables set from counting something else — |