From dede92aa78915abeb438f9ef7d91f2a9cf4bfc4c Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Sat, 21 Jun 2025 13:07:26 +0100 Subject: [PATCH] Tests: Update WPT `testharness.js` to the most recent version This ensures that our test output looks the same as that shown in the WPT runner. --- .../wasm/jsapi/global/constructor.any.txt | 2 +- .../input/wpt-import/resources/testharness.js | 267 +++++++++++------- 2 files changed, 168 insertions(+), 101 deletions(-) diff --git a/Tests/LibWeb/Text/expected/wpt-import/wasm/jsapi/global/constructor.any.txt b/Tests/LibWeb/Text/expected/wpt-import/wasm/jsapi/global/constructor.any.txt index c0e668496f5..7627cb4648a 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/wasm/jsapi/global/constructor.any.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/wasm/jsapi/global/constructor.any.txt @@ -53,7 +53,7 @@ Pass Explicit value undefined for type i64 Pass Explicit value true for type i64 Pass Explicit value false for type i64 Pass Explicit value "3" for type i64 -Pass Explicit value bigint "123" for type i64 +Pass Explicit value 123n for type i64 Pass Explicit value object with toString returning string for type i64 Pass Explicit value object with valueOf returning string for type i64 Pass Explicit value object with toString returning bigint for type i64 diff --git a/Tests/LibWeb/Text/input/wpt-import/resources/testharness.js b/Tests/LibWeb/Text/input/wpt-import/resources/testharness.js index 401f7a6c8ba..4426d9c63ef 100644 --- a/Tests/LibWeb/Text/input/wpt-import/resources/testharness.js +++ b/Tests/LibWeb/Text/input/wpt-import/resources/testharness.js @@ -89,7 +89,7 @@ status: harness_status.structured_clone(), asserts: asserts.map(assert => assert.structured_clone())}); }] - } + }; on_event(window, 'load', function() { setTimeout(() => { @@ -204,7 +204,7 @@ } }); this.message_events = new_events; - } + }; WindowTestEnvironment.prototype.next_default_test_name = function() { var suffix = this.name_counter > 0 ? " " + this.name_counter : ""; @@ -226,8 +226,8 @@ WindowTestEnvironment.prototype.test_timeout = function() { var metas = document.getElementsByTagName("meta"); for (var i = 0; i < metas.length; i++) { - if (metas[i].name == "timeout") { - if (metas[i].content == "long") { + if (metas[i].name === "timeout") { + if (metas[i].content === "long") { return settings.harness_timeout.long; } break; @@ -488,7 +488,7 @@ this.all_loaded = false; this.on_loaded_callback = null; Promise.resolve().then(function() { - this.all_loaded = true + this.all_loaded = true; if (this.on_loaded_callback) { this.on_loaded_callback(); } @@ -571,7 +571,7 @@ // The worker object may be from another execution context, // so do not use instanceof here. return 'ServiceWorker' in global_scope && - Object.prototype.toString.call(worker) == '[object ServiceWorker]'; + Object.prototype.toString.call(worker) === '[object ServiceWorker]'; } var seen_func_name = Object.create(null); @@ -818,7 +818,7 @@ return bring_promise_to_current_realm(promise) .then(test.unreached_func("Should have rejected: " + description)) .catch(function(e) { - assert_throws_js_impl(constructor, function() { throw e }, + assert_throws_js_impl(constructor, function() { throw e; }, description, "promise_rejects_js"); }); } @@ -870,7 +870,7 @@ return bring_promise_to_current_realm(promise) .then(test.unreached_func("Should have rejected: " + description)) .catch(function(e) { - assert_throws_dom_impl(type, function() { throw e }, description, + assert_throws_dom_impl(type, function() { throw e; }, description, "promise_rejects_dom", constructor); }); } @@ -889,7 +889,7 @@ return bring_promise_to_current_realm(promise) .then(test.unreached_func("Should have rejected: " + description)) .catch(function(e) { - assert_throws_exactly_impl(exception, function() { throw e }, + assert_throws_exactly_impl(exception, function() { throw e; }, description, "promise_rejects_exactly"); }); } @@ -915,7 +915,7 @@ */ function EventWatcher(test, watchedNode, eventTypes, timeoutPromise) { - if (typeof eventTypes == 'string') { + if (typeof eventTypes === 'string') { eventTypes = [eventTypes]; } @@ -980,7 +980,7 @@ if (waitingFor) { return Promise.reject('Already waiting for an event or events'); } - if (typeof types == 'string') { + if (typeof types === 'string') { types = [types]; } if (options && options.record && options.record === 'all') { @@ -995,7 +995,7 @@ // This should always fail, otherwise we should have // resolved the promise. - assert_true(waitingFor.types.length == 0, + assert_true(waitingFor.types.length === 0, 'Timed out waiting for ' + waitingFor.types.join(', ')); var result = recordedEvents; recordedEvents = null; @@ -1019,13 +1019,13 @@ /** * Stop listening for events */ - function stop_watching() { + this.stop_watching = function() { for (var i = 0; i < eventTypes.length; i++) { watchedNode.removeEventListener(eventTypes[i], eventHandler, false); } }; - test._add_cleanup(stop_watching); + test._add_cleanup(this.stop_watching); return this; } @@ -1102,7 +1102,7 @@ { if (typeof func !== "function") { tests.set_status(tests.status.ERROR, - "promise_test invoked without a function"); + "`promise_setup` invoked without a function"); tests.complete(); return; } @@ -1338,6 +1338,15 @@ "0xffff": "uffff", }; + const formatEscapeMap = { + "\\": "\\\\", + '"': '\\"' + }; + for (const p in replacements) { + formatEscapeMap[String.fromCharCode(p)] = "\\" + replacements[p]; + } + const formatEscapePattern = new RegExp(`[${Object.keys(formatEscapeMap).map(k => k === "\\" ? "\\\\" : k).join("")}]`, "g"); + /** * Convert a value to a nice, human-readable string * @@ -1388,12 +1397,7 @@ switch (typeof val) { case "string": - val = val.replace(/\\/g, "\\\\"); - for (var p in replacements) { - var replace = "\\" + replacements[p]; - val = val.replace(RegExp(String.fromCharCode(p), "g"), replace); - } - return '"' + val.replace(/"/g, '\\"') + '"'; + return '"' + val.replace(formatEscapePattern, match => formatEscapeMap[match]) + '"'; case "boolean": case "undefined": return String(val); @@ -1404,6 +1408,8 @@ return "-0"; } return String(val); + case "bigint": + return String(val) + 'n'; case "object": if (val === null) { return "null"; @@ -1427,11 +1433,11 @@ case Node.COMMENT_NODE: return "Comment node "; case Node.DOCUMENT_NODE: - return "Document node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children"); + return "Document node with " + val.childNodes.length + (val.childNodes.length === 1 ? " child" : " children"); case Node.DOCUMENT_TYPE_NODE: return "DocumentType node"; case Node.DOCUMENT_FRAGMENT_NODE: - return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children"); + return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length === 1 ? " child" : " children"); default: return "Node object of unknown type"; } @@ -1775,20 +1781,25 @@ /** * Assert that ``actual`` is a number less than ``expected``. * - * @param {number} actual - Test value. - * @param {number} expected - Number that ``actual`` must be less than. + * @param {number|bigint} actual - Test value. + * @param {number|bigint} expected - Value that ``actual`` must be less than. * @param {string} [description] - Description of the condition being tested. */ function assert_less_than(actual, expected, description) { /* - * Test if a primitive number is less than another + * Test if a primitive number (or bigint) is less than another */ - assert(typeof actual === "number", + assert(typeof actual === "number" || typeof actual === "bigint", "assert_less_than", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); + assert(typeof actual === typeof expected, + "assert_less_than", description, + "expected a ${type_expected} but got a ${type_actual}", + {type_expected:typeof expected, type_actual:typeof actual}); + assert(actual < expected, "assert_less_than", description, "expected a number less than ${expected} but got ${actual}", @@ -1799,20 +1810,25 @@ /** * Assert that ``actual`` is a number greater than ``expected``. * - * @param {number} actual - Test value. - * @param {number} expected - Number that ``actual`` must be greater than. + * @param {number|bigint} actual - Test value. + * @param {number|bigint} expected - Value that ``actual`` must be greater than. * @param {string} [description] - Description of the condition being tested. */ function assert_greater_than(actual, expected, description) { /* - * Test if a primitive number is greater than another + * Test if a primitive number (or bigint) is greater than another */ - assert(typeof actual === "number", + assert(typeof actual === "number" || typeof actual === "bigint", "assert_greater_than", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); + assert(typeof actual === typeof expected, + "assert_greater_than", description, + "expected a ${type_expected} but got a ${type_actual}", + {type_expected:typeof expected, type_actual:typeof actual}); + assert(actual > expected, "assert_greater_than", description, "expected a number greater than ${expected} but got ${actual}", @@ -1824,21 +1840,31 @@ * Assert that ``actual`` is a number greater than ``lower`` and less * than ``upper`` but not equal to either. * - * @param {number} actual - Test value. - * @param {number} lower - Number that ``actual`` must be greater than. - * @param {number} upper - Number that ``actual`` must be less than. + * @param {number|bigint} actual - Test value. + * @param {number|bigint} lower - Value that ``actual`` must be greater than. + * @param {number|bigint} upper - Value that ``actual`` must be less than. * @param {string} [description] - Description of the condition being tested. */ function assert_between_exclusive(actual, lower, upper, description) { /* - * Test if a primitive number is between two others + * Test if a primitive number (or bigint) is between two others */ - assert(typeof actual === "number", + assert(typeof lower === typeof upper, + "assert_between_exclusive", description, + "expected lower (${type_lower}) and upper (${type_upper}) types to match (test error)", + {type_lower:typeof lower, type_upper:typeof upper}); + + assert(typeof actual === "number" || typeof actual === "bigint", "assert_between_exclusive", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); + assert(typeof actual === typeof lower, + "assert_between_exclusive", description, + "expected a ${type_lower} but got a ${type_actual}", + {type_lower:typeof lower, type_actual:typeof actual}); + assert(actual > lower && actual < upper, "assert_between_exclusive", description, "expected a number greater than ${lower} " + @@ -1850,21 +1876,26 @@ /** * Assert that ``actual`` is a number less than or equal to ``expected``. * - * @param {number} actual - Test value. - * @param {number} expected - Number that ``actual`` must be less + * @param {number|bigint} actual - Test value. + * @param {number|bigint} expected - Value that ``actual`` must be less * than or equal to. * @param {string} [description] - Description of the condition being tested. */ function assert_less_than_equal(actual, expected, description) { /* - * Test if a primitive number is less than or equal to another + * Test if a primitive number (or bigint) is less than or equal to another */ - assert(typeof actual === "number", + assert(typeof actual === "number" || typeof actual === "bigint", "assert_less_than_equal", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); + assert(typeof actual === typeof expected, + "assert_less_than_equal", description, + "expected a ${type_expected} but got a ${type_actual}", + {type_expected:typeof expected, type_actual:typeof actual}); + assert(actual <= expected, "assert_less_than_equal", description, "expected a number less than or equal to ${expected} but got ${actual}", @@ -1875,21 +1906,26 @@ /** * Assert that ``actual`` is a number greater than or equal to ``expected``. * - * @param {number} actual - Test value. - * @param {number} expected - Number that ``actual`` must be greater + * @param {number|bigint} actual - Test value. + * @param {number|bigint} expected - Value that ``actual`` must be greater * than or equal to. * @param {string} [description] - Description of the condition being tested. */ function assert_greater_than_equal(actual, expected, description) { /* - * Test if a primitive number is greater than or equal to another + * Test if a primitive number (or bigint) is greater than or equal to another */ - assert(typeof actual === "number", + assert(typeof actual === "number" || typeof actual === "bigint", "assert_greater_than_equal", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); + assert(typeof actual === typeof expected, + "assert_greater_than_equal", description, + "expected a ${type_expected} but got a ${type_actual}", + {type_expected:typeof expected, type_actual:typeof actual}); + assert(actual >= expected, "assert_greater_than_equal", description, "expected a number greater than or equal to ${expected} but got ${actual}", @@ -1901,21 +1937,31 @@ * Assert that ``actual`` is a number greater than or equal to ``lower`` and less * than or equal to ``upper``. * - * @param {number} actual - Test value. - * @param {number} lower - Number that ``actual`` must be greater than or equal to. - * @param {number} upper - Number that ``actual`` must be less than or equal to. + * @param {number|bigint} actual - Test value. + * @param {number|bigint} lower - Value that ``actual`` must be greater than or equal to. + * @param {number|bigint} upper - Value that ``actual`` must be less than or equal to. * @param {string} [description] - Description of the condition being tested. */ function assert_between_inclusive(actual, lower, upper, description) { /* - * Test if a primitive number is between to two others or equal to either of them + * Test if a primitive number (or bigint) is between to two others or equal to either of them */ - assert(typeof actual === "number", + assert(typeof lower === typeof upper, + "assert_between_inclusive", description, + "expected lower (${type_lower}) and upper (${type_upper}) types to match (test error)", + {type_lower:typeof lower, type_upper:typeof upper}); + + assert(typeof actual === "number" || typeof actual === "bigint", "assert_between_inclusive", description, "expected a number but got a ${type_actual}", {type_actual:typeof actual}); + assert(typeof actual === typeof lower, + "assert_between_inclusive", description, + "expected a ${type_lower} but got a ${type_actual}", + {type_lower:typeof lower, type_actual:typeof actual}); + assert(actual >= lower && actual <= upper, "assert_between_inclusive", description, "expected a number greater than or equal to ${lower} " + @@ -2040,30 +2086,46 @@ /** - * Assert that ``object`` has a property named ``property_name`` and that the property is readonly. + * Assert that ``object`` has a property named ``property_name`` and that the property is not writable or has no setter. * - * Note: The implementation tries to update the named property, so - * any side effects of updating will be triggered. Users are - * encouraged to instead inspect the property descriptor of ``property_name`` on ``object``. - * - * @param {Object} object - Object that should have the given property in its prototype chain. + * @param {Object} object - Object that should have the given (not necessarily own) property. * @param {string} property_name - Expected property name. * @param {string} [description] - Description of the condition being tested. */ function assert_readonly(object, property_name, description) { - var initial_value = object[property_name]; - try { - //Note that this can have side effects in the case where - //the property has PutForwards - object[property_name] = initial_value + "a"; //XXX use some other value here? - assert(same_value(object[property_name], initial_value), - "assert_readonly", description, - "changing property ${p} succeeded", - {p:property_name}); - } finally { - object[property_name] = initial_value; - } + assert(property_name in object, + "assert_readonly", description, + "property ${p} not found", + {p:property_name}); + + let desc; + while (object && (desc = Object.getOwnPropertyDescriptor(object, property_name)) === undefined) { + object = Object.getPrototypeOf(object); + } + + assert(desc !== undefined, + "assert_readonly", description, + "could not find a descriptor for property ${p}", + {p:property_name}); + + if (desc.hasOwnProperty("value")) { + // We're a data property descriptor + assert(desc.writable === false, "assert_readonly", description, + "descriptor [[Writable]] expected false got ${actual}", {actual:desc.writable}); + } else if (desc.hasOwnProperty("get") || desc.hasOwnProperty("set")) { + // We're an accessor property descriptor + assert(desc.set === undefined, "assert_readonly", description, + "property ${p} is an accessor property with a [[Set]] attribute, cannot test readonly-ness", + {p:property_name}); + } else { + // We're a generic property descriptor + // This shouldn't happen, because Object.getOwnPropertyDescriptor + // forwards the return value of [[GetOwnProperty]] (P), which must + // be a fully populated Property Descriptor or Undefined. + assert(false, "assert_readonly", description, + "Object.getOwnPropertyDescriptor must return a fully populated property descriptor"); + } } expose_assert(assert_readonly, "assert_readonly"); @@ -2109,7 +2171,7 @@ {func:func}); // Basic sanity-check on the passed-in constructor - assert(typeof constructor == "function", + assert(typeof constructor === "function", assertion_type, description, "${constructor} is not a constructor", {constructor:constructor}); @@ -2184,7 +2246,7 @@ assert(maybeDescription === undefined, "Too many args passed to no-constructor version of assert_throws_dom, or accidentally explicitly passed undefined"); } - assert_throws_dom_impl(type, func, description, "assert_throws_dom", constructor) + assert_throws_dom_impl(type, func, description, "assert_throws_dom", constructor); } expose_assert(assert_throws_dom, "assert_throws_dom"); @@ -2217,8 +2279,8 @@ {func:func}); // Sanity-check our type - assert(typeof type == "number" || - typeof type == "string", + assert(typeof type === "number" || + typeof type === "string", assertion_type, description, "${type} is not a number or string", {type:type}); @@ -2447,7 +2509,7 @@ function assert_implements(condition, description) { assert(!!condition, "assert_implements", description); } - expose_assert(assert_implements, "assert_implements") + expose_assert(assert_implements, "assert_implements"); /** * Assert that an optional feature is implemented, based on a 'truthy' condition. @@ -2561,11 +2623,11 @@ 2: "Timeout", 3: "Not Run", 4: "Optional Feature Unsupported", - } + }; Test.prototype.format_status = function() { return this.status_formats[this.status]; - } + }; Test.prototype.structured_clone = function() { @@ -2875,7 +2937,7 @@ return new Promise(resolve => { this.step_wait_func(cond, resolve, description, timeout, interval); }); - } + }; /* * Private method for registering cleanup functions. `testharness.js` @@ -3108,7 +3170,7 @@ throw new Error("AbortController is not supported in this browser"); } return this._abortController.signal; - } + }; /** * A RemoteTest object mirrors a Test object on a remote worker. The @@ -3184,11 +3246,11 @@ function(callback) { callback(); }); - } + }; RemoteTest.prototype.format_status = function() { return Test.prototype.status_formats[this.status]; - } + }; /* * A RemoteContext listens for test events from a remote test context, such @@ -3460,7 +3522,7 @@ this.all_done_callbacks = []; this.hide_test_state = false; - this.pending_remotes = []; + this.remotes = []; this.current_test = null; this.asserts_run = []; @@ -3502,26 +3564,26 @@ for (var p in properties) { if (properties.hasOwnProperty(p)) { var value = properties[p]; - if (p == "allow_uncaught_exception") { + if (p === "allow_uncaught_exception") { this.allow_uncaught_exception = value; - } else if (p == "explicit_done" && value) { + } else if (p === "explicit_done" && value) { this.wait_for_finish = true; - } else if (p == "explicit_timeout" && value) { + } else if (p === "explicit_timeout" && value) { this.timeout_length = null; if (this.timeout_id) { clearTimeout(this.timeout_id); } - } else if (p == "single_test" && value) { + } else if (p === "single_test" && value) { this.set_file_is_test(); - } else if (p == "timeout_multiplier") { + } else if (p === "timeout_multiplier") { this.timeout_multiplier = value; if (this.timeout_length) { this.timeout_length *= this.timeout_multiplier; } - } else if (p == "hide_test_state") { + } else if (p === "hide_test_state") { this.hide_test_state = value; - } else if (p == "output") { + } else if (p === "output") { this.output = value; } else if (p === "debug") { settings.debug = value; @@ -3614,11 +3676,14 @@ Tests.prototype.push = function(test) { + if (this.phase === this.phases.COMPLETE) { + return; + } if (this.phase < this.phases.HAVE_TESTS) { this.start(); } this.num_pending++; - test.index = this.tests.push(test); + test.index = this.tests.push(test) - 1; this.notify_test_state(test); }; @@ -3631,11 +3696,11 @@ }; Tests.prototype.all_done = function() { - return (this.tests.length > 0 || this.pending_remotes.length > 0) && + return (this.tests.length > 0 || this.remotes.length > 0) && test_environment.all_loaded && (this.num_pending === 0 || this.is_aborted) && !this.wait_for_finish && !this.processing_callbacks && - !this.pending_remotes.some(function(w) { return w.running; }); + !this.remotes.some(function(w) { return w.running; }); }; Tests.prototype.start = function() { @@ -3704,7 +3769,8 @@ function(test, testDone) { if (test.phase === test.phases.INITIAL) { - test.phase = test.phases.COMPLETE; + test.phase = test.phases.HAS_RESULT; + test.done(); testDone(); } else { add_test_done_callback(test, testDone); @@ -3715,14 +3781,14 @@ }; Tests.prototype.set_assert = function(assert_name, args) { - this.asserts_run.push(new AssertRecord(this.current_test, assert_name, args)) - } + this.asserts_run.push(new AssertRecord(this.current_test, assert_name, args)); + }; Tests.prototype.set_assert_status = function(index, status, stack) { let assert_record = this.asserts_run[index]; assert_record.status = status; assert_record.stack = stack; - } + }; /** * Update the harness status to reflect an unrecoverable harness error that @@ -3864,7 +3930,7 @@ } var remoteContext = this.create_remote_worker(worker); - this.pending_remotes.push(remoteContext); + this.remotes.push(remoteContext); return remoteContext.done; }; @@ -3887,7 +3953,7 @@ } var remoteContext = this.create_remote_window(remote); - this.pending_remotes.push(remoteContext); + this.remotes.push(remoteContext); return remoteContext.done; }; @@ -4099,8 +4165,8 @@ } else { var root = output_document.documentElement; var is_html = (root && - root.namespaceURI == "http://www.w3.org/1999/xhtml" && - root.localName == "html"); + root.namespaceURI === "http://www.w3.org/1999/xhtml" && + root.localName === "html"); var is_svg = (output_document.defaultView && "SVGSVGElement" in output_document.defaultView && root instanceof output_document.defaultView.SVGSVGElement); @@ -4545,7 +4611,7 @@ */ function AssertionError(message) { - if (typeof message == "string") { + if (typeof message === "string") { message = sanitize_unpaired_surrogates(message); } this.message = message; @@ -4595,7 +4661,7 @@ } return lines.slice(i).join("\n"); - } + }; function OptionalFeatureUnsupportedError(message) { @@ -4790,7 +4856,8 @@ return META_TITLE; } if ('location' in global_scope && 'pathname' in location) { - return location.pathname.substring(location.pathname.lastIndexOf('/') + 1, location.pathname.indexOf('.')); + var filename = location.pathname.substring(location.pathname.lastIndexOf('/') + 1); + return filename.substring(0, filename.indexOf('.')); } return "Untitled"; }