Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC for basic web platform test integration #2585

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -582,3 +582,13 @@ new_local_repository(
visibility = ["//visibility:public"],)""",
path = "empty",
)

# ========================================================================================
# Web Platform Tests

http_archive(
name = "wpt",
integrity = "sha256-qSEOTIhox20EBQBFsBhvvqNHISNV2COHrz6ozmQfd3k=",
strip_prefix = "wpt-native-glob",
url = "https://github.com/npaun/wpt/archive/refs/tags/native-glob.tar.gz",
)
8 changes: 8 additions & 0 deletions build/BUILD.wpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
filegroup(
name = "url",
srcs = glob(
include = ["url/**/*"],
allow_empty = True,
),
visibility = ["//visibility:public"],
)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would need to export filegroups for each of the web platform test groups we want... we don't need to export filegroups for everything since most of the wpts will never be relevant

4 changes: 4 additions & 0 deletions src/workerd/api/wpt/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
load("//:build/wd_test.bzl", "wd_test")
load("//src/workerd/api/wpt:generate-tests.bzl", "gen_wpt_tests")

gen_wpt_tests(glob(["**/*.js"]))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some documentation comments around this would be helpful.

102 changes: 102 additions & 0 deletions src/workerd/api/wpt/generate-tests.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
load("@bazel_skylib//rules:write_file.bzl", "write_file")
load("//:build/wd_test.bzl", "wd_test")

# 0 - name of the service
# 1 - es module file path (ex: url-test for url-test.js)
# 2 - external modules required for the test suite to succeed
# 3 - current date in YYYY-MM-DD format
WPT_TEST_TEMPLATE = """
using Workerd = import "/workerd/workerd.capnp";

const unitTests :Workerd.Config = (
services = [
( name = "{}",
worker = (
modules = [
(name = "worker", esModule = embed "{}.js"),
(name = "harness", esModule = embed "../../../../../workerd/src/wpt/harness.js"),
{}
],
bindings = [
(name = "wpt", service = "wpt"),
],
compatibilityDate = "{}",
compatibilityFlags = ["nodejs_compat_v2", "experimental"],
)
),
(
name = "wpt",
disk = ".",
)
],
);"""

def _current_date_impl(repository_ctx):
result = repository_ctx.execute(["date", "+%Y-%m-%d"])
if result.return_code != 0:
fail("Failed to get current date")

current_date = result.stdout.strip()

repository_ctx.file("BUILD", "")
repository_ctx.file("date.bzl", "CURRENT_DATE = '%s'" % current_date)

current_date = repository_rule(
implementation = _current_date_impl,
local = True,
)

# Example: generate_wd_test_file("url-test")
def generate_wd_test_file(name, modules = ""):
return WPT_TEST_TEMPLATE.format(name, name, modules, current_date())

def generate_external_modules(directory):
"""
Generates a string for all files in the given directory in the specified format.
Example for a JS file:
(name = "url-origin.any.js", esModule = embed "../../../../../wpt/url/url-origin.any.js"),
Example for a JSON file:
(name = "resources/urltestdata.json", json = embed "../../../../../wpt/url/resources/urltestdata.json"),
"""
files = native.glob([directory + "/**/*"], allow_empty = True)
result = []

for file in files:
file_name = file.split("/")[-1]
file_path = "../" * 5 + file # Creates the "../../../../../" prefix

if file_name.endswith(".js"):
entry = """(name = {}, esModule = embed "{}"),""".format(file_name, file_path)
elif file_name.endswith(".json"):
entry = """(name = {}, json = embed "{}"),""".format(file_name, file_path)
else:
# For other file types, you can add more conditions or skip them
continue

result.append(entry)

return result.join("")

def gen_wpt_tests(files):
for file in files:
# For url-test.js, it should be url.
# We'll use this to check wpt/ folder and load necessary files.
wpt_directory = file.removesuffix("-test.js")
name = file.removesuffix(".js")
src = "{}.wd-test".format(name)
modules = generate_external_modules("@wpt//:" + wpt_directory)
write_file(
name = name + "@rule",
out = src,
content = [generate_wd_test_file(name, modules)],
)
wd_test(
name = name,
src = src,
args = ["--experimental"],
data = [
file,
"//src/wpt:wpt-test-harness",
"@wpt//:url",
],
)
17 changes: 17 additions & 0 deletions src/workerd/api/wpt/url-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as harness from 'harness';

export const urlConstructor = {
async test() {
harness.prepare();
await import('url-constructor.any.js');
harness.validate();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note: Our workerd tests could benefit from having a before() { ... } and after() { ... } mechanism.

Copy link
Member Author

@jasnell jasnell Sep 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose we could have a pattern like...

import * as harness from 'harness';

export const urlConstructor = {
  test: harness.test('url-constructor.any.js');
};

// or

export const urlConstructor = harness.test('url-constructor.any.js');

Where the harness.test(...) handles any of the necessary boilerplate for the most typical cases?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah sounds like a good idea

},
};

export const urlOrigin = {
async test() {
harness.prepare();
await import('url-origin.any.js');
harness.validate();
},
};
8 changes: 8 additions & 0 deletions src/wpt/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
filegroup(
name = "wpt-test-harness",
srcs = glob(
include = ["**/*"],
allow_empty = True,
),
visibility = ["//visibility:public"],
)
41 changes: 41 additions & 0 deletions src/wpt/harness.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { strictEqual } from 'node:assert';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note... let's try not to forget adding the copyright headers to these (speaking to myself here mostly)


globalThis.fetch = async (url) => {
const { default: data } = await import(url);
return {
async json() {
return data;
},
};
};

globalThis.promise_test = (callback) => {
callback();
};

globalThis.assert_equals = (a, b, c) => {
strictEqual(a, b, c);
};

globalThis.test = (callback, message) => {
try {
callback();
} catch (err) {
globalThis.errors.push(new AggregateError([err], message));
}
};

globalThis.errors = [];

export function prepare() {
globalThis.errors = [];
}

export function validate() {
if (globalThis.errors.length > 0) {
for (const err of globalThis.errors) {
console.error(err);
}
throw new Error('Test failed');
}
}