From 2c4661a250467019967f2ddd2c5a0584808e1569 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Fri, 7 Feb 2025 13:22:01 +0800 Subject: [PATCH 1/7] Add a new unittest build step Preserves all existing behavior (i.e. make test and zig build test are not changed in any way). The new 'unittest' only runs unit tests and is fast to build. It takes ~1.7 to build unittest, vs ~11.09 to build test. This is really the main goal, and hopefully any unit test which are (a) fast and (b) don't impact build times will be run here. The test runner is based on: https://gist.github.com/karlseguin/c6bea5b35e4e8d26af6f81c22cb5d76b It allow filtering, i.e. `make unittest F="parse query dup"`. 'unittest' does memory leak detection when tests use std.testing.allocator. Fixed a memory leak in url/query which was detected/reported with by the new 'unittest'. In order to avoid having 3 src/test_xyx.zig files, I merged the existing test_runner.zig and run_tests.zig into a single main_tests.zig. (this change is superfluous, but I thought it was cleaner this way. Happy to revert this). --- Makefile | 7 +- build.zig | 25 +- src/{run_tests.zig => main_tests.zig} | 0 src/test_runner.zig | 29 --- src/unit_tests.zig | 337 ++++++++++++++++++++++++++ src/url/query.zig | 1 + 6 files changed, 367 insertions(+), 32 deletions(-) rename src/{run_tests.zig => main_tests.zig} (100%) delete mode 100644 src/test_runner.zig create mode 100644 src/unit_tests.zig diff --git a/Makefile b/Makefile index 6dff85e5..4b824f11 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,8 @@ ZIG := zig BC := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) +# option test filter make unittest F="server" +F= # OS and ARCH kernel = $(shell uname -ms) @@ -42,7 +44,7 @@ help: # $(ZIG) commands # ------------ -.PHONY: build build-dev run run-release shell test bench download-zig wpt +.PHONY: build build-dev run run-release shell test bench download-zig wpt unittest zig_version = $(shell grep 'recommended_zig_version = "' "vendor/zig-js-runtime/build.zig" | cut -d'"' -f2) @@ -91,6 +93,9 @@ test: @$(ZIG) build test -Dengine=v8 || (printf "\e[33mTest ERROR\e[0m\n"; exit 1;) @printf "\e[33mTest OK\e[0m\n" +unittest: + @TEST_FILTER='${F}' $(ZIG) build unittest -freference-trace --summary all + # Install and build required dependencies commands # ------------ .PHONY: install-submodule diff --git a/build.zig b/build.zig index b937252a..44e99222 100644 --- a/build.zig +++ b/build.zig @@ -98,8 +98,8 @@ pub fn build(b: *std.Build) !void { // compile const tests = b.addTest(.{ - .root_source_file = b.path("src/run_tests.zig"), - .test_runner = b.path("src/test_runner.zig"), + .root_source_file = b.path("src/main_tests.zig"), + .test_runner = b.path("src/main_tests.zig"), .target = target, .optimize = mode, }); @@ -119,6 +119,27 @@ pub fn build(b: *std.Build) !void { const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_tests.step); + // unittest + // ---- + + // compile + const unit_tests = b.addTest(.{ + .root_source_file = b.path("src/unit_tests.zig"), + .test_runner = b.path("src/unit_tests.zig"), + .target = target, + .optimize = mode, + }); + try common(b, unit_tests, options); + + const run_unit_tests = b.addRunArtifact(unit_tests); + if (b.args) |args| { + run_unit_tests.addArgs(args); + } + + // step + const unit_test_step = b.step("unittest", "Run unit tests"); + unit_test_step.dependOn(&run_unit_tests.step); + // wpt // ----- diff --git a/src/run_tests.zig b/src/main_tests.zig similarity index 100% rename from src/run_tests.zig rename to src/main_tests.zig diff --git a/src/test_runner.zig b/src/test_runner.zig deleted file mode 100644 index 8358b66c..00000000 --- a/src/test_runner.zig +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) -// -// Francis Bouvier -// Pierre Tachoire -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -const std = @import("std"); - -const tests = @import("run_tests.zig"); - -pub const Types = tests.Types; -pub const UserContext = tests.UserContext; -pub const IO = tests.IO; - -pub fn main() !void { - try tests.main(); -} diff --git a/src/unit_tests.zig b/src/unit_tests.zig new file mode 100644 index 00000000..3111b1d3 --- /dev/null +++ b/src/unit_tests.zig @@ -0,0 +1,337 @@ +// Changed Jan 29, 2025 to accomodate latest Zig changes +// See history if you're using an older version of Zig. + +// in your build.zig, you can specify a custom test runner: +// const tests = b.addTest(.{ +// .target = target, +// .optimize = optimize, +// .test_runner = .{ .path = b.path("test_runner.zig"), .mode = .simple }, // add this line +// .root_source_file = b.path("src/main.zig"), +// }); + +const std = @import("std"); +const builtin = @import("builtin"); + +const Allocator = std.mem.Allocator; + +const BORDER = "=" ** 80; + +// use in custom panic handler +var current_test: ?[]const u8 = null; + +pub fn main() !void { + var mem: [8192]u8 = undefined; + var fba = std.heap.FixedBufferAllocator.init(&mem); + + const allocator = fba.allocator(); + + const env = Env.init(allocator); + defer env.deinit(allocator); + + var slowest = SlowTracker.init(allocator, 5); + defer slowest.deinit(); + + var pass: usize = 0; + var fail: usize = 0; + var skip: usize = 0; + var leak: usize = 0; + + const printer = Printer.init(); + printer.fmt("\r\x1b[0K", .{}); // beginning of line and clear to end of line + + for (builtin.test_functions) |t| { + if (isSetup(t)) { + t.func() catch |err| { + printer.status(.fail, "\nsetup \"{s}\" failed: {}\n", .{ t.name, err }); + return err; + }; + } + } + + for (builtin.test_functions) |t| { + if (std.mem.eql(u8, t.name, "unit_tests.test_0")) { + // don't display anything for this test + try t.func(); + continue; + } + + if (isSetup(t) or isTeardown(t)) { + continue; + } + + var status = Status.pass; + slowest.startTiming(); + + const is_unnamed_test = isUnnamed(t); + if (env.filter) |f| { + if (!is_unnamed_test and std.mem.indexOf(u8, t.name, f) == null) { + continue; + } + } + + const friendly_name = blk: { + const name = t.name; + var it = std.mem.splitScalar(u8, name, '.'); + while (it.next()) |value| { + if (std.mem.eql(u8, value, "test")) { + const rest = it.rest(); + break :blk if (rest.len > 0) rest else name; + } + } + break :blk name; + }; + + current_test = friendly_name; + std.testing.allocator_instance = .{}; + const result = t.func(); + current_test = null; + + const ns_taken = slowest.endTiming(friendly_name); + + if (std.testing.allocator_instance.deinit() == .leak) { + leak += 1; + printer.status(.fail, "\n{s}\n\"{s}\" - Memory Leak\n{s}\n", .{ BORDER, friendly_name, BORDER }); + } + + if (result) |_| { + pass += 1; + } else |err| switch (err) { + error.SkipZigTest => { + skip += 1; + status = .skip; + }, + else => { + status = .fail; + fail += 1; + printer.status(.fail, "\n{s}\n\"{s}\" - {s}\n{s}\n", .{ BORDER, friendly_name, @errorName(err), BORDER }); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + if (env.fail_first) { + break; + } + }, + } + + 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, ".", .{}); + } + } + + for (builtin.test_functions) |t| { + if (isTeardown(t)) { + t.func() catch |err| { + printer.status(.fail, "\nteardown \"{s}\" failed: {}\n", .{ t.name, err }); + return err; + }; + } + } + + const total_tests = pass + fail; + const status = if (fail == 0) Status.pass else Status.fail; + printer.status(status, "\n{d} of {d} test{s} passed\n", .{ pass, total_tests, if (total_tests != 1) "s" else "" }); + if (skip > 0) { + printer.status(.skip, "{d} test{s} skipped\n", .{ skip, if (skip != 1) "s" else "" }); + } + if (leak > 0) { + printer.status(.fail, "{d} test{s} leaked\n", .{ leak, if (leak != 1) "s" else "" }); + } + printer.fmt("\n", .{}); + try slowest.display(printer); + printer.fmt("\n", .{}); + std.posix.exit(if (fail == 0) 0 else 1); +} + +const Printer = struct { + out: std.fs.File.Writer, + + fn init() Printer { + return .{ + .out = std.io.getStdErr().writer(), + }; + } + + fn fmt(self: Printer, comptime format: []const u8, args: anytype) void { + std.fmt.format(self.out, format, args) catch unreachable; + } + + fn status(self: Printer, s: Status, comptime format: []const u8, args: anytype) void { + const color = switch (s) { + .pass => "\x1b[32m", + .fail => "\x1b[31m", + .skip => "\x1b[33m", + else => "", + }; + const out = self.out; + out.writeAll(color) catch @panic("writeAll failed?!"); + std.fmt.format(out, format, args) catch @panic("std.fmt.format failed?!"); + self.fmt("\x1b[0m", .{}); + } +}; + +const Status = enum { + pass, + fail, + skip, + text, +}; + +const SlowTracker = struct { + const SlowestQueue = std.PriorityDequeue(TestInfo, void, compareTiming); + max: usize, + slowest: SlowestQueue, + timer: std.time.Timer, + + fn init(allocator: Allocator, count: u32) SlowTracker { + const timer = std.time.Timer.start() catch @panic("failed to start timer"); + var slowest = SlowestQueue.init(allocator, {}); + slowest.ensureTotalCapacity(count) catch @panic("OOM"); + return .{ + .max = count, + .timer = timer, + .slowest = slowest, + }; + } + + const TestInfo = struct { + ns: u64, + name: []const u8, + }; + + fn deinit(self: SlowTracker) void { + self.slowest.deinit(); + } + + fn startTiming(self: *SlowTracker) void { + self.timer.reset(); + } + + fn endTiming(self: *SlowTracker, test_name: []const u8) u64 { + var timer = self.timer; + const ns = timer.lap(); + + var slowest = &self.slowest; + + if (slowest.count() < self.max) { + // Capacity is fixed to the # of slow tests we want to track + // If we've tracked fewer tests than this capacity, than always add + slowest.add(TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing"); + return ns; + } + + { + // Optimization to avoid shifting the dequeue for the common case + // where the test isn't one of our slowest. + const fastest_of_the_slow = slowest.peekMin() orelse unreachable; + if (fastest_of_the_slow.ns > ns) { + // the test was faster than our fastest slow test, don't add + return ns; + } + } + + // the previous fastest of our slow tests, has been pushed off. + _ = slowest.removeMin(); + slowest.add(TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing"); + return ns; + } + + fn display(self: *SlowTracker, printer: Printer) !void { + var slowest = self.slowest; + const count = slowest.count(); + printer.fmt("Slowest {d} test{s}: \n", .{ count, if (count != 1) "s" else "" }); + while (slowest.removeMinOrNull()) |info| { + const ms = @as(f64, @floatFromInt(info.ns)) / 1_000_000.0; + printer.fmt(" {d:.2}ms\t{s}\n", .{ ms, info.name }); + } + } + + fn compareTiming(context: void, a: TestInfo, b: TestInfo) std.math.Order { + _ = context; + return std.math.order(a.ns, b.ns); + } +}; + +const Env = struct { + verbose: bool, + fail_first: bool, + filter: ?[]const u8, + + fn init(allocator: Allocator) Env { + return .{ + .verbose = readEnvBool(allocator, "TEST_VERBOSE", true), + .fail_first = readEnvBool(allocator, "TEST_FAIL_FIRST", false), + .filter = readEnv(allocator, "TEST_FILTER"), + }; + } + + fn deinit(self: Env, allocator: Allocator) void { + if (self.filter) |f| { + allocator.free(f); + } + } + + fn readEnv(allocator: Allocator, key: []const u8) ?[]const u8 { + const v = std.process.getEnvVarOwned(allocator, key) catch |err| { + if (err == error.EnvironmentVariableNotFound) { + return null; + } + std.log.warn("failed to get env var {s} due to err {}", .{ key, err }); + return null; + }; + return v; + } + + fn readEnvBool(allocator: Allocator, key: []const u8, deflt: bool) bool { + const value = readEnv(allocator, key) orelse return deflt; + defer allocator.free(value); + return std.ascii.eqlIgnoreCase(value, "true"); + } +}; + +fn isUnnamed(t: std.builtin.TestFn) bool { + const marker = ".test_"; + const test_name = t.name; + const index = std.mem.indexOf(u8, test_name, marker) orelse return false; + _ = std.fmt.parseInt(u32, test_name[index + marker.len ..], 10) catch return false; + return true; +} + +fn isSetup(t: std.builtin.TestFn) bool { + return std.mem.endsWith(u8, t.name, "tests:beforeAll"); +} + +fn isTeardown(t: std.builtin.TestFn) bool { + return std.mem.endsWith(u8, t.name, "tests:afterAll"); +} + +test { + const msgTest = @import("msg.zig"); + std.testing.refAllDecls(msgTest); + + const dumpTest = @import("browser/dump.zig"); + std.testing.refAllDecls(dumpTest); + + const mimeTest = @import("browser/mime.zig"); + std.testing.refAllDecls(mimeTest); + + const cssTest = @import("css/css.zig"); + std.testing.refAllDecls(cssTest); + + const cssParserTest = @import("css/parser.zig"); + std.testing.refAllDecls(cssParserTest); + + const cssMatchTest = @import("css/match_test.zig"); + std.testing.refAllDecls(cssMatchTest); + + const cssLibdomTest = @import("css/libdom_test.zig"); + std.testing.refAllDecls(cssLibdomTest); + + const queryTest = @import("url/query.zig"); + std.testing.refAllDecls(queryTest); + + std.testing.refAllDecls(@import("generate.zig")); + std.testing.refAllDecls(@import("cdp/msg.zig")); +} diff --git a/src/url/query.zig b/src/url/query.zig index 3b1f877f..5defb288 100644 --- a/src/url/query.zig +++ b/src/url/query.zig @@ -62,6 +62,7 @@ pub const Values = struct { // append by taking the ownership of the key and the value fn appendOwned(self: *Values, k: []const u8, v: []const u8) !void { if (self.map.getPtr(k)) |list| { + self.alloc.free(k); return try list.append(self.alloc, v); } From 4229b1d2a4a122d62ed059c4720ef12045a02f15 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Sat, 8 Feb 2025 20:08:29 +0800 Subject: [PATCH 2/7] Report memory leaks when using std.testing.allocator Fix leaks in storage bottle --- src/main_tests.zig | 8 +++++++- src/storage/storage.zig | 41 ++++++++++++++++++++++++----------------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/main_tests.zig b/src/main_tests.zig index 9c0c98b9..3544cc6a 100644 --- a/src/main_tests.zig +++ b/src/main_tests.zig @@ -223,8 +223,14 @@ pub fn main() !void { try parser.init(); defer parser.deinit(); + std.testing.allocator_instance = .{}; try test_fn.func(); - std.debug.print("{s}\tOK\n", .{test_fn.name}); + + if (std.testing.allocator_instance.deinit() == .leak) { + std.debug.print("======Memory Leak: {s}======\n", .{test_fn.name}); + } else { + std.debug.print("{s}\tOK\n", .{test_fn.name}); + } } } } diff --git a/src/storage/storage.zig b/src/storage/storage.zig index 205a3895..53b5e42b 100644 --- a/src/storage/storage.zig +++ b/src/storage/storage.zig @@ -149,20 +149,22 @@ pub const Bottle = struct { } pub fn _setItem(self: *Bottle, k: []const u8, v: []const u8) !void { - const old = self.map.get(k); - if (old != null and std.mem.eql(u8, v, old.?)) return; - - // owns k and v by copying them. - const kk = try self.alloc.dupe(u8, k); - errdefer self.alloc.free(kk); - const vv = try self.alloc.dupe(u8, v); - errdefer self.alloc.free(vv); - - self.map.put(self.alloc, kk, vv) catch |e| { + const gop = self.map.getOrPut(self.alloc, k) catch |e| { log.debug("set item: {any}", .{e}); return DOMError.QuotaExceeded; }; + if (gop.found_existing == false) { + gop.key_ptr.* = try self.alloc.dupe(u8, k); + gop.value_ptr.* = try self.alloc.dupe(u8, v); + return; + } + + if (std.mem.eql(u8, v, gop.value_ptr.*) == false) { + self.alloc.free(gop.value_ptr.*); + gop.value_ptr.* = try self.alloc.dupe(u8, v); + } + // > Broadcast this with key, oldValue, and value. // https://html.spec.whatwg.org/multipage/webstorage.html#the-storageevent-interface // @@ -175,8 +177,10 @@ pub const Bottle = struct { } pub fn _removeItem(self: *Bottle, k: []const u8) !void { - const old = self.map.fetchRemove(k); - if (old == null) return; + if (self.map.fetchRemove(k)) |kv| { + self.alloc.free(kv.key); + self.alloc.free(kv.value); + } // > Broadcast this with key, oldValue, and null. // https://html.spec.whatwg.org/multipage/webstorage.html#the-storageevent-interface @@ -235,14 +239,17 @@ test "storage bottle" { var bottle = Bottle.init(std.testing.allocator); defer bottle.deinit(); - try std.testing.expect(0 == bottle.get_length()); - try std.testing.expect(null == bottle._getItem("foo")); + try std.testing.expectEqual(0, bottle.get_length()); + try std.testing.expectEqual(null, bottle._getItem("foo")); try bottle._setItem("foo", "bar"); - try std.testing.expect(std.mem.eql(u8, "bar", bottle._getItem("foo").?)); + try std.testing.expectEqualStrings("bar", bottle._getItem("foo").?); + + try bottle._setItem("foo", "other"); + try std.testing.expectEqualStrings("other", bottle._getItem("foo").?); try bottle._removeItem("foo"); - try std.testing.expect(0 == bottle.get_length()); - try std.testing.expect(null == bottle._getItem("foo")); + try std.testing.expectEqual(0, bottle.get_length()); + try std.testing.expectEqual(null, bottle._getItem("foo")); } From 62805cdf1daff042ce06aad1712816d19fcc71b3 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Sat, 8 Feb 2025 20:10:26 +0800 Subject: [PATCH 3/7] add license to file --- src/unit_tests.zig | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/unit_tests.zig b/src/unit_tests.zig index 3111b1d3..d8c96c2c 100644 --- a/src/unit_tests.zig +++ b/src/unit_tests.zig @@ -1,13 +1,20 @@ -// Changed Jan 29, 2025 to accomodate latest Zig changes -// See history if you're using an older version of Zig. - -// in your build.zig, you can specify a custom test runner: -// const tests = b.addTest(.{ -// .target = target, -// .optimize = optimize, -// .test_runner = .{ .path = b.path("test_runner.zig"), .mode = .simple }, // add this line -// .root_source_file = b.path("src/main.zig"), -// }); +// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . const std = @import("std"); const builtin = @import("builtin"); From 6e58b98b3df3b1852d01445237b1282d9808a79c Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Sun, 9 Feb 2025 10:38:54 +0800 Subject: [PATCH 4/7] Startup local HTTP server for testing Change loader tests to use local HTTP server. Add missing test scripts (i.e. storage) to unittest runs. --- src/browser/loader.zig | 12 ++++-- src/unit_tests.zig | 84 +++++++++++++++++++++++++++++------------- 2 files changed, 68 insertions(+), 28 deletions(-) diff --git a/src/browser/loader.zig b/src/browser/loader.zig index 480ec4c1..6df6d9ad 100644 --- a/src/browser/loader.zig +++ b/src/browser/loader.zig @@ -79,13 +79,19 @@ pub const Loader = struct { } }; -test "basic url get" { +test "loader: get" { const alloc = std.testing.allocator; var loader = Loader.init(alloc); defer loader.deinit(); - var result = try loader.get(alloc, "https://en.wikipedia.org/wiki/Main_Page"); + const uri = try std.Uri.parse("http://localhost:9582/loader"); + var result = try loader.get(alloc, uri); defer result.deinit(); - try std.testing.expect(result.req.response.status == std.http.Status.ok); + try std.testing.expectEqual(.ok, result.req.response.status); + + var res: [128]u8 = undefined; + const size = try result.req.readAll(&res); + try std.testing.expectEqual(6, size); + try std.testing.expectEqualStrings("Hello!", res[0..6]); } diff --git a/src/unit_tests.zig b/src/unit_tests.zig index d8c96c2c..52aee5f3 100644 --- a/src/unit_tests.zig +++ b/src/unit_tests.zig @@ -21,6 +21,10 @@ const builtin = @import("builtin"); const Allocator = std.mem.Allocator; +pub const std_options = std.Options{ + .http_disable_tls = true, +}; + const BORDER = "=" ** 80; // use in custom panic handler @@ -43,6 +47,12 @@ 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}); + defer http_thread.join(); + const printer = Printer.init(); printer.fmt("\r\x1b[0K", .{}); // beginning of line and clear to end of line @@ -314,31 +324,55 @@ fn isTeardown(t: std.builtin.TestFn) bool { return std.mem.endsWith(u8, t.name, "tests:afterAll"); } -test { - const msgTest = @import("msg.zig"); - std.testing.refAllDecls(msgTest); +fn serverHTTP(listener: *std.net.Server) !void { + var read_buffer: [1024]u8 = undefined; + ACCEPT: while (true) { + var conn = try listener.accept(); + defer conn.stream.close(); + var server = std.http.Server.init(conn, &read_buffer); - const dumpTest = @import("browser/dump.zig"); - std.testing.refAllDecls(dumpTest); + while (server.state == .ready) { + var request = server.receiveHead() catch |err| switch (err) { + error.HttpConnectionClosing => continue :ACCEPT, + else => { + std.debug.print("Test HTTP Server error: {}\n", .{err}); + return err; + } + }; - const mimeTest = @import("browser/mime.zig"); - std.testing.refAllDecls(mimeTest); - - const cssTest = @import("css/css.zig"); - std.testing.refAllDecls(cssTest); - - const cssParserTest = @import("css/parser.zig"); - std.testing.refAllDecls(cssParserTest); - - const cssMatchTest = @import("css/match_test.zig"); - std.testing.refAllDecls(cssMatchTest); - - const cssLibdomTest = @import("css/libdom_test.zig"); - std.testing.refAllDecls(cssLibdomTest); - - const queryTest = @import("url/query.zig"); - std.testing.refAllDecls(queryTest); - - std.testing.refAllDecls(@import("generate.zig")); - std.testing.refAllDecls(@import("cdp/msg.zig")); + const path = request.head.target; + if (std.mem.eql(u8, path, "/loader")) { + try writeResponse(&request, .{ + .body = "Hello!", + }); + } + } + } +} + +const Response = struct { + body: []const u8 = "", + status: std.http.Status = .ok, +}; + +fn writeResponse(req: *std.http.Server.Request, res: Response) !void { + try req.respond(res.body, .{ + .status = res.status + }); +} + +test { + std.testing.refAllDecls( @import("url/query.zig")); + std.testing.refAllDecls(@import("browser/dump.zig")); + std.testing.refAllDecls(@import("browser/loader.zig")); + std.testing.refAllDecls(@import("browser/mime.zig")); + std.testing.refAllDecls(@import("cdp/msg.zig")); + std.testing.refAllDecls(@import("css/css.zig")); + std.testing.refAllDecls(@import("css/libdom_test.zig")); + std.testing.refAllDecls(@import("css/match_test.zig")); + std.testing.refAllDecls(@import("css/parser.zig")); + std.testing.refAllDecls(@import("generate.zig")); + std.testing.refAllDecls(@import("http/Client.zig")); + std.testing.refAllDecls(@import("msg.zig")); + std.testing.refAllDecls(@import("storage/storage.zig")); } From 3af0531111c6016122b54f8cca5f8a7657b75cdf Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Sun, 9 Feb 2025 10:53:47 +0800 Subject: [PATCH 5/7] zig fmt + add U32Iterator tests --- src/iterator/iterator.zig | 26 +++++++++++++++++++++++--- src/unit_tests.zig | 11 +++++------ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/iterator/iterator.zig b/src/iterator/iterator.zig index 803cd978..4836872d 100644 --- a/src/iterator/iterator.zig +++ b/src/iterator/iterator.zig @@ -15,19 +15,39 @@ pub const U32Iterator = struct { done: bool, }; - pub fn _next(self: *U32Iterator) !Return { + pub fn _next(self: *U32Iterator) Return { const i = self.index; if (i >= self.length) { - return Return{ + return .{ .value = 0, .done = true, }; } self.index += 1; - return Return{ + return .{ .value = i, .done = false, }; } }; + +const testing = std.testing; +test "U32Iterator" { + const Return = U32Iterator.Return; + + { + var it = U32Iterator{ .length = 0 }; + try testing.expectEqual(Return{ .value = 0, .done = true }, it._next()); + try testing.expectEqual(Return{ .value = 0, .done = true }, it._next()); + } + + { + var it = U32Iterator{ .length = 3 }; + try testing.expectEqual(Return{ .value = 0, .done = false }, it._next()); + try testing.expectEqual(Return{ .value = 1, .done = false }, it._next()); + try testing.expectEqual(Return{ .value = 2, .done = false }, it._next()); + try testing.expectEqual(Return{ .value = 0, .done = true }, it._next()); + try testing.expectEqual(Return{ .value = 0, .done = true }, it._next()); + } +} diff --git a/src/unit_tests.zig b/src/unit_tests.zig index 52aee5f3..d61f5cc4 100644 --- a/src/unit_tests.zig +++ b/src/unit_tests.zig @@ -48,7 +48,7 @@ pub fn main() !void { var leak: usize = 0; const address = try std.net.Address.parseIp("127.0.0.1", 9582); - var listener = try address.listen(.{.reuse_address = true}); + var listener = try address.listen(.{ .reuse_address = true }); defer listener.deinit(); const http_thread = try std.Thread.spawn(.{}, serverHTTP, .{&listener}); defer http_thread.join(); @@ -337,7 +337,7 @@ fn serverHTTP(listener: *std.net.Server) !void { else => { std.debug.print("Test HTTP Server error: {}\n", .{err}); return err; - } + }, }; const path = request.head.target; @@ -356,13 +356,11 @@ const Response = struct { }; fn writeResponse(req: *std.http.Server.Request, res: Response) !void { - try req.respond(res.body, .{ - .status = res.status - }); + try req.respond(res.body, .{ .status = res.status }); } test { - std.testing.refAllDecls( @import("url/query.zig")); + std.testing.refAllDecls(@import("url/query.zig")); std.testing.refAllDecls(@import("browser/dump.zig")); std.testing.refAllDecls(@import("browser/loader.zig")); std.testing.refAllDecls(@import("browser/mime.zig")); @@ -375,4 +373,5 @@ test { std.testing.refAllDecls(@import("http/Client.zig")); std.testing.refAllDecls(@import("msg.zig")); std.testing.refAllDecls(@import("storage/storage.zig")); + std.testing.refAllDecls(@import("iterator/iterator.zig")); } From 2aa5f4fc8296c5f5220cd8ab9e8d72be472004f5 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Sun, 9 Feb 2025 10:54:35 +0800 Subject: [PATCH 6/7] small iterator tweak --- src/iterator/iterator.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iterator/iterator.zig b/src/iterator/iterator.zig index 4836872d..d582be11 100644 --- a/src/iterator/iterator.zig +++ b/src/iterator/iterator.zig @@ -24,7 +24,7 @@ pub const U32Iterator = struct { }; } - self.index += 1; + self.index = i + 1; return .{ .value = i, .done = false, From d01d43eccbcf86c786e6805614502720a908347c Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Sun, 9 Feb 2025 13:16:27 +0800 Subject: [PATCH 7/7] Remove setup/teardown functionality. YAGNI --- src/unit_tests.zig | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/src/unit_tests.zig b/src/unit_tests.zig index d61f5cc4..2ab87f9a 100644 --- a/src/unit_tests.zig +++ b/src/unit_tests.zig @@ -56,15 +56,6 @@ pub fn main() !void { const printer = Printer.init(); printer.fmt("\r\x1b[0K", .{}); // beginning of line and clear to end of line - for (builtin.test_functions) |t| { - if (isSetup(t)) { - t.func() catch |err| { - printer.status(.fail, "\nsetup \"{s}\" failed: {}\n", .{ t.name, err }); - return err; - }; - } - } - for (builtin.test_functions) |t| { if (std.mem.eql(u8, t.name, "unit_tests.test_0")) { // don't display anything for this test @@ -72,10 +63,6 @@ pub fn main() !void { continue; } - if (isSetup(t) or isTeardown(t)) { - continue; - } - var status = Status.pass; slowest.startTiming(); @@ -138,15 +125,6 @@ pub fn main() !void { } } - for (builtin.test_functions) |t| { - if (isTeardown(t)) { - t.func() catch |err| { - printer.status(.fail, "\nteardown \"{s}\" failed: {}\n", .{ t.name, err }); - return err; - }; - } - } - const total_tests = pass + fail; const status = if (fail == 0) Status.pass else Status.fail; printer.status(status, "\n{d} of {d} test{s} passed\n", .{ pass, total_tests, if (total_tests != 1) "s" else "" }); @@ -316,14 +294,6 @@ fn isUnnamed(t: std.builtin.TestFn) bool { return true; } -fn isSetup(t: std.builtin.TestFn) bool { - return std.mem.endsWith(u8, t.name, "tests:beforeAll"); -} - -fn isTeardown(t: std.builtin.TestFn) bool { - return std.mem.endsWith(u8, t.name, "tests:afterAll"); -} - fn serverHTTP(listener: *std.net.Server) !void { var read_buffer: [1024]u8 = undefined; ACCEPT: while (true) {