a689f425
test: server_test_clipboard rides the harness awaits
a73x 2026-08-31 21:58
Commit message
src/server/server_test_clipboard.zig
| Old | New | ||
|---|---|---|---|
| @@ -13,26 +13,18 @@ const awaitGridText = h.awaitGridText; | |||
| 13 | const connectedPair = h.connectedPair; | 13 | const connectedPair = h.connectedPair; |
| 14 | const writeDyingGapShell = h.writeDyingGapShell; | 14 | const writeDyingGapShell = h.writeDyingGapShell; |
| 15 | 15 | ||
| 16 | /// Returns the parts of the reply that outlive the frame's payload. | 16 | /// Returns the parts of the reply that outlive the frame's payload — which |
| 17 | /// is the whole reason this is not `awaitFrame` spelled at each call site: | ||
| 18 | /// `text_len` has to be read while the payload is still alive. | ||
| 17 | fn awaitSelectionReply( | 19 | fn awaitSelectionReply( |
| 18 | alloc: std.mem.Allocator, | 20 | alloc: std.mem.Allocator, |
| 19 | srv: *Server, | 21 | srv: *Server, |
| 20 | peer: std.posix.fd_t, | 22 | peer: std.posix.fd_t, |
| 21 | ) !?struct { id: u32, status: proto.SelectionStatus, text_len: usize } { | 23 | ) !?struct { id: u32, status: proto.SelectionStatus, text_len: usize } { |
| 22 | var rounds: usize = 0; | 24 | const frame = (try awaitFrame(alloc, srv, peer, .selection_reply, 400)) orelse return null; |
| 23 | while (rounds < 400) : (rounds += 1) { | 25 | defer frame.deinit(alloc); |
| 24 | try srv.pumpOnce(5); | 26 | const reply = try proto.decodeSelectionReply(frame.payload); |
| 25 | var pfd = [_]std.posix.pollfd{ | 27 | return .{ .id = reply.id, .status = reply.status, .text_len = reply.text.len }; |
| 26 | .{ .fd = peer, .events = std.posix.POLL.IN, .revents = 0 }, | ||
| 27 | }; | ||
| 28 | if (try std.posix.poll(&pfd, 0) == 0) continue; | ||
| 29 | const frame = (try proto.readFrame(alloc, peer)) orelse return null; | ||
| 30 | defer frame.deinit(alloc); | ||
| 31 | if (frame.type != .selection_reply) continue; | ||
| 32 | const reply = try proto.decodeSelectionReply(frame.payload); | ||
| 33 | return .{ .id = reply.id, .status = reply.status, .text_len = reply.text.len }; | ||
| 34 | } | ||
| 35 | return null; | ||
| 36 | } | 28 | } |
| 37 | 29 | ||
| 38 | test "Server: a session-less slot still answers a well-formed selection request" { | 30 | test "Server: a session-less slot still answers a well-formed selection request" { |
| @@ -290,6 +282,12 @@ const GapReplay = struct { | |||
| 290 | /// The 400 below is `awaitFrame`'s own budget for an attach reply, ~6ms an | 282 | /// The 400 below is `awaitFrame`'s own budget for an attach reply, ~6ms an |
| 291 | /// iteration; the 60 is the settling window that keeps running past the content | 283 | /// iteration; the 60 is the settling window that keeps running past the content |
| 292 | /// frame, so an event queued after it is caught too. | 284 | /// frame, so an event queued after it is caught too. |
| 285 | /// | ||
| 286 | /// So this stays a pump-and-poll loop rather than becoming a `Link.awaitFrame` | ||
| 287 | /// wait: both numbers are ROUND counts on purpose. The wait ends a fixed | ||
| 288 | /// number of pumps after the content frame, not when a particular frame | ||
| 289 | /// arrives, and a sink that ended the wait early would be the blindness the | ||
| 290 | /// paragraph above describes. | ||
| 293 | fn collectGapReplay(alloc: std.mem.Allocator, srv: *Server, fd: std.posix.fd_t) !GapReplay { | 291 | fn collectGapReplay(alloc: std.mem.Allocator, srv: *Server, fd: std.posix.fd_t) !GapReplay { |
| 294 | var out: GapReplay = .{}; | 292 | var out: GapReplay = .{}; |
| 295 | var i: usize = 0; | 293 | var i: usize = 0; |