Merge pull request #3088 from lightpanda-io/improve-test-output-and-logs

tests: improve test output and test log handling
This commit is contained in:
Karl Seguin authored and GitHub committed 2026-07-31 11:01:53 +08:00
commit 392bb4c772
44 files changed
+127 -256

No files matched your search

+1 -9
View File
@@ -119,16 +119,8 @@ run-debug: build-dev
@printf "\033[36mRunning...\033[0m\n"
@./zig-out/bin/lightpanda || (printf "\033[33mRun ERROR\033[0m\n"; exit 1;)
## Test - `grep` is used to filter out the huge compile command on build
ifeq ($(OS), macos)
test:
@script -q /dev/null sh -c 'TEST_FILTER="${F}" $(ZIG) build $(ZIGFLAGS) test -freference-trace' 2>&1 \
| grep --line-buffered -v "^/.*zig test -freference-trace"
else
test:
@script -qec 'TEST_FILTER="${F}" $(ZIG) build $(ZIGFLAGS) test -freference-trace' /dev/null 2>&1 \
| grep --line-buffered -v "^/.*zig test -freference-trace"
endif
TEST_FILTER="${F}" $(ZIG) build $(ZIGFLAGS) test -freference-trace
## Run demo/runner end to end tests
end2end:
-3
View File
@@ -730,8 +730,6 @@ pub fn getNodeDetails(
const testing = @import("testing.zig");
test "SemanticTree backendDOMNodeId" {
defer testing.reset();
var registry: CDPNode.Registry = .init(testing.allocator);
defer registry.deinit();
@@ -756,7 +754,6 @@ test "SemanticTree backendDOMNodeId" {
}
test "SemanticTree max_depth" {
defer testing.reset();
var registry: CDPNode.Registry = .init(testing.allocator);
defer registry.deinit();
+2 -4
View File
@@ -331,8 +331,7 @@ test "server: buildJSONVersionResponse" {
}
test "Client: http invalid request" {
const filter: testing.LogFilter = .init(&.{.cdp});
defer filter.deinit();
testing.silenceLog(&.{.cdp});
var c = try createTestClient();
defer c.deinit();
@@ -449,8 +448,7 @@ test "Client: read invalid websocket message" {
}
{
const filter: testing.LogFilter = .init(&.{.cdp});
defer filter.deinit();
testing.expectLog(&.{.cdp});
// length of message is 0, 0, 0, 0, 0, 16, 0, 1 i.e: 1024 * 1024 + 1
try assertWebSocketError(1009, &.{ 129, 255, 0, 0, 0, 0, 0, 16, 0, 1, 'm', 'a', 's', 'k' });
}
+4 -6
View File
@@ -3460,8 +3460,7 @@ test "Frame: urlBasename" {
}
test "WebApi: Frame" {
const filter: testing.LogFilter = .init(&.{.http});
defer filter.deinit();
testing.silenceLog(&.{.http});
try testing.htmlRunner("page", .{});
}
@@ -3470,8 +3469,7 @@ test "WebApi: Frames" {
}
test "WebApi: Frame Blob" {
const filter: testing.LogFilter = .init(&.{ .frame, .browser, .js });
defer filter.deinit();
testing.silenceLog(&.{ .frame, .browser, .js });
try testing.htmlRunner("frames/blob", .{});
}
@@ -3524,6 +3522,8 @@ test "Page: isSameOrigin" {
}
test "Frame: httpMetadata after navigation" {
testing.expectLog(&.{.http});
const page = try testing.pageTest("page/meta.html", .{});
defer page.close();
@@ -3544,8 +3544,6 @@ test "Frame: httpMetadata 404" {
}
test "Frame: 401" {
defer testing.reset();
var page = try testing.pageTest("401", .{});
defer page.close();
-18
View File
@@ -360,8 +360,6 @@ fn isSpecialUrl(url: []const u8) bool {
const testing = @import("../testing.zig");
test "ImportMap: exact match" {
defer testing.reset();
const im = try testParse(
\\{ "imports": { "moment": "/node_modules/moment/index.js" } }
, "https://example.com/app/index.html");
@@ -371,8 +369,6 @@ test "ImportMap: exact match" {
}
test "ImportMap: trailing slash prefix match" {
defer testing.reset();
const im = try testParse(
\\{ "imports": { "moment/": "/node_modules/moment/src/" } }
, "https://example.com/app/index.html");
@@ -382,8 +378,6 @@ test "ImportMap: trailing slash prefix match" {
}
test "ImportMap: specificity — longest match wins" {
defer testing.reset();
const im = try testParse(
\\{ "imports": {
\\ "a": "/1",
@@ -413,8 +407,6 @@ test "ImportMap: specificity — longest match wins" {
}
test "ImportMap: scopes — most specific scope wins" {
defer testing.reset();
const im = try testParse(
\\{
\\ "imports": { "a": "/a-1.mjs", "b": "/b-1.mjs", "d": "/d-1.mjs" },
@@ -444,8 +436,6 @@ test "ImportMap: scopes — most specific scope wins" {
}
test "ImportMap: bare specifier with no match returns null" {
defer testing.reset();
const im = try testParse(
\\{ "imports": { "moment": "/m.js" } }
, "https://example.com/app/index.html");
@@ -455,8 +445,6 @@ test "ImportMap: bare specifier with no match returns null" {
}
test "ImportMap: URL-like specifier falls back to itself" {
defer testing.reset();
const im: ImportMap = .empty;
const r = try testResolve(&im, "https://example.com/app.mjs", "./foo.js");
@@ -464,8 +452,6 @@ test "ImportMap: URL-like specifier falls back to itself" {
}
test "ImportMap: null entry throws (no fallback)" {
defer testing.reset();
const im = try testParse(
\\{ "imports": { "blocked": null } }
, "https://example.com/app/index.html");
@@ -474,8 +460,6 @@ test "ImportMap: null entry throws (no fallback)" {
}
test "ImportMap: backtracking out of prefix throws" {
defer testing.reset();
const im = try testParse(
\\{ "imports": { "moment/": "/node_modules/moment/src/" } }
, "https://example.com/app/index.html");
@@ -484,7 +468,6 @@ test "ImportMap: backtracking out of prefix throws" {
}
test "ImportMap: merge — first-wins on imports, new keys added" {
defer testing.reset();
const base: [:0]const u8 = "https://example.com/app/index.html";
var im = try testParse(
@@ -504,7 +487,6 @@ test "ImportMap: merge — first-wins on imports, new keys added" {
}
test "ImportMap: merge — same-prefix scopes merge their imports" {
defer testing.reset();
const base: [:0]const u8 = "https://example.com/app/index.html";
var im = try testParse(
-15
View File
@@ -810,8 +810,6 @@ fn trimRight(s: []const u8) []const u8 {
const testing = @import("../testing.zig");
test "Mime: invalid" {
defer testing.reset();
const invalids = [_][]const u8{
"",
"text",
@@ -828,7 +826,6 @@ test "Mime: invalid" {
}
test "Mime: malformed parameters are ignored" {
defer testing.reset();
// These should all parse successfully as text/html with malformed params ignored
const valid_with_malformed_params = [_][]const u8{
@@ -851,8 +848,6 @@ test "Mime: malformed parameters are ignored" {
}
test "Mime: parse common" {
defer testing.reset();
try expect(.{ .content_type = .{ .text_xml = {} } }, "text/xml");
try expect(.{ .content_type = .{ .text_html = {} } }, "text/html");
try expect(.{ .content_type = .{ .text_plain = {} } }, "text/plain");
@@ -888,8 +883,6 @@ test "Mime: parse common" {
}
test "Mime: parse uncommon" {
defer testing.reset();
const text_csv = Expectation{
.content_type = .{ .other = {} },
};
@@ -902,8 +895,6 @@ test "Mime: parse uncommon" {
}
test "Mime: parse charset" {
defer testing.reset();
try expect(.{
.content_type = .{ .text_xml = {} },
.charset = "utf-8",
@@ -936,7 +927,6 @@ test "Mime: parse charset" {
}
test "Mime: parse charset (WHATWG parameter semantics)" {
defer testing.reset();
// First charset wins (not last).
try expect(.{ .content_type = .{ .text_html = {} }, .charset = "gbk" }, "text/html;charset=gbk;charset=utf-8");
@@ -959,8 +949,6 @@ test "Mime: parse charset (WHATWG parameter semantics)" {
}
test "Mime: isHTML" {
defer testing.reset();
const assert = struct {
fn assert(expected: bool, input: []const u8) !void {
const mutable_input = try testing.arena_allocator.dupe(u8, input);
@@ -977,8 +965,6 @@ test "Mime: isHTML" {
}
test "Mime: isXML" {
defer testing.reset();
const assert = struct {
fn assert(expected: bool, input: []const u8) !void {
const mutable_input = try testing.arena_allocator.dupe(u8, input);
@@ -1105,7 +1091,6 @@ fn expect(expected: Expectation, input: []const u8) !void {
}
test "Mime: serialize" {
defer testing.reset();
const arena = testing.arena_allocator;
const expectSerialize = struct {
+2 -1
View File
@@ -407,7 +407,8 @@ fn appendFrameExecutions(frame: *Frame, origin: []const u8, arena: Allocator, li
const testing = @import("../testing.zig");
test "Page: js_error_count" {
defer testing.reset();
testing.expectLog(&.{ .js, .js, .js });
// One uncaught top-level script exception, one uncaught timer-callback
// exception.
const page = try testing.pageTest("page_js_error.html", .{});
-1
View File
@@ -474,7 +474,6 @@ test "Runner: waitForSelector timeout" {
}
test "Runner: waitForSelector" {
defer testing.reset();
const page = try testing.pageTest("runner/runner1.html", .{});
var runner = page.session.runner(.{});
-2
View File
@@ -566,7 +566,6 @@ const PreloadedScript = struct {
const testing = @import("../testing.zig");
test "ScriptManager: PreloadedScript.shutdownCallback drops a .loading preload" {
defer testing.reset();
const page = try testing.pageTest("mcp_nav.html", .{});
defer page.close();
@@ -598,7 +597,6 @@ test "ScriptManager: PreloadedScript.shutdownCallback drops a .loading preload"
}
test "ScriptManager: waitForPreload stops when teardown is pending" {
defer testing.reset();
const page = try testing.pageTest("mcp_nav.html", .{});
defer page.close();
-2
View File
@@ -1080,7 +1080,6 @@ pub const ImportedModule = struct {
const testing = @import("../testing.zig");
test "ScriptManagerBase: shutdownCallback fails a .loading module" {
defer testing.reset();
const page = try testing.pageTest("mcp_nav.html", .{});
defer page.close();
const frame = page.frame().?;
@@ -1115,7 +1114,6 @@ test "ScriptManagerBase: shutdownCallback fails a .loading module" {
}
test "ScriptManagerBase: waitForImport stops when teardown is pending" {
defer testing.reset();
const page = try testing.pageTest("mcp_nav.html", .{});
defer page.close();
const frame = page.frame().?;
-2
View File
@@ -213,8 +213,6 @@ fn isFirstMatch(self: SelectorPath, target: *Element, candidate: []const u8) boo
const testing = @import("../testing.zig");
fn expectSelector(comptime selector: []const u8, comptime expected: []const u8) !void {
defer testing.reset();
var page = try testing.pageTest("selector_path.html", .{});
defer page.close();
const frame = page.frame().?;
-17
View File
@@ -692,8 +692,6 @@ test "URL: isCompleteHTTPUrl" {
}
test "URL: resolve regression (#1093)" {
defer testing.reset();
const Case = struct {
base: [:0]const u8,
path: [:0]const u8,
@@ -715,8 +713,6 @@ test "URL: resolve regression (#1093)" {
}
test "URL: resolve" {
defer testing.reset();
const Case = struct {
base: [:0]const u8,
path: [:0]const u8,
@@ -961,8 +957,6 @@ test "URL: resolve" {
}
test "URL: resolve strips tab and newline from input" {
defer testing.reset();
const Case = struct {
base: [:0]const u8,
path: [:0]const u8,
@@ -994,7 +988,6 @@ test "URL: resolve strips tab and newline from input" {
}
test "URL: resolve validates ASCII punycode (xn--) labels" {
defer testing.reset();
// Valid punycode is left untouched.
const ok = try resolve(testing.arena_allocator, "https://example.com/", "https://xn--rksmrgs-5wao1o.se/x", .{});
@@ -1006,8 +999,6 @@ test "URL: resolve validates ASCII punycode (xn--) labels" {
}
test "URL: resolve with encoding" {
defer testing.reset();
const Case = struct {
base: [:0]const u8,
path: [:0]const u8,
@@ -1176,7 +1167,6 @@ test "URL: resolve with encoding" {
}
test "URL: eqlDocument" {
defer testing.reset();
{
const url = "https://lightpanda.io/about";
try testing.expectEqual(true, eqlDocument(url, url));
@@ -1244,7 +1234,6 @@ test "URL: eqlDocument" {
}
test "URL: concatQueryString" {
defer testing.reset();
const arena = testing.arena_allocator;
{
@@ -1274,7 +1263,6 @@ test "URL: concatQueryString" {
}
test "URL: getRobotsUrl" {
defer testing.reset();
const arena = testing.arena_allocator;
{
@@ -1302,7 +1290,6 @@ test "URL: getRobotsUrl" {
}
test "URL: unescape" {
defer testing.reset();
const arena = testing.arena_allocator;
{
@@ -1437,8 +1424,6 @@ test "URL: setPathname percent-encodes" {
}
test "URL: getOrigin" {
defer testing.reset();
const Case = struct {
url: [:0]const u8,
expected: ?[]const u8,
@@ -1680,8 +1665,6 @@ test "URL: resolve path scheme" {
}
test "URL: resolveNavigation defaults a schemeless host to http (curl-like)" {
defer testing.reset();
const Case = struct {
url: [:0]const u8,
expected: [:0]const u8,
-8
View File
@@ -72,28 +72,24 @@ fn base64Decode(arena: Allocator, input: []const u8) ![]const u8 {
const testing = @import("../testing.zig");
test "data_url: plain text, default content-type" {
defer testing.reset();
const r = try parse(testing.arena_allocator, "data:,Hello%2C%20World");
try testing.expectString("text/plain;charset=US-ASCII", r.content_type);
try testing.expectString("Hello, World", r.body);
}
test "data_url: explicit mediatype" {
defer testing.reset();
const r = try parse(testing.arena_allocator, "data:text/html,<b>hi</b>");
try testing.expectString("text/html", r.content_type);
try testing.expectString("<b>hi</b>", r.body);
}
test "data_url: base64" {
defer testing.reset();
const r = try parse(testing.arena_allocator, "data:text/plain;base64,SGVsbG8=");
try testing.expectString("text/plain", r.content_type);
try testing.expectString("Hello", r.body);
}
test "data_url: base64 without padding decodes (forgiving)" {
defer testing.reset();
const r = try parse(testing.arena_allocator, "data:application/octet-stream;base64,SGVsbG8");
try testing.expectString("Hello", r.body);
@@ -106,7 +102,6 @@ test "data_url: base64 without padding decodes (forgiving)" {
}
test "data_url: forgiving-base64 rejects misplaced/over-padding" {
defer testing.reset();
const arena = testing.arena_allocator;
try std.testing.expectError(error.InvalidBase64, parse(arena, "data:;base64,abcd=")); // len % 4 == 1
try std.testing.expectError(error.InvalidBase64, parse(arena, "data:;base64,="));
@@ -115,18 +110,15 @@ test "data_url: forgiving-base64 rejects misplaced/over-padding" {
}
test "data_url: bare charset gets text/plain prefix" {
defer testing.reset();
const r = try parse(testing.arena_allocator, "data:;charset=utf-8,x");
try testing.expectString("text/plain;charset=utf-8", r.content_type);
}
test "data_url: empty body" {
defer testing.reset();
const r = try parse(testing.arena_allocator, "data:text/plain,");
try testing.expectString("", r.body);
}
test "data_url: missing comma is an error" {
defer testing.reset();
try std.testing.expectError(error.InvalidDataUrl, parse(testing.arena_allocator, "data:text/plain"));
}
-1
View File
@@ -382,7 +382,6 @@ const testing = @import("../testing.zig");
// <base> element), so reusing one frame across opts would leak that mutation
// into later dumps.
fn expectDump(opts: Opts, expected: []const u8) !void {
defer testing.reset();
var page = try testing.pageTest("dump.html", .{});
defer page.close();
-11
View File
@@ -288,7 +288,6 @@ fn testForms(html: []const u8) ![]FormInfo {
}
test "browser.forms: login form" {
defer testing.reset();
const forms = try testForms(
\\<form action="/login" method="POST">
\\ <input type="email" name="email" required placeholder="Email">
@@ -310,7 +309,6 @@ test "browser.forms: login form" {
}
test "browser.forms: form with select" {
defer testing.reset();
const forms = try testForms(
\\<form>
\\ <select name="color">
@@ -330,7 +328,6 @@ test "browser.forms: form with select" {
}
test "browser.forms: form with textarea" {
defer testing.reset();
const forms = try testForms(
\\<form method="POST">
\\ <textarea name="message" placeholder="Your message"></textarea>
@@ -345,7 +342,6 @@ test "browser.forms: form with textarea" {
}
test "browser.forms: empty form skipped" {
defer testing.reset();
const forms = try testForms(
\\<form action="/empty">
\\ <p>No fields here</p>
@@ -357,7 +353,6 @@ test "browser.forms: empty form skipped" {
}
test "browser.forms: hidden inputs excluded" {
defer testing.reset();
const forms = try testForms(
\\<form>
\\ <input type="hidden" name="csrf" value="token123">
@@ -372,7 +367,6 @@ test "browser.forms: hidden inputs excluded" {
}
test "browser.forms: multiple forms" {
defer testing.reset();
const forms = try testForms(
\\<form action="/search" method="GET">
\\ <input type="text" name="q" placeholder="Search">
@@ -390,7 +384,6 @@ test "browser.forms: multiple forms" {
}
test "browser.forms: disabled fields flagged" {
defer testing.reset();
const forms = try testForms(
\\<form>
\\ <input type="text" name="enabled_field">
@@ -406,7 +399,6 @@ test "browser.forms: disabled fields flagged" {
}
test "browser.forms: disabled fieldset" {
defer testing.reset();
const forms = try testForms(
\\<form>
\\ <fieldset disabled>
@@ -424,7 +416,6 @@ test "browser.forms: disabled fieldset" {
}
test "browser.forms: external field via form attribute" {
defer testing.reset();
const forms = try testForms(
\\<input type="text" name="external" form="myform">
\\<form id="myform" action="/submit">
@@ -438,7 +429,6 @@ test "browser.forms: external field via form attribute" {
}
test "browser.forms: checkbox and radio return value attribute" {
defer testing.reset();
const forms = try testForms(
\\<form>
\\ <input type="checkbox" name="agree" value="yes" checked>
@@ -456,7 +446,6 @@ test "browser.forms: checkbox and radio return value attribute" {
}
test "browser.forms: form without action or method" {
defer testing.reset();
const forms = try testForms(
\\<form>
\\ <input type="text" name="q">
-2
View File
@@ -155,8 +155,6 @@ fn hasNonRemoteScheme(href: []const u8) bool {
const testing = @import("../../testing.zig");
test "preload: prescan" {
defer testing.reset();
const page = try testing.pageTest("mcp_nav.html", .{});
defer page.close();
const frame = page.frame().?;
+1 -2
View File
@@ -290,8 +290,7 @@ pub const JsApi = struct {
const testing = @import("../../testing.zig");
test "WebApi: EventTarget" {
const filter: testing.LogFilter = .init(&.{ .js, .event });
defer filter.deinit();
testing.silenceLog(&.{ .js, .event });
// we create thousands of these per frame. Nothing should bloat it.
try testing.expectEqual(16, @sizeOf(EventTarget));
+1 -2
View File
@@ -139,7 +139,6 @@ pub const JsApi = struct {
const testing = @import("../../testing.zig");
test "WebApi: SharedWorker" {
const filter: testing.LogFilter = .init(&.{.http});
defer filter.deinit();
testing.silenceLog(&.{.http});
try testing.htmlRunner("shared_worker", .{ .timeout_ms = 8000 });
}
+1 -2
View File
@@ -489,8 +489,7 @@ pub const JsApi = struct {
const testing = @import("../../testing.zig");
test "WebApi: Worker" {
const filter: testing.LogFilter = .init(&.{.http});
defer filter.deinit();
testing.silenceLog(&.{.http});
// Worker tests chain a worker-script fetch with a dynamic-import fetch
// and a cross-context postMessage. The default 2 s assertion budget can
@@ -946,7 +946,6 @@ test "normalizePropertyValue: collapse duplicate two-value shorthands" {
}
test "normalizePropertyValue: anchor() canonical order" {
defer testing.reset();
const cases = .{
// Dashed ident should come before keyword
.{ "left", "anchor(left --foo)", "anchor(--foo left)" },
+1 -2
View File
@@ -216,8 +216,7 @@ pub const JsApi = struct {
const testing = @import("../../../testing.zig");
test "WebApi: CSSStyleSheet" {
const filter: testing.LogFilter = .init(&.{.js});
defer filter.deinit();
testing.silenceLog(&.{.js});
try testing.htmlRunner("css/stylesheet.html", .{});
}
+1
View File
@@ -157,5 +157,6 @@ pub const JsApi = struct {
const testing = @import("../../../testing.zig");
test "WebApi: Svg" {
testing.expectLog(&.{ .not_implemented, .not_implemented });
try testing.htmlRunner("element/svg", .{});
}
+2 -4
View File
@@ -297,8 +297,7 @@ test "WebApi: HTML.Link" {
}
test "WebApi: HTML.Link external stylesheet" {
const filter: testing.LogFilter = .init(&.{.http});
defer filter.deinit();
testing.silenceLog(&.{.http});
try testing.htmlRunner("css/external_stylesheet.html", .{ .load_external_stylesheets = true });
}
@@ -307,7 +306,6 @@ test "WebApi: HTML.Link external stylesheet" {
// request gate during the sync window). Otherwise the deferred-script queue
// never drains and the document is stuck at readyState "loading".
test "WebApi: HTML.Link deferred script then external stylesheet" {
const filter: testing.LogFilter = .init(&.{.http});
defer filter.deinit();
testing.silenceLog(&.{.http});
try testing.htmlRunner("css/deferred_script_then_stylesheet.html", .{ .load_external_stylesheets = true });
}
+2 -2
View File
@@ -194,7 +194,7 @@ pub const Build = struct {
const testing = @import("../../../../testing.zig");
test "WebApi: Script" {
const filter: testing.LogFilter = .init(&.{.http});
defer filter.deinit();
testing.silenceLog(&.{.http});
testing.expectLog(&.{ .js, .js });
try testing.htmlRunner("element/html/script", .{});
}
+2 -4
View File
@@ -650,13 +650,11 @@ pub const JsApi = struct {
const testing = @import("../../../testing.zig");
test "WebApi: EventSource" {
const filter: testing.LogFilter = .init(&.{.http});
defer filter.deinit();
testing.silenceLog(&.{.http});
try testing.htmlRunner("net/eventsource.html", .{});
}
test "WebApi: EventSource in worker" {
const filter: testing.LogFilter = .init(&.{.http});
defer filter.deinit();
testing.silenceLog(&.{.http});
try testing.htmlRunner("net/eventsource_worker.html", .{});
}
+1
View File
@@ -1046,6 +1046,7 @@ pub const JsApi = struct {
const testing = @import("../../../testing.zig");
test "WebApi: WebSocket" {
testing.expectLog(&.{.websocket});
try testing.htmlRunner("net/websocket.html", .{});
}
-4
View File
@@ -136,14 +136,12 @@ pub fn stripUtf8Bom(bytes: []const u8) []const u8 {
const testing = @import("../../../testing.zig");
test "BodyInit: bytes pass through with text/plain" {
defer testing.reset();
const r = try (BodyInit{ .bytes = "hello" }).extract(testing.arena_allocator);
try testing.expectString("hello", r.bytes);
try testing.expectString("text/plain;charset=UTF-8", r.content_type.?);
}
test "BodyInit: URLSearchParams emit urlencoded body + content-type" {
defer testing.reset();
const arena = try testing.test_app.arena_pool.acquire(.small, "body_init test");
defer arena.release();
@@ -158,7 +156,6 @@ test "BodyInit: URLSearchParams emit urlencoded body + content-type" {
}
test "BodyInit: FormData emits multipart with random boundary" {
defer testing.reset();
const arena = try testing.test_app.arena_pool.acquire(.small, "body_init test");
defer arena.release();
@@ -184,7 +181,6 @@ test "BodyInit: FormData emits multipart with random boundary" {
}
test "BodyInit: buffer source has no default Content-Type" {
defer testing.reset();
const r = try (BodyInit{ .buffer = .{ .values = "hello" } }).extract(testing.arena_allocator);
try testing.expectString("hello", r.bytes);
try testing.expectEqual(true, r.content_type == null);
-2
View File
@@ -1666,7 +1666,6 @@ test "Selector: Parser.parseNthPattern" {
}
test "Selector: Parser.attributeValue" {
defer testing.reset();
const arena = testing.arena_allocator;
// Unquoted identifier value (unchanged path).
@@ -1759,7 +1758,6 @@ test "Selector: Parser.attributeValue" {
}
test "Selector: Parser.attributeName" {
defer testing.reset();
const arena = testing.arena_allocator;
// Plain name (fast path).
+2
View File
@@ -1449,6 +1449,8 @@ test "cdp: disconnect latches so the worker keeps exiting" {
}
test "cdp: tick sends a close frame on pending terminate" {
testing.expectLog(&.{.cdp});
var ctx = try testing.context();
defer ctx.deinit();
+4 -8
View File
@@ -188,8 +188,7 @@ test "cdp.Emulation: setUserAgentOverride with valid user agent" {
}
test "cdp.Emulation: setUserAgentOverride ignores mozilla" {
const filter: testing.LogFilter = .init(&.{.not_implemented});
defer filter.deinit();
testing.silenceLog(&.{.not_implemented});
var ctx = try testing.context();
defer ctx.deinit();
@@ -206,8 +205,7 @@ test "cdp.Emulation: setUserAgentOverride ignores mozilla" {
}
test "cdp.Emulation: setUserAgentOverride ignores mozilla case insensitive" {
const filter: testing.LogFilter = .init(&.{.not_implemented});
defer filter.deinit();
testing.silenceLog(&.{.not_implemented});
var ctx = try testing.context();
defer ctx.deinit();
@@ -224,8 +222,7 @@ test "cdp.Emulation: setUserAgentOverride ignores mozilla case insensitive" {
}
test "cdp.Emulation: setUserAgentOverride rejects non-printable characters" {
const filter: testing.LogFilter = .init(&.{.not_implemented});
defer filter.deinit();
testing.silenceLog(&.{.not_implemented});
var ctx = try testing.context();
defer ctx.deinit();
@@ -241,8 +238,7 @@ test "cdp.Emulation: setUserAgentOverride rejects non-printable characters" {
}
test "cdp.Emulation: setUserAgentOverride with optional params" {
const filter: testing.LogFilter = .init(&.{.not_implemented});
defer filter.deinit();
testing.silenceLog(&.{.not_implemented});
var ctx = try testing.context();
defer ctx.deinit();
+5 -10
View File
@@ -652,8 +652,7 @@ test "cdp.network setExtraHTTPHeaders" {
}
test "cdp.network setExtraHTTPHeaders rejects non-printable User-Agent" {
const filter: testing.LogFilter = .init(&.{.not_implemented});
defer filter.deinit();
testing.silenceLog(&.{.not_implemented});
var ctx = try testing.context();
defer ctx.deinit();
@@ -674,8 +673,7 @@ test "cdp.network setExtraHTTPHeaders rejects non-printable User-Agent" {
}
test "cdp.network setExtraHTTPHeaders rejects a Mozilla User-Agent" {
const filter: testing.LogFilter = .init(&.{.not_implemented});
defer filter.deinit();
testing.silenceLog(&.{.not_implemented});
var ctx = try testing.context();
defer ctx.deinit();
@@ -709,8 +707,7 @@ test "cdp.network setExtraHTTPHeaders accepts valid User-Agent" {
}
test "cdp.network setExtraHTTPHeaders rejects a Mozilla User-Agent smuggled via a colon in the key" {
const filter: testing.LogFilter = .init(&.{.not_implemented});
defer filter.deinit();
testing.silenceLog(&.{.not_implemented});
var ctx = try testing.context();
defer ctx.deinit();
@@ -731,8 +728,7 @@ test "cdp.network setExtraHTTPHeaders rejects a Mozilla User-Agent smuggled via
}
test "cdp.network setExtraHTTPHeaders rejects a header that smuggles CRLF" {
const filter: testing.LogFilter = .init(&.{.not_implemented});
defer filter.deinit();
testing.silenceLog(&.{.not_implemented});
var ctx = try testing.context();
defer ctx.deinit();
@@ -989,8 +985,7 @@ test "cdp.Network: configured CDP ignores setCacheDisabled" {
}
test "cdp.Network: setBlockedURLs blocks requests with inspector reason" {
const filter: testing.LogFilter = .init(&.{.http});
defer filter.deinit();
testing.silenceLog(&.{.http});
var ctx = try testing.context();
defer ctx.deinit();
+3 -8
View File
@@ -1215,9 +1215,7 @@ test "cdp.frame: child navigation preserves node registry" {
}
test "cdp.frame: captureScreenshot" {
const LogFilter = @import("../../testing.zig").LogFilter;
const filter: LogFilter = .init(&.{.not_implemented});
defer filter.deinit();
testing.silenceLog(&.{.not_implemented});
var ctx = try testing.context();
defer ctx.deinit();
@@ -1235,9 +1233,7 @@ test "cdp.frame: captureScreenshot" {
}
test "cdp.frame: printToPDF" {
const LogFilter = @import("../../testing.zig").LogFilter;
const filter: LogFilter = .init(&.{.not_implemented});
defer filter.deinit();
testing.silenceLog(&.{.not_implemented});
var ctx = try testing.context();
defer ctx.deinit();
@@ -1536,8 +1532,7 @@ test "cdp.frame: navigate does not follow Location on a non-redirect 3xx" {
}
test "cdp.frame: navigate answers with errorText when the navigation fails" {
const filter: testing.LogFilter = .init(&.{.frame});
defer filter.deinit();
testing.silenceLog(&.{.frame});
// A root navigation that fails before commit (here: connection refused —
// nothing listens on port 1) must still answer the Page.navigate command.
+1 -2
View File
@@ -169,8 +169,7 @@ pub fn consoleMessage(arena: Allocator, bc: *CDP.BrowserContext, event: *const N
const testing = @import("../testing.zig");
test "cdp.runtime: consoleAPICalled type matches the console method" {
const filter: testing.LogFilter = .init(&.{.js});
defer filter.deinit();
testing.silenceLog(&.{.js});
// Wire types per the CDP protocol: console.log -> "log",
// console.warn -> "warning" (not "warn"), console.info -> "info",
+2 -1
View File
@@ -34,7 +34,8 @@ pub const expectError = base.expectError;
pub const expectEqualSlices = base.expectEqualSlices;
pub const pageTest = base.pageTest;
pub const newString = base.newString;
pub const LogFilter = base.LogFilter;
pub const expectLog = base.expectLog;
pub const silenceLog = base.silenceLog;
const TestContext = struct {
read_at: usize = 0,
+30
View File
@@ -99,6 +99,28 @@ pub fn enabled(scope: Scope, level: Level) bool {
return true;
}
// The number of log lines each scope is expected to emit for the current test.
// A line from a scope with a pending count is consumed instead of written; see
// testing.expectLog.
var expected_logs: [num_scopes]u16 = @splat(0);
// Registers one expected log line per entry in `scopes`.
pub fn expectLog(comptime scopes: []const Scope) void {
comptime std.debug.assert(IS_TEST);
inline for (scopes) |scope| {
expected_logs[@intFromEnum(scope)] += 1;
}
}
// Called after each test. Returns the expectations that were never met.
pub fn resetTestState() [num_scopes]u16 {
std.debug.assert(IS_TEST);
const unmet = expected_logs;
expected_logs = @splat(0);
opts.scope_enabled = @splat(true);
return unmet;
}
// Ugliness to support complex debug parameters. Could add better support for
// this directly in writeValue, but we [currently] only need this in one place
// and I kind of don't want to encourage / make this easy.
@@ -151,6 +173,14 @@ pub fn log(scope: Scope, level: Level, msg: []const u8, data: anytype) void {
return;
}
if (comptime IS_TEST) {
const expected = &expected_logs[@intFromEnum(scope)];
if (expected.* > 0) {
expected.* -= 1;
return;
}
}
if (sink) |s| {
var buf: [4096]u8 = undefined;
var w: std.Io.Writer = .fixed(&buf);
-1
View File
@@ -248,7 +248,6 @@ pub fn handleResourceRead(self: *Self, arena: std.mem.Allocator, req: protocol.R
}
test "MCP.Server - Integration: synchronous smoke test" {
defer testing.reset();
const allocator = testing.allocator;
const app = testing.test_app;
-6
View File
@@ -186,7 +186,6 @@ pub const JsonEscapingWriter = struct {
const testing = @import("../testing.zig");
test "MCP.protocol - request parsing" {
defer testing.reset();
const raw_json =
\\{
\\ "jsonrpc": "2.0",
@@ -222,7 +221,6 @@ test "MCP.protocol - request parsing" {
}
test "MCP.protocol - ping request parsing" {
defer testing.reset();
const raw_json =
\\{
\\ "jsonrpc": "2.0",
@@ -243,7 +241,6 @@ test "MCP.protocol - ping request parsing" {
}
test "MCP.protocol - response formatting" {
defer testing.reset();
const response = Response{
.id = .{ .integer = 42 },
.result = .{ .string = "success" },
@@ -257,7 +254,6 @@ test "MCP.protocol - response formatting" {
}
test "MCP.protocol - error formatting" {
defer testing.reset();
const response = Response{
.id = .{ .string = "abc" },
.@"error" = .{
@@ -274,7 +270,6 @@ test "MCP.protocol - error formatting" {
}
test "MCP.protocol - JsonEscapingWriter" {
defer testing.reset();
var aw: std.Io.Writer.Allocating = .init(testing.arena_allocator);
defer aw.deinit();
@@ -288,7 +283,6 @@ test "MCP.protocol - JsonEscapingWriter" {
}
test "MCP.protocol - Tool serialization" {
defer testing.reset();
const t = Tool{
.name = "test",
.inputSchema = minify(
+1 -3
View File
@@ -123,7 +123,6 @@ const Server = @import("Server.zig");
const testing = @import("../testing.zig");
test "MCP.router - handleMessage - synchronous unit tests" {
defer testing.reset();
const allocator = testing.allocator;
const app = testing.test_app;
@@ -168,8 +167,7 @@ test "MCP.router - handleMessage - synchronous unit tests" {
// 5. Parse error
{
const filter: testing.LogFilter = .init(&.{.mcp});
defer filter.deinit();
testing.expectLog(&.{.mcp});
try handleMessage(server, aa, "invalid json");
try testing.expectJson("{\"jsonrpc\": \"2.0\", \"id\": null, \"error\": {\"code\": -32700}}", out_alloc.writer.buffered());
-37
View File
@@ -271,7 +271,6 @@ const router = @import("router.zig");
const testing = @import("../testing.zig");
test "MCP - evaluate error reporting" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -300,7 +299,6 @@ test "MCP - evaluate error reporting" {
}
test "MCP - evaluate: top-level return runs in an async wrapper" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -324,7 +322,6 @@ test "MCP - evaluate: top-level return runs in an async wrapper" {
}
test "MCP - evaluate: top-level await runs in an async wrapper" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -348,7 +345,6 @@ test "MCP - evaluate: top-level await runs in an async wrapper" {
}
test "MCP - evaluate: let declaration does not leak across calls" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -386,7 +382,6 @@ test "MCP - evaluate: let declaration does not leak across calls" {
}
test "MCP - evaluate: bare expression still returns its value" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -410,7 +405,6 @@ test "MCP - evaluate: bare expression still returns its value" {
}
test "MCP - evaluate: object return serializes as JSON" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -434,7 +428,6 @@ test "MCP - evaluate: object return serializes as JSON" {
}
test "MCP - evaluate: localStorage persists across navigations and is origin-scoped" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("http://localhost:9582/src/browser/tests/mcp_actions.html", &out.writer);
defer server.deinit();
@@ -524,7 +517,6 @@ test "MCP - evaluate: localStorage persists across navigations and is origin-sco
}
test "MCP - evaluate: save= value is readable via lp.<name> in next evaluate" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -561,7 +553,6 @@ test "MCP - evaluate: save= value is readable via lp.<name> in next evaluate" {
}
test "MCP - evaluate: save= a bare string round-trips without JSON.stringify" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -598,7 +589,6 @@ test "MCP - evaluate: save= a bare string round-trips without JSON.stringify" {
}
test "MCP - evaluate: lp.* mutations auto-sync between evaluates" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -635,7 +625,6 @@ test "MCP - evaluate: lp.* mutations auto-sync between evaluates" {
}
test "MCP - evaluate: lp.* survives navigation" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("http://localhost:9582/src/browser/tests/mcp_actions.html", &out.writer);
defer server.deinit();
@@ -686,7 +675,6 @@ test "MCP - evaluate: lp.* survives navigation" {
}
test "MCP - evaluate: delete lp.<key> removes from bridge store" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -737,7 +725,6 @@ test "MCP - evaluate: delete lp.<key> removes from bridge store" {
}
test "MCP - extract: save= exposes the result as lp.<name>" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("http://localhost:9582/src/browser/tests/mcp_actions.html", &out.writer);
defer server.deinit();
@@ -777,7 +764,6 @@ test "MCP - extract: save= exposes the result as lp.<name>" {
}
test "MCP - evaluate: Promise.resolve return value is awaited" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -800,7 +786,6 @@ test "MCP - evaluate: Promise.resolve return value is awaited" {
}
test "MCP - evaluate: async IIFE resolves to returned value" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -823,7 +808,6 @@ test "MCP - evaluate: async IIFE resolves to returned value" {
}
test "MCP - evaluate: rejected Promise surfaces as is_error" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -845,7 +829,6 @@ test "MCP - evaluate: rejected Promise surfaces as is_error" {
}
test "MCP - evaluate: async IIFE without explicit return resolves to empty text" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -868,7 +851,6 @@ test "MCP - evaluate: async IIFE without explicit return resolves to empty text"
}
test "MCP - evaluate: lp.* mutations inside async IIFE survive to the next evaluate" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -905,7 +887,6 @@ test "MCP - evaluate: lp.* mutations inside async IIFE survive to the next evalu
}
test "MCP - save rejects unsafe path" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -918,7 +899,6 @@ test "MCP - save rejects unsafe path" {
}
test "MCP - save writes the script to disk" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -938,7 +918,6 @@ test "MCP - save writes the script to disk" {
}
test "MCP - tree rejects stale backendNodeId instead of dumping whole document" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -952,7 +931,6 @@ test "MCP - tree rejects stale backendNodeId instead of dumping whole document"
}
test "MCP - PascalCase argument keys from LLMs are normalized to canonical" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("http://localhost:9582/src/browser/tests/mcp_actions.html", &out.writer);
defer server.deinit();
@@ -967,7 +945,6 @@ test "MCP - PascalCase argument keys from LLMs are normalized to canonical" {
}
test "MCP - Actions: click, fill, scroll, hover, press, selectOption, setChecked" {
defer testing.reset();
const aa = testing.arena_allocator;
var out: std.Io.Writer.Allocating = .init(aa);
@@ -1105,7 +1082,6 @@ test "MCP - Actions: click, fill, scroll, hover, press, selectOption, setChecked
// left the registry intact, and a second click on the same id dereferenced
// a freed DOMNode.
test "MCP - click that navigates clears node registry" {
defer testing.reset();
const aa = testing.arena_allocator;
var out: std.Io.Writer.Allocating = .init(aa);
@@ -1131,7 +1107,6 @@ test "MCP - click that navigates clears node registry" {
}
test "MCP - Actions by selector: hover, selectOption, setChecked" {
defer testing.reset();
const aa = testing.arena_allocator;
var out: std.Io.Writer.Allocating = .init(aa);
@@ -1200,7 +1175,6 @@ test "MCP - Actions by selector: hover, selectOption, setChecked" {
}
test "MCP - findElement" {
defer testing.reset();
const aa = testing.arena_allocator;
var out: std.Io.Writer.Allocating = .init(aa);
@@ -1245,7 +1219,6 @@ test "MCP - findElement" {
}
test "MCP - waitForSelector: existing element" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage(
"http://localhost:9582/src/browser/tests/mcp_wait_for_selector.html",
@@ -1263,7 +1236,6 @@ test "MCP - waitForSelector: existing element" {
}
test "MCP - waitForSelector: delayed element" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage(
"http://localhost:9582/src/browser/tests/mcp_wait_for_selector.html",
@@ -1281,7 +1253,6 @@ test "MCP - waitForSelector: delayed element" {
}
test "MCP - waitForSelector: timeout" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage(
"http://localhost:9582/src/browser/tests/mcp_wait_for_selector.html",
@@ -1302,7 +1273,6 @@ test "MCP - waitForSelector: timeout" {
}
test "MCP - markdown: full page, selector scope, maxBytes truncation" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("http://localhost:9582/src/browser/tests/mcp_actions.html", &out.writer);
defer server.deinit();
@@ -1331,7 +1301,6 @@ test "MCP - markdown: full page, selector scope, maxBytes truncation" {
}
test "MCP - html: full document, selector subtree, backendNodeId subtree" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("http://localhost:9582/src/browser/tests/mcp_press_form.html", &out.writer);
defer server.deinit();
@@ -1357,7 +1326,6 @@ test "MCP - html: full document, selector subtree, backendNodeId subtree" {
}
test "MCP - waitForScript: truthy returns, falsy times out" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -1377,7 +1345,6 @@ test "MCP - waitForScript: truthy returns, falsy times out" {
}
test "MCP - press Enter on form input triggers submit (lowercase alias)" {
defer testing.reset();
const aa = testing.arena_allocator;
var out: std.Io.Writer.Allocating = .init(aa);
const server = try testLoadPage("http://localhost:9582/src/browser/tests/mcp_press_form.html", &out.writer);
@@ -1399,7 +1366,6 @@ test "MCP - press Enter on form input triggers submit (lowercase alias)" {
}
test "MCP - getCookies: defaults to current page, url filter, all flag" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("http://localhost:9582/src/browser/tests/mcp_press_form.htm", &out.writer);
defer server.deinit();
@@ -1439,7 +1405,6 @@ test "MCP - getCookies: defaults to current page, url filter, all flag" {
}
test "MCP - getCookies without a loaded page refuses instead of dumping the jar" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
var server = try Server.init(testing.allocator, testing.test_app, &out.writer);
defer server.deinit();
@@ -1456,7 +1421,6 @@ test "MCP - getCookies without a loaded page refuses instead of dumping the jar"
}
test "MCP - waitForState with bad state surfaces rich error" {
defer testing.reset();
var out: std.Io.Writer.Allocating = .init(testing.arena_allocator);
const server = try testLoadPage("about:blank", &out.writer);
defer server.deinit();
@@ -1472,7 +1436,6 @@ test "MCP - waitForState with bad state surfaces rich error" {
}
test "MCP - sessions: new, list, attach isolation, close" {
defer testing.reset();
const aa = testing.arena_allocator;
var out: std.Io.Writer.Allocating = .init(aa);
var server = try Server.init(testing.allocator, testing.test_app, &out.writer);
+1 -3
View File
@@ -554,9 +554,7 @@ test "FsCache: put override" {
}
test "FsCache: garbage file" {
const LogFilter = @import("../../testing.zig").LogFilter;
const filter: LogFilter = .init(&.{.cache});
defer filter.deinit();
@import("../../testing.zig").silenceLog(&.{.cache});
var setup = try setupCache();
defer {
+1 -2
View File
@@ -1029,8 +1029,7 @@ test "Headers.set adds a new header and preserves defaults" {
}
test "opensocketCallback: private IPv4 returns CURL_SOCKET_BAD" {
const lf: testing.LogFilter = .init(&.{.http});
defer lf.deinit();
testing.silenceLog(&.{.http});
const filter = IpFilter.init(true, null);
var sa = makeSockAddrV4(.{ 127, 0, 0, 1 });
+1 -29
View File
@@ -997,7 +997,6 @@ fn terminateRuntimeSoon(runtime: *Runtime) void {
}
test "agent script runtime: goto and evaluate dispatch through browser tools" {
defer testing.reset();
defer testing.test_session.closeAllPages();
var registry = CDPNode.Registry.init(testing.allocator);
@@ -1019,8 +1018,6 @@ test "agent script runtime: goto and evaluate dispatch through browser tools" {
}
test "agent script runtime: Page must be called with new" {
defer testing.reset();
var registry = CDPNode.Registry.init(testing.allocator);
defer registry.deinit();
@@ -1032,8 +1029,6 @@ test "agent script runtime: Page must be called with new" {
}
test "agent script runtime: a method on an un-navigated page errors" {
defer testing.reset();
var registry = CDPNode.Registry.init(testing.allocator);
defer registry.deinit();
@@ -1048,7 +1043,6 @@ test "agent script runtime: a method on an un-navigated page errors" {
}
test "agent script runtime: page.close stales the handle" {
defer testing.reset();
defer testing.test_session.closeAllPages();
var registry = CDPNode.Registry.init(testing.allocator);
@@ -1069,7 +1063,6 @@ test "agent script runtime: page.close stales the handle" {
}
test "agent script runtime: parallel gotos coexist and route per page" {
defer testing.reset();
defer testing.test_session.closeAllPages();
var registry = CDPNode.Registry.init(testing.allocator);
@@ -1094,7 +1087,6 @@ test "agent script runtime: parallel gotos coexist and route per page" {
}
test "agent script runtime: goto resolves $LP_* placeholders" {
defer testing.reset();
defer testing.test_session.closeAllPages();
var registry = CDPNode.Registry.init(testing.allocator);
@@ -1118,7 +1110,6 @@ extern fn setenv(name: [*:0]u8, value: [*:0]u8, override: c_int) c_int;
extern fn unsetenv(name: [*:0]u8) c_int;
test "agent script runtime: goto with invalid arguments rejects instead of crashing" {
defer testing.reset();
defer testing.test_session.closeAllPages();
var registry = CDPNode.Registry.init(testing.allocator);
@@ -1138,7 +1129,6 @@ test "agent script runtime: goto with invalid arguments rejects instead of crash
}
test "agent script runtime: a tool-triggered navigation keeps the handle routable" {
defer testing.reset();
defer testing.test_session.closeAllPages();
var registry = CDPNode.Registry.init(testing.allocator);
@@ -1159,7 +1149,6 @@ test "agent script runtime: a tool-triggered navigation keeps the handle routabl
}
test "agent script runtime: re-goto on the same page object replaces its page" {
defer testing.reset();
defer testing.test_session.closeAllPages();
var registry = CDPNode.Registry.init(testing.allocator);
@@ -1178,10 +1167,8 @@ test "agent script runtime: re-goto on the same page object replaces its page" {
}
test "agent script runtime: a failed navigation rejects the goto promise" {
const filter: testing.LogFilter = .init(&.{.frame});
defer filter.deinit();
testing.silenceLog(&.{.frame});
defer testing.reset();
defer testing.test_session.closeAllPages();
var registry = CDPNode.Registry.init(testing.allocator);
@@ -1203,7 +1190,6 @@ test "agent script runtime: a failed navigation rejects the goto promise" {
}
test "agent script runtime: extract returns a JavaScript object" {
defer testing.reset();
defer testing.test_session.closeAllPages();
var registry = CDPNode.Registry.init(testing.allocator);
@@ -1258,7 +1244,6 @@ test "agent script runtime: extract returns a JavaScript object" {
}
test "agent script runtime: extract tolerates list selectors that match nothing" {
defer testing.reset();
defer testing.test_session.closeAllPages();
var registry = CDPNode.Registry.init(testing.allocator);
@@ -1287,7 +1272,6 @@ test "agent script runtime: extract tolerates list selectors that match nothing"
}
test "agent script runtime: strict-mode scripts can call primitives" {
defer testing.reset();
defer testing.test_session.closeAllPages();
var registry = CDPNode.Registry.init(testing.allocator);
@@ -1306,8 +1290,6 @@ test "agent script runtime: strict-mode scripts can call primitives" {
}
test "agent script runtime: promise microtasks run to completion" {
defer testing.reset();
var registry = CDPNode.Registry.init(testing.allocator);
defer registry.deinit();
@@ -1326,7 +1308,6 @@ test "agent script runtime: promise microtasks run to completion" {
}
test "agent script runtime: primitives re-entered from argument callbacks stay isolated" {
defer testing.reset();
defer testing.test_session.closeAllPages();
var registry = CDPNode.Registry.init(testing.allocator);
@@ -1354,7 +1335,6 @@ test "agent script runtime: primitives re-entered from argument callbacks stay i
}
test "agent script runtime: terminate interrupts local JavaScript" {
defer testing.reset();
defer testing.test_session.closeAllPages();
var registry = CDPNode.Registry.init(testing.allocator);
@@ -1372,7 +1352,6 @@ test "agent script runtime: terminate interrupts local JavaScript" {
}
test "agent script runtime: agent variables persist and page globals are isolated" {
defer testing.reset();
defer testing.test_session.closeAllPages();
var registry = CDPNode.Registry.init(testing.allocator);
@@ -1397,7 +1376,6 @@ test "agent script runtime: agent variables persist and page globals are isolate
}
test "agent script runtime: page evaluate cannot see agent primitives or bindings" {
defer testing.reset();
defer testing.test_session.closeAllPages();
var registry = CDPNode.Registry.init(testing.allocator);
@@ -1417,8 +1395,6 @@ test "agent script runtime: page evaluate cannot see agent primitives or binding
}
test "agent script runtime: console is available in agent context" {
defer testing.reset();
var registry = CDPNode.Registry.init(testing.allocator);
defer registry.deinit();
@@ -1438,7 +1414,6 @@ test "agent script runtime: console is available in agent context" {
}
test "agent script runtime: tool errors throw and stop execution" {
defer testing.reset();
defer testing.test_session.closeAllPages();
var registry = CDPNode.Registry.init(testing.allocator);
@@ -1465,7 +1440,6 @@ test "agent script runtime: tool errors throw and stop execution" {
}
test "agent script runtime: builtin argument marshalling (positional + options)" {
defer testing.reset();
defer testing.test_session.closeAllPages();
var registry = CDPNode.Registry.init(testing.allocator);
@@ -1524,8 +1498,6 @@ test "agent script runtime: builtin argument marshalling (positional + options)"
}
test "agent script runtime: top-level await runs in an async wrapper" {
defer testing.reset();
var registry = CDPNode.Registry.init(testing.allocator);
defer registry.deinit();
+26 -3
View File
@@ -90,7 +90,11 @@ const Runner = struct {
Printer.fmt("\r\x1b[0K", .{}); // beginning of line and clear to end of line
var after_each: ?std.builtin.TestFn = null;
for (builtin.test_functions) |t| {
if (isAfterEach(t)) {
after_each = t;
}
if (isSetup(t)) {
t.func() catch |err| {
Printer.status(.fail, "\nsetup \"{s}\" failed: {}\n", .{ t.name, err });
@@ -104,7 +108,7 @@ const Runner = struct {
const webapi_html_test_mode = self.env.filter == null and self.env.subfilter != null;
for (builtin.test_functions) |t| {
if (isSetup(t) or isTeardown(t)) {
if (isSetup(t) or isTeardown(t) or isAfterEach(t)) {
continue;
}
@@ -145,7 +149,16 @@ const Runner = struct {
current_test = friendly_name;
std.testing.allocator_instance = .{};
const result = t.func();
var result = t.func();
if (after_each) |ae| {
// always runs, so that it can reset state, but it can only
// turn a passing test into a failing one.
ae.func() catch |err| {
if (result) |_| {
result = err;
} else |_| {}
};
}
current_test = null;
if (webapi_html_test_mode and self.subtests.items.len == 0) {
@@ -256,6 +269,12 @@ const Runner = struct {
}
};
// When only part of a test runs, expectations about what the whole test logs
// can't be enforced.
pub fn hasSubfilter() bool {
return RUNNER.env.subfilter != null;
}
pub fn shouldRun(name: []const u8) bool {
const sf = RUNNER.env.subfilter orelse return true;
return std.mem.indexOf(u8, name, sf) != null;
@@ -384,7 +403,7 @@ const Env = struct {
.filter = filter,
.subfilter = subfilter,
.metrics = readEnvBool(map, "METRICS", false),
.verbose = readEnvBool(map, "TEST_VERBOSE", true),
.verbose = readEnvBool(map, "TEST_VERBOSE", false),
.fail_first = readEnvBool(map, "TEST_FAIL_FIRST", false),
};
}
@@ -444,6 +463,10 @@ fn isTeardown(t: std.builtin.TestFn) bool {
return std.mem.endsWith(u8, t.name, "tests:afterAll");
}
fn isAfterEach(t: std.builtin.TestFn) bool {
return std.mem.endsWith(u8, t.name, "tests:afterEach");
}
pub const TrackingAllocator = struct {
parent_allocator: Allocator,
free_count: usize = 0,
+29 -16
View File
@@ -1042,24 +1042,37 @@ fn testHTTPHandler(req: *std.http.Server.Request) !void {
unreachable;
}
/// LogFilter provides a scoped way to suppress specific log categories during tests.
/// This is useful for tests that trigger expected errors or warnings.
pub const LogFilter = struct {
old_filter: [log.num_scopes]bool,
/// Declares the log lines a test expects to emit: one entry per line, so
/// `&.{ .js, .js, .http }` covers two `js` lines and one `http` line.
pub const expectLog = log.expectLog;
/// Sets the log filter to suppress the specified scope(s).
/// Returns a LogFilter that should be deinitialized to restore previous filters.
pub fn init(comptime scopes: []const log.Scope) LogFilter {
comptime std.debug.assert(@TypeOf(scopes) == []const log.Scope);
const old_filter = log.opts.scope_enabled;
inline for (scopes) |scope| {
log.opts.scope_enabled[@intFromEnum(scope)] = false;
/// Suppresses every line from `scopes` for the rest of the test.
pub fn silenceLog(comptime scopes: []const log.Scope) void {
inline for (scopes) |scope| {
log.opts.scope_enabled[@intFromEnum(scope)] = false;
}
}
test "tests:afterEach" {
defer reset();
const unmet = log.resetTestState();
if (@import("root").hasSubfilter()) {
// only part of the test ran, so expectations about what it logs
// don't hold.
return;
}
var failed = false;
for (unmet, 0..) |count, i| {
if (count == 0) {
continue;
}
return .{ .old_filter = old_filter };
failed = true;
const scope: log.Scope = @enumFromInt(i);
std.debug.print("expected {d} more {s} log line(s)\n", .{ count, @tagName(scope) });
}
/// Restores the log filters to their previous state.
pub fn deinit(self: LogFilter) void {
log.opts.scope_enabled = self.old_filter;
if (failed) {
return error.UnmetLogExpectation;
}
};
}