| ... | ... | @@ -5,6 +5,7 @@ const Io = std.Io; |
| 5 | 5 | const net = std.Io.net; |
| 6 | 6 | const mem = std.mem; |
| 7 | 7 | const testing = std.testing; |
| 8 | const Allocator = std.mem.Allocator; |
| 8 | 9 | |
| 9 | 10 | test "parse and render IP addresses at comptime" { |
| 10 | 11 | comptime { |
| ... | ... | @@ -84,28 +85,19 @@ test "IPv6 address parse failures" { |
| 84 | 85 | } |
| 85 | 86 | |
| 86 | 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 | | return error.SkipZigTest; // TODO |
| 91 | | } |
| 90 | const io = testing.io; |
| 92 | 91 | |
| 93 | 92 | try testing.expectError(error.InterfaceNotFound, net.IpAddress.resolveIp6(io, "ff01::fb%123s45678901234", 0)); |
| 94 | 93 | } |
| 95 | 94 | |
| 96 | 95 | test "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"); |
| 109 | 101 | |
| 110 | 102 | try testing.expectError(error.Overflow, net.IpAddress.parseIp4("256.0.0.1", 0)); |
| 111 | 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 | 107 | try testing.expectError(error.NonCanonical, net.IpAddress.parseIp4("127.01.0.1", 0)); |
| 116 | 108 | } |
| 117 | 109 | |
| 118 | | test "resolve DNS" { |
| 119 | | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 110 | fn 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 | } |
| 120 | 117 | |
| 118 | test "resolve DNS" { |
| 121 | 119 | const io = testing.io; |
| 122 | 120 | |
| 123 | 121 | // Resolve localhost, this should not fail. |
| ... | ... | @@ -129,10 +127,13 @@ test "resolve DNS" { |
| 129 | 127 | var results_buffer: [32]net.HostName.LookupResult = undefined; |
| 130 | 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 | 131 | .port = 80, |
| 134 | 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 | 138 | var addresses_found: usize = 0; |
| 138 | 139 | |
| ... | ... | @@ -177,17 +178,16 @@ test "resolve DNS" { |
| 177 | 178 | } |
| 178 | 179 | |
| 179 | 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 | 181 | const io = testing.io; |
| 185 | 182 | |
| 186 | 183 | // Try only the IPv4 variant as some CI builders have no IPv6 localhost |
| 187 | 184 | // configured. |
| 188 | 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 | 191 | defer server.deinit(io); |
| 192 | 192 | |
| 193 | 193 | const S = struct { |
| ... | ... | @@ -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}); |
| 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 {}; |
| 205 | 207 | |
| 206 | 208 | var stream = try server.accept(io); |
| 207 | 209 | defer stream.close(io); |
| 210 | |
| 208 | 211 | var buf: [16]u8 = undefined; |
| 209 | 212 | var stream_reader = stream.reader(io, &.{}); |
| 210 | 213 | const n = try stream_reader.interface.readSliceShort(&buf); |
| 211 | 214 | |
| 212 | 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 | 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 | 222 | const io = testing.io; |
| 223 | 223 | |
| 224 | 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 | 230 | defer server1.deinit(io); |
| 228 | 231 | |
| 229 | 232 | var server2 = try server1.socket.address.listen(io, .{ .reuse_address = true }); |
| 230 | 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 | 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 | 237 | const io = testing.io; |
| 238 | const gpa = testing.allocator; |
| 278 | 239 | |
| 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); |
| 281 | 242 | |
| 282 | 243 | const socket_addr = try net.UnixAddress.init(socket_path); |
| 283 | 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 | 251 | defer server.socket.close(io); |
| 287 | 252 | |
| 288 | 253 | const S = struct { |
| ... | ... | @@ -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}); |
| 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 {}; |
| 301 | 268 | |
| 302 | 269 | var stream = try server.accept(io); |
| 303 | 270 | defer stream.close(io); |
| 271 | |
| 304 | 272 | var buf: [16]u8 = undefined; |
| 305 | 273 | var stream_reader = stream.reader(io, &.{}); |
| 306 | 274 | const n = try stream_reader.interface.readSliceShort(&buf); |
| 307 | 275 | |
| 308 | 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 | 283 | const random_bytes_count = 12; |
| 314 | 284 | const sub_path_len = comptime std.base64.url_safe.Encoder.calcSize(random_bytes_count); |
| 315 | 285 | var random_bytes: [12]u8 = undefined; |
| 316 | 286 | io.random(&random_bytes); |
| 317 | 287 | var sub_path: [sub_path_len]u8 = undefined; |
| 318 | 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 }); |
| 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); |
| 289 | return std.fmt.allocPrint(gpa, "{s}-{s}", .{ sub_path[0..], base_name }); |
| 352 | 290 | } |
| 353 | 291 | |
| 354 | 292 | test "decompress compressed DNS name" { |
| ... | ... | @@ -400,3 +338,26 @@ test "decompress compressed DNS name" { |
| 400 | 338 | try testing.expectEqual(packet.len - cname_data_index, n_consumed); |
| 401 | 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 | } |