From 1aeb996164ffb4242a95caae1d427395507f7579 Mon Sep 17 00:00:00 2001 From: Nicholas Paun Date: Thu, 7 Nov 2024 07:17:13 -0800 Subject: [PATCH 1/2] WPT: Autogenerate a test case for each test file --- build/wpt_test.bzl | 97 ++++++++-- src/workerd/api/wpt/BUILD.bazel | 10 +- src/workerd/api/wpt/url-test-config.js | 110 +++++++++++ src/workerd/api/wpt/url-test.js | 132 ------------- src/workerd/api/wpt/urlpattern-test-config.js | 180 +++++++++++++++++ src/workerd/api/wpt/urlpattern-test.js | 183 ------------------ src/wpt/harness.js | 29 ++- 7 files changed, 397 insertions(+), 344 deletions(-) create mode 100644 src/workerd/api/wpt/url-test-config.js delete mode 100644 src/workerd/api/wpt/url-test.js create mode 100644 src/workerd/api/wpt/urlpattern-test-config.js delete mode 100644 src/workerd/api/wpt/urlpattern-test.js diff --git a/build/wpt_test.bzl b/build/wpt_test.bzl index 35bff703d29..585a92eedd0 100644 --- a/build/wpt_test.bzl +++ b/build/wpt_test.bzl @@ -9,43 +9,69 @@ 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, +def wpt_test(name, wpt_directory, test_config): + js_gen_rule = "{}@_wpt_js_test_gen".format(name) + _wpt_js_test_gen( + name = js_gen_rule, test_name = name, wpt_directory = wpt_directory, - test_js = test_js, + test_config = test_config, + ) + + wd_gen_rule = "{}@_wpt_wd_test_gen".format(name) + _wpt_wd_test_gen( + name = wd_gen_rule, + test_name = name, + wpt_directory = wpt_directory, + test_config = test_config, + test_js = js_gen_rule, ) wd_test( name = "{}".format(name), - src = test_gen_rule, + src = wd_gen_rule, args = ["--experimental"], data = [ "//src/wpt:wpt-test-harness", - test_js, + test_config, + js_gen_rule, 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)) +def _wpt_wd_test_gen_impl(ctx): + wd_src = ctx.actions.declare_file("{}.wd-test".format(ctx.attr.test_name)) + ctx.actions.write( - output = src, - content = WPT_TEST_TEMPLATE.format( + output = wd_src, + content = WD_TEST_TEMPLATE.format( test_name = ctx.attr.test_name, test_js = wd_relative_path(ctx.file.test_js), + test_config = wd_relative_path(ctx.file.test_config), modules = generate_external_modules(ctx.attr.wpt_directory.files), ), ) return DefaultInfo( - files = depset([src]), + files = depset([wd_src]), + ) + +def _wpt_js_test_gen_impl(ctx): + test_src = ctx.actions.declare_file("{}-test.js".format(ctx.attr.test_name)) + + ctx.actions.write( + output = test_src, + content = JS_TEST_TEMPLATE.format( + cases = generate_external_cases(ctx.attr.wpt_directory.files), + ), + ) + + return DefaultInfo( + files = depset([test_src]), ) -WPT_TEST_TEMPLATE = """ +WD_TEST_TEMPLATE = """ using Workerd = import "/workerd/workerd.capnp"; const unitTests :Workerd.Config = ( services = [ @@ -53,6 +79,7 @@ const unitTests :Workerd.Config = ( worker = ( modules = [ (name = "worker", esModule = embed "{test_js}"), + (name = "config", esModule = embed "{test_config}"), (name = "harness", esModule = embed "../../../../../workerd/src/wpt/harness.js"), {modules} ], @@ -70,6 +97,15 @@ const unitTests :Workerd.Config = ( ], );""" +JS_TEST_TEMPLATE = """ +import {{ runner }} from 'harness'; +import {{ config }} from 'config'; + +const run = runner(config); + +{cases} +""" + def wd_relative_path(file): """ Returns a relative path which can be referenced in the .wd-test file. @@ -102,14 +138,43 @@ def generate_external_modules(files): return ",\n".join(result) -_wpt_test_gen = rule( - implementation = _wpt_test_gen_impl, +def generate_external_cases(files): + result = [] + for file in files.to_list(): + file_path = wd_relative_path(file) + if file.extension == "js": + entry = """export const {} = run("{}");""".format(file_to_identifier(file.basename), file.basename) + result.append(entry) + + return "\n".join(result) + +def file_to_identifier(file): + stem = file.replace(".js", "").replace(".any", "") + stem_title = "".join([ch for ch in stem.title().elems() if ch.isalnum()]) + return stem_title[0].lower() + stem_title[1:] + +_wpt_wd_test_gen = rule( + implementation = _wpt_wd_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. + # A JS file containing the config for the test cases + "test_config": attr.label(allow_single_file = True), + # The generated JS file invoking each test case "test_js": attr.label(allow_single_file = True), }, ) + +_wpt_js_test_gen = rule( + implementation = _wpt_js_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 config for the test cases + "test_config": attr.label(allow_single_file = True), + }, +) diff --git a/src/workerd/api/wpt/BUILD.bazel b/src/workerd/api/wpt/BUILD.bazel index 22b83a5bc2f..ae9c54ee9a6 100644 --- a/src/workerd/api/wpt/BUILD.bazel +++ b/src/workerd/api/wpt/BUILD.bazel @@ -4,8 +4,10 @@ load("//:build/wpt_test.bzl", "wpt_test") +TEST_CONFIG_SUFFIX = "-test-config.js" + [wpt_test( - name = file.replace("-test.js", ""), - test_js = file, - wpt_directory = "@wpt//:{}".format(file.replace("-test.js", "")), -) for file in glob(["*-test.js"])] + name = file.replace(TEST_CONFIG_SUFFIX, ""), + test_config = file, + wpt_directory = "@wpt//:{}".format(file.replace(TEST_CONFIG_SUFFIX, "")), +) for file in glob(["*" + TEST_CONFIG_SUFFIX])] diff --git a/src/workerd/api/wpt/url-test-config.js b/src/workerd/api/wpt/url-test-config.js new file mode 100644 index 00000000000..075234ad446 --- /dev/null +++ b/src/workerd/api/wpt/url-test-config.js @@ -0,0 +1,110 @@ +// 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 + +export const config = { + 'a-element.js': { ignore: true }, + 'a-element-origin.js': { ignore: true }, + 'idlharness.any.js': { ignore: true }, + 'idlharness-shadowrealm.window.js': { ignore: true }, + 'javascript-urls.window.js': { ignore: true }, + 'toascii.window.js': { ignore: true }, + 'url-setters-a-area.window.js': { ignore: true }, + 'percent-encoding.window.js': { ignore: true }, + 'historical.any.js': { + expectedFailures: ["Setting URL's href attribute and base URLs"], + }, + 'url-constructor.any.js': { + expectedFailures: [ + 'Parsing: without base', + ], + }, + 'urlencoded-parser.any.js': { + expectedFailures: [ + 'request.formData() with input: test', + 'response.formData() with input: test', + 'request.formData() with input: \uFEFFtest=\uFEFF', + 'response.formData() with input: \uFEFFtest=\uFEFF', + 'request.formData() with input: %EF%BB%BFtest=%EF%BB%BF', + 'response.formData() with input: %EF%BB%BFtest=%EF%BB%BF', + 'request.formData() with input: %EF%BF%BF=%EF%BF%BF', + 'response.formData() with input: %EF%BF%BF=%EF%BF%BF', + 'request.formData() with input: %FE%FF', + 'response.formData() with input: %FE%FF', + 'request.formData() with input: %FF%FE', + 'response.formData() with input: %FF%FE', + 'request.formData() with input: †&†=x', + 'response.formData() with input: †&†=x', + 'request.formData() with input: %C2', + 'response.formData() with input: %C2', + 'request.formData() with input: %C2x', + 'response.formData() with input: %C2x', + 'request.formData() with input: _charset_=windows-1252&test=%C2x', + 'response.formData() with input: _charset_=windows-1252&test=%C2x', + 'request.formData() with input: ', + 'response.formData() with input: ', + 'request.formData() with input: a', + 'response.formData() with input: a', + 'request.formData() with input: a=b', + 'response.formData() with input: a=b', + 'request.formData() with input: a=', + 'response.formData() with input: a=', + 'request.formData() with input: =b', + 'response.formData() with input: =b', + 'request.formData() with input: &', + 'response.formData() with input: &', + 'request.formData() with input: &a', + 'response.formData() with input: &a', + 'request.formData() with input: a&', + 'response.formData() with input: a&', + 'request.formData() with input: a&a', + 'response.formData() with input: a&a', + 'request.formData() with input: a&b&c', + 'response.formData() with input: a&b&c', + 'request.formData() with input: a=b&c=d', + 'response.formData() with input: a=b&c=d', + 'request.formData() with input: a=b&c=d&', + 'response.formData() with input: a=b&c=d&', + 'request.formData() with input: &&&a=b&&&&c=d&', + 'response.formData() with input: &&&a=b&&&&c=d&', + 'request.formData() with input: a=a&a=b&a=c', + 'response.formData() with input: a=a&a=b&a=c', + 'request.formData() with input: a==a', + 'response.formData() with input: a==a', + 'request.formData() with input: a=a+b+c+d', + 'response.formData() with input: a=a+b+c+d', + 'request.formData() with input: %=a', + 'response.formData() with input: %=a', + 'request.formData() with input: %a=a', + 'response.formData() with input: %a=a', + 'request.formData() with input: %a_=a', + 'response.formData() with input: %a_=a', + 'request.formData() with input: %61=a', + 'response.formData() with input: %61=a', + 'request.formData() with input: %61+%4d%4D=', + 'response.formData() with input: %61+%4d%4D=', + 'request.formData() with input: id=0&value=%', + 'response.formData() with input: id=0&value=%', + 'request.formData() with input: b=%2sf%2a', + 'response.formData() with input: b=%2sf%2a', + 'request.formData() with input: b=%2%2af%2a', + 'response.formData() with input: b=%2%2af%2a', + 'request.formData() with input: b=%%2a', + 'response.formData() with input: b=%%2a', + ], + }, + 'urlsearchparams-constructor.any.js': { + expectedFailures: [ + 'URLSearchParams constructor, DOMException as argument', + 'Construct with 2 unpaired surrogates (no trailing)', + 'Construct with 3 unpaired surrogates (no leading)', + 'Construct with object with NULL, non-ASCII, and surrogate keys', + ], + }, + 'urlsearchparams-foreach.any.js': { + skippedTests: ['For-of Check'], + }, + 'urlsearchparams-sort.any.js': { + expectedFailures: ['Parse and sort: ffi&🌈', 'URL parse and sort: ffi&🌈'], + }, +}; diff --git a/src/workerd/api/wpt/url-test.js b/src/workerd/api/wpt/url-test.js deleted file mode 100644 index 7c62efdd2ff..00000000000 --- a/src/workerd/api/wpt/url-test.js +++ /dev/null @@ -1,132 +0,0 @@ -// 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 { run } from 'harness'; - -export const historical = run('historical.any.js', { - expectedFailures: ["Setting URL's href attribute and base URLs"], -}); - -export const urlConstructor = run('url-constructor.any.js', { - expectedFailures: [ - 'Parsing: without base', - ], -}); - -export const urlOrigin = run('url-origin.any.js'); -export const urlSearchParams = run('url-searchparams.any.js'); -export const urlSettersStripping = run('url-setters-stripping.any.js'); -export const urlSetters = run('url-setters.any.js'); -export const urlStaticsCanParse = run('url-statics-canparse.any.js'); -export const urlStaticsParse = run('url-statics-parse.any.js'); -export const urlToJson = run('url-tojson.any.js'); - -export const urlencodedParser = run('urlencoded-parser.any.js', { - expectedFailures: [ - 'request.formData() with input: test', - 'response.formData() with input: test', - 'request.formData() with input: \uFEFFtest=\uFEFF', - 'response.formData() with input: \uFEFFtest=\uFEFF', - 'request.formData() with input: %EF%BB%BFtest=%EF%BB%BF', - 'response.formData() with input: %EF%BB%BFtest=%EF%BB%BF', - 'request.formData() with input: %EF%BF%BF=%EF%BF%BF', - 'response.formData() with input: %EF%BF%BF=%EF%BF%BF', - 'request.formData() with input: %FE%FF', - 'response.formData() with input: %FE%FF', - 'request.formData() with input: %FF%FE', - 'response.formData() with input: %FF%FE', - 'request.formData() with input: †&†=x', - 'response.formData() with input: †&†=x', - 'request.formData() with input: %C2', - 'response.formData() with input: %C2', - 'request.formData() with input: %C2x', - 'response.formData() with input: %C2x', - 'request.formData() with input: _charset_=windows-1252&test=%C2x', - 'response.formData() with input: _charset_=windows-1252&test=%C2x', - 'request.formData() with input: ', - 'response.formData() with input: ', - 'request.formData() with input: a', - 'response.formData() with input: a', - 'request.formData() with input: a=b', - 'response.formData() with input: a=b', - 'request.formData() with input: a=', - 'response.formData() with input: a=', - 'request.formData() with input: =b', - 'response.formData() with input: =b', - 'request.formData() with input: &', - 'response.formData() with input: &', - 'request.formData() with input: &a', - 'response.formData() with input: &a', - 'request.formData() with input: a&', - 'response.formData() with input: a&', - 'request.formData() with input: a&a', - 'response.formData() with input: a&a', - 'request.formData() with input: a&b&c', - 'response.formData() with input: a&b&c', - 'request.formData() with input: a=b&c=d', - 'response.formData() with input: a=b&c=d', - 'request.formData() with input: a=b&c=d&', - 'response.formData() with input: a=b&c=d&', - 'request.formData() with input: &&&a=b&&&&c=d&', - 'response.formData() with input: &&&a=b&&&&c=d&', - 'request.formData() with input: a=a&a=b&a=c', - 'response.formData() with input: a=a&a=b&a=c', - 'request.formData() with input: a==a', - 'response.formData() with input: a==a', - 'request.formData() with input: a=a+b+c+d', - 'response.formData() with input: a=a+b+c+d', - 'request.formData() with input: %=a', - 'response.formData() with input: %=a', - 'request.formData() with input: %a=a', - 'response.formData() with input: %a=a', - 'request.formData() with input: %a_=a', - 'response.formData() with input: %a_=a', - 'request.formData() with input: %61=a', - 'response.formData() with input: %61=a', - 'request.formData() with input: %61+%4d%4D=', - 'response.formData() with input: %61+%4d%4D=', - 'request.formData() with input: id=0&value=%', - 'response.formData() with input: id=0&value=%', - 'request.formData() with input: b=%2sf%2a', - 'response.formData() with input: b=%2sf%2a', - 'request.formData() with input: b=%2%2af%2a', - 'response.formData() with input: b=%2%2af%2a', - 'request.formData() with input: b=%%2a', - 'response.formData() with input: b=%%2a', - ], -}); - -export const urlSearchParamsAppend = run('urlsearchparams-append.any.js'); - -export const urlSearchParamsConstructor = run( - 'urlsearchparams-constructor.any.js', - { - expectedFailures: [ - 'URLSearchParams constructor, DOMException as argument', - 'Construct with 2 unpaired surrogates (no trailing)', - 'Construct with 3 unpaired surrogates (no leading)', - 'Construct with object with NULL, non-ASCII, and surrogate keys', - ], - } -); - -export const urlSearchParamsDelete = run('urlsearchparams-delete.any.js'); - -export const urlSearchParamsForEach = run('urlsearchparams-foreach.any.js', { - skippedTests: ['For-of Check'], -}); - -export const urlSearchParamsGet = run('urlsearchparams-get.any.js'); -export const urlSearchParamsGetAll = run('urlsearchparams-getall.any.js'); -export const urlSearchParamsHas = run('urlsearchparams-has.any.js'); -export const urlSearchParamsSet = run('urlsearchparams-set.any.js'); -export const urlSearchParamsSize = run('urlsearchparams-size.any.js'); - -export const urlSearchParamsSort = await run('urlsearchparams-sort.any.js', { - expectedFailures: ['Parse and sort: ffi&🌈', 'URL parse and sort: ffi&🌈'], -}); - -export const urlSearchParamsStringifier = run( - 'urlsearchparams-stringifier.any.js' -); diff --git a/src/workerd/api/wpt/urlpattern-test-config.js b/src/workerd/api/wpt/urlpattern-test-config.js new file mode 100644 index 00000000000..effab581caa --- /dev/null +++ b/src/workerd/api/wpt/urlpattern-test-config.js @@ -0,0 +1,180 @@ +// 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 + +export const config = { + 'urlpattern-compare-tests.js': { + expectedFailures: [ + // Each of these *ought* to pass. They are included here because we + // know they currently do not. Each needs to be investigated. + 'Component: pathname Left: {"pathname":"/foo/a"} Right: {"pathname":"/foo/b"}', + 'Component: pathname Left: {"pathname":"/foo/b"} Right: {"pathname":"/foo/bar"}', + 'Component: pathname Left: {"pathname":"/foo/bar"} Right: {"pathname":"/foo/:bar"}', + 'Component: pathname Left: {"pathname":"/foo/"} Right: {"pathname":"/foo/:bar"}', + 'Component: pathname Left: {"pathname":"/foo/:bar"} Right: {"pathname":"/foo/*"}', + 'Component: pathname Left: {"pathname":"/foo/{bar}"} Right: {"pathname":"/foo/(bar)"}', + 'Component: pathname Left: {"pathname":"/foo/{bar}"} Right: {"pathname":"/foo/{bar}+"}', + 'Component: pathname Left: {"pathname":"/foo/{bar}+"} Right: {"pathname":"/foo/{bar}?"}', + 'Component: pathname Left: {"pathname":"/foo/{bar}?"} Right: {"pathname":"/foo/{bar}*"}', + 'Component: pathname Left: {"pathname":"/foo/(123)"} Right: {"pathname":"/foo/(12)"}', + 'Component: pathname Left: {"pathname":"/foo/:b"} Right: {"pathname":"/foo/:a"}', + 'Component: pathname Left: {"pathname":"*/foo"} Right: {"pathname":"*"}', + 'Component: port Left: {"port":"9"} Right: {"port":"100"}', + 'Component: pathname Left: {"pathname":"foo/:bar?/baz"} Right: {"pathname":"foo/{:bar}?/baz"}', + 'Component: pathname Left: {"pathname":"foo/:bar?/baz"} Right: {"pathname":"foo{/:bar}?/baz"}', + 'Component: pathname Left: {"pathname":"foo/:bar?/baz"} Right: {"pathname":"fo{o/:bar}?/baz"}', + 'Component: pathname Left: {"pathname":"foo/:bar?/baz"} Right: {"pathname":"foo{/:bar/}?baz"}', + 'Component: pathname Left: "https://a.example.com/b?a" Right: "https://b.example.com/a?b"', + 'Component: pathname Left: {"pathname":"/foo/{bar}/baz"} Right: {"pathname":"/foo/bar/baz"}', + 'Component: protocol Left: {"protocol":"a"} Right: {"protocol":"b"}', + 'Component: username Left: {"username":"a"} Right: {"username":"b"}', + 'Component: password Left: {"password":"a"} Right: {"password":"b"}', + 'Component: hostname Left: {"hostname":"a"} Right: {"hostname":"b"}', + 'Component: search Left: {"search":"a"} Right: {"search":"b"}', + 'Component: hash Left: {"hash":"a"} Right: {"hash":"b"}', + ], + }, + 'urlpattern-hasregexpgroups-tests.js': { + expectedFailures: [ + // Each of these *ought* to pass. They are included here because we + // know they currently do not. Each needs to be investigated. + '', // This file consists of one unnamed subtest + ], + }, + 'urlpatterntests.js': { + expectedFailures: [ + // Each of these *ought* to pass. They are included here because we + // know they currently do not. Each needs to be investigated. + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: [{"pathname":"/foo/bar"}]', + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: [{"hostname":"example.com","pathname":"/foo/bar"}]', + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: [{"protocol":"https","hostname":"example.com","pathname":"/foo/bar"}]', + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com"}] Inputs: [{"protocol":"https","hostname":"example.com","pathname":"/foo/bar"}]', + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com"}] Inputs: [{"protocol":"https","hostname":"example.com","pathname":"/foo/bar/baz"}]', + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: [{"protocol":"https","hostname":"example.com","pathname":"/foo/bar","search":"otherquery","hash":"otherhash"}]', + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com"}] Inputs: [{"protocol":"https","hostname":"example.com","pathname":"/foo/bar","search":"otherquery","hash":"otherhash"}]', + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?otherquery#otherhash"}] Inputs: [{"protocol":"https","hostname":"example.com","pathname":"/foo/bar","search":"otherquery","hash":"otherhash"}]', + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: ["https://example.com/foo/bar"]', + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: ["https://example.com/foo/bar?otherquery#otherhash"]', + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: ["https://example.com/foo/bar?query#hash"]', + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: ["https://example.com/foo/bar/baz"]', + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: ["https://other.com/foo/bar"]', + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: ["http://other.com/foo/bar"]', + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: [{"pathname":"/foo/bar","baseURL":"https://example.com"}]', + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}]', + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: [{"pathname":"/foo/bar/baz","baseURL":"https://example.com"}]', + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: [{"pathname":"/foo/bar","baseURL":"https://other.com"}]', + 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: [{"pathname":"/foo/bar","baseURL":"http://example.com"}]', + 'Pattern: [{"pathname":"/foo/:bar?"}] Inputs: [{"pathname":"/foo"}]', + 'Pattern: [{"pathname":"/foo/:bar*"}] Inputs: [{"pathname":"/foo"}]', + 'Pattern: [{"pathname":"/foo/(.*)?"}] Inputs: [{"pathname":"/foo"}]', + 'Pattern: [{"pathname":"/foo/*?"}] Inputs: [{"pathname":"/foo"}]', + 'Pattern: [{"pathname":"/foo/(.*)*"}] Inputs: [{"pathname":"/foo"}]', + 'Pattern: [{"pathname":"/foo/**"}] Inputs: [{"pathname":"/foo"}]', + 'Pattern: [{"protocol":"(.*)"}] Inputs: [{"protocol":"café"}]', + 'Pattern: [{"hostname":"xn--caf-dma.com"}] Inputs: [{"hostname":"café.com"}]', + 'Pattern: [{"hostname":"café.com"}] Inputs: [{"hostname":"café.com"}]', + 'Pattern: [{"protocol":"http","port":"80"}] Inputs: [{"protocol":"http","port":"80"}]', + 'Pattern: [{"protocol":"http","port":"80 "}] Inputs: [{"protocol":"http","port":"80"}]', + 'Pattern: [{"port":"(.*)"}] Inputs: [{"port":"invalid80"}]', + 'Pattern: [{"pathname":"/foo/bar"}] Inputs: [{"pathname":"foo/bar"}]', + 'Pattern: [{"pathname":"./foo/bar","baseURL":"https://example.com"}] Inputs: [{"pathname":"foo/bar","baseURL":"https://example.com"}]', + 'Pattern: [{"pathname":"","baseURL":"https://example.com"}] Inputs: [{"pathname":"/","baseURL":"https://example.com"}]', + 'Pattern: [{"pathname":"{/bar}","baseURL":"https://example.com/foo/"}] Inputs: [{"pathname":"./bar","baseURL":"https://example.com/foo/"}]', + 'Pattern: [{"pathname":"\\\\/bar","baseURL":"https://example.com/foo/"}] Inputs: [{"pathname":"./bar","baseURL":"https://example.com/foo/"}]', + 'Pattern: [{"pathname":"b","baseURL":"https://example.com/foo/"}] Inputs: [{"pathname":"./b","baseURL":"https://example.com/foo/"}]', + 'Pattern: [{"pathname":"foo/bar","baseURL":"https://example.com"}] Inputs: ["https://example.com/foo/bar"]', + 'Pattern: [{"pathname":":name.html","baseURL":"https://example.com"}] Inputs: ["https://example.com/foo.html"]', + 'Pattern: [{"protocol":"javascript","pathname":"var x = 1;"}] Inputs: [{"protocol":"javascript","pathname":"var x = 1;"}]', + 'Pattern: [{"protocol":"(data|javascript)","pathname":"var x = 1;"}] Inputs: [{"protocol":"javascript","pathname":"var x = 1;"}]', + 'Pattern: [{"pathname":"var x = 1;"}] Inputs: [{"pathname":"var x = 1;"}]', + 'Pattern: ["https://example.com:8080/foo?bar#baz"] Inputs: [{"pathname":"/foo","search":"bar","hash":"baz","baseURL":"https://example.com:8080"}]', + 'Pattern: ["/foo?bar#baz","https://example.com:8080"] Inputs: [{"pathname":"/foo","search":"bar","hash":"baz","baseURL":"https://example.com:8080"}]', + 'Pattern: ["http{s}?://{*.}?example.com/:product/:endpoint"] Inputs: ["https://sub.example.com/foo/bar"]', + 'Pattern: ["https://example.com?foo"] Inputs: ["https://example.com/?foo"]', + 'Pattern: ["https://example.com#foo"] Inputs: ["https://example.com/#foo"]', + 'Pattern: ["https://example.com:8080?foo"] Inputs: ["https://example.com:8080/?foo"]', + 'Pattern: ["https://example.com:8080#foo"] Inputs: ["https://example.com:8080/#foo"]', + 'Pattern: ["https://example.com/?foo"] Inputs: ["https://example.com/?foo"]', + 'Pattern: ["https://example.com/#foo"] Inputs: ["https://example.com/#foo"]', + 'Pattern: ["https://example.com/*?foo"] Inputs: ["https://example.com/?foo"]', + 'Pattern: ["https://example.com/*\\\\?foo"] Inputs: ["https://example.com/?foo"]', + 'Pattern: ["https://example.com/:name?foo"] Inputs: ["https://example.com/bar?foo"]', + 'Pattern: ["https://example.com/:name\\\\?foo"] Inputs: ["https://example.com/bar?foo"]', + 'Pattern: ["https://example.com/(bar)?foo"] Inputs: ["https://example.com/bar?foo"]', + 'Pattern: ["https://example.com/(bar)\\\\?foo"] Inputs: ["https://example.com/bar?foo"]', + 'Pattern: ["https://example.com/{bar}?foo"] Inputs: ["https://example.com/bar?foo"]', + 'Pattern: ["https://example.com/{bar}\\\\?foo"] Inputs: ["https://example.com/bar?foo"]', + 'Pattern: ["https://example.com/"] Inputs: ["https://example.com:8080/"]', + 'Pattern: ["data\\\\:foobar"] Inputs: ["data:foobar"]', + 'Pattern: ["https://{sub.}?example.com/foo"] Inputs: ["https://example.com/foo"]', + 'Pattern: ["https://(sub.)?example.com/foo"] Inputs: ["https://example.com/foo"]', + 'Pattern: ["https://(sub.)?example(.com/)foo"] Inputs: ["https://example.com/foo"]', + 'Pattern: ["https://(sub(?:.))?example.com/foo"] Inputs: ["https://example.com/foo"]', + 'Pattern: ["file:///foo/bar"] Inputs: ["file:///foo/bar"]', + 'Pattern: ["data:"] Inputs: ["data:"]', + 'Pattern: ["foo://bar"] Inputs: ["foo://bad_url_browser_interop"]', + 'Pattern: ["https://example.com/foo?bar#baz"] Inputs: [{"protocol":"https:","search":"?bar","hash":"#baz","baseURL":"http://example.com/foo"}]', + 'Pattern: ["?bar#baz","https://example.com/foo"] Inputs: ["?bar#baz","https://example.com/foo"]', + 'Pattern: ["?bar","https://example.com/foo#baz"] Inputs: ["?bar","https://example.com/foo#snafu"]', + 'Pattern: ["#baz","https://example.com/foo?bar"] Inputs: ["#baz","https://example.com/foo?bar"]', + 'Pattern: ["#baz","https://example.com/foo"] Inputs: ["#baz","https://example.com/foo"]', + 'Pattern: ["https://foo\\\\:bar@example.com"] Inputs: ["https://foo:bar@example.com"]', + 'Pattern: ["https://foo@example.com"] Inputs: ["https://foo@example.com"]', + 'Pattern: ["https://\\\\:bar@example.com"] Inputs: ["https://:bar@example.com"]', + 'Pattern: ["https://:user::pass@example.com"] Inputs: ["https://foo:bar@example.com"]', + 'Pattern: ["https\\\\:foo\\\\:bar@example.com"] Inputs: ["https:foo:bar@example.com"]', + 'Pattern: ["data\\\\:foo\\\\:bar@example.com"] Inputs: ["data:foo:bar@example.com"]', + 'Pattern: ["https://foo{\\\\:}bar@example.com"] Inputs: ["https://foo:bar@example.com"]', + 'Pattern: ["data{\\\\:}channel.html","https://example.com"] Inputs: ["https://example.com/data:channel.html"]', + 'Pattern: ["http://[\\\\:\\\\:1]/"] Inputs: ["http://[::1]/"]', + 'Pattern: ["http://[\\\\:\\\\:1]:8080/"] Inputs: ["http://[::1]:8080/"]', + 'Pattern: ["http://[\\\\:\\\\:a]/"] Inputs: ["http://[::a]/"]', + 'Pattern: ["http://[:address]/"] Inputs: ["http://[::1]/"]', + 'Pattern: ["http://[\\\\:\\\\:AB\\\\::num]/"] Inputs: ["http://[::ab:1]/"]', + 'Pattern: [{"hostname":"[\\\\:\\\\:AB\\\\::num]"}] Inputs: [{"hostname":"[::ab:1]"}]', + 'Pattern: ["data\\\\:text/javascript,let x = 100/:tens?5;"] Inputs: ["data:text/javascript,let x = 100/5;"]', + 'Pattern: [{"pathname":"/foo"},"https://example.com"] Inputs: undefined', + 'Pattern: [{"pathname":":name*"}] Inputs: [{"pathname":"foobar"}]', + 'Pattern: [{"pathname":":name+"}] Inputs: [{"pathname":"foobar"}]', + 'Pattern: [{"pathname":":name"}] Inputs: [{"pathname":"foobar"}]', + 'Pattern: [{"pathname":"(foo)(.*)"}] Inputs: [{"pathname":"foobarbaz"}]', + 'Pattern: [{"pathname":"{(foo)bar}(.*)"}] Inputs: [{"pathname":"foobarbaz"}]', + 'Pattern: [{"pathname":"(foo)?(.*)"}] Inputs: [{"pathname":"foobarbaz"}]', + 'Pattern: [{"pathname":"{:foo}(.*)"}] Inputs: [{"pathname":"foobarbaz"}]', + 'Pattern: [{"pathname":"{:foo}(barbaz)"}] Inputs: [{"pathname":"foobarbaz"}]', + 'Pattern: [{"pathname":"{:foo}{(.*)}"}] Inputs: [{"pathname":"foobarbaz"}]', + 'Pattern: [{"pathname":"{:foo}{bar(.*)}"}] Inputs: [{"pathname":"foobarbaz"}]', + 'Pattern: [{"pathname":"{:foo}:bar(.*)"}] Inputs: [{"pathname":"foobarbaz"}]', + 'Pattern: [{"pathname":"{:foo}?(.*)"}] Inputs: [{"pathname":"foobarbaz"}]', + 'Pattern: [{"pathname":"{:foo\\\\bar}"}] Inputs: [{"pathname":"foobar"}]', + 'Pattern: [{"pathname":"{:foo\\\\.bar}"}] Inputs: [{"pathname":"foo.bar"}]', + 'Pattern: [{"pathname":"{:foo(foo)bar}"}] Inputs: [{"pathname":"foobar"}]', + 'Pattern: [{"pathname":"{:foo}bar"}] Inputs: [{"pathname":"foobar"}]', + 'Pattern: [{"pathname":":foo\\\\bar"}] Inputs: [{"pathname":"foobar"}]', + 'Pattern: [{"pathname":":foo{}(.*)"}] Inputs: [{"pathname":"foobar"}]', + 'Pattern: [{"pathname":":foo{}bar"}] Inputs: [{"pathname":"foobar"}]', + 'Pattern: [{"pathname":":foo{}?bar"}] Inputs: [{"pathname":"foobar"}]', + 'Pattern: [{"pathname":"*{}**?"}] Inputs: [{"pathname":"foobar"}]', + 'Pattern: [{"pathname":":foo(baz)(.*)"}] Inputs: [{"pathname":"bazbar"}]', + 'Pattern: [{"pathname":":foo(baz)bar"}] Inputs: [{"pathname":"bazbar"}]', + 'Pattern: [{"pathname":"*/*"}] Inputs: [{"pathname":"foo/bar"}]', + 'Pattern: [{"pathname":"*\\\\/*"}] Inputs: [{"pathname":"foo/bar"}]', + 'Pattern: [{"pathname":"*/{*}"}] Inputs: [{"pathname":"foo/bar"}]', + 'Pattern: [{"pathname":"./foo"}] Inputs: [{"pathname":"./foo"}]', + 'Pattern: [{"pathname":"../foo"}] Inputs: [{"pathname":"../foo"}]', + 'Pattern: [{"pathname":":foo./"}] Inputs: [{"pathname":"bar./"}]', + 'Pattern: [{"pathname":":foo../"}] Inputs: [{"pathname":"bar../"}]', + 'Pattern: [{"pathname":"/:foo\\\\bar"}] Inputs: [{"pathname":"/bazbar"}]', + 'Pattern: [{"pathname":"/foo/bar"},{"ignoreCase":true}] Inputs: [{"pathname":"/FOO/BAR"}]', + 'Pattern: ["https://example.com:8080/foo?bar#baz",{"ignoreCase":true}] Inputs: [{"pathname":"/FOO","search":"BAR","hash":"BAZ","baseURL":"https://example.com:8080"}]', + 'Pattern: ["/foo?bar#baz","https://example.com:8080",{"ignoreCase":true}] Inputs: [{"pathname":"/FOO","search":"BAR","hash":"BAZ","baseURL":"https://example.com:8080"}]', + 'Pattern: [{"search":"foo","baseURL":"https://example.com/a/+/b"}] Inputs: [{"search":"foo","baseURL":"https://example.com/a/+/b"}]', + 'Pattern: [{"hash":"foo","baseURL":"https://example.com/?q=*&v=?&hmm={}&umm=()"}] Inputs: [{"hash":"foo","baseURL":"https://example.com/?q=*&v=?&hmm={}&umm=()"}]', + 'Pattern: ["#foo","https://example.com/?q=*&v=?&hmm={}&umm=()"] Inputs: ["https://example.com/?q=*&v=?&hmm={}&umm=()#foo"]', + 'Pattern: [{"pathname":"/([[a-z]--a])"}] Inputs: [{"pathname":"/a"}]', + 'Pattern: [{"pathname":"/([[a-z]--a])"}] Inputs: [{"pathname":"/z"}]', + 'Pattern: [{"pathname":"/([\\\\d&&[0-1]])"}] Inputs: [{"pathname":"/0"}]', + 'Pattern: [{"pathname":"/([\\\\d&&[0-1]])"}] Inputs: [{"pathname":"/3"}]', + ], + }, +}; diff --git a/src/workerd/api/wpt/urlpattern-test.js b/src/workerd/api/wpt/urlpattern-test.js deleted file mode 100644 index f399c8a1ba0..00000000000 --- a/src/workerd/api/wpt/urlpattern-test.js +++ /dev/null @@ -1,183 +0,0 @@ -// 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 { run } from 'harness'; - -export const urlpatternCompare = run('urlpattern-compare-tests.js', { - expectedFailures: [ - // Each of these *ought* to pass. They are included here because we - // know they currently do not. Each needs to be investigated. - 'Component: pathname Left: {"pathname":"/foo/a"} Right: {"pathname":"/foo/b"}', - 'Component: pathname Left: {"pathname":"/foo/b"} Right: {"pathname":"/foo/bar"}', - 'Component: pathname Left: {"pathname":"/foo/bar"} Right: {"pathname":"/foo/:bar"}', - 'Component: pathname Left: {"pathname":"/foo/"} Right: {"pathname":"/foo/:bar"}', - 'Component: pathname Left: {"pathname":"/foo/:bar"} Right: {"pathname":"/foo/*"}', - 'Component: pathname Left: {"pathname":"/foo/{bar}"} Right: {"pathname":"/foo/(bar)"}', - 'Component: pathname Left: {"pathname":"/foo/{bar}"} Right: {"pathname":"/foo/{bar}+"}', - 'Component: pathname Left: {"pathname":"/foo/{bar}+"} Right: {"pathname":"/foo/{bar}?"}', - 'Component: pathname Left: {"pathname":"/foo/{bar}?"} Right: {"pathname":"/foo/{bar}*"}', - 'Component: pathname Left: {"pathname":"/foo/(123)"} Right: {"pathname":"/foo/(12)"}', - 'Component: pathname Left: {"pathname":"/foo/:b"} Right: {"pathname":"/foo/:a"}', - 'Component: pathname Left: {"pathname":"*/foo"} Right: {"pathname":"*"}', - 'Component: port Left: {"port":"9"} Right: {"port":"100"}', - 'Component: pathname Left: {"pathname":"foo/:bar?/baz"} Right: {"pathname":"foo/{:bar}?/baz"}', - 'Component: pathname Left: {"pathname":"foo/:bar?/baz"} Right: {"pathname":"foo{/:bar}?/baz"}', - 'Component: pathname Left: {"pathname":"foo/:bar?/baz"} Right: {"pathname":"fo{o/:bar}?/baz"}', - 'Component: pathname Left: {"pathname":"foo/:bar?/baz"} Right: {"pathname":"foo{/:bar/}?baz"}', - 'Component: pathname Left: "https://a.example.com/b?a" Right: "https://b.example.com/a?b"', - 'Component: pathname Left: {"pathname":"/foo/{bar}/baz"} Right: {"pathname":"/foo/bar/baz"}', - 'Component: protocol Left: {"protocol":"a"} Right: {"protocol":"b"}', - 'Component: username Left: {"username":"a"} Right: {"username":"b"}', - 'Component: password Left: {"password":"a"} Right: {"password":"b"}', - 'Component: hostname Left: {"hostname":"a"} Right: {"hostname":"b"}', - 'Component: search Left: {"search":"a"} Right: {"search":"b"}', - 'Component: hash Left: {"hash":"a"} Right: {"hash":"b"}', - ], -}); -export const urlpatternHasRegexGroups = run( - 'urlpattern-hasregexpgroups-tests.js', - { - expectedFailures: [ - // Each of these *ought* to pass. They are included here because we - // know they currently do not. Each needs to be investigated. - '', // This file consists of one unnamed subtest - ], - } -); -export const urlpattern = run('urlpatterntests.js', { - expectedFailures: [ - // Each of these *ought* to pass. They are included here because we - // know they currently do not. Each needs to be investigated. - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: [{"pathname":"/foo/bar"}]', - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: [{"hostname":"example.com","pathname":"/foo/bar"}]', - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: [{"protocol":"https","hostname":"example.com","pathname":"/foo/bar"}]', - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com"}] Inputs: [{"protocol":"https","hostname":"example.com","pathname":"/foo/bar"}]', - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com"}] Inputs: [{"protocol":"https","hostname":"example.com","pathname":"/foo/bar/baz"}]', - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: [{"protocol":"https","hostname":"example.com","pathname":"/foo/bar","search":"otherquery","hash":"otherhash"}]', - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com"}] Inputs: [{"protocol":"https","hostname":"example.com","pathname":"/foo/bar","search":"otherquery","hash":"otherhash"}]', - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?otherquery#otherhash"}] Inputs: [{"protocol":"https","hostname":"example.com","pathname":"/foo/bar","search":"otherquery","hash":"otherhash"}]', - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: ["https://example.com/foo/bar"]', - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: ["https://example.com/foo/bar?otherquery#otherhash"]', - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: ["https://example.com/foo/bar?query#hash"]', - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: ["https://example.com/foo/bar/baz"]', - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: ["https://other.com/foo/bar"]', - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: ["http://other.com/foo/bar"]', - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: [{"pathname":"/foo/bar","baseURL":"https://example.com"}]', - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}]', - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: [{"pathname":"/foo/bar/baz","baseURL":"https://example.com"}]', - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: [{"pathname":"/foo/bar","baseURL":"https://other.com"}]', - 'Pattern: [{"pathname":"/foo/bar","baseURL":"https://example.com?query#hash"}] Inputs: [{"pathname":"/foo/bar","baseURL":"http://example.com"}]', - 'Pattern: [{"pathname":"/foo/:bar?"}] Inputs: [{"pathname":"/foo"}]', - 'Pattern: [{"pathname":"/foo/:bar*"}] Inputs: [{"pathname":"/foo"}]', - 'Pattern: [{"pathname":"/foo/(.*)?"}] Inputs: [{"pathname":"/foo"}]', - 'Pattern: [{"pathname":"/foo/*?"}] Inputs: [{"pathname":"/foo"}]', - 'Pattern: [{"pathname":"/foo/(.*)*"}] Inputs: [{"pathname":"/foo"}]', - 'Pattern: [{"pathname":"/foo/**"}] Inputs: [{"pathname":"/foo"}]', - 'Pattern: [{"protocol":"(.*)"}] Inputs: [{"protocol":"café"}]', - 'Pattern: [{"hostname":"xn--caf-dma.com"}] Inputs: [{"hostname":"café.com"}]', - 'Pattern: [{"hostname":"café.com"}] Inputs: [{"hostname":"café.com"}]', - 'Pattern: [{"protocol":"http","port":"80"}] Inputs: [{"protocol":"http","port":"80"}]', - 'Pattern: [{"protocol":"http","port":"80 "}] Inputs: [{"protocol":"http","port":"80"}]', - 'Pattern: [{"port":"(.*)"}] Inputs: [{"port":"invalid80"}]', - 'Pattern: [{"pathname":"/foo/bar"}] Inputs: [{"pathname":"foo/bar"}]', - 'Pattern: [{"pathname":"./foo/bar","baseURL":"https://example.com"}] Inputs: [{"pathname":"foo/bar","baseURL":"https://example.com"}]', - 'Pattern: [{"pathname":"","baseURL":"https://example.com"}] Inputs: [{"pathname":"/","baseURL":"https://example.com"}]', - 'Pattern: [{"pathname":"{/bar}","baseURL":"https://example.com/foo/"}] Inputs: [{"pathname":"./bar","baseURL":"https://example.com/foo/"}]', - 'Pattern: [{"pathname":"\\\\/bar","baseURL":"https://example.com/foo/"}] Inputs: [{"pathname":"./bar","baseURL":"https://example.com/foo/"}]', - 'Pattern: [{"pathname":"b","baseURL":"https://example.com/foo/"}] Inputs: [{"pathname":"./b","baseURL":"https://example.com/foo/"}]', - 'Pattern: [{"pathname":"foo/bar","baseURL":"https://example.com"}] Inputs: ["https://example.com/foo/bar"]', - 'Pattern: [{"pathname":":name.html","baseURL":"https://example.com"}] Inputs: ["https://example.com/foo.html"]', - 'Pattern: [{"protocol":"javascript","pathname":"var x = 1;"}] Inputs: [{"protocol":"javascript","pathname":"var x = 1;"}]', - 'Pattern: [{"protocol":"(data|javascript)","pathname":"var x = 1;"}] Inputs: [{"protocol":"javascript","pathname":"var x = 1;"}]', - 'Pattern: [{"pathname":"var x = 1;"}] Inputs: [{"pathname":"var x = 1;"}]', - 'Pattern: ["https://example.com:8080/foo?bar#baz"] Inputs: [{"pathname":"/foo","search":"bar","hash":"baz","baseURL":"https://example.com:8080"}]', - 'Pattern: ["/foo?bar#baz","https://example.com:8080"] Inputs: [{"pathname":"/foo","search":"bar","hash":"baz","baseURL":"https://example.com:8080"}]', - 'Pattern: ["http{s}?://{*.}?example.com/:product/:endpoint"] Inputs: ["https://sub.example.com/foo/bar"]', - 'Pattern: ["https://example.com?foo"] Inputs: ["https://example.com/?foo"]', - 'Pattern: ["https://example.com#foo"] Inputs: ["https://example.com/#foo"]', - 'Pattern: ["https://example.com:8080?foo"] Inputs: ["https://example.com:8080/?foo"]', - 'Pattern: ["https://example.com:8080#foo"] Inputs: ["https://example.com:8080/#foo"]', - 'Pattern: ["https://example.com/?foo"] Inputs: ["https://example.com/?foo"]', - 'Pattern: ["https://example.com/#foo"] Inputs: ["https://example.com/#foo"]', - 'Pattern: ["https://example.com/*?foo"] Inputs: ["https://example.com/?foo"]', - 'Pattern: ["https://example.com/*\\\\?foo"] Inputs: ["https://example.com/?foo"]', - 'Pattern: ["https://example.com/:name?foo"] Inputs: ["https://example.com/bar?foo"]', - 'Pattern: ["https://example.com/:name\\\\?foo"] Inputs: ["https://example.com/bar?foo"]', - 'Pattern: ["https://example.com/(bar)?foo"] Inputs: ["https://example.com/bar?foo"]', - 'Pattern: ["https://example.com/(bar)\\\\?foo"] Inputs: ["https://example.com/bar?foo"]', - 'Pattern: ["https://example.com/{bar}?foo"] Inputs: ["https://example.com/bar?foo"]', - 'Pattern: ["https://example.com/{bar}\\\\?foo"] Inputs: ["https://example.com/bar?foo"]', - 'Pattern: ["https://example.com/"] Inputs: ["https://example.com:8080/"]', - 'Pattern: ["data\\\\:foobar"] Inputs: ["data:foobar"]', - 'Pattern: ["https://{sub.}?example.com/foo"] Inputs: ["https://example.com/foo"]', - 'Pattern: ["https://(sub.)?example.com/foo"] Inputs: ["https://example.com/foo"]', - 'Pattern: ["https://(sub.)?example(.com/)foo"] Inputs: ["https://example.com/foo"]', - 'Pattern: ["https://(sub(?:.))?example.com/foo"] Inputs: ["https://example.com/foo"]', - 'Pattern: ["file:///foo/bar"] Inputs: ["file:///foo/bar"]', - 'Pattern: ["data:"] Inputs: ["data:"]', - 'Pattern: ["foo://bar"] Inputs: ["foo://bad_url_browser_interop"]', - 'Pattern: ["https://example.com/foo?bar#baz"] Inputs: [{"protocol":"https:","search":"?bar","hash":"#baz","baseURL":"http://example.com/foo"}]', - 'Pattern: ["?bar#baz","https://example.com/foo"] Inputs: ["?bar#baz","https://example.com/foo"]', - 'Pattern: ["?bar","https://example.com/foo#baz"] Inputs: ["?bar","https://example.com/foo#snafu"]', - 'Pattern: ["#baz","https://example.com/foo?bar"] Inputs: ["#baz","https://example.com/foo?bar"]', - 'Pattern: ["#baz","https://example.com/foo"] Inputs: ["#baz","https://example.com/foo"]', - 'Pattern: ["https://foo\\\\:bar@example.com"] Inputs: ["https://foo:bar@example.com"]', - 'Pattern: ["https://foo@example.com"] Inputs: ["https://foo@example.com"]', - 'Pattern: ["https://\\\\:bar@example.com"] Inputs: ["https://:bar@example.com"]', - 'Pattern: ["https://:user::pass@example.com"] Inputs: ["https://foo:bar@example.com"]', - 'Pattern: ["https\\\\:foo\\\\:bar@example.com"] Inputs: ["https:foo:bar@example.com"]', - 'Pattern: ["data\\\\:foo\\\\:bar@example.com"] Inputs: ["data:foo:bar@example.com"]', - 'Pattern: ["https://foo{\\\\:}bar@example.com"] Inputs: ["https://foo:bar@example.com"]', - 'Pattern: ["data{\\\\:}channel.html","https://example.com"] Inputs: ["https://example.com/data:channel.html"]', - 'Pattern: ["http://[\\\\:\\\\:1]/"] Inputs: ["http://[::1]/"]', - 'Pattern: ["http://[\\\\:\\\\:1]:8080/"] Inputs: ["http://[::1]:8080/"]', - 'Pattern: ["http://[\\\\:\\\\:a]/"] Inputs: ["http://[::a]/"]', - 'Pattern: ["http://[:address]/"] Inputs: ["http://[::1]/"]', - 'Pattern: ["http://[\\\\:\\\\:AB\\\\::num]/"] Inputs: ["http://[::ab:1]/"]', - 'Pattern: [{"hostname":"[\\\\:\\\\:AB\\\\::num]"}] Inputs: [{"hostname":"[::ab:1]"}]', - 'Pattern: ["data\\\\:text/javascript,let x = 100/:tens?5;"] Inputs: ["data:text/javascript,let x = 100/5;"]', - 'Pattern: [{"pathname":"/foo"},"https://example.com"] Inputs: undefined', - 'Pattern: [{"pathname":":name*"}] Inputs: [{"pathname":"foobar"}]', - 'Pattern: [{"pathname":":name+"}] Inputs: [{"pathname":"foobar"}]', - 'Pattern: [{"pathname":":name"}] Inputs: [{"pathname":"foobar"}]', - 'Pattern: [{"pathname":"(foo)(.*)"}] Inputs: [{"pathname":"foobarbaz"}]', - 'Pattern: [{"pathname":"{(foo)bar}(.*)"}] Inputs: [{"pathname":"foobarbaz"}]', - 'Pattern: [{"pathname":"(foo)?(.*)"}] Inputs: [{"pathname":"foobarbaz"}]', - 'Pattern: [{"pathname":"{:foo}(.*)"}] Inputs: [{"pathname":"foobarbaz"}]', - 'Pattern: [{"pathname":"{:foo}(barbaz)"}] Inputs: [{"pathname":"foobarbaz"}]', - 'Pattern: [{"pathname":"{:foo}{(.*)}"}] Inputs: [{"pathname":"foobarbaz"}]', - 'Pattern: [{"pathname":"{:foo}{bar(.*)}"}] Inputs: [{"pathname":"foobarbaz"}]', - 'Pattern: [{"pathname":"{:foo}:bar(.*)"}] Inputs: [{"pathname":"foobarbaz"}]', - 'Pattern: [{"pathname":"{:foo}?(.*)"}] Inputs: [{"pathname":"foobarbaz"}]', - 'Pattern: [{"pathname":"{:foo\\\\bar}"}] Inputs: [{"pathname":"foobar"}]', - 'Pattern: [{"pathname":"{:foo\\\\.bar}"}] Inputs: [{"pathname":"foo.bar"}]', - 'Pattern: [{"pathname":"{:foo(foo)bar}"}] Inputs: [{"pathname":"foobar"}]', - 'Pattern: [{"pathname":"{:foo}bar"}] Inputs: [{"pathname":"foobar"}]', - 'Pattern: [{"pathname":":foo\\\\bar"}] Inputs: [{"pathname":"foobar"}]', - 'Pattern: [{"pathname":":foo{}(.*)"}] Inputs: [{"pathname":"foobar"}]', - 'Pattern: [{"pathname":":foo{}bar"}] Inputs: [{"pathname":"foobar"}]', - 'Pattern: [{"pathname":":foo{}?bar"}] Inputs: [{"pathname":"foobar"}]', - 'Pattern: [{"pathname":"*{}**?"}] Inputs: [{"pathname":"foobar"}]', - 'Pattern: [{"pathname":":foo(baz)(.*)"}] Inputs: [{"pathname":"bazbar"}]', - 'Pattern: [{"pathname":":foo(baz)bar"}] Inputs: [{"pathname":"bazbar"}]', - 'Pattern: [{"pathname":"*/*"}] Inputs: [{"pathname":"foo/bar"}]', - 'Pattern: [{"pathname":"*\\\\/*"}] Inputs: [{"pathname":"foo/bar"}]', - 'Pattern: [{"pathname":"*/{*}"}] Inputs: [{"pathname":"foo/bar"}]', - 'Pattern: [{"pathname":"./foo"}] Inputs: [{"pathname":"./foo"}]', - 'Pattern: [{"pathname":"../foo"}] Inputs: [{"pathname":"../foo"}]', - 'Pattern: [{"pathname":":foo./"}] Inputs: [{"pathname":"bar./"}]', - 'Pattern: [{"pathname":":foo../"}] Inputs: [{"pathname":"bar../"}]', - 'Pattern: [{"pathname":"/:foo\\\\bar"}] Inputs: [{"pathname":"/bazbar"}]', - 'Pattern: [{"pathname":"/foo/bar"},{"ignoreCase":true}] Inputs: [{"pathname":"/FOO/BAR"}]', - 'Pattern: ["https://example.com:8080/foo?bar#baz",{"ignoreCase":true}] Inputs: [{"pathname":"/FOO","search":"BAR","hash":"BAZ","baseURL":"https://example.com:8080"}]', - 'Pattern: ["/foo?bar#baz","https://example.com:8080",{"ignoreCase":true}] Inputs: [{"pathname":"/FOO","search":"BAR","hash":"BAZ","baseURL":"https://example.com:8080"}]', - 'Pattern: [{"search":"foo","baseURL":"https://example.com/a/+/b"}] Inputs: [{"search":"foo","baseURL":"https://example.com/a/+/b"}]', - 'Pattern: [{"hash":"foo","baseURL":"https://example.com/?q=*&v=?&hmm={}&umm=()"}] Inputs: [{"hash":"foo","baseURL":"https://example.com/?q=*&v=?&hmm={}&umm=()"}]', - 'Pattern: ["#foo","https://example.com/?q=*&v=?&hmm={}&umm=()"] Inputs: ["https://example.com/?q=*&v=?&hmm={}&umm=()#foo"]', - 'Pattern: [{"pathname":"/([[a-z]--a])"}] Inputs: [{"pathname":"/a"}]', - 'Pattern: [{"pathname":"/([[a-z]--a])"}] Inputs: [{"pathname":"/z"}]', - 'Pattern: [{"pathname":"/([\\\\d&&[0-1]])"}] Inputs: [{"pathname":"/0"}]', - 'Pattern: [{"pathname":"/([\\\\d&&[0-1]])"}] Inputs: [{"pathname":"/3"}]', - ], -}); diff --git a/src/wpt/harness.js b/src/wpt/harness.js index a36c0b20f81..dc1c4250f1b 100644 --- a/src/wpt/harness.js +++ b/src/wpt/harness.js @@ -143,7 +143,7 @@ function sanitizeMessage(message) { async function validate(options) { await Promise.all(globalThis.promises); - const expectedFailures = options.expectedFailures ?? []; + const expectedFailures = new Set(options.expectedFailures ?? []); let failed = false; for (const err of globalThis.errors) { @@ -151,25 +151,36 @@ async function validate(options) { err.errors, sanitizeMessage(err.message) ); - if (expectedFailures.includes(err.message)) { + if (expectedFailures.delete(err.message)) { console.warn('Expected failure: ', sanitizedError); } else { console.error(sanitizedError); failed = true; } } + if (expectedFailures.size > 0) { + console.log('Missing expected failures:', expectedFailures); + failed = true; + } if (failed) { throw new Error('Test failed'); } } -export function run(file, options = {}) { - return { - async test() { - prepare(options); - await import(file); - await validate(options); - }, +export function runner(config) { + return function run(file) { + const options = config[file] ?? {}; + if (options.ignore) { + return {}; + } + + return { + async test() { + prepare(options); + await import(file); + await validate(options); + }, + }; }; } From 4c4eb9f2e1b874d205f31e64bf113901fa721c53 Mon Sep 17 00:00:00 2001 From: Nicholas Paun Date: Thu, 7 Nov 2024 08:34:53 -0800 Subject: [PATCH 2/2] Add basic docs for adding WPT test suites --- src/workerd/api/wpt/README.md | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/workerd/api/wpt/README.md diff --git a/src/workerd/api/wpt/README.md b/src/workerd/api/wpt/README.md new file mode 100644 index 00000000000..258e81f986c --- /dev/null +++ b/src/workerd/api/wpt/README.md @@ -0,0 +1,45 @@ +## How to add WPT test suites to workerd + +[WPT groups its test suites into directories](https://github.com/web-platform-tests/wpt). To add a new test suite to workerd, simply create a file named `suite-test-config.js`. For example, `url/` tests are added in the `url-test-config.js` file. + +A test config file is expected to export an object named `config`. This object can be empty to run all tests and subtests. + +```js +export const config = {}; +``` + +A test export is generated for each JS file in the directory. WPT test files are sometimes located in subdirectories, but the test importer flattens the hierarchy within the test suite. + +Each test file can contain several subtests, created by invoking `test` or `promise_test` within the test file. They are named according to the message argument provided. + +## Options + +Each entry in the `config` object specifies the test file name, and the options to apply to those tests. For example, `urlsearchparams-sort.any.js` can be configured using: + +```js +export const config = { + 'urlsearchparams-sort.any.js': { + expectedFailures: ['Parse and sort: ffi&🌈', 'URL parse and sort: ffi&🌈'], + }, +}; +``` + + +The following options are currently supported: + +* `ignore: bool`: Don't import a test file that is irrelevant for workerd tests +* `skippedTests: string[]`: A list of subtests that should not be executed. This should only be used for subtests that would crash workerd. +* `expectedFailures: string[]`: A list of subtests that are expected to fail, either due to a bug in workerd or an intentional choice not to support a feature. + +## Implementation + +Once a test config file is detected, the `wpt_test` macro is invoked for the suite. A JS test file is created that invokes the test harness on each JS file within the suite. + +A WD test file is generated which links all of the necessary components: + +* Generated JS test file (e.g. `url-test.js`) +* The test config file (e.g. `url-test-config.js`) +* Test harness (`harness.js`) +* Test files provided by WPT (e.g. `url/urlsearchparams-sort.any.js`) +* JSON resources provided by WPT (e.g. `url/resources/urltestdata.json`) +