mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
remove WPT specific code
Using both lightpanda-io/wpt and lightpanda-io/demo/wptrunner remove the need for code specific to run WPT from browser.
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
|||||||
[submodule "tests/wpt"]
|
|
||||||
path = tests/wpt
|
|
||||||
url = https://github.com/lightpanda-io/wpt
|
|
||||||
11
Makefile
11
Makefile
@@ -47,7 +47,7 @@ help:
|
|||||||
|
|
||||||
# $(ZIG) commands
|
# $(ZIG) commands
|
||||||
# ------------
|
# ------------
|
||||||
.PHONY: build build-v8-snapshot build-dev run run-release shell test bench wpt data end2end
|
.PHONY: build build-v8-snapshot build-dev run run-release shell test bench data end2end
|
||||||
|
|
||||||
## Build v8 snapshot
|
## Build v8 snapshot
|
||||||
build-v8-snapshot:
|
build-v8-snapshot:
|
||||||
@@ -82,15 +82,6 @@ shell:
|
|||||||
@printf "\033[36mBuilding shell...\033[0m\n"
|
@printf "\033[36mBuilding shell...\033[0m\n"
|
||||||
@$(ZIG) build shell || (printf "\033[33mBuild ERROR\033[0m\n"; exit 1;)
|
@$(ZIG) build shell || (printf "\033[33mBuild ERROR\033[0m\n"; exit 1;)
|
||||||
|
|
||||||
## Run WPT tests
|
|
||||||
wpt:
|
|
||||||
@printf "\033[36mBuilding wpt...\033[0m\n"
|
|
||||||
@$(ZIG) build wpt -- $(filter-out $@,$(MAKECMDGOALS)) || (printf "\033[33mBuild ERROR\033[0m\n"; exit 1;)
|
|
||||||
|
|
||||||
wpt-summary:
|
|
||||||
@printf "\033[36mBuilding wpt...\033[0m\n"
|
|
||||||
@$(ZIG) build wpt -- --summary $(filter-out $@,$(MAKECMDGOALS)) || (printf "\033[33mBuild ERROR\033[0m\n"; exit 1;)
|
|
||||||
|
|
||||||
## Test - `grep` is used to filter out the huge compile command on build
|
## Test - `grep` is used to filter out the huge compile command on build
|
||||||
ifeq ($(OS), macos)
|
ifeq ($(OS), macos)
|
||||||
test:
|
test:
|
||||||
|
|||||||
64
README.md
64
README.md
@@ -281,35 +281,75 @@ make end2end
|
|||||||
Lightpanda is tested against the standardized [Web Platform
|
Lightpanda is tested against the standardized [Web Platform
|
||||||
Tests](https://web-platform-tests.org/).
|
Tests](https://web-platform-tests.org/).
|
||||||
|
|
||||||
The relevant tests cases are committed in a [dedicated repository](https://github.com/lightpanda-io/wpt) which is fetched by the `make install-submodule` command.
|
We use [a fork](https://github.com/lightpanda-io/wpt/tree/fork) including a custom
|
||||||
|
[`testharnessreport.js`](https://github.com/lightpanda-io/wpt/commit/01a3115c076a3ad0c84849dbbf77a6e3d199c56f).
|
||||||
All the tests cases executed are located in the `tests/wpt` sub-directory.
|
|
||||||
|
|
||||||
For reference, you can easily execute a WPT test case with your browser via
|
For reference, you can easily execute a WPT test case with your browser via
|
||||||
[wpt.live](https://wpt.live).
|
[wpt.live](https://wpt.live).
|
||||||
|
|
||||||
|
#### Configure WPT HTTP server
|
||||||
|
|
||||||
|
To run the test, you must clone the repository, configure the custom hosts and generate the
|
||||||
|
`MANIFEST.json` file.
|
||||||
|
|
||||||
|
Clone the repository with the `fork` branch.
|
||||||
|
```
|
||||||
|
git clone -b fork --depth=1 git@github.com:lightpanda-io/wpt.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Enter into the `wpt/` dir.
|
||||||
|
|
||||||
|
Install custom domains in your `/etc/hosts`
|
||||||
|
```
|
||||||
|
./wpt make-hosts-file | sudo tee -a /etc/hosts
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate `MANIFEST.json`
|
||||||
|
```
|
||||||
|
./wpt manifest
|
||||||
|
```
|
||||||
|
Use the [WPT's setup
|
||||||
|
guide](https://web-platform-tests.org/running-tests/from-local-system.html) for
|
||||||
|
details.
|
||||||
|
|
||||||
#### Run WPT test suite
|
#### Run WPT test suite
|
||||||
|
|
||||||
To run all the tests:
|
An external [Go](https://go.dev) runner is provided by
|
||||||
|
[github.com/lightpanda-io/demo/](https://github.com/lightpanda-io/demo/)
|
||||||
|
repository, located into `wptrunner/` dir.
|
||||||
|
You need to clone the project first.
|
||||||
|
|
||||||
|
First start the WPT's HTTP server from your `wpt/` clone dir.
|
||||||
|
```
|
||||||
|
./wpt serve
|
||||||
|
```
|
||||||
|
|
||||||
|
Run a Lightpanda browser
|
||||||
|
|
||||||
```
|
```
|
||||||
make wpt
|
zig build run
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can start the wptrunner from the Demo's clone dir:
|
||||||
|
```
|
||||||
|
cd wptrunner && go run .
|
||||||
```
|
```
|
||||||
|
|
||||||
Or one specific test:
|
Or one specific test:
|
||||||
|
|
||||||
```
|
```
|
||||||
make wpt Node-childNodes.html
|
cd wptrunner && go run . Node-childNodes.html
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Add a new WPT test case
|
`wptrunner` command accepts `--summary` and `--json` options modifying output.
|
||||||
|
Also `--concurrency` define the concurrency limit.
|
||||||
|
|
||||||
We add new relevant tests cases files when we implemented changes in Lightpanda.
|
:warning: Running the whole test suite will take a long time. In this case,
|
||||||
|
it's useful to build in `releaseFast` mode to make tests faster.
|
||||||
|
|
||||||
To add a new test, copy the file you want from the [WPT
|
```
|
||||||
repo](https://github.com/web-platform-tests/wpt) into the `tests/wpt` directory.
|
zig build -Doptimize=ReleaseFast run
|
||||||
|
```
|
||||||
:warning: Please keep the original directory tree structure of `tests/wpt`.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
26
build.zig
26
build.zig
@@ -146,32 +146,6 @@ pub fn build(b: *Build) !void {
|
|||||||
const run_step = b.step("legacy_test", "Run the app");
|
const run_step = b.step("legacy_test", "Run the app");
|
||||||
run_step.dependOn(&run_cmd.step);
|
run_step.dependOn(&run_cmd.step);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
// wpt
|
|
||||||
const exe = b.addExecutable(.{
|
|
||||||
.name = "lightpanda-wpt",
|
|
||||||
.use_llvm = true,
|
|
||||||
.root_module = b.createModule(.{
|
|
||||||
.root_source_file = b.path("src/main_wpt.zig"),
|
|
||||||
.target = target,
|
|
||||||
.optimize = optimize,
|
|
||||||
.sanitize_c = enable_csan,
|
|
||||||
.sanitize_thread = enable_tsan,
|
|
||||||
.imports = &.{
|
|
||||||
.{ .name = "lightpanda", .module = lightpanda_module },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
b.installArtifact(exe);
|
|
||||||
|
|
||||||
const run_cmd = b.addRunArtifact(exe);
|
|
||||||
if (b.args) |args| {
|
|
||||||
run_cmd.addArgs(args);
|
|
||||||
}
|
|
||||||
const run_step = b.step("wpt", "Run WPT tests");
|
|
||||||
run_step.dependOn(&run_cmd.step);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn linkV8(
|
fn linkV8(
|
||||||
|
|||||||
616
src/main_wpt.zig
616
src/main_wpt.zig
@@ -1,616 +0,0 @@
|
|||||||
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
|
||||||
//
|
|
||||||
// Francis Bouvier <francis@lightpanda.io>
|
|
||||||
// Pierre Tachoire <pierre@lightpanda.io>
|
|
||||||
//
|
|
||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
const lp = @import("lightpanda");
|
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
|
||||||
|
|
||||||
const WPT_DIR = "tests/wpt";
|
|
||||||
|
|
||||||
// use in custom panic handler
|
|
||||||
var current_test: ?[]const u8 = null;
|
|
||||||
|
|
||||||
pub fn main() !void {
|
|
||||||
var gpa: std.heap.DebugAllocator(.{}) = .init;
|
|
||||||
defer _ = gpa.deinit();
|
|
||||||
|
|
||||||
const allocator = gpa.allocator();
|
|
||||||
|
|
||||||
var http_server = try TestHTTPServer.init();
|
|
||||||
defer http_server.deinit();
|
|
||||||
|
|
||||||
{
|
|
||||||
var wg: std.Thread.WaitGroup = .{};
|
|
||||||
wg.startMany(1);
|
|
||||||
var thrd = try std.Thread.spawn(.{}, TestHTTPServer.run, .{ &http_server, &wg });
|
|
||||||
thrd.detach();
|
|
||||||
wg.wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
// An arena for the runner itself, lives for the duration of the the process
|
|
||||||
var ra = ArenaAllocator.init(allocator);
|
|
||||||
defer ra.deinit();
|
|
||||||
const runner_arena = ra.allocator();
|
|
||||||
|
|
||||||
const cmd = try parseArgs(runner_arena);
|
|
||||||
|
|
||||||
var it = try TestIterator.init(allocator, WPT_DIR, cmd.filters);
|
|
||||||
defer it.deinit();
|
|
||||||
|
|
||||||
var writer = try Writer.init(allocator, cmd.format);
|
|
||||||
defer writer.deinit();
|
|
||||||
|
|
||||||
lp.log.opts.level = .warn;
|
|
||||||
const config = try lp.Config.init(allocator, "lightpanda-wpt", .{ .serve = .{
|
|
||||||
.common = .{
|
|
||||||
.tls_verify_host = false,
|
|
||||||
.user_agent_suffix = "internal-tester",
|
|
||||||
},
|
|
||||||
} });
|
|
||||||
defer config.deinit(allocator);
|
|
||||||
|
|
||||||
var app = try lp.App.init(allocator, &config);
|
|
||||||
defer app.deinit();
|
|
||||||
|
|
||||||
const http_client = try app.http.createClient(allocator);
|
|
||||||
defer http_client.deinit();
|
|
||||||
|
|
||||||
var browser = try lp.Browser.init(app, .{ .http_client = http_client });
|
|
||||||
defer browser.deinit();
|
|
||||||
|
|
||||||
// An arena for running each tests. Is reset after every test.
|
|
||||||
var test_arena = ArenaAllocator.init(allocator);
|
|
||||||
defer test_arena.deinit();
|
|
||||||
|
|
||||||
var i: usize = 0;
|
|
||||||
while (try it.next()) |test_file| {
|
|
||||||
defer _ = test_arena.reset(.retain_capacity);
|
|
||||||
|
|
||||||
defer current_test = null;
|
|
||||||
current_test = test_file;
|
|
||||||
|
|
||||||
var err_out: ?[]const u8 = null;
|
|
||||||
const result = run(
|
|
||||||
test_arena.allocator(),
|
|
||||||
&browser,
|
|
||||||
test_file,
|
|
||||||
&err_out,
|
|
||||||
) catch |err| blk: {
|
|
||||||
if (err_out == null) {
|
|
||||||
err_out = @errorName(err);
|
|
||||||
}
|
|
||||||
break :blk null;
|
|
||||||
};
|
|
||||||
try writer.process(test_file, result, err_out);
|
|
||||||
// if (@mod(i, 10) == 0) {
|
|
||||||
// std.debug.print("\n\n=== V8 Memory {d}===\n", .{i});
|
|
||||||
// browser.env.dumpMemoryStats();
|
|
||||||
// }
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
try writer.finalize();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
arena: Allocator,
|
|
||||||
browser: *lp.Browser,
|
|
||||||
test_file: []const u8,
|
|
||||||
err_out: *?[]const u8,
|
|
||||||
) ![]const u8 {
|
|
||||||
const notification = try lp.Notification.init(browser.allocator);
|
|
||||||
defer notification.deinit();
|
|
||||||
|
|
||||||
const session = try browser.newSession(notification);
|
|
||||||
defer browser.closeSession();
|
|
||||||
|
|
||||||
const page = try session.createPage();
|
|
||||||
defer session.removePage();
|
|
||||||
|
|
||||||
const url = try std.fmt.allocPrintSentinel(arena, "http://localhost:9582/{s}", .{test_file}, 0);
|
|
||||||
try page.navigate(url, .{});
|
|
||||||
|
|
||||||
_ = session.wait(2000);
|
|
||||||
|
|
||||||
var ls: lp.js.Local.Scope = undefined;
|
|
||||||
page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
|
|
||||||
var try_catch: lp.js.TryCatch = undefined;
|
|
||||||
try_catch.init(&ls.local);
|
|
||||||
defer try_catch.deinit();
|
|
||||||
|
|
||||||
// Check the final test status.
|
|
||||||
ls.local.eval("report.status", "teststatus") catch |err| {
|
|
||||||
const caught = try_catch.caughtOrError(arena, err);
|
|
||||||
err_out.* = caught.exception;
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
|
|
||||||
// return the detailed result.
|
|
||||||
const value = ls.local.exec("report.log", "report") catch |err| {
|
|
||||||
const caught = try_catch.caughtOrError(arena, err);
|
|
||||||
err_out.* = caught.exception;
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
|
|
||||||
return value.toStringSliceWithAlloc(arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Writer = struct {
|
|
||||||
format: Format,
|
|
||||||
allocator: Allocator,
|
|
||||||
pass_count: usize = 0,
|
|
||||||
fail_count: usize = 0,
|
|
||||||
case_pass_count: usize = 0,
|
|
||||||
case_fail_count: usize = 0,
|
|
||||||
writer: std.fs.File.Writer,
|
|
||||||
cases: std.ArrayList(Case) = .{},
|
|
||||||
|
|
||||||
const Format = enum { json, text, summary, quiet };
|
|
||||||
|
|
||||||
fn init(allocator: Allocator, format: Format) !Writer {
|
|
||||||
const out = std.fs.File.stdout();
|
|
||||||
var writer = out.writer(&.{});
|
|
||||||
|
|
||||||
if (format == .json) {
|
|
||||||
try writer.interface.writeByte('[');
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.format = format,
|
|
||||||
.writer = writer,
|
|
||||||
.allocator = allocator,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deinit(self: *Writer) void {
|
|
||||||
self.cases.deinit(self.allocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finalize(self: *Writer) !void {
|
|
||||||
var writer = &self.writer.interface;
|
|
||||||
if (self.format == .json) {
|
|
||||||
// When we write a test output, we add a trailing comma to act as
|
|
||||||
// a separator for the next test. We need to add this dummy entry
|
|
||||||
// to make it valid json.
|
|
||||||
// Better option could be to change the formatter to work on JSONL:
|
|
||||||
// https://github.com/lightpanda-io/perf-fmt/blob/main/wpt/wpt.go
|
|
||||||
try writer.writeAll("{\"name\":\"empty\",\"pass\": true, \"cases\": []}]");
|
|
||||||
} else {
|
|
||||||
try writer.print("\n==Summary==\nTests: {d}/{d}\nCases: {d}/{d}\n", .{
|
|
||||||
self.pass_count,
|
|
||||||
self.pass_count + self.fail_count,
|
|
||||||
self.case_pass_count,
|
|
||||||
self.case_pass_count + self.case_fail_count,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process(self: *Writer, test_file: []const u8, result_: ?[]const u8, err_: ?[]const u8) !void {
|
|
||||||
var writer = &self.writer.interface;
|
|
||||||
if (err_) |err| {
|
|
||||||
self.fail_count += 1;
|
|
||||||
switch (self.format) {
|
|
||||||
.text => return writer.print("Fail\t{s}\n\t{s}\n", .{ test_file, err }),
|
|
||||||
.summary => return writer.print("Fail 0/0\t{s}\n", .{test_file}),
|
|
||||||
.json => {
|
|
||||||
try std.json.Stringify.value(Test{
|
|
||||||
.pass = false,
|
|
||||||
.name = test_file,
|
|
||||||
.cases = &.{},
|
|
||||||
}, .{ .whitespace = .indent_2 }, writer);
|
|
||||||
return writer.writeByte(',');
|
|
||||||
},
|
|
||||||
.quiet => {},
|
|
||||||
}
|
|
||||||
// just make sure we didn't fall through by mistake
|
|
||||||
unreachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we don't have an error, we must have a result
|
|
||||||
const result = result_ orelse return error.InvalidResult;
|
|
||||||
|
|
||||||
var cases = &self.cases;
|
|
||||||
cases.clearRetainingCapacity(); // from previous run
|
|
||||||
|
|
||||||
var pass = true;
|
|
||||||
var case_pass_count: usize = 0;
|
|
||||||
var case_fail_count: usize = 0;
|
|
||||||
|
|
||||||
var lines = std.mem.splitScalar(u8, result, '\n');
|
|
||||||
while (lines.next()) |line| {
|
|
||||||
if (line.len == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// case names can have | in them, so we can't simply split on |
|
|
||||||
var case_name = line;
|
|
||||||
var case_pass = false; // so pessimistic!
|
|
||||||
var case_message: []const u8 = "";
|
|
||||||
|
|
||||||
if (std.mem.endsWith(u8, line, "|Pass")) {
|
|
||||||
case_name = line[0 .. line.len - 5];
|
|
||||||
case_pass = true;
|
|
||||||
case_pass_count += 1;
|
|
||||||
} else {
|
|
||||||
// both cases names and messages can have | in them. Our only
|
|
||||||
// chance to "parse" this is to anchor off the |$Status.
|
|
||||||
const statuses = [_][]const u8{ "|Fail", "|Timeout", "|Not Run", "|Optional Feature Unsupported" };
|
|
||||||
var pos_: ?usize = null;
|
|
||||||
var message_start: usize = 0;
|
|
||||||
for (statuses) |status| {
|
|
||||||
if (std.mem.indexOf(u8, line, status)) |idx| {
|
|
||||||
pos_ = idx;
|
|
||||||
message_start = idx + status.len;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const pos = pos_ orelse {
|
|
||||||
std.debug.print("invalid result line: {s}\n", .{line});
|
|
||||||
return error.InvalidResult;
|
|
||||||
};
|
|
||||||
|
|
||||||
case_name = line[0..pos];
|
|
||||||
case_message = line[message_start..];
|
|
||||||
pass = false;
|
|
||||||
case_fail_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
try cases.append(self.allocator, .{
|
|
||||||
.name = case_name,
|
|
||||||
.pass = case_pass,
|
|
||||||
.message = case_message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// our global counters
|
|
||||||
if (pass) {
|
|
||||||
self.pass_count += 1;
|
|
||||||
} else {
|
|
||||||
self.fail_count += 1;
|
|
||||||
}
|
|
||||||
self.case_pass_count += case_pass_count;
|
|
||||||
self.case_fail_count += case_fail_count;
|
|
||||||
|
|
||||||
switch (self.format) {
|
|
||||||
.summary => try writer.print("{s} {d}/{d}\t{s}\n", .{ statusText(pass), case_pass_count, case_pass_count + case_fail_count, test_file }),
|
|
||||||
.text => {
|
|
||||||
try writer.print("{s}\t{s}\n", .{ statusText(pass), test_file });
|
|
||||||
for (cases.items) |c| {
|
|
||||||
try writer.print("\t{s}\t{s}\n", .{ statusText(c.pass), c.name });
|
|
||||||
if (c.message) |msg| {
|
|
||||||
try writer.print("\t\t{s}\n", .{msg});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.json => {
|
|
||||||
try std.json.Stringify.value(Test{
|
|
||||||
.pass = pass,
|
|
||||||
.name = test_file,
|
|
||||||
.cases = cases.items,
|
|
||||||
}, .{ .whitespace = .indent_2 }, writer);
|
|
||||||
// separator, see `finalize` for the hack we use to terminate this
|
|
||||||
try writer.writeByte(',');
|
|
||||||
},
|
|
||||||
.quiet => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn statusText(pass: bool) []const u8 {
|
|
||||||
return if (pass) "Pass" else "Fail";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Command = struct {
|
|
||||||
format: Writer.Format,
|
|
||||||
filters: [][]const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn parseArgs(arena: Allocator) !Command {
|
|
||||||
const usage =
|
|
||||||
\\usage: {s} [options] [test filter]
|
|
||||||
\\ Run the Web Test Platform.
|
|
||||||
\\
|
|
||||||
\\ -h, --help Print this help message and exit.
|
|
||||||
\\ --json result is formatted in JSON.
|
|
||||||
\\ --summary print a summary result. Incompatible w/ --json or --quiet
|
|
||||||
\\ --quiet No output. Incompatible w/ --json or --summary
|
|
||||||
\\
|
|
||||||
;
|
|
||||||
|
|
||||||
var args = try std.process.argsWithAllocator(arena);
|
|
||||||
|
|
||||||
// get the exec name.
|
|
||||||
const exec_name = args.next().?;
|
|
||||||
|
|
||||||
var format = Writer.Format.text;
|
|
||||||
var filters: std.ArrayList([]const u8) = .{};
|
|
||||||
|
|
||||||
while (args.next()) |arg| {
|
|
||||||
if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) {
|
|
||||||
std.debug.print(usage, .{exec_name});
|
|
||||||
std.posix.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (std.mem.eql(u8, "--json", arg)) {
|
|
||||||
format = .json;
|
|
||||||
} else if (std.mem.eql(u8, "--summary", arg)) {
|
|
||||||
format = .summary;
|
|
||||||
} else if (std.mem.eql(u8, "--quiet", arg)) {
|
|
||||||
format = .quiet;
|
|
||||||
} else {
|
|
||||||
try filters.append(arena, arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.format = format,
|
|
||||||
.filters = filters.items,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const TestIterator = struct {
|
|
||||||
dir: Dir,
|
|
||||||
walker: Dir.Walker,
|
|
||||||
filters: [][]const u8,
|
|
||||||
read_arena: ArenaAllocator,
|
|
||||||
|
|
||||||
const Dir = std.fs.Dir;
|
|
||||||
|
|
||||||
fn init(allocator: Allocator, root: []const u8, filters: [][]const u8) !TestIterator {
|
|
||||||
var dir = try std.fs.cwd().openDir(root, .{ .iterate = true, .no_follow = true });
|
|
||||||
errdefer dir.close();
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.dir = dir,
|
|
||||||
.filters = filters,
|
|
||||||
.walker = try dir.walk(allocator),
|
|
||||||
.read_arena = ArenaAllocator.init(allocator),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deinit(self: *TestIterator) void {
|
|
||||||
self.walker.deinit();
|
|
||||||
self.dir.close();
|
|
||||||
self.read_arena.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next(self: *TestIterator) !?[]const u8 {
|
|
||||||
NEXT: while (try self.walker.next()) |entry| {
|
|
||||||
if (entry.kind != .file) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (std.mem.startsWith(u8, entry.path, "resources/")) {
|
|
||||||
// resources for running the tests themselves, not actual tests
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!std.mem.endsWith(u8, entry.basename, ".html") and !std.mem.endsWith(u8, entry.basename, ".htm")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const path = entry.path;
|
|
||||||
for (self.filters) |filter| {
|
|
||||||
if (std.mem.indexOf(u8, path, filter) == null) {
|
|
||||||
continue :NEXT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
defer _ = self.read_arena.reset(.retain_capacity);
|
|
||||||
// We need to read the file's content to see if there's a
|
|
||||||
// "testharness.js" in it. If there isn't, it isn't a test.
|
|
||||||
// Shame we have to do this.
|
|
||||||
|
|
||||||
const arena = self.read_arena.allocator();
|
|
||||||
const full_path = try std.fs.path.join(arena, &.{ WPT_DIR, path });
|
|
||||||
const file = try std.fs.cwd().openFile(full_path, .{});
|
|
||||||
defer file.close();
|
|
||||||
const html = try file.readToEndAlloc(arena, 128 * 1024);
|
|
||||||
|
|
||||||
if (std.mem.indexOf(u8, html, "testharness.js") == null) {
|
|
||||||
// This isn't a test. A lot of files are helpers/content for tests to
|
|
||||||
// make use of.
|
|
||||||
continue :NEXT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Case = struct {
|
|
||||||
pass: bool,
|
|
||||||
name: []const u8,
|
|
||||||
message: ?[]const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Test = struct {
|
|
||||||
pass: bool,
|
|
||||||
crash: bool = false,
|
|
||||||
name: []const u8,
|
|
||||||
cases: []Case,
|
|
||||||
};
|
|
||||||
|
|
||||||
const TestHTTPServer = struct {
|
|
||||||
shutdown: bool,
|
|
||||||
dir: std.fs.Dir,
|
|
||||||
listener: ?std.net.Server,
|
|
||||||
|
|
||||||
pub fn init() !TestHTTPServer {
|
|
||||||
return .{
|
|
||||||
.dir = try std.fs.cwd().openDir(WPT_DIR, .{}),
|
|
||||||
.shutdown = true,
|
|
||||||
.listener = null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *TestHTTPServer) void {
|
|
||||||
self.shutdown = true;
|
|
||||||
if (self.listener) |*listener| {
|
|
||||||
listener.deinit();
|
|
||||||
}
|
|
||||||
self.dir.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(self: *TestHTTPServer, wg: *std.Thread.WaitGroup) !void {
|
|
||||||
const address = try std.net.Address.parseIp("127.0.0.1", 9582);
|
|
||||||
|
|
||||||
self.listener = try address.listen(.{ .reuse_address = true });
|
|
||||||
var listener = &self.listener.?;
|
|
||||||
|
|
||||||
wg.finish();
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const conn = listener.accept() catch |err| {
|
|
||||||
if (self.shutdown) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
const thrd = try std.Thread.spawn(.{}, handleConnection, .{ self, conn });
|
|
||||||
thrd.detach();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handleConnection(self: *TestHTTPServer, conn: std.net.Server.Connection) !void {
|
|
||||||
defer conn.stream.close();
|
|
||||||
|
|
||||||
var req_buf: [2048]u8 = undefined;
|
|
||||||
var conn_reader = conn.stream.reader(&req_buf);
|
|
||||||
var conn_writer = conn.stream.writer(&req_buf);
|
|
||||||
|
|
||||||
var http_server = std.http.Server.init(conn_reader.interface(), &conn_writer.interface);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
var req = http_server.receiveHead() catch |err| switch (err) {
|
|
||||||
error.ReadFailed => continue,
|
|
||||||
error.HttpConnectionClosing => continue,
|
|
||||||
else => {
|
|
||||||
std.debug.print("Test HTTP Server error: {}\n", .{err});
|
|
||||||
return err;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
self.handler(&req) catch |err| {
|
|
||||||
std.debug.print("test http error '{s}': {}\n", .{ req.head.target, err });
|
|
||||||
try req.respond("server error", .{ .status = .internal_server_error });
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handler(server: *TestHTTPServer, req: *std.http.Server.Request) !void {
|
|
||||||
const path = req.head.target;
|
|
||||||
|
|
||||||
if (std.mem.eql(u8, path, "/")) {
|
|
||||||
// There's 1 test that does an XHR request to this, and it just seems
|
|
||||||
// to want a 200 success.
|
|
||||||
return req.respond("Hello!", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
// strip out leading '/' to make the path relative
|
|
||||||
const file = try server.dir.openFile(path[1..], .{});
|
|
||||||
defer file.close();
|
|
||||||
|
|
||||||
const stat = try file.stat();
|
|
||||||
var send_buffer: [4096]u8 = undefined;
|
|
||||||
|
|
||||||
var res = try req.respondStreaming(&send_buffer, .{
|
|
||||||
.content_length = stat.size,
|
|
||||||
.respond_options = .{
|
|
||||||
.extra_headers = &.{
|
|
||||||
.{ .name = "content-type", .value = getContentType(path) },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
var read_buffer: [4096]u8 = undefined;
|
|
||||||
var reader = file.reader(&read_buffer);
|
|
||||||
_ = try res.writer.sendFileAll(&reader, .unlimited);
|
|
||||||
try res.writer.flush();
|
|
||||||
try res.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sendFile(req: *std.http.Server.Request, file_path: []const u8) !void {
|
|
||||||
var file = std.fs.cwd().openFile(file_path, .{}) catch |err| switch (err) {
|
|
||||||
error.FileNotFound => return req.respond("server error", .{ .status = .not_found }),
|
|
||||||
else => return err,
|
|
||||||
};
|
|
||||||
defer file.close();
|
|
||||||
|
|
||||||
const stat = try file.stat();
|
|
||||||
var send_buffer: [4096]u8 = undefined;
|
|
||||||
|
|
||||||
var res = try req.respondStreaming(&send_buffer, .{
|
|
||||||
.content_length = stat.size,
|
|
||||||
.respond_options = .{
|
|
||||||
.extra_headers = &.{
|
|
||||||
.{ .name = "content-type", .value = getContentType(file_path) },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
var read_buffer: [4096]u8 = undefined;
|
|
||||||
var reader = file.reader(&read_buffer);
|
|
||||||
_ = try res.writer.sendFileAll(&reader, .unlimited);
|
|
||||||
try res.writer.flush();
|
|
||||||
try res.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getContentType(file_path: []const u8) []const u8 {
|
|
||||||
if (std.mem.endsWith(u8, file_path, ".js")) {
|
|
||||||
return "application/json";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (std.mem.endsWith(u8, file_path, ".html")) {
|
|
||||||
return "text/html";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (std.mem.endsWith(u8, file_path, ".htm")) {
|
|
||||||
return "text/html";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (std.mem.endsWith(u8, file_path, ".xml")) {
|
|
||||||
// some wpt tests do this
|
|
||||||
return "text/xml";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (std.mem.endsWith(u8, file_path, ".mjs")) {
|
|
||||||
// mjs are ECMAScript modules
|
|
||||||
return "application/json";
|
|
||||||
}
|
|
||||||
|
|
||||||
std.debug.print("TestHTTPServer asked to serve an unknown file type: {s}\n", .{file_path});
|
|
||||||
return "text/html";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const panic = std.debug.FullPanic(struct {
|
|
||||||
pub fn panicFn(msg: []const u8, first_trace_addr: ?usize) noreturn {
|
|
||||||
if (current_test) |ct| {
|
|
||||||
std.debug.print("===panic running: {s}===\n", .{ct});
|
|
||||||
}
|
|
||||||
std.debug.defaultPanic(msg, first_trace_addr);
|
|
||||||
}
|
|
||||||
}.panicFn);
|
|
||||||
Submodule tests/wpt deleted from 69c6afabd8
Reference in New Issue
Block a user