authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-22 17:54:46-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-23 02:37:11-07:00
log737e7be46c825363a34f410cd3e4ea3045b3fce1
tree9b2b78649bbe43abb2e05e7d71ce6209ffa1fecf
parentabde76a808df816ea12a8a2dbf8e6b53ff9b110f

std.http: refactor unit tests

avoid a little bit of boilerplate

1 files changed, 138 insertions(+), 152 deletions(-)

lib/std/http/test.zig+138-152
......@@ -1,28 +1,57 @@
11const builtin = @import("builtin");
22const std = @import("std");
3const testing = std.testing;
43const native_endian = builtin.cpu.arch.endian();
4const expect = std.testing.expect;
5const expectEqual = std.testing.expectEqual;
6const expectEqualStrings = std.testing.expectEqualStrings;
7const expectError = std.testing.expectError;
58
69test "trailers" {
7 if (builtin.single_threaded) return error.SkipZigTest;
8 if (builtin.os.tag == .wasi) return error.SkipZigTest;
10 const test_server = try createTestServer(struct {
11 fn run(net_server: *std.net.Server) anyerror!void {
12 var header_buffer: [1024]u8 = undefined;
13 var remaining: usize = 1;
14 while (remaining != 0) : (remaining -= 1) {
15 const conn = try net_server.accept();
16 defer conn.stream.close();
917
10 const gpa = testing.allocator;
18 var server = std.http.Server.init(conn, &header_buffer);
1119
12 const address = try std.net.Address.parseIp("127.0.0.1", 0);
13 var http_server = try address.listen(.{
14 .reuse_address = true,
15 });
20 try expectEqual(.ready, server.state);
21 var request = try server.receiveHead();
22 try serve(&request);
23 try expectEqual(.ready, server.state);
24 }
25 }
1626
17 const port = http_server.listen_address.in.getPort();
27 fn serve(request: *std.http.Server.Request) !void {
28 try expectEqualStrings(request.head.target, "/trailer");
1829
19 const server_thread = try std.Thread.spawn(.{}, serverThread, .{&http_server});
20 defer server_thread.join();
30 var send_buffer: [1024]u8 = undefined;
31 var response = request.respondStreaming(.{
32 .send_buffer = &send_buffer,
33 });
34 try response.writeAll("Hello, ");
35 try response.flush();
36 try response.writeAll("World!\n");
37 try response.flush();
38 try response.endChunked(.{
39 .trailers = &.{
40 .{ .name = "X-Checksum", .value = "aaaa" },
41 },
42 });
43 }
44 });
45 defer test_server.destroy();
46
47 const gpa = std.testing.allocator;
2148
2249 var client: std.http.Client = .{ .allocator = gpa };
2350 defer client.deinit();
2451
25 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/trailer", .{port});
52 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/trailer", .{
53 test_server.port(),
54 });
2655 defer gpa.free(location);
2756 const uri = try std.Uri.parse(location);
2857
......@@ -39,94 +68,38 @@ test "trailers" {
3968 const body = try req.reader().readAllAlloc(gpa, 8192);
4069 defer gpa.free(body);
4170
42 try testing.expectEqualStrings("Hello, World!\n", body);
71 try expectEqualStrings("Hello, World!\n", body);
4372
4473 var it = req.response.iterateHeaders();
4574 {
4675 const header = it.next().?;
47 try testing.expect(!it.is_trailer);
48 try testing.expectEqualStrings("connection", header.name);
49 try testing.expectEqualStrings("keep-alive", header.value);
76 try expect(!it.is_trailer);
77 try expectEqualStrings("connection", header.name);
78 try expectEqualStrings("keep-alive", header.value);
5079 }
5180 {
5281 const header = it.next().?;
53 try testing.expect(!it.is_trailer);
54 try testing.expectEqualStrings("transfer-encoding", header.name);
55 try testing.expectEqualStrings("chunked", header.value);
82 try expect(!it.is_trailer);
83 try expectEqualStrings("transfer-encoding", header.name);
84 try expectEqualStrings("chunked", header.value);
5685 }
5786 {
5887 const header = it.next().?;
59 try testing.expect(it.is_trailer);
60 try testing.expectEqualStrings("X-Checksum", header.name);
61 try testing.expectEqualStrings("aaaa", header.value);
88 try expect(it.is_trailer);
89 try expectEqualStrings("X-Checksum", header.name);
90 try expectEqualStrings("aaaa", header.value);
6291 }
63 try testing.expectEqual(null, it.next());
92 try expectEqual(null, it.next());
6493 }
6594
6695 // connection has been kept alive
67 try testing.expect(client.connection_pool.free_len == 1);
68}
69
70fn serverThread(http_server: *std.net.Server) anyerror!void {
71 var header_buffer: [1024]u8 = undefined;
72 var remaining: usize = 1;
73 while (remaining != 0) : (remaining -= 1) {
74 const conn = try http_server.accept();
75 defer conn.stream.close();
76
77 var server = std.http.Server.init(conn, &header_buffer);
78
79 try testing.expectEqual(.ready, server.state);
80 var request = try server.receiveHead();
81 try serve(&request);
82 try testing.expectEqual(.ready, server.state);
83 }
84}
85
86fn serve(request: *std.http.Server.Request) !void {
87 try testing.expectEqualStrings(request.head.target, "/trailer");
88
89 var send_buffer: [1024]u8 = undefined;
90 var response = request.respondStreaming(.{
91 .send_buffer = &send_buffer,
92 });
93 try response.writeAll("Hello, ");
94 try response.flush();
95 try response.writeAll("World!\n");
96 try response.flush();
97 try response.endChunked(.{
98 .trailers = &.{
99 .{ .name = "X-Checksum", .value = "aaaa" },
100 },
101 });
96 try expect(client.connection_pool.free_len == 1);
10297}
10398
10499test "HTTP server handles a chunked transfer coding request" {
105 // This test requires spawning threads.
106 if (builtin.single_threaded) {
107 return error.SkipZigTest;
108 }
109
110 if (builtin.zig_backend == .stage2_llvm and native_endian == .big) {
111 // https://github.com/ziglang/zig/issues/13782
112 return error.SkipZigTest;
113 }
114
115 if (builtin.os.tag == .wasi) return error.SkipZigTest;
116
117 const allocator = std.testing.allocator;
118 const expect = std.testing.expect;
119
120 const max_header_size = 8192;
121
122 const address = try std.net.Address.parseIp("127.0.0.1", 0);
123 var socket_server = try address.listen(.{ .reuse_address = true });
124 defer socket_server.deinit();
125 const server_port = socket_server.listen_address.in.getPort();
126
127 const server_thread = try std.Thread.spawn(.{}, (struct {
128 fn apply(net_server: *std.net.Server) !void {
129 var header_buffer: [max_header_size]u8 = undefined;
100 const test_server = try createTestServer(struct {
101 fn run(net_server: *std.net.Server) !void {
102 var header_buffer: [8192]u8 = undefined;
130103 const conn = try net_server.accept();
131104 defer conn.stream.close();
132105
......@@ -146,7 +119,8 @@ test "HTTP server handles a chunked transfer coding request" {
146119 .keep_alive = false,
147120 });
148121 }
149 }).apply, .{&socket_server});
122 });
123 defer test_server.destroy();
150124
151125 const request_bytes =
152126 "POST / HTTP/1.1\r\n" ++
......@@ -162,30 +136,48 @@ test "HTTP server handles a chunked transfer coding request" {
162136 "0\r\n" ++
163137 "\r\n";
164138
165 const stream = try std.net.tcpConnectToHost(allocator, "127.0.0.1", server_port);
139 const gpa = std.testing.allocator;
140 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
166141 defer stream.close();
167142 try stream.writeAll(request_bytes);
168
169 server_thread.join();
170143}
171144
172145test "echo content server" {
173 if (builtin.single_threaded) return error.SkipZigTest;
174 if (builtin.os.tag == .wasi) return error.SkipZigTest;
146 const test_server = try createTestServer(struct {
147 fn run(net_server: *std.net.Server) anyerror!void {
148 var read_buffer: [1024]u8 = undefined;
175149
176 if (builtin.zig_backend == .stage2_llvm and native_endian == .big) {
177 // https://github.com/ziglang/zig/issues/13782
178 return error.SkipZigTest;
179 }
150 accept: while (true) {
151 const conn = try net_server.accept();
152 defer conn.stream.close();
180153
181 const gpa = std.testing.allocator;
154 var http_server = std.http.Server.init(conn, &read_buffer);
182155
183 const address = try std.net.Address.parseIp("127.0.0.1", 0);
184 var socket_server = try address.listen(.{ .reuse_address = true });
185 defer socket_server.deinit();
186 const port = socket_server.listen_address.in.getPort();
156 while (http_server.state == .ready) {
157 var request = http_server.receiveHead() catch |err| switch (err) {
158 error.HttpConnectionClosing => continue :accept,
159 else => |e| return e,
160 };
161 if (std.mem.eql(u8, request.head.target, "/end")) {
162 return request.respond("", .{ .keep_alive = false });
163 }
164 if (request.head.expect) |expect_header_value| {
165 if (std.mem.eql(u8, expect_header_value, "garbage")) {
166 try expectError(error.HttpExpectationFailed, request.reader());
167 try request.respond("", .{ .keep_alive = false });
168 continue;
169 }
170 }
171 handleRequest(&request) catch |err| {
172 // This message helps the person troubleshooting determine whether
173 // output comes from the server thread or the client thread.
174 std.debug.print("handleRequest failed with '{s}'\n", .{@errorName(err)});
175 return err;
176 };
177 }
178 }
179 }
187180
188 const server_thread = try std.Thread.spawn(.{}, (struct {
189181 fn handleRequest(request: *std.http.Server.Request) !void {
190182 //std.debug.print("server received {s} {s} {s}\n", .{
191183 // @tagName(request.head.method),
......@@ -196,9 +188,9 @@ test "echo content server" {
196188 const body = try (try request.reader()).readAllAlloc(std.testing.allocator, 8192);
197189 defer std.testing.allocator.free(body);
198190
199 try testing.expect(std.mem.startsWith(u8, request.head.target, "/echo-content"));
200 try testing.expectEqualStrings("Hello, World!\n", body);
201 try testing.expectEqualStrings("text/plain", request.head.content_type.?);
191 try expect(std.mem.startsWith(u8, request.head.target, "/echo-content"));
192 try expectEqualStrings("Hello, World!\n", body);
193 try expectEqualStrings("text/plain", request.head.content_type.?);
202194
203195 var send_buffer: [100]u8 = undefined;
204196 var response = request.respondStreaming(.{
......@@ -206,7 +198,7 @@ test "echo content server" {
206198 .content_length = switch (request.head.transfer_encoding) {
207199 .chunked => null,
208200 .none => len: {
209 try testing.expectEqual(14, request.head.content_length.?);
201 try expectEqual(14, request.head.content_length.?);
210202 break :len 14;
211203 },
212204 },
......@@ -219,54 +211,19 @@ test "echo content server" {
219211 try response.end();
220212 //std.debug.print(" server finished responding\n", .{});
221213 }
222
223 fn run(net_server: *std.net.Server) anyerror!void {
224 var read_buffer: [1024]u8 = undefined;
225
226 accept: while (true) {
227 const conn = try net_server.accept();
228 defer conn.stream.close();
229
230 var http_server = std.http.Server.init(conn, &read_buffer);
231
232 while (http_server.state == .ready) {
233 var request = http_server.receiveHead() catch |err| switch (err) {
234 error.HttpConnectionClosing => continue :accept,
235 else => |e| return e,
236 };
237 if (std.mem.eql(u8, request.head.target, "/end")) {
238 return request.respond("", .{ .keep_alive = false });
239 }
240 if (request.head.expect) |expect| {
241 if (std.mem.eql(u8, expect, "garbage")) {
242 try testing.expectError(error.HttpExpectationFailed, request.reader());
243 try request.respond("", .{ .keep_alive = false });
244 continue;
245 }
246 }
247 handleRequest(&request) catch |err| {
248 // This message helps the person troubleshooting determine whether
249 // output comes from the server thread or the client thread.
250 std.debug.print("handleRequest failed with '{s}'\n", .{@errorName(err)});
251 return err;
252 };
253 }
254 }
255 }
256 }).run, .{&socket_server});
257
258 defer server_thread.join();
214 });
215 defer test_server.destroy();
259216
260217 {
261 var client: std.http.Client = .{ .allocator = gpa };
218 var client: std.http.Client = .{ .allocator = std.testing.allocator };
262219 defer client.deinit();
263220
264 try echoTests(&client, port);
221 try echoTests(&client, test_server.port());
265222 }
266223}
267224
268225fn echoTests(client: *std.http.Client, port: u16) !void {
269 const gpa = testing.allocator;
226 const gpa = std.testing.allocator;
270227 var location_buffer: [100]u8 = undefined;
271228
272229 { // send content-length request
......@@ -295,11 +252,11 @@ fn echoTests(client: *std.http.Client, port: u16) !void {
295252 const body = try req.reader().readAllAlloc(gpa, 8192);
296253 defer gpa.free(body);
297254
298 try testing.expectEqualStrings("Hello, World!\n", body);
255 try expectEqualStrings("Hello, World!\n", body);
299256 }
300257
301258 // connection has been kept alive
302 try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
259 try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
303260
304261 { // send chunked request
305262 const uri = try std.Uri.parse(try std.fmt.bufPrint(
......@@ -329,11 +286,11 @@ fn echoTests(client: *std.http.Client, port: u16) !void {
329286 const body = try req.reader().readAllAlloc(gpa, 8192);
330287 defer gpa.free(body);
331288
332 try testing.expectEqualStrings("Hello, World!\n", body);
289 try expectEqualStrings("Hello, World!\n", body);
333290 }
334291
335292 // connection has been kept alive
336 try testing.expect(client.http_proxy != null or client.connection_pool.free_len == 1);
293 try expect(client.http_proxy != null or client.connection_pool.free_len == 1);
337294
338295 { // Client.fetch()
339296
......@@ -352,8 +309,8 @@ fn echoTests(client: *std.http.Client, port: u16) !void {
352309 },
353310 .response_storage = .{ .dynamic = &body },
354311 });
355 try testing.expectEqual(.ok, res.status);
356 try testing.expectEqualStrings("Hello, World!\n", body.items);
312 try expectEqual(.ok, res.status);
313 try expectEqualStrings("Hello, World!\n", body.items);
357314 }
358315
359316 { // expect: 100-continue
......@@ -379,12 +336,12 @@ fn echoTests(client: *std.http.Client, port: u16) !void {
379336 try req.finish();
380337
381338 try req.wait();
382 try testing.expectEqual(.ok, req.response.status);
339 try expectEqual(.ok, req.response.status);
383340
384341 const body = try req.reader().readAllAlloc(gpa, 8192);
385342 defer gpa.free(body);
386343
387 try testing.expectEqualStrings("Hello, World!\n", body);
344 try expectEqualStrings("Hello, World!\n", body);
388345 }
389346
390347 { // expect: garbage
......@@ -406,7 +363,7 @@ fn echoTests(client: *std.http.Client, port: u16) !void {
406363
407364 try req.send(.{});
408365 try req.wait();
409 try testing.expectEqual(.expectation_failed, req.response.status);
366 try expectEqual(.expectation_failed, req.response.status);
410367 }
411368
412369 _ = try client.fetch(.{
......@@ -415,3 +372,32 @@ fn echoTests(client: *std.http.Client, port: u16) !void {
415372 },
416373 });
417374}
375
376const TestServer = struct {
377 server_thread: std.Thread,
378 net_server: std.net.Server,
379
380 fn destroy(self: *@This()) void {
381 self.server_thread.join();
382 std.testing.allocator.destroy(self);
383 }
384
385 fn port(self: @This()) u16 {
386 return self.net_server.listen_address.in.getPort();
387 }
388};
389
390fn createTestServer(S: type) !*TestServer {
391 if (builtin.single_threaded) return error.SkipZigTest;
392 if (builtin.os.tag == .wasi) return error.SkipZigTest;
393 if (builtin.zig_backend == .stage2_llvm and native_endian == .big) {
394 // https://github.com/ziglang/zig/issues/13782
395 return error.SkipZigTest;
396 }
397
398 const address = try std.net.Address.parseIp("127.0.0.1", 0);
399 const test_server = try std.testing.allocator.create(TestServer);
400 test_server.net_server = try address.listen(.{ .reuse_address = true });
401 test_server.server_thread = try std.Thread.spawn(.{}, S.run, .{&test_server.net_server});
402 return test_server;
403}