4ab43004
test: the nag test pins the grace it is named for
a73x 2026-09-04 10:16
Commit message
src/server/server_test_harness.zig
| Old | New | ||
|---|---|---|---|
| @@ -1,6 +1,6 @@ | |||
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const Engine = @import("term").engine.Engine; | 2 | const Engine = @import("term").engine.Engine; |
| 3 | const Pty = @import("pty").Pty; | 3 | pub const Pty = @import("pty").Pty; |
| 4 | const proto = @import("term").protocol; | 4 | const proto = @import("term").protocol; |
| 5 | const replica_mod = @import("term").replica; | 5 | const replica_mod = @import("term").replica; |
| 6 | const quic = @import("quic"); | 6 | const quic = @import("quic"); |
src/server/server_test_session.zig
| Old | New | ||
|---|---|---|---|
| @@ -8,6 +8,7 @@ const xdg = @import("xdg"); | |||
| 8 | const TmpDir = @import("testtmp").TmpDir; | 8 | const TmpDir = @import("testtmp").TmpDir; |
| 9 | const h = @import("server_test_harness.zig"); | 9 | const h = @import("server_test_harness.zig"); |
| 10 | const dial = h.dial; | 10 | const dial = h.dial; |
| 11 | const Pty = h.Pty; | ||
| 11 | const srv_mod = @import("server.zig"); | 12 | const srv_mod = @import("server.zig"); |
| 12 | const SessionTable = @import("server_sessions.zig").SessionTable; | 13 | const SessionTable = @import("server_sessions.zig").SessionTable; |
| 13 | const Server = srv_mod.Server; | 14 | const Server = srv_mod.Server; |
| @@ -1665,6 +1666,9 @@ test "Server: a repeated end_req does not push the SIGKILL deadline out" { | |||
| 1665 | const r = (try awaitFrame(alloc, &td.srv, c.handle, .end_reply, 200)) orelse return error.NoEndReply; | 1666 | const r = (try awaitFrame(alloc, &td.srv, c.handle, .end_reply, 200)) orelse return error.NoEndReply; |
| 1666 | defer r.deinit(alloc); | 1667 | defer r.deinit(alloc); |
| 1667 | try std.testing.expect((proto.parseEndReply(r.payload) orelse return error.BadEndReply).accepted); | 1668 | try std.testing.expect((proto.parseEndReply(r.payload) orelse return error.BadEndReply).accepted); |
| 1669 | // The clock the assertions below read, started where the daemon started | ||
| 1670 | // its own: `endSession` arms the deadline when it accepts. | ||
| 1671 | const t_end = std.time.milliTimestamp(); | ||
| 1668 | 1672 | ||
| 1669 | // An observer nagging faster than the grace: if each accept restarted the | 1673 | // An observer nagging faster than the grace: if each accept restarted the |
| 1670 | // clock, the shell would outlive every deadline it was ever given. | 1674 | // clock, the shell would outlive every deadline it was ever given. |
| @@ -1679,5 +1683,39 @@ test "Server: a repeated end_req does not push the SIGKILL deadline out" { | |||
| 1679 | try td.srv.pumpOnce(20); | 1683 | try td.srv.pumpOnce(20); |
| 1680 | std.Thread.sleep(60 * std.time.ns_per_ms); | 1684 | std.Thread.sleep(60 * std.time.ns_per_ms); |
| 1681 | } | 1685 | } |
| 1686 | const outlived_ms = std.time.milliTimestamp() - t_end; | ||
| 1682 | try std.testing.expect(!alive(pid)); | 1687 | try std.testing.expect(!alive(pid)); |
| 1688 | |||
| 1689 | // A dead shell is only half the claim, and it was the only half asserted: | ||
| 1690 | // a shell that died to the FIRST SIGTERM is dead too, and a fixture that | ||
| 1691 | // let that happen passed this test while the deadline it is named for was | ||
| 1692 | // never reached. That is not hypothetical — it is what a wait on the wrong | ||
| 1693 | // session did here, twice, and the difference was visible only as the | ||
| 1694 | // suite running faster. So the duration is the pin now. A wall clock and | ||
| 1695 | // not the loop counter above, which counts nominal turns rather than time. | ||
| 1696 | // | ||
| 1697 | // The LOWER bound is the real one: living past the grace is what proves | ||
| 1698 | // the shell ignored every TERM and left to the SIGKILL. The upper bound | ||
| 1699 | // catches a deadline that moved a few turns; a deadline that moved | ||
| 1700 | // FOREVER is already caught by the loop's own budget and the `!alive` | ||
| 1701 | // above, so this one is deliberately loose rather than a timing gate. | ||
| 1702 | // Measured 2026-09-04 against a 500 ms grace: 661 ms on Linux and 668 ms | ||
| 1703 | // on macOS under `make check`. A bound at 2x would sit 330 ms above that, | ||
| 1704 | // which is a flake waiting for a slower box. | ||
| 1705 | if (outlived_ms < Pty.term_grace_ms) { | ||
| 1706 | std.debug.print( | ||
| 1707 | "the shell died {d} ms after the accepted end, INSIDE the {d} ms grace: " ++ | ||
| 1708 | "it did not ignore the TERM, so the SIGKILL deadline was never reached\n", | ||
| 1709 | .{ outlived_ms, Pty.term_grace_ms }, | ||
| 1710 | ); | ||
| 1711 | return error.ShellDiedInsideTheGrace; | ||
| 1712 | } | ||
| 1713 | if (outlived_ms >= 4 * Pty.term_grace_ms) { | ||
| 1714 | std.debug.print( | ||
| 1715 | "the shell lived {d} ms after the accepted end, past 4x the {d} ms grace: " ++ | ||
| 1716 | "the nagging looks to have pushed the deadline out\n", | ||
| 1717 | .{ outlived_ms, Pty.term_grace_ms }, | ||
| 1718 | ); | ||
| 1719 | return error.GraceDeadlineMoved; | ||
| 1720 | } | ||
| 1683 | } | 1721 | } |