authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-13 08:27:14+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-13 08:27:14+01:00
log2c61deaae26ee492fa5a7b68726801c9414305a5
tree7ead45fae4dd852cc3ad3357156c2daa7dde9f34
parentab1a4b26ec2b90f514059b6e446b83c3881dbe4d
parent314264ee7d76c365a80105e13db40819ecd33329

Merge pull request 'std.Io.net: enable more tests + coverage for canceling accept' (#31468) from net-test-coverage into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31468

2 files changed, 105 insertions(+), 130 deletions(-)

lib/std/Io/Threaded.zig+27-13
......@@ -11846,7 +11846,10 @@ fn netListenUnixWindows(
1184611846 var syscall: AlertableSyscall = try .start();
1184711847 while (true) {
1184811848 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);
11849 if (rc != ws2_32.SOCKET_ERROR) break;
11849 if (rc != ws2_32.SOCKET_ERROR) {
11850 syscall.finish();
11851 break;
11852 }
1185011853 switch (ws2_32.WSAGetLastError()) {
1185111854 .NOTINITIALISED => {
1185211855 syscall.finish();
......@@ -11869,15 +11872,18 @@ fn netListenUnixWindows(
1186911872 }
1187011873 }
1187111874
11875 syscall = try .start();
1187211876 while (true) {
11873 try syscall.checkCancel();
1187411877 const rc = ws2_32.listen(socket_handle, options.kernel_backlog);
1187511878 if (rc != ws2_32.SOCKET_ERROR) {
1187611879 syscall.finish();
1187711880 return socket_handle;
1187811881 }
1187911882 switch (ws2_32.WSAGetLastError()) {
11880 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue,
11883 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
11884 try syscall.checkCancel();
11885 continue;
11886 },
1188111887 .NOTINITIALISED => {
1188211888 syscall.finish();
1188311889 try initializeWsa(t);
......@@ -12160,9 +12166,12 @@ fn setSocketOption(fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void
1216012166fn setSocketOptionWsa(t: *Threaded, socket: Io.net.Socket.Handle, level: i32, opt_name: u32, option: u32) !void {
1216112167 const o: []const u8 = @ptrCast(&option);
1216212168 var syscall: AlertableSyscall = try .start();
12163 const rc = ws2_32.setsockopt(socket, level, @bitCast(opt_name), o.ptr, @intCast(o.len));
1216412169 while (true) {
12165 if (rc != ws2_32.SOCKET_ERROR) return syscall.finish();
12170 const rc = ws2_32.setsockopt(socket, level, @bitCast(opt_name), o.ptr, @intCast(o.len));
12171 if (rc != ws2_32.SOCKET_ERROR) {
12172 syscall.finish();
12173 return;
12174 }
1216612175 switch (ws2_32.WSAGetLastError()) {
1216712176 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
1216812177 try syscall.checkCancel();
......@@ -12175,7 +12184,9 @@ fn setSocketOptionWsa(t: *Threaded, socket: Io.net.Socket.Handle, level: i32, op
1217512184 continue;
1217612185 },
1217712186 .ENETDOWN => return syscall.fail(error.NetworkDown),
12178 .EFAULT, .ENOTSOCK, .EINVAL => |err| return syscall.wsaErrorBug(err),
12187 .EFAULT => |err| return syscall.wsaErrorBug(err),
12188 .ENOTSOCK => |err| return syscall.wsaErrorBug(err),
12189 .EINVAL => |err| return syscall.wsaErrorBug(err),
1217912190 else => |err| return syscall.unexpectedWsaError(err),
1218012191 }
1218112192 }
......@@ -12313,7 +12324,10 @@ fn netConnectUnixWindows(
1231312324 var syscall: AlertableSyscall = try .start();
1231412325 while (true) {
1231512326 const rc = ws2_32.connect(socket_handle, &storage.any, addr_len);
12316 if (rc != ws2_32.SOCKET_ERROR) break;
12327 if (rc != ws2_32.SOCKET_ERROR) {
12328 syscall.finish();
12329 break;
12330 }
1231712331 switch (ws2_32.WSAGetLastError()) {
1231812332 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
1231912333 try syscall.checkCancel();
......@@ -12326,14 +12340,14 @@ fn netConnectUnixWindows(
1232612340 continue;
1232712341 },
1232812342 .ECONNREFUSED => return syscall.fail(error.FileNotFound),
12329 .EFAULT => |err| return syscall.wsaErrorBug(err),
12330 .EINVAL => |err| return syscall.wsaErrorBug(err),
12331 .EISCONN => |err| return syscall.wsaErrorBug(err),
12332 .ENOTSOCK => |err| return syscall.wsaErrorBug(err),
1233312343 .EWOULDBLOCK => return syscall.fail(error.WouldBlock),
1233412344 .EACCES => return syscall.fail(error.AccessDenied),
1233512345 .ENOBUFS => return syscall.fail(error.SystemResources),
1233612346 .EAFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported),
12347 .EFAULT => |err| return syscall.wsaErrorBug(err),
12348 .EINVAL => |err| return syscall.wsaErrorBug(err),
12349 .EISCONN => |err| return syscall.wsaErrorBug(err),
12350 .ENOTSOCK => |err| return syscall.wsaErrorBug(err),
1233712351 else => |err| return syscall.unexpectedWsaError(err),
1233812352 }
1233912353 }
......@@ -13439,7 +13453,6 @@ fn netWriteWindows(
1343913453 return n;
1344013454 }
1344113455 switch (ws2_32.WSAGetLastError()) {
13442 .IO_PENDING => unreachable, // not overlapped
1344313456 .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => {
1344413457 try syscall.checkCancel();
1344513458 continue;
......@@ -13461,6 +13474,7 @@ fn netWriteWindows(
1346113474 .ENOTSOCK => |err| return syscall.wsaErrorBug(err),
1346213475 .EOPNOTSUPP => |err| return syscall.wsaErrorBug(err),
1346313476 .ESHUTDOWN => |err| return syscall.wsaErrorBug(err),
13477 .IO_PENDING => |err| return syscall.wsaErrorBug(err),
1346413478 else => |err| return syscall.unexpectedWsaError(err),
1346513479 }
1346613480 }
......@@ -15321,7 +15335,7 @@ fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT
1532115335 const handle = child.id.?;
1532215336 _ = windows.ntdll.RtlReportSilentProcessExit(handle, @enumFromInt(exit_code));
1532315337 switch (windows.ntdll.NtTerminateProcess(handle, @enumFromInt(exit_code))) {
15324 .SUCCESS => {
15338 .SUCCESS, .PROCESS_IS_TERMINATING => {
1532515339 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
1532615340 _ = windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &infinite_timeout);
1532715341 childCleanupWindows(child);
lib/std/Io/net/test.zig+78-117
......@@ -5,6 +5,7 @@ const Io = std.Io;
55const net = std.Io.net;
66const mem = std.mem;
77const testing = std.testing;
8const Allocator = std.mem.Allocator;
89
910test "parse and render IP addresses at comptime" {
1011 comptime {
......@@ -84,28 +85,19 @@ test "IPv6 address parse failures" {
8485}
8586
8687test "invalid but parseable IPv6 scope ids" {
87 const io = testing.io;
88 if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin()) return error.SkipZigTest;
8889
89 if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin()) {
90 return error.SkipZigTest; // TODO
91 }
90 const io = testing.io;
9291
9392 try testing.expectError(error.InterfaceNotFound, net.IpAddress.resolveIp6(io, "ff01::fb%123s45678901234", 0));
9493}
9594
9695test "parse and render IPv4 addresses" {
97 var buffer: [18]u8 = undefined;
98 for ([_][]const u8{
99 "0.0.0.0",
100 "255.255.255.255",
101 "1.2.3.4",
102 "123.255.0.91",
103 "127.0.0.1",
104 }) |ip| {
105 const addr = net.IpAddress.parseIp4(ip, 0) catch unreachable;
106 var newIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr}) catch unreachable;
107 try testing.expect(std.mem.eql(u8, ip, newIp[0 .. newIp.len - 2]));
108 }
96 try testIp4ParseAndRender("0.0.0.0");
97 try testIp4ParseAndRender("255.255.255.255");
98 try testIp4ParseAndRender("1.2.3.4");
99 try testIp4ParseAndRender("123.255.0.91");
100 try testIp4ParseAndRender("127.0.0.1");
109101
110102 try testing.expectError(error.Overflow, net.IpAddress.parseIp4("256.0.0.1", 0));
111103 try testing.expectError(error.InvalidCharacter, net.IpAddress.parseIp4("x.0.0.1", 0));
......@@ -115,9 +107,15 @@ test "parse and render IPv4 addresses" {
115107 try testing.expectError(error.NonCanonical, net.IpAddress.parseIp4("127.01.0.1", 0));
116108}
117109
118test "resolve DNS" {
119 if (builtin.os.tag == .wasi) return error.SkipZigTest;
110fn testIp4ParseAndRender(text: []const u8) !void {
111 var buffer: [18]u8 = undefined;
112 const addr = try net.IpAddress.parseIp4(text, 0);
113 const rendered = try std.fmt.bufPrint(&buffer, "{f}", .{addr});
114 const without_port = rendered[0 .. rendered.len - 2];
115 try testing.expectEqualStrings(text, without_port);
116}
120117
118test "resolve DNS" {
121119 const io = testing.io;
122120
123121 // Resolve localhost, this should not fail.
......@@ -129,10 +127,13 @@ test "resolve DNS" {
129127 var results_buffer: [32]net.HostName.LookupResult = undefined;
130128 var results: Io.Queue(net.HostName.LookupResult) = .init(&results_buffer);
131129
132 try net.HostName.lookup(try .init("localhost"), io, &results, .{
130 net.HostName.lookup(try .init("localhost"), io, &results, .{
133131 .port = 80,
134132 .canonical_name_buffer = &canonical_name_buffer,
135 });
133 }) catch |err| switch (err) {
134 error.NetworkDown => return error.SkipZigTest,
135 else => |e| return e,
136 };
136137
137138 var addresses_found: usize = 0;
138139
......@@ -177,17 +178,16 @@ test "resolve DNS" {
177178}
178179
179180test "listen on a port, send bytes, receive bytes" {
180 if (true) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/31388
181 if (builtin.single_threaded) return error.SkipZigTest;
182 if (builtin.os.tag == .wasi) return error.SkipZigTest;
183
184181 const io = testing.io;
185182
186183 // Try only the IPv4 variant as some CI builders have no IPv6 localhost
187184 // configured.
188185 const localhost: net.IpAddress = .{ .ip4 = .loopback(0) };
189186
190 var server = try localhost.listen(io, .{});
187 var server = localhost.listen(io, .{}) catch |err| switch (err) {
188 error.NetworkDown => return error.SkipZigTest,
189 else => |e| return e,
190 };
191191 defer server.deinit(io);
192192
193193 const S = struct {
......@@ -200,89 +200,54 @@ test "listen on a port, send bytes, receive bytes" {
200200 }
201201 };
202202
203 const t = try std.Thread.spawn(.{}, S.clientFn, .{server.socket.address});
204 defer t.join();
203 var client_task = io.concurrent(S.clientFn, .{server.socket.address}) catch |err| switch (err) {
204 error.ConcurrencyUnavailable => return error.SkipZigTest,
205 };
206 defer client_task.cancel(io) catch {};
205207
206208 var stream = try server.accept(io);
207209 defer stream.close(io);
210
208211 var buf: [16]u8 = undefined;
209212 var stream_reader = stream.reader(io, &.{});
210213 const n = try stream_reader.interface.readSliceShort(&buf);
211214
212215 try testing.expectEqual(@as(usize, 12), n);
213 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
216 try testing.expectEqualStrings("Hello world!", buf[0..n]);
217
218 try client_task.await(io);
214219}
215220
216221test "listen on an in use port" {
217 if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin() and builtin.os.tag != .windows) {
218 // TODO build abstractions for other operating systems
219 return error.SkipZigTest;
220 }
221
222222 const io = testing.io;
223223
224224 const localhost: net.IpAddress = .{ .ip4 = .loopback(0) };
225225
226 var server1 = try localhost.listen(io, .{ .reuse_address = true });
226 var server1 = localhost.listen(io, .{ .reuse_address = true }) catch |err| switch (err) {
227 error.NetworkDown => return error.SkipZigTest,
228 else => |e| return e,
229 };
227230 defer server1.deinit(io);
228231
229232 var server2 = try server1.socket.address.listen(io, .{ .reuse_address = true });
230233 defer server2.deinit(io);
231234}
232235
233fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void {
234 if (builtin.os.tag == .wasi) return error.SkipZigTest;
235
236 const io = testing.io;
237
238 const connection = try net.tcpConnectToHost(allocator, name, port);
239 defer connection.close(io);
240
241 var buf: [100]u8 = undefined;
242 const len = try connection.read(&buf);
243 const msg = buf[0..len];
244 try testing.expect(mem.eql(u8, msg, "hello from server\n"));
245}
246
247fn testClient(addr: net.IpAddress) anyerror!void {
248 if (builtin.os.tag == .wasi) return error.SkipZigTest;
249
250 const io = testing.io;
251
252 const socket_file = try net.tcpConnectToAddress(addr);
253 defer socket_file.close(io);
254
255 var buf: [100]u8 = undefined;
256 const len = try socket_file.read(&buf);
257 const msg = buf[0..len];
258 try testing.expect(mem.eql(u8, msg, "hello from server\n"));
259}
260
261fn testServer(server: *net.Server) anyerror!void {
262 if (builtin.os.tag == .wasi) return error.SkipZigTest;
263
264 const io = testing.io;
265
266 var stream = try server.accept(io);
267 var writer = stream.writer(io, &.{});
268 try writer.interface.print("hello from server\n", .{});
269}
270
271236test "listen on a unix socket, send bytes, receive bytes" {
272 if (builtin.single_threaded) return error.SkipZigTest;
273 if (!net.has_unix_sockets) return error.SkipZigTest;
274 if (builtin.os.tag == .windows) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/25983
275 if (builtin.cpu.arch == .mipsel) return error.SkipZigTest; // TODO
276
277237 const io = testing.io;
238 const gpa = testing.allocator;
278239
279 const socket_path = try generateFileName(io, "socket.unix");
280 defer testing.allocator.free(socket_path);
240 const socket_path = try generateFileName(gpa, io, "socket.unix");
241 defer gpa.free(socket_path);
281242
282243 const socket_addr = try net.UnixAddress.init(socket_path);
283244 defer Io.Dir.cwd().deleteFile(io, socket_path) catch {};
284245
285 var server = try socket_addr.listen(io, .{});
246 var server = socket_addr.listen(io, .{}) catch |err| switch (err) {
247 error.AddressFamilyUnsupported => return error.SkipZigTest,
248 error.NetworkDown => return error.SkipZigTest,
249 else => |e| return e,
250 };
286251 defer server.socket.close(io);
287252
288253 const S = struct {
......@@ -296,59 +261,32 @@ test "listen on a unix socket, send bytes, receive bytes" {
296261 }
297262 };
298263
299 const t = try std.Thread.spawn(.{}, S.clientFn, .{socket_path});
300 defer t.join();
264 var client_task = io.concurrent(S.clientFn, .{socket_path}) catch |err| switch (err) {
265 error.ConcurrencyUnavailable => return error.SkipZigTest,
266 };
267 defer client_task.cancel(io) catch {};
301268
302269 var stream = try server.accept(io);
303270 defer stream.close(io);
271
304272 var buf: [16]u8 = undefined;
305273 var stream_reader = stream.reader(io, &.{});
306274 const n = try stream_reader.interface.readSliceShort(&buf);
307275
308276 try testing.expectEqual(@as(usize, 12), n);
309 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
277 try testing.expectEqualStrings("Hello world!", buf[0..n]);
278
279 try client_task.await(io);
310280}
311281
312fn generateFileName(io: Io, base_name: []const u8) ![]const u8 {
282fn generateFileName(gpa: Allocator, io: Io, base_name: []const u8) ![]const u8 {
313283 const random_bytes_count = 12;
314284 const sub_path_len = comptime std.base64.url_safe.Encoder.calcSize(random_bytes_count);
315285 var random_bytes: [12]u8 = undefined;
316286 io.random(&random_bytes);
317287 var sub_path: [sub_path_len]u8 = undefined;
318288 _ = std.base64.url_safe.Encoder.encode(&sub_path, &random_bytes);
319 return std.fmt.allocPrint(testing.allocator, "{s}-{s}", .{ sub_path[0..], base_name });
320}
321
322test "non-blocking tcp server" {
323 if (builtin.os.tag == .wasi) return error.SkipZigTest;
324 if (true) {
325 // https://github.com/ziglang/zig/issues/18315
326 return error.SkipZigTest;
327 }
328
329 const io = testing.io;
330
331 const localhost: net.IpAddress = .{ .ip4 = .loopback(0) };
332 var server = localhost.listen(io, .{ .force_nonblocking = true });
333 defer server.deinit(io);
334
335 const accept_err = server.accept(io);
336 try testing.expectError(error.WouldBlock, accept_err);
337
338 const socket_file = try net.tcpConnectToAddress(server.socket.address);
339 defer socket_file.close(io);
340
341 var stream = try server.accept(io);
342 defer stream.close(io);
343 var writer = stream.writer(io, .{});
344 try writer.interface.print("hello from server\n", .{});
345
346 var buf: [100]u8 = undefined;
347 const len = try socket_file.read(&buf);
348 const msg = buf[0..len];
349 try testing.expect(mem.eql(u8, msg, "hello from server\n"));
350
351 try stream.shutdown(io, .both);
289 return std.fmt.allocPrint(gpa, "{s}-{s}", .{ sub_path[0..], base_name });
352290}
353291
354292test "decompress compressed DNS name" {
......@@ -400,3 +338,26 @@ test "decompress compressed DNS name" {
400338 try testing.expectEqual(packet.len - cname_data_index, n_consumed);
401339 try testing.expectEqualStrings("target.ziglang.org", result.bytes);
402340}
341
342test "cancel accept" {
343 if (builtin.os.tag == .windows) {
344 // https://codeberg.org/ziglang/zig/issues/30865
345 return error.SkipZigTest;
346 }
347
348 const io = testing.io;
349 const localhost: net.IpAddress = .{ .ip4 = .loopback(0) };
350
351 var server = localhost.listen(io, .{}) catch |err| switch (err) {
352 error.NetworkDown => return error.SkipZigTest,
353 else => |e| return e,
354 };
355 defer server.deinit(io);
356
357 var accept = io.concurrent(std.Io.net.Server.accept, .{ &server, io }) catch |err| switch (err) {
358 error.ConcurrencyUnavailable => return error.SkipZigTest,
359 };
360 defer if (accept.cancel(io)) |stream| stream.close(io) else |_| {};
361
362 try io.sleep(.fromNanoseconds(1), .awake);
363}