a73x

e97d10f9

test: server_test_await and server_test_deliver ride the harness awaits

a73x   2026-08-31 21:58

Commit message
test: server_test_await and server_test_deliver ride the harness awaits

`anyReturnWithin` is this pair of files' one convertible loop: it wants a
`cmd_state` in the `returned` phase, and a cmd_state in any other phase is
not the answer — so the answer comes out of a sink rather than out of
`want`, which would take the first one and call it a return. It keeps its
wall-clock budget, since its contract is "inside this many milliseconds,
nothing came back", and the harness's pumping await counts rounds.

Everything else in these two files stays, and now says why in place:
`readQueued` must NOT pump and ends on silence; the bare `poll(fd, 0)` is
a claim about this instant, not a window; the two byte-stream loops in
the pollout test compare what the peer read BYTE for byte; and the
settle-drains and `PeerDrainer` end on silence, which `Link.awaitFrame`
does not report — a poll that timed out inside it is indistinguishable
from the deadline running out.

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

src/server/server_test_await.zig
Old New
@@ -91,18 +91,32 @@ fn anyReturnWithin(
91 fd: std.posix.fd_t, 91 fd: std.posix.fd_t,
92 budget_ms: i64, 92 budget_ms: i64,
93 ) !?proto.CmdState { 93 ) !?proto.CmdState {
94 // The answer comes out of the SINK, because a cmd_state in any other
95 // phase is not it: `want` would take the first one and call it the
96 // return. The wall clock stays the budget — the harness's pumping await
97 // counts rounds, and this one's contract is "inside this many
98 // milliseconds, nothing came back".
99 const Returned = struct {
100 st: proto.CmdState = undefined,
101 fn on(ctx: ?*anyopaque, frame: proto.Frame) anyerror!void {
102 if (frame.type != .cmd_state) return;
103 const self: *@This() = @ptrCast(@alignCast(ctx.?));
104 const st = try proto.decodeCmdState(frame.payload);
105 if (st.phase != .returned) return;
106 self.st = st;
107 return error.CommandReturned;
108 }
109 };
110 var seen: Returned = .{};
94 const deadline = std.time.milliTimestamp() + budget_ms; 111 const deadline = std.time.milliTimestamp() + budget_ms;
95 while (std.time.milliTimestamp() < deadline) { 112 while (std.time.milliTimestamp() < deadline) {
96 try srv.pumpOnce(5); 113 _ = h.awaitFrameSink(alloc, srv, fd, h.never_from_daemon, 1, .{
97 var pfd = [_]std.posix.pollfd{ 114 .ctx = &seen,
98 .{ .fd = fd, .events = std.posix.POLL.IN, .revents = 0 }, 115 .on = Returned.on,
116 }) catch |err| switch (err) {
117 error.CommandReturned => return seen.st,
118 else => return err,
99 }; 119 };
100 if ((std.posix.poll(&pfd, 1) catch 0) == 0) continue;
101 const frame = (try proto.readFrame(alloc, fd)) orelse return null;
102 defer frame.deinit(alloc);
103 if (frame.type != .cmd_state) continue;
104 const st = try proto.decodeCmdState(frame.payload);
105 if (st.phase == .returned) return st;
106 } 120 }
107 return null; 121 return null;
108 } 122 }
@@ -412,6 +426,10 @@ test "Server: an unknown shell is not injected into at all — no directory, no
412 /// of `want` turns up or the socket goes quiet — the "did the daemon answer 426 /// of `want` turns up or the socket goes quiet — the "did the daemon answer
413 /// inside that one pump" observable. An answer needing another pump reads as 427 /// inside that one pump" observable. An answer needing another pump reads as
414 /// absent, which is the distinction the immediate-answer test makes. 428 /// absent, which is the distinction the immediate-answer test makes.
429 ///
430 /// Its own poll rather than `Link.awaitFrame` for both halves of that: it
431 /// must not pump, and it ends on SILENCE, which the primitive does not
432 /// report — a poll that timed out inside it looks the same as a deadline.
415 fn readQueued(alloc: std.mem.Allocator, fd: std.posix.fd_t, want: proto.MsgType) !?proto.Frame { 433 fn readQueued(alloc: std.mem.Allocator, fd: std.posix.fd_t, want: proto.MsgType) !?proto.Frame {
416 var guard: usize = 0; 434 var guard: usize = 0;
417 while (guard < 16) : (guard += 1) { 435 while (guard < 16) : (guard += 1) {
@@ -963,6 +981,9 @@ test "Server: a promoted-but-unattached slot receives nothing" {
963 // The slot was told nothing — nothing queued, nothing on the wire. 981 // The slot was told nothing — nothing queued, nothing on the wire.
964 try std.testing.expect(td.srv.clients[0] != null); 982 try std.testing.expect(td.srv.clients[0] != null);
965 try std.testing.expectEqual(@as(usize, 0), td.srv.clients[0].?.pending.items.len); 983 try std.testing.expectEqual(@as(usize, 0), td.srv.clients[0].?.pending.items.len);
984 // A bare poll, not a wait: the claim is that nothing is readable at this
985 // instant, and any budget at all would turn it into a slower claim about
986 // a window instead.
966 var pfd = [_]std.posix.pollfd{ 987 var pfd = [_]std.posix.pollfd{
967 .{ .fd = c.peer, .events = std.posix.POLL.IN, .revents = 0 }, 988 .{ .fd = c.peer, .events = std.posix.POLL.IN, .revents = 0 },
968 }; 989 };
src/server/server_test_deliver.zig
Old New
@@ -125,7 +125,8 @@ test "Server: a writable backlog is flushed by poll, not mistaken for input" {
125 defer td.deinit(); 125 defer td.deinit();
126 126
127 // Drain the shell's startup output first, so the pump below has exactly 127 // Drain the shell's startup output first, so the pump below has exactly
128 // one thing to react to: the client's writability. 128 // one thing to react to: the client's writability. Raw bytes off the pty
129 // master, so there is no Link here to await on.
129 while (true) { 130 while (true) {
130 var pfd = [_]std.posix.pollfd{ 131 var pfd = [_]std.posix.pollfd{
131 .{ .fd = td.srv.sessions.table[0].?.pty.master, .events = std.posix.POLL.IN, .revents = 0 }, 132 .{ .fd = td.srv.sessions.table[0].?.pty.master, .events = std.posix.POLL.IN, .revents = 0 },
@@ -192,6 +193,9 @@ test "Server: a partially flushed queue delivers every byte exactly once" {
192 try std.testing.expect(td.srv.clients[0].?.pending.items.len > 0); 193 try std.testing.expect(td.srv.clients[0].?.pending.items.len > 0);
193 194
194 // Drain and flush alternately: each flush starts where the last stopped. 195 // Drain and flush alternately: each flush starts where the last stopped.
196 // Byte-level on purpose, all the way down: the verdict below is that
197 // what the peer read equals `expect` BYTE for byte, so these two loops
198 // read a stream rather than frames and cannot be a frame await.
195 var got: std.ArrayList(u8) = .empty; 199 var got: std.ArrayList(u8) = .empty;
196 defer got.deinit(alloc); 200 defer got.deinit(alloc);
197 var spins: usize = 0; 201 var spins: usize = 0;
@@ -232,7 +236,10 @@ test "Server: the shell's exit status reaches an attached client" {
232 defer c.close(); 236 defer c.close();
233 237
234 // Settle first, so the shell is ready for input and the exit below is 238 // Settle first, so the shell is ready for input and the exit below is
235 // the next thing that happens. 239 // the next thing that happens. Its own poll, like the quiet-drains in
240 // server_test_attach.zig: the condition is SILENCE, and inside
241 // `Link.awaitFrame` a poll that timed out is indistinguishable from the
242 // deadline running out.
236 var quiet_ms: u64 = 0; 243 var quiet_ms: u64 = 0;
237 var deadline_ms: u64 = 10_000; 244 var deadline_ms: u64 = 10_000;
238 while (deadline_ms > 0 and quiet_ms < 500) { 245 while (deadline_ms > 0 and quiet_ms < 500) {
@@ -266,7 +273,8 @@ test "Server: the shell's exit status reaches an attached client" {
266 273
267 /// Test helper: read one fd until it has been quiet for `quiet_ms`, keeping 274 /// Test helper: read one fd until it has been quiet for `quiet_ms`, keeping
268 /// everything. Runs on its own thread so a drain under test has a peer that 275 /// everything. Runs on its own thread so a drain under test has a peer that
269 /// is actually consuming. 276 /// is actually consuming. Raw bytes and a silence condition, so neither half
277 /// of it is a frame await.
270 const PeerDrainer = struct { 278 const PeerDrainer = struct {
271 fd: std.posix.fd_t, 279 fd: std.posix.fd_t,
272 alloc: std.mem.Allocator, 280 alloc: std.mem.Allocator,