a73x

2fe0d38a

test: server_test_upgrade and server_test_quic ride the harness awaits

a73x   2026-08-31 21:58

Commit message
test: server_test_upgrade and server_test_quic ride the harness awaits

One counted wait left in these two, and it becomes `pumpUntil` on
`Emptied`: the assertion after the old loop could not tell "the sessions
went away" from "500 rounds ran out", and now the wait names the
condition it failed to reach.

Everything else stays, with its reason in place. Every poll in
server_test_quic is on the QUIC socket, driving a TestPeer's own ngtcp2
state rather than awaiting a frame — and a QUIC `Link` owns its client,
so wrapping one here would close a connection the test still holds. The
load loop queues a frame per round, so it is applying pressure, not
waiting. The `i < 63` runs are literal counts against
`max_drain_stalls`, spelled out on purpose so the test cannot move its
own goalposts.

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

src/server/server_test_quic.zig
Old New
@@ -127,6 +127,9 @@ test "Server: output reaches a silent QUIC client without waiting for it to spea
127 // tick() drains on expiry and delivers the frame below whether or not 127 // tick() drains on expiry and delivers the frame below whether or not
128 // anything else does. Settle until the next timer is comfortably in the 128 // anything else does. Settle until the next timer is comfortably in the
129 // future; only then is tick ruled out as the deliverer. 129 // future; only then is tick ruled out as the deliverer.
130 // Every poll in this file is on the QUIC socket, driving a TestPeer's
131 // own ngtcp2 state — none of them is a frame await, so none becomes a
132 // `Link.awaitFrame`. A QUIC Link owns its client and would close it.
130 var settle: usize = 0; 133 var settle: usize = 0;
131 while (settle < 400) : (settle += 1) { 134 while (settle < 400) : (settle += 1) {
132 if (q.l.timeoutMs(1000) > 100) break; 135 if (q.l.timeoutMs(1000) > 100) break;
@@ -161,6 +164,8 @@ test "Server: output reaches a silent QUIC client without waiting for it to spea
161 164
162 // Reading is not transmitting: the client takes whatever already 165 // Reading is not transmitting: the client takes whatever already
163 // arrived, and never gives the server an inbound packet to react to. 166 // arrived, and never gives the server an inbound packet to react to.
167 // And this one must NOT pump the daemon at all, which is the whole
168 // isolation the paragraph above sets up.
164 var tries: usize = 0; 169 var tries: usize = 0;
165 while (tries < 60 and findFrame(cl.cl.in.items, .delta) == null) : (tries += 1) { 170 while (tries < 60 and findFrame(cl.cl.in.items, .delta) == null) : (tries += 1) {
166 var pfd = [_]std.posix.pollfd{ 171 var pfd = [_]std.posix.pollfd{
@@ -318,6 +323,8 @@ test "Server: a QUIC client that stops reading is dropped by the cap, not tolera
318 defer alloc.free(chunk); 323 defer alloc.free(chunk);
319 @memset(chunk, 'q'); 324 @memset(chunk, 'q');
320 325
326 // Not a `pumpUntil`: each round QUEUES another frame, so the loop is
327 // applying load rather than waiting for a condition.
321 var i: usize = 0; 328 var i: usize = 0;
322 while (i < 200 and srv.clients[0] != null) : (i += 1) { 329 while (i < 200 and srv.clients[0] != null) : (i += 1) {
323 _ = srv.queueFrame(0, .snapshot, chunk); 330 _ = srv.queueFrame(0, .snapshot, chunk);
src/server/server_test_upgrade.zig
Old New
@@ -508,9 +508,13 @@ test "Server: an EMPTY daemon upgrades — the manifest names no session and the
508 .version = "0.0.1-1", 508 .version = "0.0.1-1",
509 }); 509 });
510 510
511 var rounds: usize = 0; 511 const Emptied = struct {
512 while (rounds < 500 and srv.sessions.live() != 0) : (rounds += 1) try srv.pumpOnce(5); 512 srv: *Server,
513 try std.testing.expectEqual(@as(usize, 0), srv.sessions.live()); 513 fn yes(self: @This()) bool {
514 return self.srv.sessions.live() == 0;
515 }
516 };
517 try std.testing.expect(try h.pumpUntil(&srv, 3000, Emptied{ .srv = &srv }, Emptied.yes));
514 518
515 // A candidate that passes every check, run against a daemon holding 519 // A candidate that passes every check, run against a daemon holding
516 // nothing: emptiness must not be mistaken for the one state that DOES 520 // nothing: emptiness must not be mistaken for the one state that DOES