a73x

f3f916f1

test: the pty grace test fails when the child died inside the grace

a73x   2026-09-04 10:16

Commit message
test: the pty grace test fails when the child died inside the grace

`deinit is bounded even when the child ignores HUP and TERM` asserted only
an upper bound, so a child that died to the TERM sent before its `trap`
line ran satisfied both assertions faster than an armed one. The SIGKILL
path the test is named for was never walked, and the only symptom was the
suite running quicker.

The lower bound is the nag test's shape: below `Pty.term_grace_ms` is a
named error with a sentence saying what happened. `pty` cannot reach the
daemon harness's armed-marker door, so the clock is the pin available here.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SakwJEwD9dXBoRP5kWbemW

src/server/pty.zig
Old New
@@ -233,8 +233,25 @@ test "Pty: deinit is bounded even when the child ignores HUP and TERM" {
233 pty.deinit(); 233 pty.deinit();
234 const elapsed_ms = t.read() / std.time.ns_per_ms; 234 const elapsed_ms = t.read() / std.time.ns_per_ms;
235 235
236 // Returned at all is the headline. The bound is the assertion that can 236 // The LOWER bound is the property. A child that died to the TERM sent
237 // still fail fast if someone widens the grace period without meaning to. 237 // before its `trap` line ran is dead too, and every assertion here holds
238 // for it while the SIGKILL path this test is named for was never walked —
239 // a vacuous pass whose only symptom is the suite running faster. The
240 // 300 ms above is 15x the worst arming measured (21 ms, bash 3.2 on a
241 // loaded Mac), so losing that race means the box is slow, not that the
242 // product changed; this turns it into a sentence rather than a green run.
243 // `pty` cannot reach the harness's armed-marker door — that is the
244 // daemon's fixture and this file is under it — so the clock is the pin.
245 if (elapsed_ms < Pty.term_grace_ms) {
246 std.debug.print(
247 "the child died {d} ms into the {d} ms grace: it did not ignore the TERM, " ++
248 "so the SIGKILL that bounds deinit was never reached\n",
249 .{ elapsed_ms, Pty.term_grace_ms },
250 );
251 return error.ChildDiedInsideTheGrace;
252 }
253 // Returned at all is the headline. The upper bound is the assertion that
254 // can still fail fast if someone widens the grace period without meaning to.
238 try std.testing.expect(elapsed_ms < 3000); 255 try std.testing.expect(elapsed_ms < 3000);
239 // Reaped, not merely abandoned: a deinit that returned while leaving a 256 // Reaped, not merely abandoned: a deinit that returned while leaving a
240 // zombie would satisfy the clock and leak the process. 257 // zombie would satisfy the clock and leak the process.