| ... | @@ -5,6 +5,7 @@ const Io = std.Io; | ... | @@ -5,6 +5,7 @@ const Io = std.Io; |
| 5 | const net = std.Io.net; | 5 | const net = std.Io.net; |
| 6 | const mem = std.mem; | 6 | const mem = std.mem; |
| 7 | const testing = std.testing; | 7 | const testing = std.testing; |
| | 8 | const Allocator = std.mem.Allocator; |
| 8 | | 9 | |
| 9 | test "parse and render IP addresses at comptime" { | 10 | test "parse and render IP addresses at comptime" { |
| 10 | comptime { | 11 | comptime { |
| ... | @@ -84,28 +85,19 @@ test "IPv6 address parse failures" { | ... | @@ -84,28 +85,19 @@ test "IPv6 address parse failures" { |
| 84 | } | 85 | } |
| 85 | | 86 | |
| 86 | test "invalid but parseable IPv6 scope ids" { | 87 | test "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; |
| 88 | | 89 | |
| 89 | if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin()) { | 90 | const io = testing.io; |
| 90 | return error.SkipZigTest; // TODO | | |
| 91 | } | | |
| 92 | | 91 | |
| 93 | try testing.expectError(error.InterfaceNotFound, net.IpAddress.resolveIp6(io, "ff01::fb%123s45678901234", 0)); | 92 | try testing.expectError(error.InterfaceNotFound, net.IpAddress.resolveIp6(io, "ff01::fb%123s45678901234", 0)); |
| 94 | } | 93 | } |
| 95 | | 94 | |
| 96 | test "parse and render IPv4 addresses" { | 95 | test "parse and render IPv4 addresses" { |
| 97 | var buffer: [18]u8 = undefined; | 96 | try testIp4ParseAndRender("0.0.0.0"); |
| 98 | for ([_][]const u8{ | 97 | try testIp4ParseAndRender("255.255.255.255"); |
| 99 | "0.0.0.0", | 98 | try testIp4ParseAndRender("1.2.3.4"); |
| 100 | "255.255.255.255", | 99 | try testIp4ParseAndRender("123.255.0.91"); |
| 101 | "1.2.3.4", | 100 | try testIp4ParseAndRender("127.0.0.1"); |
| 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 | } | | |
| 109 | | 101 | |
| 110 | try testing.expectError(error.Overflow, net.IpAddress.parseIp4("256.0.0.1", 0)); | 102 | try testing.expectError(error.Overflow, net.IpAddress.parseIp4("256.0.0.1", 0)); |
| 111 | try testing.expectError(error.InvalidCharacter, net.IpAddress.parseIp4("x.0.0.1", 0)); | 103 | try testing.expectError(error.InvalidCharacter, net.IpAddress.parseIp4("x.0.0.1", 0)); |
| ... | @@ -115,9 +107,15 @@ test "parse and render IPv4 addresses" { | ... | @@ -115,9 +107,15 @@ test "parse and render IPv4 addresses" { |
| 115 | try testing.expectError(error.NonCanonical, net.IpAddress.parseIp4("127.01.0.1", 0)); | 107 | try testing.expectError(error.NonCanonical, net.IpAddress.parseIp4("127.01.0.1", 0)); |
| 116 | } | 108 | } |
| 117 | | 109 | |
| 118 | test "resolve DNS" { | 110 | fn testIp4ParseAndRender(text: []const u8) !void { |
| 119 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | 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 | } |
| 120 | | 117 | |
| | 118 | test "resolve DNS" { |
| 121 | const io = testing.io; | 119 | const io = testing.io; |
| 122 | | 120 | |
| 123 | // Resolve localhost, this should not fail. | 121 | // Resolve localhost, this should not fail. |
| ... | @@ -129,10 +127,13 @@ test "resolve DNS" { | ... | @@ -129,10 +127,13 @@ test "resolve DNS" { |
| 129 | var results_buffer: [32]net.HostName.LookupResult = undefined; | 127 | var results_buffer: [32]net.HostName.LookupResult = undefined; |
| 130 | var results: Io.Queue(net.HostName.LookupResult) = .init(&results_buffer); | 128 | var results: Io.Queue(net.HostName.LookupResult) = .init(&results_buffer); |
| 131 | | 129 | |
| 132 | try net.HostName.lookup(try .init("localhost"), io, &results, .{ | 130 | net.HostName.lookup(try .init("localhost"), io, &results, .{ |
| 133 | .port = 80, | 131 | .port = 80, |
| 134 | .canonical_name_buffer = &canonical_name_buffer, | 132 | .canonical_name_buffer = &canonical_name_buffer, |
| 135 | }); | 133 | }) catch |err| switch (err) { |
| | 134 | error.NetworkDown => return error.SkipZigTest, |
| | 135 | else => |e| return e, |
| | 136 | }; |
| 136 | | 137 | |
| 137 | var addresses_found: usize = 0; | 138 | var addresses_found: usize = 0; |
| 138 | | 139 | |
| ... | @@ -177,17 +178,16 @@ test "resolve DNS" { | ... | @@ -177,17 +178,16 @@ test "resolve DNS" { |
| 177 | } | 178 | } |
| 178 | | 179 | |
| 179 | test "listen on a port, send bytes, receive bytes" { | 180 | test "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 | | | |
| 184 | const io = testing.io; | 181 | const io = testing.io; |
| 185 | | 182 | |
| 186 | // Try only the IPv4 variant as some CI builders have no IPv6 localhost | 183 | // Try only the IPv4 variant as some CI builders have no IPv6 localhost |
| 187 | // configured. | 184 | // configured. |
| 188 | const localhost: net.IpAddress = .{ .ip4 = .loopback(0) }; | 185 | const localhost: net.IpAddress = .{ .ip4 = .loopback(0) }; |
| 189 | | 186 | |
| 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 | }; |
| 191 | defer server.deinit(io); | 191 | defer server.deinit(io); |
| 192 | | 192 | |
| 193 | const S = struct { | 193 | const S = struct { |
| ... | @@ -200,89 +200,54 @@ test "listen on a port, send bytes, receive bytes" { | ... | @@ -200,89 +200,54 @@ test "listen on a port, send bytes, receive bytes" { |
| 200 | } | 200 | } |
| 201 | }; | 201 | }; |
| 202 | | 202 | |
| 203 | const t = try std.Thread.spawn(.{}, S.clientFn, .{server.socket.address}); | 203 | var client_task = io.concurrent(S.clientFn, .{server.socket.address}) catch |err| switch (err) { |
| 204 | defer t.join(); | 204 | error.ConcurrencyUnavailable => return error.SkipZigTest, |
| | 205 | }; |
| | 206 | defer client_task.cancel(io) catch {}; |
| 205 | | 207 | |
| 206 | var stream = try server.accept(io); | 208 | var stream = try server.accept(io); |
| 207 | defer stream.close(io); | 209 | defer stream.close(io); |
| | 210 | |
| 208 | var buf: [16]u8 = undefined; | 211 | var buf: [16]u8 = undefined; |
| 209 | var stream_reader = stream.reader(io, &.{}); | 212 | var stream_reader = stream.reader(io, &.{}); |
| 210 | const n = try stream_reader.interface.readSliceShort(&buf); | 213 | const n = try stream_reader.interface.readSliceShort(&buf); |
| 211 | | 214 | |
| 212 | try testing.expectEqual(@as(usize, 12), n); | 215 | 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); |
| 214 | } | 219 | } |
| 215 | | 220 | |
| 216 | test "listen on an in use port" { | 221 | test "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 | | | |
| 222 | const io = testing.io; | 222 | const io = testing.io; |
| 223 | | 223 | |
| 224 | const localhost: net.IpAddress = .{ .ip4 = .loopback(0) }; | 224 | const localhost: net.IpAddress = .{ .ip4 = .loopback(0) }; |
| 225 | | 225 | |
| 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 | }; |
| 227 | defer server1.deinit(io); | 230 | defer server1.deinit(io); |
| 228 | | 231 | |
| 229 | var server2 = try server1.socket.address.listen(io, .{ .reuse_address = true }); | 232 | var server2 = try server1.socket.address.listen(io, .{ .reuse_address = true }); |
| 230 | defer server2.deinit(io); | 233 | defer server2.deinit(io); |
| 231 | } | 234 | } |
| 232 | | 235 | |
| 233 | fn 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 | | | |
| 247 | fn 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 | | | |
| 261 | fn 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 | | | |
| 271 | test "listen on a unix socket, send bytes, receive bytes" { | 236 | test "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 | | | |
| 277 | const io = testing.io; | 237 | const io = testing.io; |
| | 238 | const gpa = testing.allocator; |
| 278 | | 239 | |
| 279 | const socket_path = try generateFileName(io, "socket.unix"); | 240 | const socket_path = try generateFileName(gpa, io, "socket.unix"); |
| 280 | defer testing.allocator.free(socket_path); | 241 | defer gpa.free(socket_path); |
| 281 | | 242 | |
| 282 | const socket_addr = try net.UnixAddress.init(socket_path); | 243 | const socket_addr = try net.UnixAddress.init(socket_path); |
| 283 | defer Io.Dir.cwd().deleteFile(io, socket_path) catch {}; | 244 | defer Io.Dir.cwd().deleteFile(io, socket_path) catch {}; |
| 284 | | 245 | |
| 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 | }; |
| 286 | defer server.socket.close(io); | 251 | defer server.socket.close(io); |
| 287 | | 252 | |
| 288 | const S = struct { | 253 | const S = struct { |
| ... | @@ -296,59 +261,32 @@ test "listen on a unix socket, send bytes, receive bytes" { | ... | @@ -296,59 +261,32 @@ test "listen on a unix socket, send bytes, receive bytes" { |
| 296 | } | 261 | } |
| 297 | }; | 262 | }; |
| 298 | | 263 | |
| 299 | const t = try std.Thread.spawn(.{}, S.clientFn, .{socket_path}); | 264 | var client_task = io.concurrent(S.clientFn, .{socket_path}) catch |err| switch (err) { |
| 300 | defer t.join(); | 265 | error.ConcurrencyUnavailable => return error.SkipZigTest, |
| | 266 | }; |
| | 267 | defer client_task.cancel(io) catch {}; |
| 301 | | 268 | |
| 302 | var stream = try server.accept(io); | 269 | var stream = try server.accept(io); |
| 303 | defer stream.close(io); | 270 | defer stream.close(io); |
| | 271 | |
| 304 | var buf: [16]u8 = undefined; | 272 | var buf: [16]u8 = undefined; |
| 305 | var stream_reader = stream.reader(io, &.{}); | 273 | var stream_reader = stream.reader(io, &.{}); |
| 306 | const n = try stream_reader.interface.readSliceShort(&buf); | 274 | const n = try stream_reader.interface.readSliceShort(&buf); |
| 307 | | 275 | |
| 308 | try testing.expectEqual(@as(usize, 12), n); | 276 | 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); |
| 310 | } | 280 | } |
| 311 | | 281 | |
| 312 | fn generateFileName(io: Io, base_name: []const u8) ![]const u8 { | 282 | fn generateFileName(gpa: Allocator, io: Io, base_name: []const u8) ![]const u8 { |
| 313 | const random_bytes_count = 12; | 283 | const random_bytes_count = 12; |
| 314 | const sub_path_len = comptime std.base64.url_safe.Encoder.calcSize(random_bytes_count); | 284 | const sub_path_len = comptime std.base64.url_safe.Encoder.calcSize(random_bytes_count); |
| 315 | var random_bytes: [12]u8 = undefined; | 285 | var random_bytes: [12]u8 = undefined; |
| 316 | io.random(&random_bytes); | 286 | io.random(&random_bytes); |
| 317 | var sub_path: [sub_path_len]u8 = undefined; | 287 | var sub_path: [sub_path_len]u8 = undefined; |
| 318 | _ = std.base64.url_safe.Encoder.encode(&sub_path, &random_bytes); | 288 | _ = std.base64.url_safe.Encoder.encode(&sub_path, &random_bytes); |
| 319 | return std.fmt.allocPrint(testing.allocator, "{s}-{s}", .{ sub_path[0..], base_name }); | 289 | return std.fmt.allocPrint(gpa, "{s}-{s}", .{ sub_path[0..], base_name }); |
| 320 | } | | |
| 321 | | | |
| 322 | test "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); | | |
| 352 | } | 290 | } |
| 353 | | 291 | |
| 354 | test "decompress compressed DNS name" { | 292 | test "decompress compressed DNS name" { |
| ... | @@ -400,3 +338,26 @@ test "decompress compressed DNS name" { | ... | @@ -400,3 +338,26 @@ test "decompress compressed DNS name" { |
| 400 | try testing.expectEqual(packet.len - cname_data_index, n_consumed); | 338 | try testing.expectEqual(packet.len - cname_data_index, n_consumed); |
| 401 | try testing.expectEqualStrings("target.ziglang.org", result.bytes); | 339 | try testing.expectEqualStrings("target.ziglang.org", result.bytes); |
| 402 | } | 340 | } |
| | 341 | |
| | 342 | test "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 | } |