lib/sys/src/event/test.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const subject = @import("root.zig");
  3 const sys = @import("../root.zig");
  4 
  5 const net = sys.net;
  6 
  7 const required_capabilities = subject.required_capabilities;
  8 const AcceptError = subject.AcceptError;
  9 const CallbackAction = subject.CallbackAction;
 10 const CloseError = subject.CloseError;
 11 const Datagram = subject.Datagram;
 12 const Options = subject.Options;
 13 const PollError = subject.PollError;
 14 const PollEvent = subject.PollEvent;
 15 const ReadBuffer = subject.ReadBuffer;
 16 const ReadError = subject.ReadError;
 17 const RecvError = subject.RecvError;
 18 const RunMode = subject.RunMode;
 19 const SendError = subject.SendError;
 20 const WriteBuffer = subject.WriteBuffer;
 21 const WriteError = subject.WriteError;
 22 const Async = subject.Async;
 23 const Completion = subject.Completion;
 24 const File = subject.File;
 25 const Loop = subject.Loop;
 26 const TCP = subject.TCP;
 27 const Timer = subject.Timer;
 28 const UDP = subject.UDP;
 29 
 30 const capture_count_max: usize = 2;
 31 const capture_bytes_max: usize = 64;
 32 
 33 const UdpPair = struct {
 34     sender: UDP,
 35     receiver: UDP,
 36     sender_address: net.IpAddress,
 37     receiver_address: net.IpAddress,
 38 
 39     fn init(address: net.IpAddress) !UdpPair {
 40         const sender = try UDP.bind(address);
 41         errdefer sender.close();
 42         const receiver = try UDP.bind(address);
 43         errdefer receiver.close();
 44         const sender_name = try net.socketAddressValue(sender.fd);
 45         const receiver_name = try net.socketAddressValue(receiver.fd);
 46         return .{
 47             .sender = sender,
 48             .receiver = receiver,
 49             .sender_address = sender_name.ipAddress() orelse return error.MissingIpAddress,
 50             .receiver_address = receiver_name.ipAddress() orelse return error.MissingIpAddress,
 51         };
 52     }
 53 
 54     fn close(self: UdpPair) void {
 55         self.sender.close();
 56         self.receiver.close();
 57     }
 58 };
 59 
 60 const ReceiveCapture = struct {
 61     expected_pointer: ?[*]u8,
 62     expected_length: usize,
 63     target: u8 = 1,
 64     hits: u8 = 0,
 65     same_buffer: bool = true,
 66     overflow: bool = false,
 67     datagrams: [capture_count_max]?Datagram = @splat(null),
 68     errors: [capture_count_max]?RecvError = @splat(null),
 69     payloads: [capture_count_max][capture_bytes_max]u8 = undefined,
 70     payload_lengths: [capture_count_max]usize = @splat(0),
 71 };
 72 
 73 const SendCapture = struct {
 74     expected: []const u8,
 75     expected_pointer: ?[*]const u8,
 76     hits: u8 = 0,
 77     same_buffer: bool = true,
 78     bytes: ?usize = null,
 79     err: ?SendError = null,
 80 };
 81 
 82 fn receiveCapture(buffer: ReadBuffer, target: u8) ReceiveCapture {
 83     return switch (buffer) {
 84         .slice => |slice| .{
 85             .expected_pointer = slice.ptr,
 86             .expected_length = slice.len,
 87             .target = target,
 88         },
 89         .array => |array| .{
 90             .expected_pointer = null,
 91             .expected_length = array.len,
 92             .target = target,
 93         },
 94     };
 95 }
 96 
 97 fn sendCapture(buffer: WriteBuffer, expected: []const u8) SendCapture {
 98     return switch (buffer) {
 99         .slice => |slice| .{ .expected = expected, .expected_pointer = slice.ptr },
100         .array => .{ .expected = expected, .expected_pointer = null },
101     };
102 }
103 
104 fn captureReceivedBytes(
105     capture: *ReceiveCapture,
106     index: usize,
107     buffer: ReadBuffer,
108     bytes: usize,
109 ) void {
110     var source: []const u8 = undefined;
111     switch (buffer) {
112         .slice => |slice| {
113             source = slice;
114             capture.same_buffer = capture.same_buffer and
115                 capture.expected_pointer == slice.ptr and
116                 capture.expected_length == slice.len;
117         },
118         .array => |array| {
119             source = &array;
120             capture.same_buffer = capture.same_buffer and
121                 capture.expected_pointer == null and
122                 capture.expected_length == array.len;
123         },
124     }
125     if (bytes > capture.payloads[index].len) {
126         capture.overflow = true;
127         return;
128     }
129     @memcpy(capture.payloads[index][0..bytes], source[0..bytes]);
130     capture.payload_lengths[index] = bytes;
131 }
132 
133 fn receiveCallback(
134     state: ?*ReceiveCapture,
135     _: *Loop,
136     _: *Completion,
137     _: UDP,
138     buffer: ReadBuffer,
139     result: RecvError!Datagram,
140 ) CallbackAction {
141     const capture = state.?;
142     if (capture.hits >= capture.datagrams.len) {
143         capture.overflow = true;
144         return .disarm;
145     }
146     const index = capture.hits;
147     capture.hits += 1;
148     if (result) |datagram| {
149         capture.datagrams[index] = datagram;
150         captureReceivedBytes(capture, index, buffer, datagram.bytes);
151     } else |err| {
152         capture.errors[index] = err;
153     }
154     return if (capture.hits < capture.target) .rearm else .disarm;
155 }
156 
157 fn sendCallback(
158     state: ?*SendCapture,
159     _: *Loop,
160     _: *Completion,
161     _: UDP,
162     buffer: WriteBuffer,
163     result: SendError!usize,
164 ) CallbackAction {
165     const capture = state.?;
166     capture.hits += 1;
167     const bytes = switch (buffer) {
168         .slice => |slice| blk: {
169             capture.same_buffer = capture.expected_pointer == slice.ptr;
170             break :blk slice;
171         },
172         .array => |array| array.array[0..array.len],
173     };
174     capture.same_buffer = capture.same_buffer and std.mem.eql(u8, capture.expected, bytes);
175     if (result) |sent| capture.bytes = sent else |err| capture.err = err;
176     return .disarm;
177 }
178 
179 fn runRoundTrip(
180     pair: *const UdpPair,
181     read_buffer: ReadBuffer,
182     write_buffer: WriteBuffer,
183     expected: []const u8,
184 ) !void {
185     var loop = try Loop.init(.{ .allocator = std.testing.allocator, .entries = 2 });
186     defer loop.deinit();
187     var completions: [2]Completion = @splat(.{});
188     var receive = receiveCapture(read_buffer, 1);
189     var send = sendCapture(write_buffer, expected);
190     pair.receiver.recvFrom(
191         &loop,
192         &completions[0],
193         read_buffer,
194         ReceiveCapture,
195         &receive,
196         receiveCallback,
197     );
198     pair.sender.sendTo(
199         &loop,
200         &completions[1],
201         write_buffer,
202         pair.receiver_address,
203         SendCapture,
204         &send,
205         sendCallback,
206     );
207     try loop.run(.until_done);
208 
209     try std.testing.expectEqual(@as(u8, 1), receive.hits);
210     try std.testing.expectEqual(@as(u8, 1), send.hits);
211     try std.testing.expect(receive.same_buffer);
212     try std.testing.expect(send.same_buffer);
213     try std.testing.expectEqual(@as(?RecvError, null), receive.errors[0]);
214     try std.testing.expectEqual(@as(?SendError, null), send.err);
215     try std.testing.expectEqual(@as(?usize, expected.len), send.bytes);
216     const datagram = receive.datagrams[0] orelse return error.MissingDatagram;
217     try std.testing.expect(net.ipAddressEql(pair.sender_address, datagram.address));
218     try std.testing.expectEqual(expected.len, datagram.bytes);
219     try std.testing.expect(!datagram.truncated);
220     try std.testing.expectEqualSlices(u8, expected, receive.payloads[0][0..datagram.bytes]);
221 }
222 
223 test "event UDP IPv4 loopback round trip with inline buffers" {
224     var pair = try UdpPair.init(.{ .ip4 = net.ip4Address(.{ 127, 0, 0, 1 }, 0) });
225     defer pair.close();
226     const payload = "inline datagram";
227     var write_array: [32]u8 = undefined;
228     @memcpy(write_array[0..payload.len], payload);
229     try runRoundTrip(
230         &pair,
231         .{ .array = undefined },
232         .{ .array = .{ .array = write_array, .len = payload.len } },
233         payload,
234     );
235 }
236 
237 test "event UDP IPv4 loopback round trip with slice buffers" {
238     var pair = try UdpPair.init(.{ .ip4 = net.ip4Address(.{ 127, 0, 0, 1 }, 0) });
239     defer pair.close();
240     const payload = "slice datagram";
241     var read_buffer: [32]u8 = undefined;
242     try runRoundTrip(
243         &pair,
244         .{ .slice = &read_buffer },
245         .{ .slice = payload },
246         payload,
247     );
248 }
249 
250 test "event UDP IPv6 ::1 round trip skips when the host lacks IPv6 loopback" {
251     const loopback = net.ip6Address(.{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, 0);
252     var pair = UdpPair.init(.{ .ip6 = loopback }) catch |err| switch (err) {
253         error.UnsupportedPlatform,
254         error.AddressFamilyUnsupported,
255         error.ProtocolUnsupportedBySystem,
256         error.ProtocolUnsupportedByAddressFamily,
257         error.BindFailed,
258         => return error.SkipZigTest,
259         else => return err,
260     };
261     defer pair.close();
262     const payload = "ipv6 datagram";
263     var read_buffer: [32]u8 = undefined;
264     try runRoundTrip(
265         &pair,
266         .{ .slice = &read_buffer },
267         .{ .slice = payload },
268         payload,
269     );
270 }
271 
272 test "event UDP reports a truncated datagram and copied byte count" {
273     var pair = try UdpPair.init(.{ .ip4 = net.ip4Address(.{ 127, 0, 0, 1 }, 0) });
274     defer pair.close();
275     var loop = try Loop.init(.{ .allocator = std.testing.allocator, .entries = 2 });
276     defer loop.deinit();
277     var completions: [2]Completion = @splat(.{});
278     var read_buffer: [16]u8 = undefined;
279     var payload: [64]u8 = undefined;
280     for (&payload, 0..) |*byte, index| byte.* = @intCast(index);
281     const read_value: ReadBuffer = .{ .slice = &read_buffer };
282     const write_value: WriteBuffer = .{ .slice = &payload };
283     var receive = receiveCapture(read_value, 1);
284     var send = sendCapture(write_value, &payload);
285     pair.receiver.recvFrom(
286         &loop,
287         &completions[0],
288         read_value,
289         ReceiveCapture,
290         &receive,
291         receiveCallback,
292     );
293     pair.sender.sendTo(
294         &loop,
295         &completions[1],
296         write_value,
297         pair.receiver_address,
298         SendCapture,
299         &send,
300         sendCallback,
301     );
302     try loop.run(.until_done);
303 
304     const datagram = receive.datagrams[0] orelse return error.MissingDatagram;
305     try std.testing.expect(datagram.truncated);
306     try std.testing.expectEqual(read_buffer.len, datagram.bytes);
307     try std.testing.expectEqualSlices(u8, payload[0..read_buffer.len], &read_buffer);
308     try std.testing.expectEqual(@as(?usize, payload.len), send.bytes);
309 }
310 
311 test "event UDP disarm preserves the receive buffer and suppresses its callback" {
312     var pair = try UdpPair.init(.{ .ip4 = net.ip4Address(.{ 127, 0, 0, 1 }, 0) });
313     defer pair.close();
314     var loop = try Loop.init(.{ .allocator = std.testing.allocator, .entries = 2 });
315     defer loop.deinit();
316     var completions: [2]Completion = @splat(.{});
317     const initial: [16]u8 = @splat(0xa5);
318     var read_buffer = initial;
319     const read_value: ReadBuffer = .{ .slice = &read_buffer };
320     var receive = receiveCapture(read_value, 1);
321     pair.receiver.recvFrom(
322         &loop,
323         &completions[0],
324         read_value,
325         ReceiveCapture,
326         &receive,
327         receiveCallback,
328     );
329     try std.testing.expect(completions[0].active);
330     loop.disarm(&completions[0]);
331     try std.testing.expect(!completions[0].active);
332 
333     const payload = "after disarm";
334     const write_value: WriteBuffer = .{ .slice = payload };
335     var send = sendCapture(write_value, payload);
336     pair.sender.sendTo(
337         &loop,
338         &completions[1],
339         write_value,
340         pair.receiver_address,
341         SendCapture,
342         &send,
343         sendCallback,
344     );
345     try loop.run(.until_done);
346     try std.testing.expectEqual(@as(u8, 0), receive.hits);
347     try std.testing.expectEqual(initial, read_buffer);
348     try std.testing.expectEqual(@as(u8, 1), send.hits);
349 }
350 
351 test "event UDP rearm receives two datagrams through one completion" {
352     var pair = try UdpPair.init(.{ .ip4 = net.ip4Address(.{ 127, 0, 0, 1 }, 0) });
353     defer pair.close();
354     var loop = try Loop.init(.{ .allocator = std.testing.allocator, .entries = 3 });
355     defer loop.deinit();
356     var completions: [3]Completion = @splat(.{});
357     var read_buffer: [32]u8 = undefined;
358     const read_value: ReadBuffer = .{ .slice = &read_buffer };
359     var receive = receiveCapture(read_value, 2);
360     pair.receiver.recvFrom(
361         &loop,
362         &completions[0],
363         read_value,
364         ReceiveCapture,
365         &receive,
366         receiveCallback,
367     );
368 
369     const payloads = [_][]const u8{ "first", "second" };
370     var sends: [payloads.len]SendCapture = undefined;
371     for (payloads, 0..) |payload, index| {
372         const write_value: WriteBuffer = .{ .slice = payload };
373         sends[index] = sendCapture(write_value, payload);
374         pair.sender.sendTo(
375             &loop,
376             &completions[index + 1],
377             write_value,
378             pair.receiver_address,
379             SendCapture,
380             &sends[index],
381             sendCallback,
382         );
383     }
384     try loop.run(.until_done);
385 
386     try std.testing.expectEqual(@as(u8, payloads.len), receive.hits);
387     try std.testing.expect(receive.same_buffer);
388     for (payloads, 0..) |payload, index| {
389         const datagram = receive.datagrams[index] orelse return error.MissingDatagram;
390         try std.testing.expect(net.ipAddressEql(pair.sender_address, datagram.address));
391         try std.testing.expectEqualSlices(
392             u8,
393             payload,
394             receive.payloads[index][0..receive.payload_lengths[index]],
395         );
396         try std.testing.expectEqual(@as(u8, 1), sends[index].hits);
397     }
398 }
399 
400 test "event UDP surfaces EMSGSIZE for an oversized datagram" {
401     var pair = try UdpPair.init(.{ .ip4 = net.ip4Address(.{ 127, 0, 0, 1 }, 0) });
402     defer pair.close();
403     var loop = try Loop.init(.{ .allocator = std.testing.allocator, .entries = 1 });
404     defer loop.deinit();
405     var completion: Completion = .{};
406     var payload: [std.math.maxInt(u16) + 1]u8 = @splat(0xa5);
407     const write_value: WriteBuffer = .{ .slice = &payload };
408     var send = sendCapture(write_value, &payload);
409     pair.sender.sendTo(
410         &loop,
411         &completion,
412         write_value,
413         pair.receiver_address,
414         SendCapture,
415         &send,
416         sendCallback,
417     );
418     try loop.run(.until_done);
419     try std.testing.expectEqual(@as(u8, 1), send.hits);
420     try std.testing.expectEqual(@as(?SendError, error.MessageTooLarge), send.err);
421     try std.testing.expectEqual(@as(?usize, null), send.bytes);
422     try std.testing.expect(send.same_buffer);
423 }
424 
425 test {
426     _ = @import("poll.zig");
427     std.testing.refAllDecls(subject);
428 }