Skip to content

Commit 7d46e8f

Browse files
committed
Start unifying test and code
Depends on #993 There's currently 3 ways to execute a page: 1 - page.navigate (as used in both the 'fetch' and 'serve' commands) 2 - jsRunner as used in unit tests 3 - main_wpt as used in the WPT runner Both jsRunner and main_wpt replicate the page.navigate code, but in their own hack-ish way. main_wpt re-implements the DOM walking in order to extract and execute <script> tags, as well as the needed page lifecycle events. This PR replaces the existing main_wpt loader with a call to page.navigate. To support this, a test HTTP server was added. (The test HTTP server is extracted from the existing unit test test server, and re-used between the two). There are benefits to this approach: 1 - The code is simpler 2 - More of the actual code and flow is tested 3 - There's 1 way to do things (page.navigate) 4 - Having an HTTP server might unlock some WPT tests Technically, we're replacing file IO with network IO i.e. http requests). This has potential downsides: 1 - The tests might be more brittle 2 - The tests might be slower I think we need to run it for a while to see if we get flaky behavior. The goal for following PRs is to bring this unification to the jsRunner.
1 parent 6c41245 commit 7d46e8f

File tree

6 files changed

+265
-215
lines changed

6 files changed

+265
-215
lines changed

src/TestHTTPServer.zig

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
const std = @import("std");
2+
3+
const TestHTTPServer = @This();
4+
5+
shutdown: bool,
6+
listener: ?std.net.Server,
7+
handler: Handler,
8+
9+
const Handler = *const fn (req: *std.http.Server.Request) anyerror!void;
10+
11+
pub fn init(handler: Handler) TestHTTPServer {
12+
return .{
13+
.shutdown = true,
14+
.listener = null,
15+
.handler = handler,
16+
};
17+
}
18+
19+
pub fn deinit(self: *TestHTTPServer) void {
20+
self.shutdown = true;
21+
if (self.listener) |*listener| {
22+
listener.deinit();
23+
}
24+
}
25+
26+
pub fn run(self: *TestHTTPServer, wg: *std.Thread.WaitGroup) !void {
27+
const address = try std.net.Address.parseIp("127.0.0.1", 9582);
28+
29+
self.listener = try address.listen(.{ .reuse_address = true });
30+
var listener = &self.listener.?;
31+
32+
wg.finish();
33+
34+
while (true) {
35+
const conn = listener.accept() catch |err| {
36+
if (self.shutdown) {
37+
return;
38+
}
39+
return err;
40+
};
41+
const thrd = try std.Thread.spawn(.{}, handleConnection, .{ self, conn });
42+
thrd.detach();
43+
}
44+
}
45+
46+
fn handleConnection(self: *TestHTTPServer, conn: std.net.Server.Connection) !void {
47+
defer conn.stream.close();
48+
49+
var req_buf: [2048]u8 = undefined;
50+
var conn_reader = conn.stream.reader(&req_buf);
51+
var conn_writer = conn.stream.writer(&req_buf);
52+
53+
var http_server = std.http.Server.init(conn_reader.interface(), &conn_writer.interface);
54+
55+
while (true) {
56+
var req = http_server.receiveHead() catch |err| switch (err) {
57+
error.ReadFailed => continue,
58+
error.HttpConnectionClosing => continue,
59+
else => {
60+
std.debug.print("Test HTTP Server error: {}\n", .{err});
61+
return err;
62+
},
63+
};
64+
self.handler(&req) catch |err| {
65+
std.debug.print("test http error '{s}': {}\n", .{ req.head.target, err });
66+
try req.respond("server error", .{ .status = .internal_server_error });
67+
return;
68+
};
69+
}
70+
}
71+
72+
pub fn sendFile(req: *std.http.Server.Request, file_path: []const u8) !void {
73+
var file = std.fs.cwd().openFile(file_path, .{}) catch |err| switch (err) {
74+
error.FileNotFound => return req.respond("server error", .{ .status = .not_found }),
75+
else => return err,
76+
};
77+
78+
const stat = try file.stat();
79+
var send_buffer: [4096]u8 = undefined;
80+
81+
var res = try req.respondStreaming(&send_buffer, .{
82+
.content_length = stat.size,
83+
.respond_options = .{
84+
.extra_headers = &.{
85+
.{ .name = "content-type", .value = getContentType(file_path) },
86+
},
87+
},
88+
});
89+
90+
var read_buffer: [4096]u8 = undefined;
91+
var reader = file.reader(&read_buffer);
92+
_ = try res.writer.sendFileAll(&reader, .unlimited);
93+
try res.writer.flush();
94+
try res.end();
95+
}
96+
97+
fn getContentType(file_path: []const u8) []const u8 {
98+
if (std.mem.endsWith(u8, file_path, ".js")) {
99+
return "application/json";
100+
}
101+
102+
if (std.mem.endsWith(u8, file_path, ".html")) {
103+
return "text/html";
104+
}
105+
106+
if (std.mem.endsWith(u8, file_path, ".htm")) {
107+
return "text/html";
108+
}
109+
110+
if (std.mem.endsWith(u8, file_path, ".xml")) {
111+
// some wpt tests do this
112+
return "text/xml";
113+
}
114+
115+
std.debug.print("TestHTTPServer asked to serve an unknown file type: {s}\n", .{file_path});
116+
return "text/html";
117+
}

src/browser/ScriptManager.zig

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -403,14 +403,7 @@ fn startCallback(transfer: *Http.Transfer) !void {
403403

404404
fn headerCallback(transfer: *Http.Transfer) !void {
405405
const script: *PendingScript = @ptrCast(@alignCast(transfer.ctx));
406-
script.headerCallback(transfer) catch |err| {
407-
log.err(.http, "SM.headerCallback", .{
408-
.err = err,
409-
.transfer = transfer,
410-
.status = transfer.response_header.?.status,
411-
});
412-
return err;
413-
};
406+
script.headerCallback(transfer);
414407
}
415408

416409
fn dataCallback(transfer: *Http.Transfer, data: []const u8) !void {
@@ -463,18 +456,23 @@ const PendingScript = struct {
463456
log.debug(.http, "script fetch start", .{ .req = transfer });
464457
}
465458

466-
fn headerCallback(self: *PendingScript, transfer: *Http.Transfer) !void {
459+
fn headerCallback(self: *PendingScript, transfer: *Http.Transfer) void {
467460
const header = &transfer.response_header.?;
461+
if (header.status != 200) {
462+
log.info(.http, "script header", .{
463+
.req = transfer,
464+
.status = header.status,
465+
.content_type = header.contentType(),
466+
});
467+
return;
468+
}
469+
468470
log.debug(.http, "script header", .{
469471
.req = transfer,
470472
.status = header.status,
471473
.content_type = header.contentType(),
472474
});
473475

474-
if (header.status != 200) {
475-
return error.InvalidStatusCode;
476-
}
477-
478476
// If this isn't true, then we'll likely leak memory. If you don't
479477
// set `CURLOPT_SUPPRESS_CONNECT_HEADERS` and CONNECT to a proxy, this
480478
// will fail. This assertion exists to catch incorrect assumptions about

src/browser/xhr/xhr.zig

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -785,8 +785,7 @@ test "Browser.XHR.XMLHttpRequest" {
785785
.{ "req.statusText", "OK" },
786786
.{ "req.getResponseHeader('Content-Type')", "text/html; charset=utf-8" },
787787
.{ "req.getAllResponseHeaders()", "content-length: 100\r\n" ++
788-
"Content-Type: text/html; charset=utf-8\r\n" ++
789-
"Connection: Close\r\n" },
788+
"Content-Type: text/html; charset=utf-8\r\n" },
790789
.{ "req.responseText.length", "100" },
791790
.{ "req.response.length == req.responseText.length", "true" },
792791
.{ "req.responseXML instanceof Document", "true" },

src/main.zig

Lines changed: 38 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -719,23 +719,26 @@ test {
719719
std.testing.refAllDecls(@This());
720720
}
721721

722+
const TestHTTPServer = @import("TestHTTPServer.zig");
723+
722724
var test_cdp_server: ?Server = null;
725+
var test_http_server: ?TestHTTPServer = null;
726+
723727
test "tests:beforeAll" {
724728
log.opts.level = .err;
725729
log.opts.format = .logfmt;
726-
727730
try testing.setup();
728-
729731
var wg: std.Thread.WaitGroup = .{};
730732
wg.startMany(2);
731733

732734
{
733-
const thread = try std.Thread.spawn(.{}, serveHTTP, .{&wg});
735+
const thread = try std.Thread.spawn(.{}, serveCDP, .{&wg});
734736
thread.detach();
735737
}
736738

739+
test_http_server = TestHTTPServer.init(testHTTPHandler);
737740
{
738-
const thread = try std.Thread.spawn(.{}, serveCDP, .{&wg});
741+
const thread = try std.Thread.spawn(.{}, TestHTTPServer.run, .{ &test_http_server.?, &wg });
739742
thread.detach();
740743
}
741744

@@ -748,59 +751,10 @@ test "tests:afterAll" {
748751
if (test_cdp_server) |*server| {
749752
server.deinit();
750753
}
751-
testing.shutdown();
752-
}
753-
754-
fn serveHTTP(wg: *std.Thread.WaitGroup) !void {
755-
const address = try std.net.Address.parseIp("127.0.0.1", 9582);
756-
757-
var listener = try address.listen(.{ .reuse_address = true });
758-
defer listener.deinit();
759-
760-
wg.finish();
761-
762-
var buf: [1024]u8 = undefined;
763-
while (true) {
764-
var conn = try listener.accept();
765-
defer conn.stream.close();
766-
var conn_reader = conn.stream.reader(&buf);
767-
var conn_writer = conn.stream.writer(&buf);
768-
769-
var http_server = std.http.Server.init(conn_reader.interface(), &conn_writer.interface);
770-
771-
var request = http_server.receiveHead() catch |err| switch (err) {
772-
error.HttpConnectionClosing => continue,
773-
else => {
774-
std.debug.print("Test HTTP Server error: {}\n", .{err});
775-
return err;
776-
},
777-
};
778-
779-
const path = request.head.target;
780-
781-
if (std.mem.eql(u8, path, "/loader")) {
782-
try request.respond("Hello!", .{
783-
.extra_headers = &.{.{ .name = "Connection", .value = "close" }},
784-
});
785-
} else if (std.mem.eql(u8, path, "/xhr")) {
786-
try request.respond("1234567890" ** 10, .{
787-
.extra_headers = &.{
788-
.{ .name = "Content-Type", .value = "text/html; charset=utf-8" },
789-
.{ .name = "Connection", .value = "Close" },
790-
},
791-
});
792-
} else if (std.mem.eql(u8, path, "/xhr/json")) {
793-
try request.respond("{\"over\":\"9000!!!\"}", .{
794-
.extra_headers = &.{
795-
.{ .name = "Content-Type", .value = "application/json" },
796-
.{ .name = "Connection", .value = "Close" },
797-
},
798-
});
799-
} else {
800-
// should not have an unknown path
801-
unreachable;
802-
}
754+
if (test_http_server) |*server| {
755+
server.deinit();
803756
}
757+
testing.shutdown();
804758
}
805759

806760
fn serveCDP(wg: *std.Thread.WaitGroup) !void {
@@ -816,3 +770,31 @@ fn serveCDP(wg: *std.Thread.WaitGroup) !void {
816770
return err;
817771
};
818772
}
773+
774+
fn testHTTPHandler(req: *std.http.Server.Request) !void {
775+
const path = req.head.target;
776+
777+
if (std.mem.eql(u8, path, "/loader")) {
778+
return req.respond("Hello!", .{
779+
.extra_headers = &.{.{ .name = "Connection", .value = "close" }},
780+
});
781+
}
782+
783+
if (std.mem.eql(u8, path, "/xhr")) {
784+
return req.respond("1234567890" ** 10, .{
785+
.extra_headers = &.{
786+
.{ .name = "Content-Type", .value = "text/html; charset=utf-8" },
787+
},
788+
});
789+
}
790+
791+
if (std.mem.eql(u8, path, "/xhr/json")) {
792+
return req.respond("{\"over\":\"9000!!!\"}", .{
793+
.extra_headers = &.{
794+
.{ .name = "Content-Type", .value = "application/json" },
795+
},
796+
});
797+
}
798+
799+
unreachable;
800+
}

0 commit comments

Comments
 (0)