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 }