Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/browser/browser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,12 @@ pub const Page = struct {

// add global objects
log.debug("setup global env", .{});
try self.session.env.bindGlobal(&self.session.window);

if (comptime builtin.is_test == false) {
// By not loading this during tests, we aren't required to load
// all of the interfaces into zig-js-runtime.
try self.session.env.bindGlobal(&self.session.window);
}

// load polyfills
try polyfill.load(self.arena.allocator(), self.session.env);
Expand Down
6 changes: 0 additions & 6 deletions src/main_tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -337,12 +337,6 @@ test {

std.testing.refAllDecls(@import("generate.zig"));
std.testing.refAllDecls(@import("cdp/msg.zig"));

// Don't use refAllDecls, as this will pull in the entire project
// and break the test build.
// We should fix this. See this branch & the commit message for details:
// https://github.com/karlseguin/browser/commit/193ab5ceab3d3758ea06db04f7690460d79eb79e
_ = @import("server.zig");
}

fn testJSRuntime(alloc: std.mem.Allocator) !void {
Expand Down
118 changes: 110 additions & 8 deletions src/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@ const Server = struct {
self.queueClose(client.socket);
return;
};
if (size == 0) {
if (self.client != null) {
self.client = null;
}
self.queueAccept();
return;
}

const more = client.processData(size) catch |err| {
log.err("Client Processing Error: {}\n", .{err});
Expand Down Expand Up @@ -1053,14 +1060,6 @@ pub fn run(
timeout: u64,
loop: *jsruntime.Loop,
) !void {
if (comptime builtin.is_test) {
// There's bunch of code that won't compiler in a test build (because
// it relies on a global root.Types). So we fight the compiler and make
// sure it doesn't include any of that code. Hopefully one day we can
// remove all this.
return;
}

// create socket
const flags = posix.SOCK.STREAM | posix.SOCK.CLOEXEC | posix.SOCK.NONBLOCK;
const listener = try posix.socket(address.any.family, flags, posix.IPPROTO.TCP);
Expand Down Expand Up @@ -1631,6 +1630,49 @@ test "server: mask" {
}
}

test "server: 404" {
var c = try createTestClient();
defer c.deinit();

const res = try c.httpRequest("GET /unknown HTTP/1.1\r\n\r\n");
try testing.expectEqualStrings("HTTP/1.1 404 \r\n" ++
"Connection: Close\r\n" ++
"Content-Length: 9\r\n\r\n" ++
"Not found", res);
}

test "server: get /json/version" {
const expected_response =
"HTTP/1.1 200 OK\r\n" ++
"Content-Length: 48\r\n" ++
"Content-Type: application/json; charset=UTF-8\r\n\r\n" ++
"{\"webSocketDebuggerUrl\": \"ws://127.0.0.1:9583/\"}";

{
// twice on the same connection
var c = try createTestClient();
defer c.deinit();

const res1 = try c.httpRequest("GET /json/version HTTP/1.1\r\n\r\n");
try testing.expectEqualStrings(expected_response, res1);

const res2 = try c.httpRequest("GET /json/version HTTP/1.1\r\n\r\n");
try testing.expectEqualStrings(expected_response, res2);
}

{
// again on a new connection
var c = try createTestClient();
defer c.deinit();

const res1 = try c.httpRequest("GET /json/version HTTP/1.1\r\n\r\n");
try testing.expectEqualStrings(expected_response, res1);

const res2 = try c.httpRequest("GET /json/version HTTP/1.1\r\n\r\n");
try testing.expectEqualStrings(expected_response, res2);
}
}

fn assertHTTPError(
expected_error: HTTPError,
comptime expected_status: u16,
Expand Down Expand Up @@ -1762,3 +1804,63 @@ const MockServer = struct {
}
}
};

fn createTestClient() !TestClient {
const address = std.net.Address.initIp4([_]u8{ 127, 0, 0, 1 }, 9583);
const stream = try std.net.tcpConnectToAddress(address);

const timeout = std.mem.toBytes(posix.timeval{
.tv_sec = 2,
.tv_usec = 0,
});
try posix.setsockopt(stream.handle, posix.SOL.SOCKET, posix.SO.RCVTIMEO, &timeout);
try posix.setsockopt(stream.handle, posix.SOL.SOCKET, posix.SO.SNDTIMEO, &timeout);
return .{ .stream = stream };
}

const TestClient = struct {
stream: std.net.Stream,
buf: [1024]u8 = undefined,

fn deinit(self: *TestClient) void {
self.stream.close();
}

fn httpRequest(self: *TestClient, req: []const u8) ![]const u8 {
try self.stream.writeAll(req);

var pos: usize = 0;
var total_length: ?usize = null;
while (true) {
pos += try self.stream.read(self.buf[pos..]);
const response = self.buf[0..pos];
if (total_length == null) {
const header_end = std.mem.indexOf(u8, response, "\r\n\r\n") orelse continue;
const header = response[0 .. header_end + 4];

const cl_header = "Content-Length: ";
const start = (std.mem.indexOf(u8, header, cl_header) orelse {
return error.MissingContentLength;
}) + cl_header.len;

const end = std.mem.indexOfScalarPos(u8, header, start, '\r') orelse {
return error.InvalidContentLength;
};
const cl = std.fmt.parseInt(usize, header[start..end], 10) catch {
return error.InvalidContentLength;
};

total_length = cl + header.len;
}

if (total_length) |tl| {
if (pos == tl) {
return response;
}
if (pos > tl) {
return error.DataExceedsContentLength;
}
}
}
}
};
59 changes: 47 additions & 12 deletions src/unit_tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,17 @@

const std = @import("std");
const builtin = @import("builtin");
const parser = @import("netsurf");

const Allocator = std.mem.Allocator;

const jsruntime = @import("jsruntime");
pub const Types = jsruntime.reflect(@import("generate.zig").Tuple(.{}){});
pub const UserContext = @import("user_context.zig").UserContext;
// pub const IO = @import("asyncio").Wrapper(jsruntime.Loop);

pub const std_options = std.Options{
.log_level = .err,
.http_disable_tls = true,
};

Expand All @@ -31,11 +38,16 @@ const BORDER = "=" ** 80;
var current_test: ?[]const u8 = null;

pub fn main() !void {
try parser.init();
defer parser.deinit();

var mem: [8192]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&mem);

const allocator = fba.allocator();

var loop = try jsruntime.Loop.init(allocator);
defer loop.deinit();

const env = Env.init(allocator);
defer env.deinit(allocator);

Expand All @@ -47,12 +59,20 @@ pub fn main() !void {
var skip: usize = 0;
var leak: usize = 0;

const address = try std.net.Address.parseIp("127.0.0.1", 9582);
var listener = try address.listen(.{ .reuse_address = true });
defer listener.deinit();
const http_thread = try std.Thread.spawn(.{}, serverHTTP, .{&listener});
const http_thread = blk: {
const address = try std.net.Address.parseIp("127.0.0.1", 9582);
const thread = try std.Thread.spawn(.{}, serveHTTP, .{address});
break :blk thread;
};
defer http_thread.join();

const cdp_thread = blk: {
const address = try std.net.Address.parseIp("127.0.0.1", 9583);
const thread = try std.Thread.spawn(.{}, serveCDP, .{ allocator, address, &loop });
break :blk thread;
};
defer cdp_thread.join();

const printer = Printer.init();
printer.fmt("\r\x1b[0K", .{}); // beginning of line and clear to end of line

Expand Down Expand Up @@ -98,7 +118,9 @@ pub fn main() !void {
}

if (result) |_| {
pass += 1;
if (is_unnamed_test == false) {
pass += 1;
}
} else |err| switch (err) {
error.SkipZigTest => {
skip += 1;
Expand All @@ -117,11 +139,13 @@ pub fn main() !void {
},
}

if (env.verbose) {
const ms = @as(f64, @floatFromInt(ns_taken)) / 1_000_000.0;
printer.status(status, "{s} ({d:.2}ms)\n", .{ friendly_name, ms });
} else {
printer.status(status, ".", .{});
if (is_unnamed_test == false) {
if (env.verbose) {
const ms = @as(f64, @floatFromInt(ns_taken)) / 1_000_000.0;
printer.status(status, "{s} ({d:.2}ms)\n", .{ friendly_name, ms });
} else {
printer.status(status, ".", .{});
}
}
}

Expand Down Expand Up @@ -294,7 +318,10 @@ fn isUnnamed(t: std.builtin.TestFn) bool {
return true;
}

fn serverHTTP(listener: *std.net.Server) !void {
fn serveHTTP(address: std.net.Address) !void {
var listener = try address.listen(.{ .reuse_address = true });
defer listener.deinit();

var read_buffer: [1024]u8 = undefined;
ACCEPT: while (true) {
var conn = try listener.accept();
Expand All @@ -320,6 +347,14 @@ fn serverHTTP(listener: *std.net.Server) !void {
}
}

fn serveCDP(allocator: Allocator, address: std.net.Address, loop: *jsruntime.Loop) !void {
const server = @import("server.zig");
server.run(allocator, address, std.time.ns_per_s * 2, loop) catch |err| {
std.debug.print("CDP server error: {}", .{err});
return err;
};
}

const Response = struct {
body: []const u8 = "",
status: std.http.Status = .ok,
Expand Down