diff --git a/WORKSPACE b/WORKSPACE index b5439e1baef..831d6f9197f 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -518,3 +518,14 @@ http_file( integrity = "sha256-4V2KXVoX5Ny1J7ABfVRx0nAHpAGegykhzac7zW3nK0k=", url = "https://github.com/npaun/bins/releases/download/llvm-18.1.8/llvm-18.1.8-windows-amd64-clang-format.exe", ) + +# ======================================================================================== +# Web Platform Tests + +http_archive( + name = "wpt", + build_file = "//:build/BUILD.wpt", + integrity = "sha256-Hxn/D6x6lI9ISlCQFq620sb8x9iXplVzXPV6zumX84A=", + strip_prefix = "wpt-merge_pr_48695", + url = "https://github.com/web-platform-tests/wpt/archive/refs/tags/merge_pr_48695.tar.gz", +) diff --git a/build/BUILD.wpt b/build/BUILD.wpt new file mode 100644 index 00000000000..757b227041c --- /dev/null +++ b/build/BUILD.wpt @@ -0,0 +1,20 @@ +# Copyright (c) 2017-2022 Cloudflare, Inc. +# Licensed under the Apache 2.0 license found in the LICENSE file or at: +# https://opensource.org/licenses/Apache-2.0 + +directories = glob( + ["*"], + exclude = glob( + ["*"], + exclude_directories = 1, + ) + [ + ".*", + ], + exclude_directories = 0, +) + +[filegroup( + name = dir, + srcs = glob(["{}/**/*".format(dir)]), + visibility = ["//visibility:public"], +) for dir in directories] diff --git a/build/wpt_test.bzl b/build/wpt_test.bzl new file mode 100644 index 00000000000..f94915ba88b --- /dev/null +++ b/build/wpt_test.bzl @@ -0,0 +1,114 @@ +# Copyright (c) 2017-2022 Cloudflare, Inc. +# Licensed under the Apache 2.0 license found in the LICENSE file or at: +# https://opensource.org/licenses/Apache-2.0 + +# The public entry point is a macro named wpt_test. It first invokes a private +# rule named _wpt_test_gen to access the files in the wpt filegroup and +# generate a corresponding wd-test file. It then invokes the wd_test macro +# to set up the test. + +load("//:build/wd_test.bzl", "wd_test") + +def wpt_test(name, wpt_directory, test_js): + test_gen_rule = "{}@_wpt_test_gen".format(name) + _wpt_test_gen( + name = test_gen_rule, + test_name = name, + wpt_directory = wpt_directory, + test_js = test_js, + ) + + wd_test( + name = "{}".format(name), + src = test_gen_rule, + args = ["--experimental"], + data = [ + "//src/wpt:wpt-test-harness", + test_js, + wpt_directory, + "//src/workerd/io:trimmed-supported-compatibility-date.txt", + ], + ) + +def _wpt_test_gen_impl(ctx): + src = ctx.actions.declare_file("{}.wd-test".format(ctx.attr.test_name)) + ctx.actions.write( + output = src, + content = WPT_TEST_TEMPLATE.format( + test_name = ctx.attr.test_name, + test_js = wd_relative_path(ctx.file.test_js), + modules = generate_external_modules(ctx.attr.wpt_directory.files), + ), + ) + + return DefaultInfo( + files = depset([src]), + ) + +WPT_TEST_TEMPLATE = """ +using Workerd = import "/workerd/workerd.capnp"; +const unitTests :Workerd.Config = ( + services = [ + ( name = "{test_name}", + worker = ( + modules = [ + (name = "worker", esModule = embed "{test_js}"), + (name = "harness", esModule = embed "../../../../../workerd/src/wpt/harness.js"), + {modules} + ], + bindings = [ + (name = "wpt", service = "wpt"), + ], + compatibilityDate = embed "../../../../../workerd/src/workerd/io/trimmed-supported-compatibility-date.txt", + compatibilityFlags = ["nodejs_compat", "experimental"], + ) + ), + ( + name = "wpt", + disk = ".", + ) + ], +);""" + +def wd_relative_path(file): + """ + Returns a relative path which can be referenced in the .wd-test file. + This is four directories up from the bazel short_path + """ + return "../" * 4 + file.short_path + +def generate_external_modules(files): + """ + 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"), + """ + result = [] + + for file in files.to_list(): + file_path = wd_relative_path(file) + if file.basename.endswith(".js"): + entry = """(name = "{}", esModule = embed "{}")""".format(file.basename, file_path) + elif file.basename.endswith(".json"): + entry = """(name = "{}", json = embed "{}")""".format(file.basename, file_path) + else: + # For other file types, you can add more conditions or skip them + continue + + result.append(entry) + + return ",\n".join(result) + +_wpt_test_gen = rule( + implementation = _wpt_test_gen_impl, + attrs = { + # A string to use as the test name. Used in the wd-test filename and the worker's name + "test_name": attr.string(), + # A file group representing a directory of wpt tests. All files in the group will be embedded. + "wpt_directory": attr.label(), + # A JS file containing the actual test logic. + "test_js": attr.label(allow_single_file = True), + }, +) diff --git a/src/workerd/api/wpt/BUILD.bazel b/src/workerd/api/wpt/BUILD.bazel new file mode 100644 index 00000000000..22b83a5bc2f --- /dev/null +++ b/src/workerd/api/wpt/BUILD.bazel @@ -0,0 +1,11 @@ +# Copyright (c) 2017-2022 Cloudflare, Inc. +# Licensed under the Apache 2.0 license found in the LICENSE file or at: +# https://opensource.org/licenses/Apache-2.0 + +load("//:build/wpt_test.bzl", "wpt_test") + +[wpt_test( + name = file.replace("-test.js", ""), + test_js = file, + wpt_directory = "@wpt//:{}".format(file.replace("-test.js", "")), +) for file in glob(["*-test.js"])] diff --git a/src/workerd/api/wpt/url-test.js b/src/workerd/api/wpt/url-test.js new file mode 100644 index 00000000000..7f5862dd8cb --- /dev/null +++ b/src/workerd/api/wpt/url-test.js @@ -0,0 +1,21 @@ +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +import * as harness from 'harness'; + +export const urlConstructor = { + async test() { + harness.prepare(); + await import('url-constructor.any.js'); + harness.validate(); + }, +}; + +export const urlOrigin = { + async test() { + harness.prepare(); + await import('url-origin.any.js'); + harness.validate(); + }, +}; diff --git a/src/workerd/io/BUILD.bazel b/src/workerd/io/BUILD.bazel index b68d51ebda1..e541cb645cc 100644 --- a/src/workerd/io/BUILD.bazel +++ b/src/workerd/io/BUILD.bazel @@ -242,6 +242,7 @@ genrule( outs = ["trimmed-supported-compatibility-date.txt"], cmd = "tr -d '\n' < $(location supported-compatibility-date.txt) > $(location trimmed-supported-compatibility-date.txt)", cmd_ps = "(Get-Content $(location supported-compatibility-date.txt) -Raw -Encoding Ascii).TrimEnd() | Set-Content $(location trimmed-supported-compatibility-date.txt) -NoNewLine -Encoding Ascii", + visibility = ["//visibility:public"], ) capnp_embed( diff --git a/src/wpt/BUILD.bazel b/src/wpt/BUILD.bazel new file mode 100644 index 00000000000..4eda7837a58 --- /dev/null +++ b/src/wpt/BUILD.bazel @@ -0,0 +1,8 @@ +filegroup( + name = "wpt-test-harness", + srcs = glob( + include = ["**/*"], + allow_empty = True, + ), + visibility = ["//visibility:public"], +) diff --git a/src/wpt/harness.js b/src/wpt/harness.js new file mode 100644 index 00000000000..56ef91b885c --- /dev/null +++ b/src/wpt/harness.js @@ -0,0 +1,46 @@ +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +// Copyright © web-platform-tests contributors. BSD license + +import { strictEqual } from 'node:assert'; + +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'); + } +}