From 342377f7d119e4970eadc8fd13a3f65b1feffd07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= <targos@protonmail.com> Date: Sun, 21 Feb 2021 18:36:29 +0100 Subject: [PATCH] test: update all Web Platform Tests --- test/fixtures/wpt/README.md | 18 +- .../common/third_party/reftest-analyzer.xhtml | 934 ++++++++++++++++++ .../gb18030/gb18030-decoder.any.js | 63 ++ .../legacy-mb-schinese/gbk/gbk-decoder.any.js | 28 + .../wpt/encoding/textdecoder-arguments.any.js | 49 + .../wpt/encoding/textdecoder-labels.any.js | 1 + test/fixtures/wpt/hr-time/basic.any.js | 9 + .../timers/negative-setinterval.any.js | 12 + .../timers/negative-settimeout.any.js | 3 + .../timers/type-long-setinterval.any.js | 8 + .../timers/type-long-settimeout.any.js | 3 + test/fixtures/wpt/interfaces/html.idl | 97 +- test/fixtures/wpt/resources/idlharness.js | 18 +- test/fixtures/wpt/resources/test-only-api.js | 69 -- test/fixtures/wpt/resources/testharness.js | 379 ++++--- test/fixtures/wpt/url/url-constructor.any.js | 39 + test/fixtures/wpt/url/url-origin.any.js | 17 + .../url/urlsearchparams-constructor.any.js | 2 +- .../wpt/url/urlsearchparams-foreach.any.js | 10 +- test/fixtures/wpt/versions.json | 50 +- test/wpt/status/encoding.json | 11 + test/wpt/status/hr-time.json | 9 +- test/wpt/status/html/webappapis/timers.json | 15 +- test/wpt/status/url.json | 6 + test/wpt/test-hr-time.js | 3 + 25 files changed, 1573 insertions(+), 280 deletions(-) create mode 100644 test/fixtures/wpt/common/third_party/reftest-analyzer.xhtml create mode 100644 test/fixtures/wpt/encoding/legacy-mb-schinese/gb18030/gb18030-decoder.any.js create mode 100644 test/fixtures/wpt/encoding/legacy-mb-schinese/gbk/gbk-decoder.any.js create mode 100644 test/fixtures/wpt/encoding/textdecoder-arguments.any.js create mode 100644 test/fixtures/wpt/html/webappapis/timers/negative-setinterval.any.js create mode 100644 test/fixtures/wpt/html/webappapis/timers/negative-settimeout.any.js create mode 100644 test/fixtures/wpt/html/webappapis/timers/type-long-setinterval.any.js create mode 100644 test/fixtures/wpt/html/webappapis/timers/type-long-settimeout.any.js create mode 100644 test/fixtures/wpt/url/url-constructor.any.js create mode 100644 test/fixtures/wpt/url/url-origin.any.js diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index a3cbbe667434e2..ee3b1cb61460b8 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -10,17 +10,17 @@ See [test/wpt](../../wpt/README.md) for information on how these tests are run. Last update: +- common: https://github.com/web-platform-tests/wpt/tree/3586ff740b/common - console: https://github.com/web-platform-tests/wpt/tree/3b1f72e99a/console -- encoding: https://github.com/web-platform-tests/wpt/tree/3c9820d1cc/encoding -- url: https://github.com/web-platform-tests/wpt/tree/1783c9bccf/url -- resources: https://github.com/web-platform-tests/wpt/tree/351a99782b/resources -- interfaces: https://github.com/web-platform-tests/wpt/tree/b4be9a3fdf/interfaces -- html/webappapis/microtask-queuing: https://github.com/web-platform-tests/wpt/tree/2c5c3c4c27/html/webappapis/microtask-queuing -- html/webappapis/timers: https://github.com/web-platform-tests/wpt/tree/264f12bc7b/html/webappapis/timers -- hr-time: https://github.com/web-platform-tests/wpt/tree/a5d1774ecf/hr-time -- common: https://github.com/web-platform-tests/wpt/tree/841a51412f/common -- dom/abort: https://github.com/web-platform-tests/wpt/tree/7caa3de747/dom/abort +- dom/abort: https://github.com/web-platform-tests/wpt/tree/625e1310ce/dom/abort +- encoding: https://github.com/web-platform-tests/wpt/tree/35f70910d3/encoding - FileAPI: https://github.com/web-platform-tests/wpt/tree/3b279420d4/FileAPI +- hr-time: https://github.com/web-platform-tests/wpt/tree/9910784394/hr-time +- html/webappapis/microtask-queuing: https://github.com/web-platform-tests/wpt/tree/2c5c3c4c27/html/webappapis/microtask-queuing +- html/webappapis/timers: https://github.com/web-platform-tests/wpt/tree/5873f2d8f1/html/webappapis/timers +- interfaces: https://github.com/web-platform-tests/wpt/tree/8602e9c9a1/interfaces +- resources: https://github.com/web-platform-tests/wpt/tree/e366371a19/resources +- url: https://github.com/web-platform-tests/wpt/tree/59d28c8f2d/url [Web Platform Tests]: https://github.com/web-platform-tests/wpt [`git node wpt`]: https://github.com/nodejs/node-core-utils/blob/master/docs/git-node.md#git-node-wpt diff --git a/test/fixtures/wpt/common/third_party/reftest-analyzer.xhtml b/test/fixtures/wpt/common/third_party/reftest-analyzer.xhtml new file mode 100644 index 00000000000000..4c7b26511acfa0 --- /dev/null +++ b/test/fixtures/wpt/common/third_party/reftest-analyzer.xhtml @@ -0,0 +1,934 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- -*- Mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- --> +<!-- vim: set shiftwidth=2 tabstop=2 autoindent expandtab: --> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<!-- + +Features to add: +* make the left and right parts of the viewer independently scrollable +* make the test list filterable +** default to only showing unexpecteds +* add other ways to highlight differences other than circling? +* add zoom/pan to images +* Add ability to load log via XMLHttpRequest (also triggered via URL param) +* color the test list based on pass/fail and expected/unexpected/random/skip +* ability to load multiple logs ? +** rename them by clicking on the name and editing +** turn the test list into a collapsing tree view +** move log loading into popup from viewer UI + +--> +<!DOCTYPE html> +<html lang="en-US" xml:lang="en-US" xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Reftest analyzer</title> + <style type="text/css"><![CDATA[ + + html, body { margin: 0; } + html { padding: 0; } + body { padding: 4px; } + + #pixelarea, #itemlist, #images { position: absolute; } + #itemlist, #images { overflow: auto; } + #pixelarea { top: 0; left: 0; width: 320px; height: 84px; overflow: visible } + #itemlist { top: 84px; left: 0; width: 320px; bottom: 0; } + #images { top: 0; bottom: 0; left: 320px; right: 0; } + + #leftpane { width: 320px; } + #images { position: fixed; top: 10px; left: 340px; } + + form#imgcontrols { margin: 0; display: block; } + + #itemlist > table { border-collapse: collapse; } + #itemlist > table > tbody > tr > td { border: 1px solid; padding: 1px; } + #itemlist td.activeitem { background-color: yellow; } + + /* + #itemlist > table > tbody > tr.pass > td.url { background: lime; } + #itemlist > table > tbody > tr.fail > td.url { background: red; } + */ + + #magnification > svg { display: block; width: 84px; height: 84px; } + + #pixelinfo { font: small sans-serif; position: absolute; width: 200px; left: 84px; } + #pixelinfo table { border-collapse: collapse; } + #pixelinfo table th { white-space: nowrap; text-align: left; padding: 0; } + #pixelinfo table td { font-family: monospace; padding: 0 0 0 0.25em; } + + #pixelhint { display: inline; color: #88f; cursor: help; } + #pixelhint > * { display: none; position: absolute; margin: 8px 0 0 8px; padding: 4px; width: 400px; background: #ffa; color: black; box-shadow: 3px 3px 2px #888; z-index: 1; } + #pixelhint:hover { color: #000; } + #pixelhint:hover > * { display: block; } + #pixelhint p { margin: 0; } + #pixelhint p + p { margin-top: 1em; } + + ]]></style> + <script type="text/javascript"><![CDATA[ + +var XLINK_NS = "http://www.w3.org/1999/xlink"; +var SVG_NS = "http://www.w3.org/2000/svg"; +var IMAGE_NOT_AVAILABLE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAASCAYAAADczdVTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAHy0lEQVRoge2aX2hb5xnGf2dYabROgqQkpMuKnWUJLmxHMFaa/SscteQiF5EvUgqLctEVrDJKK1+MolzkQr4IctgW+SLIheJc1BpFpswJw92FbaZsTCGTL0465AtntUekJdJ8lByVHbnnwLsLKbKdSJbiZBVjeuAYn+/P+z3fc97vfd9zbEVEhB566BK+1m0CPfx/o+eAPXQVbR3QqVapOl8FlR46h0O1Wu02iacCZfsasMKEz8vbx1JYE6fY/dXx6mEbFObPcvDVDBlznpc9G+2r8xNcvLqK2w39r4UI+fs7tFjmytgFFu718865EIebPGincI3zFz7Bcrtx97/GL0P+p+IPbSOgRwXtW3vpewqL/a/g5rgf39hit2m0hGUAHOHrrq3trmef4/lDB7Ay57n01zuPZXPX7jUunv+Yf9ktR7D/0CHca7/n3KXPsHbAuynkCWCZptgiImKLaVqP9NuW1bT9ceybpr3j+WJbYrVa3rbEatGZi2uixvWdrysilmWKae2M+5PqlktoosayLfubcrN10dAk24aynUsIxMVsadwUs+EX7dEyAlaXLqMoCj6fj5HkUqO9MD+Govjx+xXcXi+uoRAhvwuv182Z8Ws4AJUlxoZ8uNxuvF43ii/EtdXNNUuV68lR/IqC4gsxPj7KkE/BF5qmClRXrzFSt+/1ulDOjLNU6eQ4OcyPDqH4hhg5O4LicuN2K4xcvk6jjHUKJM8O1fvcKMoZkouFOq1VPp1OcuXGAvrvfsv0lWmSySTzN0sdH+jyYhK/ouB2e/G6XfjPJikBVG8SUhT8fl99nwVGfQp+vx+f4iO5VO1AtwJjfgXF58M/kqSVJP9ef0xuAI6NlwWmL41xxqeg+PyMXr72yBqW3cI4JaZHh1DcXrxeLy5liORiB7q1PiZFyeV0mQqz9TRZeUmFVUGLSjqdkgCIFp2RTCosEJOiiIihSyKWkDl9WYrFnCQCCNF0w0QmHhBQJTEzJ+nZSQmAoEYks2KIGBkJgASiM5I3LbGMnCSCCEQl38GJMvMZiag1e+nlFcmmIgKaZEwREaPGhWGZ1VfEMFZkNj4sgCSyhoihSzwSlqCGoAUlEo1IJByW+Oxyh+dZJJ+eklhiRnIrRcnrM6KCxLOmiNiipyICSGR2pTY2O1m7T2XEsNrrJmJLfjkn6amwoMbFaMEhG28eAVtzExErW3sOBCWVzkpmNiEqCOEZ2RyLTT3eJAKaMhVEUMOSXjHEtg3JTIUFkNTK9rGwbQrWm2xGb6QoWxIqEtdtEWO28aDtoi6JSFCAjUtL1AUzJA4SSW/IZ2VjjU0V0zEBJBiJSzwWk1g8IZEAAmrdidrBkoSKxB4IW08tGVNEzIxoIJM5a8v4SQ1RY5lGSy6x8xScz6QkHFBre1Zre49nH+y1KDEQLV7TcyU1LBCtHVppp9smxk2dYAMtHXA7blZWNJDZ4sZ4MxPbdHjrbc3WNuvOq4YlkYhLLBaXeKx2sLcrBUS2ScFtUbUBh3WgajvgOYgGuKjw4Rsqb1uvkssbWLbJXFQFqL/I9IEKa2WzYcqy16E2BNteB1R+cuwoRwcHGRx4nlfenWMuPclRDx3goSraqd+7Gj/Y5d76SrXLu3VKLYW1rMZbo/QpB4+9zt6fT1I0Law/LRMBaLzC7ePNuSgL7/2GpcotLr7+AZG5t9gH0Fa3zuFq1tiWG4DKs5tebV1NDDW1XYd26iWO9A8wODjAUfUN5ubm+Ch4ZFuuLRzQoVwqUCqXyN9fg3tFSuUShVIZhyr5O2vo94o42DwD/PP23fq8Bf5urLO+BoHBwxzc20c++wcmz+lAkWLFATwcf3+YDwIDhMYmuDw+wt5j5+C5ZwDYP/gSoLP6xX5+fOIkJ47/lIP8g49/Nc3tDj59OZUiRR3uFYsAVO/eZoE1yvkyeA6gAaff+zU3SxUcp8LilQucnoFTP3hhix19/garlQqFW9eZOBti9Mqt9mubXwBw+NALeDC4cfVDzgP3i3keUN/nf4uo+hEver/DRaK84/9mY/72uoFTKVMolVn5/HPgPvlSmVKhRL2bSrlEqVyidH8N/d7t2u/lakfcKneLgM4rvxhncbXA6tI8kTffB+0NjnrAqZYplcrk83ceXdtzgB+psHD7S/pfPs7JkydQB1x8dnWS2SVje9GaxkVLl+DmNNC4NJn/S6JxH5nJyNRwrW7Qi7oMgxBMyd9molvmRKO1cExgshG6l9NTEhkOynAkLlOJoKBuhPV8ZlK0h9aNTqVbv3ltEK/VIiAQEN0yZVLbuM+aImLoEgts3VdsJrfFil1M1/ZSv9RAROaWO8n/hkyF1Q3bgeFGygvPrDRG5Wcf1IJbq9rlNrrNbra96aqlUVMSWrNnNiw5uw23T/4o4Xq7FtA29h2My3K9WtETgRZr13UxdIk+pGswkpCcsX0N2OZD9BOgWqFsgWePp20KWb0ywkDgEIa8y55Gq0O5XKHP7cGz++l/haxWylgOuD17aG7eoVpxwL27RX8b27jZ42n1qdahXKrg2bfnUW0eQ7edoD232l+/LPp2pHvNfh8eT2f8/3sO2AZLyRAvns6gqToLOgxP6Uz87HvdoNJDF9E1B6ysLrLw5yW+3PUNvv3dH/L9wX3doNFDl9E1B+yhB+j9O1YPXcZ/AAl9BWJNvZE7AAAAAElFTkSuQmCC"; + +var gPhases = null; + +var gIDCache = {}; + +var gMagPixPaths = []; // 2D array of array-of-two <path> objects used in the pixel magnifier +var gMagWidth = 5; // number of zoomed in pixels to show horizontally +var gMagHeight = 5; // number of zoomed in pixels to show vertically +var gMagZoom = 16; // size of the zoomed in pixels +var gImage1Data; // ImageData object for the reference image +var gImage2Data; // ImageData object for the test output image +var gFlashingPixels = []; // array of <path> objects that should be flashed due to pixel color mismatch +var gParams; + +function ID(id) { + if (!(id in gIDCache)) + gIDCache[id] = document.getElementById(id); + return gIDCache[id]; +} + +function hash_parameters() { + var result = { }; + var params = window.location.hash.substr(1).split(/[&;]/); + for (var i = 0; i < params.length; i++) { + var parts = params[i].split("="); + result[parts[0]] = unescape(unescape(parts[1])); + } + return result; +} + +function load() { + gPhases = [ ID("entry"), ID("loading"), ID("viewer") ]; + build_mag(); + gParams = hash_parameters(); + if (gParams.log) { + show_phase("loading"); + process_log(gParams.log); + } else if (gParams.logurl) { + show_phase("loading"); + var req = new XMLHttpRequest(); + req.onreadystatechange = function() { + if (req.readyState === 4) { + process_log(req.responseText); + } + }; + req.open('GET', gParams.logurl, true); + req.send(); + } + window.addEventListener('keypress', handle_keyboard_shortcut); + window.addEventListener('keydown', handle_keydown); + ID("image1").addEventListener('error', image_load_error); + ID("image2").addEventListener('error', image_load_error); +} + +function image_load_error(e) { + e.target.setAttributeNS(XLINK_NS, "xlink:href", IMAGE_NOT_AVAILABLE); +} + +function build_mag() { + var mag = ID("mag"); + + var r = document.createElementNS(SVG_NS, "rect"); + r.setAttribute("x", gMagZoom * -gMagWidth / 2); + r.setAttribute("y", gMagZoom * -gMagHeight / 2); + r.setAttribute("width", gMagZoom * gMagWidth); + r.setAttribute("height", gMagZoom * gMagHeight); + mag.appendChild(r); + + mag.setAttribute("transform", "translate(" + (gMagZoom * (gMagWidth / 2) + 1) + "," + (gMagZoom * (gMagHeight / 2) + 1) + ")"); + + for (var x = 0; x < gMagWidth; x++) { + gMagPixPaths[x] = []; + for (var y = 0; y < gMagHeight; y++) { + var p1 = document.createElementNS(SVG_NS, "path"); + p1.setAttribute("d", "M" + ((x - gMagWidth / 2) + 1) * gMagZoom + "," + (y - gMagHeight / 2) * gMagZoom + "h" + -gMagZoom + "v" + gMagZoom); + p1.setAttribute("stroke", "black"); + p1.setAttribute("stroke-width", "1px"); + p1.setAttribute("fill", "#aaa"); + + var p2 = document.createElementNS(SVG_NS, "path"); + p2.setAttribute("d", "M" + ((x - gMagWidth / 2) + 1) * gMagZoom + "," + (y - gMagHeight / 2) * gMagZoom + "v" + gMagZoom + "h" + -gMagZoom); + p2.setAttribute("stroke", "black"); + p2.setAttribute("stroke-width", "1px"); + p2.setAttribute("fill", "#888"); + + mag.appendChild(p1); + mag.appendChild(p2); + gMagPixPaths[x][y] = [p1, p2]; + } + } + + var flashedOn = false; + setInterval(function() { + flashedOn = !flashedOn; + flash_pixels(flashedOn); + }, 500); +} + +function show_phase(phaseid) { + for (var i in gPhases) { + var phase = gPhases[i]; + phase.style.display = (phase.id == phaseid) ? "" : "none"; + } + + if (phase == "viewer") + ID("images").style.display = "none"; +} + +function fileentry_changed() { + show_phase("loading"); + var input = ID("fileentry"); + var files = input.files; + if (files.length > 0) { + // Only handle the first file; don't handle multiple selection. + // The parts of the log we care about are ASCII-only. Since we + // can ignore lines we don't care about, best to read in as + // iso-8859-1, which guarantees we don't get decoding errors. + var fileReader = new FileReader(); + fileReader.onload = function(e) { + var log = null; + + log = e.target.result; + + if (log) + process_log(log); + else + show_phase("entry"); + } + fileReader.readAsText(files[0], "iso-8859-1"); + } + // So the user can process the same filename again (after + // overwriting the log), clear the value on the form input so we + // will always get an onchange event. + input.value = ""; +} + +function log_pasted() { + show_phase("loading"); + var entry = ID("logentry"); + var log = entry.value; + entry.value = ""; + process_log(log); +} + +var gTestItems; + +// This function is not used in production code, but can be invoked manually +// from the devtools console in order to test changes to the parsing regexes +// in process_log. +function test_parsing() { + // Note that the logs in these testcases have been manually edited to strip + // out stuff for brevity. + var testcases = [ + { "name": "empty log", + "log": "", + "expected": { "pass": 0, "unexpected": 0, "random": 0, "skip": 0 }, + "expected_images": 0, + }, + { "name": "android log", + "log": `[task 2018-12-28T10:36:45.718Z] 10:36:45 INFO - REFTEST TEST-START | a == b +[task 2018-12-28T10:36:45.719Z] 10:36:45 INFO - REFTEST TEST-LOAD | a | 78 / 275 (28%) +[task 2018-12-28T10:36:56.138Z] 10:36:56 INFO - REFTEST TEST-LOAD | b | 78 / 275 (28%) +[task 2018-12-28T10:37:06.559Z] 10:37:06 INFO - REFTEST TEST-UNEXPECTED-FAIL | a == b | image comparison, max difference: 255, number of differing pixels: 5950 +[task 2018-12-28T10:37:06.568Z] 10:37:06 INFO - REFTEST IMAGE 1 (TEST): data:image/png;base64, +[task 2018-12-28T10:37:06.577Z] 10:37:06 INFO - REFTEST IMAGE 2 (REFERENCE): data:image/png;base64, +[task 2018-12-28T10:37:06.577Z] 10:37:06 INFO - REFTEST INFO | Saved log: stuff trimmed here +[task 2018-12-28T10:37:06.582Z] 10:37:06 INFO - REFTEST TEST-END | a == b +[task 2018-12-28T10:37:06.583Z] 10:37:06 INFO - REFTEST TEST-START | a2 == b2 +[task 2018-12-28T10:37:06.583Z] 10:37:06 INFO - REFTEST TEST-LOAD | a2 | 79 / 275 (28%) +[task 2018-12-28T10:37:06.584Z] 10:37:06 INFO - REFTEST TEST-LOAD | b2 | 79 / 275 (28%) +[task 2018-12-28T10:37:16.982Z] 10:37:16 INFO - REFTEST TEST-PASS | a2 == b2 | image comparison, max difference: 0, number of differing pixels: 0 +[task 2018-12-28T10:37:16.982Z] 10:37:16 INFO - REFTEST TEST-END | a2 == b2`, + "expected": { "pass": 1, "unexpected": 1, "random": 0, "skip": 0 }, + "expected_images": 2, + }, + { "name": "local reftest run (Linux)", + "log": `REFTEST TEST-START | file:///a == file:///b +REFTEST TEST-LOAD | file:///a | 73 / 86 (84%) +REFTEST TEST-LOAD | file:///b | 73 / 86 (84%) +REFTEST TEST-PASS | file:///a == file:///b | image comparison, max difference: 0, number of differing pixels: 0 +REFTEST TEST-END | file:///a == file:///b`, + "expected": { "pass": 1, "unexpected": 0, "random": 0, "skip": 0 }, + "expected_images": 0, + }, + { "name": "wpt reftests (Linux automation)", + "log": `16:50:43 INFO - TEST-START | /a +16:50:43 INFO - PID 4276 | 1548694243694 Marionette INFO Testing http://web-platform.test:8000/a == http://web-platform.test:8000/b +16:50:43 INFO - PID 4276 | 1548694243963 Marionette INFO No differences allowed +16:50:44 INFO - TEST-PASS | /a | took 370ms +16:50:44 INFO - TEST-START | /a2 +16:50:44 INFO - PID 4276 | 1548694244066 Marionette INFO Testing http://web-platform.test:8000/a2 == http://web-platform.test:8000/b2 +16:50:44 INFO - PID 4276 | 1548694244792 Marionette INFO No differences allowed +16:50:44 INFO - PID 4276 | 1548694244792 Marionette INFO Found 28 pixels different, maximum difference per channel 14 +16:50:44 INFO - TEST-UNEXPECTED-FAIL | /a2 | Testing http://web-platform.test:8000/a2 == http://web-platform.test:8000/b2 +16:50:44 INFO - REFTEST IMAGE 1 (TEST): data:image/png;base64, +16:50:44 INFO - REFTEST IMAGE 2 (REFERENCE): data:image/png;base64, +16:50:44 INFO - TEST-INFO took 840ms`, + "expected": { "pass": 1, "unexpected": 1, "random": 0, "skip": 0 }, + "expected_images": 2, + }, + { "name": "windows log", + "log": `12:17:14 INFO - REFTEST TEST-START | a == b +12:17:14 INFO - REFTEST TEST-LOAD | a | 1603 / 2053 (78%) +12:17:14 INFO - REFTEST TEST-LOAD | b | 1603 / 2053 (78%) +12:17:14 INFO - REFTEST TEST-PASS(EXPECTED RANDOM) | a == b | image comparison, max difference: 0, number of differing pixels: 0 +12:17:14 INFO - REFTEST TEST-END | a == b +12:17:14 INFO - REFTEST TEST-START | a2 == b2 +12:17:14 INFO - REFTEST TEST-LOAD | a2 | 1604 / 2053 (78%) +12:17:14 INFO - REFTEST TEST-LOAD | b2 | 1604 / 2053 (78%) +12:17:14 INFO - REFTEST TEST-UNEXPECTED-FAIL | a2 == b2 | image comparison, max difference: 255, number of differing pixels: 9976 +12:17:14 INFO - REFTEST IMAGE 1 (TEST): data:image/png;base64, +12:17:14 INFO - REFTEST IMAGE 2 (REFERENCE): data:image/png;base64, +12:17:14 INFO - REFTEST INFO | Saved log: stuff trimmed here +12:17:14 INFO - REFTEST TEST-END | a2 == b2 +12:01:09 INFO - REFTEST TEST-START | a3 == b3 +12:01:09 INFO - REFTEST TEST-LOAD | a3 | 66 / 189 (34%) +12:01:09 INFO - REFTEST TEST-LOAD | b3 | 66 / 189 (34%) +12:01:09 INFO - REFTEST TEST-KNOWN-FAIL | a3 == b3 | image comparison, max difference: 255, number of differing pixels: 9654 +12:01:09 INFO - REFTEST TEST-END | a3 == b3`, + "expected": { "pass": 1, "unexpected": 1, "random": 1, "skip": 0 }, + "expected_images": 2, + }, + { "name": "webrender wrench log (windows)", + "log": `[task 2018-12-29T04:29:48.800Z] REFTEST a == b +[task 2018-12-29T04:29:48.984Z] REFTEST a2 == b2 +[task 2018-12-29T04:29:49.053Z] REFTEST TEST-UNEXPECTED-FAIL | a2 == b2 | image comparison, max difference: 255, number of differing pixels: 3128 +[task 2018-12-29T04:29:49.053Z] REFTEST IMAGE 1 (TEST): data:image/png; +[task 2018-12-29T04:29:49.053Z] REFTEST IMAGE 2 (REFERENCE): data:image/png; +[task 2018-12-29T04:29:49.053Z] REFTEST TEST-END | a2 == b2`, + "expected": { "pass": 0, "unexpected": 1, "random": 0, "skip": 0 }, + "expected_images": 2, + }, + { "name": "wpt reftests (Linux local; Bug 1530008)", + "log": `SUITE-START | Running 1 tests +TEST-START | /css/css-backgrounds/border-image-6.html +TEST-UNEXPECTED-FAIL | /css/css-backgrounds/border-image-6.html | Testing http://web-platform.test:8000/css/css-backgrounds/border-image-6.html == http://web-platform.test:8000/css/css-backgrounds/border-image-6-ref.html +REFTEST IMAGE 1 (TEST): data:image/png;base64, +REFTEST IMAGE 2 (REFERENCE): data:image/png;base64, +TEST-INFO took 425ms +SUITE-END | took 2s`, + "expected": { "pass": 0, "unexpected": 1, "random": 0, "skip": 0 }, + "expected_images": 2, + }, + { "name": "wpt reftests (taskcluster log from macOS CI)", + "log": `[task 2020-06-26T01:35:29.065Z] 01:35:29 INFO - TEST-START | /html/rendering/non-replaced-elements/the-page/iframe-scrolling-attribute-values.html +[task 2020-06-26T01:35:29.065Z] 01:35:29 INFO - PID 1353 | 1593135329040 Marionette INFO Testing http://web-platform.test:8000/html/rendering/non-replaced-elements/the-page/iframe-scrolling-attribute-values.html == http://web-platform.test:8000/html/rendering/non-replaced-elements/the-page/iframe-scrolling-attribute-values-ref.html +[task 2020-06-26T01:35:29.673Z] 01:35:29 INFO - PID 1353 | 1593135329633 Marionette INFO No differences allowed +[task 2020-06-26T01:35:29.726Z] 01:35:29 INFO - TEST-KNOWN-INTERMITTENT-FAIL | /html/rendering/non-replaced-elements/the-page/iframe-scrolling-attribute-values.html | took 649ms +[task 2020-06-26T01:35:29.726Z] 01:35:29 INFO - REFTEST IMAGE 1 (TEST): data:image/png; +[task 2020-06-26T01:35:29.726Z] 01:35:29 INFO - REFTEST IMAGE 2 (REFERENCE): data:image/png;`, + "expected": { "pass": 0, "unexpected": 0, "random": 1, "skip": 0 }, + "expected_images": 2, + }, + { "name": "wpt reftests (taskcluster log from Windows CI)", + "log": `[task 2020-06-26T01:41:19.205Z] 01:41:19 INFO - TEST-START | /html/rendering/non-replaced-elements/the-page/iframe-scrolling-attribute-values.html +[task 2020-06-26T01:41:19.214Z] 01:41:19 INFO - PID 5920 | 1593135679202 Marionette WARN [24] http://web-platform.test:8000/css/WOFF2/metadatadisplay-schema-license-022-ref.xht overflows viewport (width: 783, height: 731) +[task 2020-06-26T01:41:19.214Z] 01:41:19 INFO - PID 9692 | 1593135679208 Marionette INFO Testing http://web-platform.test:8000/html/rendering/non-replaced-elements/the-page/iframe-scrolling-attribute-values.html == http://web-platform.test:8000/html/rendering/non-replaced-elements/the-page/iframe-scrolling-attribute-values-ref.html +[task 2020-06-26T01:41:19.638Z] 01:41:19 INFO - PID 9692 | 1593135679627 Marionette INFO No differences allowed +[task 2020-06-26T01:41:19.688Z] 01:41:19 INFO - TEST-KNOWN-INTERMITTENT-PASS | /html/rendering/non-replaced-elements/the-page/iframe-scrolling-attribute-values.html | took 474ms +[task 2020-06-26T01:41:19.688Z] 01:41:19 INFO - REFTEST IMAGE 1 (TEST): data:image/png; +[task 2020-06-26T01:41:19.689Z] 01:41:19 INFO - REFTEST IMAGE 2 (REFERENCE): data:image/png;`, + "expected": { "pass": 1, "unexpected": 0, "random": 1, "skip": 0 }, + "expected_images": 2, + }, + { "name": "local reftest run with timestamps (Linux; Bug 1167712)", + "log": ` 0:05.21 REFTEST TEST-START | a + 0:05.21 REFTEST REFTEST TEST-LOAD | a | 0 / 1 (0%) + 0:05.27 REFTEST REFTEST TEST-LOAD | b | 0 / 1 (0%) + 0:05.66 REFTEST TEST-UNEXPECTED-FAIL | a | image comparison (==), max difference: 106, number of differing pixels: 800 + 0:05.67 REFTEST REFTEST IMAGE 1 (TEST): data:image/png;base64, + 0:05.67 REFTEST REFTEST IMAGE 2 (REFERENCE): data:image/png;base64, + 0:05.73 REFTEST REFTEST TEST-END | a`, + "expected": { "pass": 0, "unexpected": 1, "random": 0, "skip": 0 }, + "expected_images": 2, + }, + { "name": "reftest run with whitespace compressed (Treeherder; Bug 1084322)", + "log": ` REFTEST TEST-START | a +REFTEST TEST-LOAD | a | 0 / 1 (0%) +REFTEST TEST-LOAD | b | 0 / 1 (0%) +REFTEST TEST-UNEXPECTED-FAIL | a | image comparison (==), max difference: 106, number of differing pixels: 800 +REFTEST REFTEST IMAGE 1 (TEST): data:image/png;base64, +REFTEST REFTEST IMAGE 2 (REFERENCE): data:image/png;base64, +REFTEST REFTEST TEST-END | a`, + "expected": { "pass": 0, "unexpected": 1, "random": 0, "skip": 0 }, + "expected_images": 2, + }, + ]; + + var current_test = 0; + + // Override the build_viewer function invoked at the end of process_log to + // actually just check the results of parsing. + build_viewer = function() { + var expected = testcases[current_test].expected; + var expected_images = testcases[current_test].expected_images; + for (var result of gTestItems) { + for (let type in expected) { // type is "pass", "unexpected" etc. + if (result[type]) { + expected[type]--; + } + } + } + var failed = false; + for (let type in expected) { + if (expected[type] != 0) { + console.log(`Failure: for testcase ${testcases[current_test].name} got ${expected[type]} fewer ${type} results than expected!`); + failed = true; + } + } + + let total_images = 0; + for (var result of gTestItems) { + total_images += result.images.length; + } + if (total_images !== expected_images) { + console.log(`Failure: for testcase ${testcases[current_test].name} got ${total_images} images, expected ${expected_images}`); + failed = true; + } + + if (!failed) { + console.log(`Success for testcase ${testcases[current_test].name}`); + } + }; + + while (current_test < testcases.length) { + process_log(testcases[current_test].log); + current_test++; + } +} + +function process_log(contents) { + var lines = contents.split(/[\r\n]+/); + gTestItems = []; + for (var j in lines) { + + // !!!!!! + // When making any changes to this code, please add a test to the + // test_parsing function above, and ensure all existing tests pass. + // !!!!!! + + var line = lines[j]; + // Ignore duplicated output in logcat. + if (line.match(/I\/Gecko.*?REFTEST/)) + continue; + var match = line.match(/^.*?(?:REFTEST\s+)+(.*)$/); + if (!match) { + // WPT reftests don't always have the "REFTEST" prefix but do have + // mozharness prefixing. Trying to match both prefixes optionally with a + // single regex either makes an unreadable mess or matches everything so + // we do them separately. + match = line.match(/^(?:.*? (?:INFO|ERROR) -\s+)(.*)$/); + } + if (match) + line = match[1]; + match = line.match(/^(TEST-PASS|TEST-UNEXPECTED-PASS|TEST-FAIL|TEST-KNOWN-FAIL|TEST-UNEXPECTED-FAIL|TEST-DEBUG-INFO|TEST-KNOWN-INTERMITTENT-FAIL|TEST-KNOWN-INTERMITTENT-PASS)(\(EXPECTED RANDOM\)|) \| ([^\|]+)(?: \|(.*)|$)/); + if (match) { + var state = match[1]; + var random = match[2]; + var url = match[3]; + var extra = match[4]; + gTestItems.push( + { + pass: !state.match(/DEBUG-INFO$|FAIL$/), + // only one of the following three should ever be true + unexpected: !!state.match(/^TEST-UNEXPECTED/), + random: (random == "(EXPECTED RANDOM)" || state == "TEST-KNOWN-INTERMITTENT-FAIL" || state == "TEST-KNOWN-INTERMITTENT-PASS"), + skip: (extra == " (SKIP)"), + url: url, + images: [], + imageLabels: [] + }); + continue; + } + match = line.match(/^IMAGE([^:]*): (data:.*)$/); + if (match) { + var item = gTestItems[gTestItems.length - 1]; + item.images.push(match[2]); + item.imageLabels.push(match[1]); + } + } + + build_viewer(); +} + +function build_viewer() { + if (gTestItems.length == 0) { + show_phase("entry"); + return; + } + + var cell = ID("itemlist"); + while (cell.childNodes.length > 0) + cell.removeChild(cell.childNodes[cell.childNodes.length - 1]); + + var table = document.createElement("table"); + var tbody = document.createElement("tbody"); + table.appendChild(tbody); + + for (var i in gTestItems) { + var item = gTestItems[i]; + + // optional url filter for only showing unexpected results + if (parseInt(gParams.only_show_unexpected) && !item.unexpected) + continue; + + // XXX regardless skip expected pass items until we have filtering UI + if (item.pass && !item.unexpected) + continue; + + var tr = document.createElement("tr"); + var rowclass = item.pass ? "pass" : "fail"; + var td; + var text; + + td = document.createElement("td"); + text = ""; + if (item.unexpected) { text += "!"; rowclass += " unexpected"; } + if (item.random) { text += "R"; rowclass += " random"; } + if (item.skip) { text += "S"; rowclass += " skip"; } + td.appendChild(document.createTextNode(text)); + tr.appendChild(td); + + td = document.createElement("td"); + td.id = "item" + i; + td.className = "url"; + // Only display part of URL after "/mozilla/". + var match = item.url.match(/\/mozilla\/(.*)/); + text = document.createTextNode(match ? match[1] : item.url); + if (item.images.length > 0) { + var a = document.createElement("a"); + a.href = "javascript:show_images(" + i + ")"; + a.appendChild(text); + td.appendChild(a); + } else { + td.appendChild(text); + } + tr.appendChild(td); + + tbody.appendChild(tr); + } + + cell.appendChild(table); + + show_phase("viewer"); +} + +function get_image_data(src, whenReady) { + var img = new Image(); + img.onload = function() { + var canvas = document.createElement("canvas"); + canvas.width = img.naturalWidth; + canvas.height = img.naturalHeight; + + var ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0); + + whenReady(ctx.getImageData(0, 0, img.naturalWidth, img.naturalHeight)); + }; + img.src = src; +} + +function sync_svg_size(imageData) { + // We need the size of the 'svg' and its 'image' elements to match the size + // of the ImageData objects that we're going to read pixels from or else our + // magnify() function will be very broken. + ID("svg").setAttribute("width", imageData.width); + ID("svg").setAttribute("height", imageData.height); +} + +function show_images(i) { + var item = gTestItems[i]; + var cell = ID("images"); + + // Remove activeitem class from any existing elements + var activeItems = document.querySelectorAll(".activeitem"); + for (var activeItemIdx = activeItems.length; activeItemIdx-- != 0;) { + activeItems[activeItemIdx].classList.remove("activeitem"); + } + + ID("item" + i).classList.add("activeitem"); + ID("image1").style.display = ""; + ID("image2").style.display = "none"; + ID("diffrect").style.display = "none"; + ID("imgcontrols").reset(); + ID("pixel-differences").textContent = ""; + + ID("image1").setAttributeNS(XLINK_NS, "xlink:href", item.images[0]); + // Making the href be #image1 doesn't seem to work + ID("feimage1").setAttributeNS(XLINK_NS, "xlink:href", item.images[0]); + if (item.images.length == 1) { + ID("imgcontrols").style.display = "none"; + } else { + ID("imgcontrols").style.display = ""; + + ID("image2").setAttributeNS(XLINK_NS, "xlink:href", item.images[1]); + // Making the href be #image2 doesn't seem to work + ID("feimage2").setAttributeNS(XLINK_NS, "xlink:href", item.images[1]); + + ID("label1").textContent = 'Image ' + item.imageLabels[0]; + ID("label2").textContent = 'Image ' + item.imageLabels[1]; + } + + cell.style.display = ""; + + let loaded = [false, false]; + + function images_loaded(id) { + loaded[id] = true; + if (loaded.every(x => x)) { + update_pixel_difference_text() + } + } + + get_image_data(item.images[0], function(data) { gImage1Data = data; sync_svg_size(gImage1Data); images_loaded(0)}); + get_image_data(item.images[1], function(data) { gImage2Data = data; images_loaded(1)}); + +} + +function update_pixel_difference_text() { + let differenceText; + if (gImage1Data.height !== gImage2Data.height || + gImage1Data.width !== gImage2Data.width) { + differenceText = "Images are different sizes" + } else { + let [numPixels, maxPerChannel] = get_pixel_differences(); + if (!numPixels) { + differenceText = "Images are identical"; + } else { + differenceText = `Maximum difference per channel ${maxPerChannel}, ${numPixels} pixels differ`; + } + } + // Disable this for now, because per bug 1633504, the numbers may be + // inaccurate and dependent on the browser's configuration. + // ID("pixel-differences").textContent = differenceText; +} + +function get_pixel_differences() { + let numPixels = 0; + let maxPerChannel = 0; + for (var i=0; i<gImage1Data.data.length; i+=4) { + let r1 = gImage1Data.data[i]; + let r2 = gImage2Data.data[i]; + let g1 = gImage1Data.data[i+1]; + let g2 = gImage2Data.data[i+1]; + let b1 = gImage1Data.data[i+2]; + let b2 = gImage2Data.data[i+2]; + // Ignore alpha. + if (r1 == r2 && g1 == g2 && b1 == b2) { + continue; + } + numPixels += 1; + let maxDiff = Math.max(Math.abs(r1-r2), + Math.abs(g1-g2), + Math.abs(b1-b2)); + if (maxDiff > maxPerChannel) { + maxPerChannel = maxDiff + } + } + return [numPixels, maxPerChannel]; +} + +function show_image(i) { + if (i == 1) { + ID("image1").style.display = ""; + ID("image2").style.display = "none"; + } else { + ID("image1").style.display = "none"; + ID("image2").style.display = ""; + } +} + +function handle_keyboard_shortcut(event) { + switch (event.charCode) { + case 49: // "1" key + document.getElementById("radio1").checked = true; + show_image(1); + break; + case 50: // "2" key + document.getElementById("radio2").checked = true; + show_image(2); + break; + case 100: // "d" key + document.getElementById("differences").click(); + break; + case 112: // "p" key + shift_images(-1); + break; + case 110: // "n" key + shift_images(1); + break; + } +} + +function handle_keydown(event) { + switch (event.keyCode) { + case 37: // left arrow + move_pixel(-1, 0); + break; + case 38: // up arrow + move_pixel(0,-1); + break; + case 39: // right arrow + move_pixel(1, 0); + break; + case 40: // down arrow + move_pixel(0, 1); + break; + } +} + +function shift_images(dir) { + var activeItem = document.querySelector(".activeitem"); + if (!activeItem) { + return; + } + for (var elm = activeItem; elm; elm = elm.parentElement) { + if (elm.tagName != "tr") { + continue; + } + elm = dir > 0 ? elm.nextElementSibling : elm.previousElementSibling; + if (elm) { + elm.getElementsByTagName("a")[0].click(); + } + return; + } +} + +function show_differences(cb) { + ID("diffrect").style.display = cb.checked ? "" : "none"; +} + +function flash_pixels(on) { + var stroke = on ? "red" : "black"; + var strokeWidth = on ? "2px" : "1px"; + for (var i = 0; i < gFlashingPixels.length; i++) { + gFlashingPixels[i].setAttribute("stroke", stroke); + gFlashingPixels[i].setAttribute("stroke-width", strokeWidth); + } +} + +function cursor_point(evt) { + var m = evt.target.getScreenCTM().inverse(); + var p = ID("svg").createSVGPoint(); + p.x = evt.clientX; + p.y = evt.clientY; + p = p.matrixTransform(m); + return { x: Math.floor(p.x), y: Math.floor(p.y) }; +} + +function hex2(i) { + return (i < 16 ? "0" : "") + i.toString(16); +} + +function canvas_pixel_as_hex(data, x, y) { + var offset = (y * data.width + x) * 4; + var r = data.data[offset]; + var g = data.data[offset + 1]; + var b = data.data[offset + 2]; + return "#" + hex2(r) + hex2(g) + hex2(b); +} + +function hex_as_rgb(hex) { + return "rgb(" + [parseInt(hex.substring(1, 3), 16), parseInt(hex.substring(3, 5), 16), parseInt(hex.substring(5, 7), 16)] + ")"; +} + +function magnify(evt) { + var { x: x, y: y } = cursor_point(evt); + do_magnify(x, y); +} + +function do_magnify(x, y) { + var centerPixelColor1, centerPixelColor2; + + var dx_lo = -Math.floor(gMagWidth / 2); + var dx_hi = Math.floor(gMagWidth / 2); + var dy_lo = -Math.floor(gMagHeight / 2); + var dy_hi = Math.floor(gMagHeight / 2); + + flash_pixels(false); + gFlashingPixels = []; + for (var j = dy_lo; j <= dy_hi; j++) { + for (var i = dx_lo; i <= dx_hi; i++) { + var px = x + i; + var py = y + j; + var p1 = gMagPixPaths[i + dx_hi][j + dy_hi][0]; + var p2 = gMagPixPaths[i + dx_hi][j + dy_hi][1]; + // Here we just use the dimensions of gImage1Data since we expect test + // and reference to have the same dimensions. + if (px < 0 || py < 0 || px >= gImage1Data.width || py >= gImage1Data.height) { + p1.setAttribute("fill", "#aaa"); + p2.setAttribute("fill", "#888"); + } else { + var color1 = canvas_pixel_as_hex(gImage1Data, x + i, y + j); + var color2 = canvas_pixel_as_hex(gImage2Data, x + i, y + j); + p1.setAttribute("fill", color1); + p2.setAttribute("fill", color2); + if (color1 != color2) { + gFlashingPixels.push(p1, p2); + p1.parentNode.appendChild(p1); + p2.parentNode.appendChild(p2); + } + if (i == 0 && j == 0) { + centerPixelColor1 = color1; + centerPixelColor2 = color2; + } + } + } + } + flash_pixels(true); + show_pixelinfo(x, y, centerPixelColor1, hex_as_rgb(centerPixelColor1), centerPixelColor2, hex_as_rgb(centerPixelColor2)); +} + +function show_pixelinfo(x, y, pix1rgb, pix1hex, pix2rgb, pix2hex) { + var pixelinfo = ID("pixelinfo"); + ID("coords").textContent = [x, y]; + ID("pix1hex").textContent = pix1hex; + ID("pix1rgb").textContent = pix1rgb; + ID("pix2hex").textContent = pix2hex; + ID("pix2rgb").textContent = pix2rgb; +} + +function move_pixel(deltax, deltay) { + coords = ID("coords").textContent.split(','); + x = parseInt(coords[0]); + y = parseInt(coords[1]); + if (isNaN(x) || isNaN(y)) { + return; + } + x = x + deltax; + y = y + deltay; + if (x >= 0 && y >= 0 && x < gImage1Data.width && y < gImage1Data.height) { + do_magnify(x, y); + } +} + + ]]></script> + +</head> +<body onload="load()"> + +<div id="entry"> + +<h1>Reftest analyzer: load reftest log</h1> + +<p>Either paste your log into this textarea:<br /> +<textarea cols="80" rows="10" id="logentry"/><br/> +<input type="button" value="Process pasted log" onclick="log_pasted()" /></p> + +<p>... or load it from a file:<br/> +<input type="file" id="fileentry" onchange="fileentry_changed()" /> +</p> +</div> + +<div id="loading" style="display:none">Loading log...</div> + +<div id="viewer" style="display:none"> + <div id="pixelarea"> + <div id="pixelinfo"> + <table> + <tbody> + <tr><th>Pixel at:</th><td colspan="2" id="coords"/></tr> + <tr><th>Image 1:</th><td id="pix1rgb"></td><td id="pix1hex"></td></tr> + <tr><th>Image 2:</th><td id="pix2rgb"></td><td id="pix2hex"></td></tr> + </tbody> + </table> + <div> + <div id="pixelhint">★ + <div> + <p>Move the mouse over the reftest image on the right to show + magnified pixels on the left. The color information above is for + the pixel centered in the magnified view.</p> + <p>Image 1 is shown in the upper triangle of each pixel and Image 2 + is shown in the lower triangle.</p> + </div> + </div> + </div> + </div> + <div id="magnification"> + <svg xmlns="http://www.w3.org/2000/svg" width="84" height="84" shape-rendering="optimizeSpeed"> + <g id="mag"/> + </svg> + </div> + </div> + <div id="itemlist"></div> + <div id="images" style="display:none"> + <form id="imgcontrols"> + <input id="radio1" type="radio" name="which" value="0" onchange="show_image(1)" checked="checked" /><label id="label1" title="1" for="radio1">Image 1</label> + <input id="radio2" type="radio" name="which" value="1" onchange="show_image(2)" /><label id="label2" title="2" for="radio2">Image 2</label> + <label><input id="differences" type="checkbox" onchange="show_differences(this)" />Circle differences</label> + </form> + <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800" height="1000" id="svg"> + <defs> + <!-- use sRGB to avoid loss of data --> + <filter id="showDifferences" x="0%" y="0%" width="100%" height="100%" + style="color-interpolation-filters: sRGB"> + <feImage id="feimage1" result="img1" xlink:href="#image1" /> + <feImage id="feimage2" result="img2" xlink:href="#image2" /> + <!-- inv1 and inv2 are the images with RGB inverted --> + <feComponentTransfer result="inv1" in="img1"> + <feFuncR type="linear" slope="-1" intercept="1" /> + <feFuncG type="linear" slope="-1" intercept="1" /> + <feFuncB type="linear" slope="-1" intercept="1" /> + </feComponentTransfer> + <feComponentTransfer result="inv2" in="img2"> + <feFuncR type="linear" slope="-1" intercept="1" /> + <feFuncG type="linear" slope="-1" intercept="1" /> + <feFuncB type="linear" slope="-1" intercept="1" /> + </feComponentTransfer> + <!-- w1 will have non-white pixels anywhere that img2 + is brighter than img1, and w2 for the reverse. + It would be nice not to have to go through these + intermediate states, but feComposite + type="arithmetic" can't transform the RGB channels + and leave the alpha channel untouched. --> + <feComposite result="w1" in="img1" in2="inv2" operator="arithmetic" k2="1" k3="1" /> + <feComposite result="w2" in="img2" in2="inv1" operator="arithmetic" k2="1" k3="1" /> + <!-- c1 will have non-black pixels anywhere that img2 + is brighter than img1, and c2 for the reverse --> + <feComponentTransfer result="c1" in="w1"> + <feFuncR type="linear" slope="-1" intercept="1" /> + <feFuncG type="linear" slope="-1" intercept="1" /> + <feFuncB type="linear" slope="-1" intercept="1" /> + </feComponentTransfer> + <feComponentTransfer result="c2" in="w2"> + <feFuncR type="linear" slope="-1" intercept="1" /> + <feFuncG type="linear" slope="-1" intercept="1" /> + <feFuncB type="linear" slope="-1" intercept="1" /> + </feComponentTransfer> + <!-- c will be nonblack (and fully on) for every pixel+component where there are differences --> + <feComposite result="c" in="c1" in2="c2" operator="arithmetic" k2="255" k3="255" /> + <!-- a will be opaque for every pixel with differences and transparent for all others --> + <feColorMatrix result="a" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0" /> + + <!-- a, dilated by 1 pixel --> + <feMorphology result="dila1" in="a" operator="dilate" radius="1" /> + <!-- a, dilated by 2 pixels --> + <feMorphology result="dila2" in="dila1" operator="dilate" radius="1" /> + + <!-- all the pixels in the 2-pixel dilation of a but not in the 1-pixel dilation, to highlight the diffs --> + <feComposite result="highlight" in="dila2" in2="dila1" operator="out" /> + + <feFlood result="red" flood-color="red" /> + <feComposite result="redhighlight" in="red" in2="highlight" operator="in" /> + <feFlood result="black" flood-color="black" flood-opacity="0.5" /> + <feMerge> + <feMergeNode in="black" /> + <feMergeNode in="redhighlight" /> + </feMerge> + </filter> + </defs> + <g onmousemove="magnify(evt)"> + <image x="0" y="0" width="100%" height="100%" id="image1" /> + <image x="0" y="0" width="100%" height="100%" id="image2" /> + </g> + <rect id="diffrect" filter="url(#showDifferences)" pointer-events="none" x="0" y="0" width="100%" height="100%" /> + </svg> + <div id="pixel-differences"></div> + </div> +</div> + +</body> +</html> diff --git a/test/fixtures/wpt/encoding/legacy-mb-schinese/gb18030/gb18030-decoder.any.js b/test/fixtures/wpt/encoding/legacy-mb-schinese/gb18030/gb18030-decoder.any.js new file mode 100644 index 00000000000000..99a0253ba6b680 --- /dev/null +++ b/test/fixtures/wpt/encoding/legacy-mb-schinese/gb18030/gb18030-decoder.any.js @@ -0,0 +1,63 @@ +// META: script=./resources/ranges.js + +const decode = (input, output, desc) => { + test(function () { + for (const encoding of ["gb18030", "gbk"]) { + assert_equals( + new TextDecoder(encoding).decode(new Uint8Array(input)), + output, + ); + } + }, "gb18030 decoder: " + desc); +}; + +decode([115], "s", "ASCII"); +decode([0x80], "\u20AC", "euro"); +decode([0xFF], "\uFFFD", "initial byte out of accepted ranges"); +decode([0x81], "\uFFFD", "end of queue, gb18030 first not 0"); +decode([0x81, 0x28], "\ufffd(", "two bytes 0x81 0x28"); +decode([0x81, 0x40], "\u4E02", "two bytes 0x81 0x40"); +decode([0x81, 0x7E], "\u4E8A", "two bytes 0x81 0x7e"); +decode([0x81, 0x7F], "\ufffd\u007f", "two bytes 0x81 0x7f"); +decode([0x81, 0x80], "\u4E90", "two bytes 0x81 0x80"); +decode([0x81, 0xFE], "\u4FA2", "two bytes 0x81 0xFE"); +decode([0x81, 0xFF], "\ufffd", "two bytes 0x81 0xFF"); +decode([0xFE, 0x40], "\uFA0C", "two bytes 0xFE 0x40"); +decode([0xFE, 0xFE], "\uE4C5", "two bytes 0xFE 0xFE"); +decode([0xFE, 0xFF], "\ufffd", "two bytes 0xFE 0xFF"); +decode([0x81, 0x30], "\ufffd", "two bytes 0x81 0x30"); +decode([0x81, 0x30, 0xFE], "\ufffd", "three bytes 0x81 0x30 0xFE"); +decode([0x81, 0x30, 0xFF], "\ufffd0\ufffd", "three bytes 0x81 0x30 0xFF"); +decode( + [0x81, 0x30, 0xFE, 0x29], + "\ufffd0\ufffd)", + "four bytes 0x81 0x30 0xFE 0x29", +); +decode([0xFE, 0x39, 0xFE, 0x39], "\ufffd", "four bytes 0xFE 0x39 0xFE 0x39"); +decode([0x81, 0x35, 0xF4, 0x36], "\u1E3E", "pointer 7458"); +decode([0x81, 0x35, 0xF4, 0x37], "\ue7c7", "pointer 7457"); +decode([0x81, 0x35, 0xF4, 0x38], "\u1E40", "pointer 7459"); +decode([0x84, 0x31, 0xA4, 0x39], "\uffff", "pointer 39419"); +decode([0x84, 0x31, 0xA5, 0x30], "\ufffd", "pointer 39420"); +decode([0x8F, 0x39, 0xFE, 0x39], "\ufffd", "pointer 189999"); +decode([0x90, 0x30, 0x81, 0x30], "\u{10000}", "pointer 189000"); +decode([0xE3, 0x32, 0x9A, 0x35], "\u{10FFFF}", "pointer 1237575"); +decode([0xE3, 0x32, 0x9A, 0x36], "\ufffd", "pointer 1237576"); +decode([0x83, 0x36, 0xC8, 0x30], "\uE7C8", "legacy ICU special case 1"); +decode([0xA1, 0xAD], "\u2026", "legacy ICU special case 2"); +decode([0xA1, 0xAB], "\uFF5E", "legacy ICU special case 3"); + +let i = 0; +for (const range of ranges) { + const pointer = range[0]; + decode( + [ + Math.floor(pointer / 12600) + 0x81, + Math.floor((pointer % 12600) / 1260) + 0x30, + Math.floor((pointer % 1260) / 10) + 0x81, + pointer % 10 + 0x30, + ], + range[1], + "range " + i++, + ); +} diff --git a/test/fixtures/wpt/encoding/legacy-mb-schinese/gbk/gbk-decoder.any.js b/test/fixtures/wpt/encoding/legacy-mb-schinese/gbk/gbk-decoder.any.js new file mode 100644 index 00000000000000..c0221480da156d --- /dev/null +++ b/test/fixtures/wpt/encoding/legacy-mb-schinese/gbk/gbk-decoder.any.js @@ -0,0 +1,28 @@ +const gbkPointers = [ + 6432, 7533, 7536, 7672, 7673, 7674, 7675, 7676, 7677, 7678, 7679, 7680, 7681, 7682, 7683, 7684, + 23766, 23770, 23771, 23772, 23773, 23774, 23776, 23777, 23778, 23779, 23780, 23781, 23782, 23784, 23785, 23786, + 23787, 23790, 23791, 23792, 23793, 23796, 23797, 23798, 23799, 23800, 23801, 23802, 23803, 23805, 23806, 23807, + 23808, 23809, 23810, 23811, 23813, 23814, 23815, 23816, 23817, 23818, 23819, 23820, 23821, 23822, 23823, 23824, + 23825, 23826, 23827, 23828, 23831, 23832, 23833, 23834, 23835, 23836, 23837, 23838, 23839, 23840, 23841, 23842, + 23843, 23844 +]; +const codePoints = [ + 0x20ac, 0x1e3f, 0x01f9, 0x303e, 0x2ff0, 0x2ff1, 0x2ff2, 0x2ff3, 0x2ff4, 0x2ff5, 0x2ff6, 0x2ff7, 0x2ff8, 0x2ff9, 0x2ffa, 0x2ffb, + 0x2e81, 0x2e84, 0x3473, 0x3447, 0x2e88, 0x2e8b, 0x359e, 0x361a, 0x360e, 0x2e8c, 0x2e97, 0x396e, 0x3918, 0x39cf, 0x39df, 0x3a73, + 0x39d0, 0x3b4e, 0x3c6e, 0x3ce0, 0x2ea7, 0x2eaa, 0x4056, 0x415f, 0x2eae, 0x4337, 0x2eb3, 0x2eb6, 0x2eb7, 0x43b1, 0x43ac, 0x2ebb, + 0x43dd, 0x44d6, 0x4661, 0x464c, 0x4723, 0x4729, 0x477c, 0x478d, 0x2eca, 0x4947, 0x497a, 0x497d, 0x4982, 0x4983, 0x4985, 0x4986, + 0x499f, 0x499b, 0x49b7, 0x49b6, 0x4ca3, 0x4c9f, 0x4ca0, 0x4ca1, 0x4c77, 0x4ca2, 0x4d13, 0x4d14, 0x4d15, 0x4d16, 0x4d17, 0x4d18, + 0x4d19, 0x4dae +]; + +for (let i = 0; i < gbkPointers.length; i++) { + const pointer = gbkPointers[i]; + test(function() { + const lead = pointer / 190 + 0x81; + const trail = pointer % 190; + const offset = trail < 0x3F ? 0x40 : 0x41; + const encoded = [lead, trail + offset]; + const decoded = new TextDecoder("GBK").decode(new Uint8Array(encoded)).charCodeAt(0); + assert_equals(decoded, codePoints[i]); + }, "gbk pointer: " + pointer) +} diff --git a/test/fixtures/wpt/encoding/textdecoder-arguments.any.js b/test/fixtures/wpt/encoding/textdecoder-arguments.any.js new file mode 100644 index 00000000000000..f469dcd30eaf87 --- /dev/null +++ b/test/fixtures/wpt/encoding/textdecoder-arguments.any.js @@ -0,0 +1,49 @@ +// META: title=Encoding API: TextDecoder decode() optional arguments + +test(t => { + const decoder = new TextDecoder(); + + // Just passing nothing. + assert_equals( + decoder.decode(undefined), '', + 'Undefined as first arg should decode to empty string'); + + // Flushing an incomplete sequence. + decoder.decode(new Uint8Array([0xc9]), {stream: true}); + assert_equals( + decoder.decode(undefined), '\uFFFD', + 'Undefined as first arg should flush the stream'); + +}, 'TextDecoder decode() with explicit undefined'); + +test(t => { + const decoder = new TextDecoder(); + + // Just passing nothing. + assert_equals( + decoder.decode(undefined, undefined), '', + 'Undefined as first arg should decode to empty string'); + + // Flushing an incomplete sequence. + decoder.decode(new Uint8Array([0xc9]), {stream: true}); + assert_equals( + decoder.decode(undefined, undefined), '\uFFFD', + 'Undefined as first arg should flush the stream'); + +}, 'TextDecoder decode() with undefined and undefined'); + +test(t => { + const decoder = new TextDecoder(); + + // Just passing nothing. + assert_equals( + decoder.decode(undefined, {}), '', + 'Undefined as first arg should decode to empty string'); + + // Flushing an incomplete sequence. + decoder.decode(new Uint8Array([0xc9]), {stream: true}); + assert_equals( + decoder.decode(undefined, {}), '\uFFFD', + 'Undefined as first arg should flush the stream'); + +}, 'TextDecoder decode() with undefined and options'); diff --git a/test/fixtures/wpt/encoding/textdecoder-labels.any.js b/test/fixtures/wpt/encoding/textdecoder-labels.any.js index ed407a32547923..efc4ae2d2ea586 100644 --- a/test/fixtures/wpt/encoding/textdecoder-labels.any.js +++ b/test/fixtures/wpt/encoding/textdecoder-labels.any.js @@ -1,5 +1,6 @@ // META: title=Encoding API: Encoding labels // META: script=resources/encodings.js +// META: timeout=long var whitespace = [' ', '\t', '\n', '\f', '\r']; encodings_table.forEach(function(section) { diff --git a/test/fixtures/wpt/hr-time/basic.any.js b/test/fixtures/wpt/hr-time/basic.any.js index 364dd81a344818..4cd6e42dfc2274 100644 --- a/test/fixtures/wpt/hr-time/basic.any.js +++ b/test/fixtures/wpt/hr-time/basic.any.js @@ -26,3 +26,12 @@ async_test(function() { this.done(); }, 2000); }, 'High resolution time has approximately the right relative magnitude'); + +test(function() { + var didHandle = false; + self.performance.addEventListener("testEvent", function() { + didHandle = true; + }, { once: true} ); + self.performance.dispatchEvent(new Event("testEvent")); + assert_true(didHandle, "Performance extends EventTarget, so event dispatching should work."); +}, "Performance interface extends EventTarget."); diff --git a/test/fixtures/wpt/html/webappapis/timers/negative-setinterval.any.js b/test/fixtures/wpt/html/webappapis/timers/negative-setinterval.any.js new file mode 100644 index 00000000000000..5646140c2a45f3 --- /dev/null +++ b/test/fixtures/wpt/html/webappapis/timers/negative-setinterval.any.js @@ -0,0 +1,12 @@ +setup({ single_test: true }); +var i = 0; +var interval; +function next() { + i++; + if (i === 20) { + clearInterval(interval); + done(); + } +} +setTimeout(assert_unreached, 1000); +interval = setInterval(next, -100); diff --git a/test/fixtures/wpt/html/webappapis/timers/negative-settimeout.any.js b/test/fixtures/wpt/html/webappapis/timers/negative-settimeout.any.js new file mode 100644 index 00000000000000..da191f1bf00d92 --- /dev/null +++ b/test/fixtures/wpt/html/webappapis/timers/negative-settimeout.any.js @@ -0,0 +1,3 @@ +setup({ single_test: true }); +setTimeout(done, -100); +setTimeout(assert_unreached, 10); diff --git a/test/fixtures/wpt/html/webappapis/timers/type-long-setinterval.any.js b/test/fixtures/wpt/html/webappapis/timers/type-long-setinterval.any.js new file mode 100644 index 00000000000000..164527f18b1e6f --- /dev/null +++ b/test/fixtures/wpt/html/webappapis/timers/type-long-setinterval.any.js @@ -0,0 +1,8 @@ +setup({ single_test: true }); +var interval; +function next() { + clearInterval(interval); + done(); +} +interval = setInterval(next, Math.pow(2, 32)); +setTimeout(assert_unreached, 100); diff --git a/test/fixtures/wpt/html/webappapis/timers/type-long-settimeout.any.js b/test/fixtures/wpt/html/webappapis/timers/type-long-settimeout.any.js new file mode 100644 index 00000000000000..9092f13f3b1000 --- /dev/null +++ b/test/fixtures/wpt/html/webappapis/timers/type-long-settimeout.any.js @@ -0,0 +1,3 @@ +setup({ single_test: true }); +setTimeout(done, Math.pow(2, 32)); +setTimeout(assert_unreached, 100); diff --git a/test/fixtures/wpt/interfaces/html.idl b/test/fixtures/wpt/interfaces/html.idl index dfe4e1e586b5a2..08cc505da91ba0 100644 --- a/test/fixtures/wpt/interfaces/html.idl +++ b/test/fixtures/wpt/interfaces/html.idl @@ -1397,11 +1397,11 @@ interface mixin CanvasDrawImage { interface mixin CanvasImageData { // pixel manipulation - ImageData createImageData(long sw, long sh); + ImageData createImageData([EnforceRange] long sw, [EnforceRange] long sh); ImageData createImageData(ImageData imagedata); - ImageData getImageData(long sx, long sy, long sw, long sh); - undefined putImageData(ImageData imagedata, long dx, long dy); - undefined putImageData(ImageData imagedata, long dx, long dy, long dirtyX, long dirtyY, long dirtyWidth, long dirtyHeight); + ImageData getImageData([EnforceRange] long sx, [EnforceRange] long sy, [EnforceRange] long sw, [EnforceRange] long sh); + undefined putImageData(ImageData imagedata, [EnforceRange] long dx, [EnforceRange] long dy); + undefined putImageData(ImageData imagedata, [EnforceRange] long dx, [EnforceRange] long dy, [EnforceRange] long dirtyX, [EnforceRange] long dirtyY, [EnforceRange] long dirtyWidth, [EnforceRange] long dirtyHeight); }; enum CanvasLineCap { "butt", "round", "square" }; @@ -1440,8 +1440,8 @@ interface mixin CanvasPath { undefined bezierCurveTo(unrestricted double cp1x, unrestricted double cp1y, unrestricted double cp2x, unrestricted double cp2y, unrestricted double x, unrestricted double y); undefined arcTo(unrestricted double x1, unrestricted double y1, unrestricted double x2, unrestricted double y2, unrestricted double radius); undefined rect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h); - undefined arc(unrestricted double x, unrestricted double y, unrestricted double radius, unrestricted double startAngle, unrestricted double endAngle, optional boolean anticlockwise = false); - undefined ellipse(unrestricted double x, unrestricted double y, unrestricted double radiusX, unrestricted double radiusY, unrestricted double rotation, unrestricted double startAngle, unrestricted double endAngle, optional boolean anticlockwise = false); + undefined arc(unrestricted double x, unrestricted double y, unrestricted double radius, unrestricted double startAngle, unrestricted double endAngle, optional boolean counterclockwise = false); + undefined ellipse(unrestricted double x, unrestricted double y, unrestricted double radiusX, unrestricted double radiusY, unrestricted double rotation, unrestricted double startAngle, unrestricted double endAngle, optional boolean counterclockwise = false); }; [Exposed=(Window,Worker)] @@ -2027,48 +2027,6 @@ interface mixin NavigatorCookies { readonly attribute boolean cookieEnabled; }; -interface mixin NavigatorPlugins { - [SameObject] readonly attribute PluginArray plugins; - [SameObject] readonly attribute MimeTypeArray mimeTypes; - boolean javaEnabled(); -}; - -[Exposed=Window, - LegacyUnenumerableNamedProperties] -interface PluginArray { - undefined refresh(optional boolean reload = false); - readonly attribute unsigned long length; - getter Plugin? item(unsigned long index); - getter Plugin? namedItem(DOMString name); -}; - -[Exposed=Window, - LegacyUnenumerableNamedProperties] -interface MimeTypeArray { - readonly attribute unsigned long length; - getter MimeType? item(unsigned long index); - getter MimeType? namedItem(DOMString name); -}; - -[Exposed=Window, - LegacyUnenumerableNamedProperties] -interface Plugin { - readonly attribute DOMString name; - readonly attribute DOMString description; - readonly attribute DOMString filename; - readonly attribute unsigned long length; - getter MimeType? item(unsigned long index); - getter MimeType? namedItem(DOMString name); -}; - -[Exposed=Window] -interface MimeType { - readonly attribute DOMString type; - readonly attribute DOMString description; - readonly attribute DOMString suffixes; // comma-separated - readonly attribute Plugin enabledPlugin; -}; - [Exposed=(Window,Worker), Serializable, Transferable] interface ImageBitmap { readonly attribute unsigned long width; @@ -2396,10 +2354,6 @@ interface HTMLMarqueeElement : HTMLElement { [CEReactions] attribute unsigned long vspace; [CEReactions] attribute DOMString width; - attribute EventHandler onbounce; - attribute EventHandler onfinish; - attribute EventHandler onstart; - undefined start(); undefined stop(); }; @@ -2678,3 +2632,42 @@ interface External { undefined AddSearchProvider(); undefined IsSearchProviderInstalled(); }; + +interface mixin NavigatorPlugins { + [SameObject] readonly attribute PluginArray plugins; + [SameObject] readonly attribute MimeTypeArray mimeTypes; + boolean javaEnabled(); +}; + +[Exposed=Window] +interface PluginArray { + undefined refresh(); + readonly attribute unsigned long length; + getter object? item(unsigned long index); + object? namedItem(DOMString name); +}; + +[Exposed=Window] +interface MimeTypeArray { + readonly attribute unsigned long length; + getter object? item(unsigned long index); + object? namedItem(DOMString name); +}; + +[Exposed=Window] +interface Plugin { + readonly attribute undefined name; + readonly attribute undefined description; + readonly attribute undefined filename; + readonly attribute undefined length; + getter undefined item(unsigned long index); + undefined namedItem(DOMString name); +}; + +[Exposed=Window] +interface MimeType { + readonly attribute undefined type; + readonly attribute undefined description; + readonly attribute undefined suffixes; + readonly attribute undefined enabledPlugin; +}; diff --git a/test/fixtures/wpt/resources/idlharness.js b/test/fixtures/wpt/resources/idlharness.js index 994a0d82ef444b..76131e7c9602b9 100644 --- a/test/fixtures/wpt/resources/idlharness.js +++ b/test/fixtures/wpt/resources/idlharness.js @@ -3483,6 +3483,22 @@ IdlNamespace.prototype.test_self = function () subsetTestByKey(this.name, test, () => { assert_equals(typeof namespaceObject, "object"); }, `${this.name} namespace: typeof is "object"`); + + subsetTestByKey(this.name, test, () => { + assert_equals( + Object.getOwnPropertyDescriptor(namespaceObject, "length"), + undefined, + "length property must be undefined" + ); + }, `${this.name} namespace: has no length property`); + + subsetTestByKey(this.name, test, () => { + assert_equals( + Object.getOwnPropertyDescriptor(namespaceObject, "name"), + undefined, + "name property must be undefined" + ); + }, `${this.name} namespace: has no name property`); }; IdlNamespace.prototype.test = function () @@ -3527,8 +3543,6 @@ IdlNamespace.prototype.test = function () function idl_test(srcs, deps, idl_setup_func) { return promise_test(function (t) { var idl_array = new IdlArray(); - srcs = (srcs instanceof Array) ? srcs : [srcs] || []; - deps = (deps instanceof Array) ? deps : [deps] || []; var setup_error = null; const validationIgnored = [ "constructor-member", diff --git a/test/fixtures/wpt/resources/test-only-api.js b/test/fixtures/wpt/resources/test-only-api.js index ef66e0e733f9c6..a66eb44ede7c15 100644 --- a/test/fixtures/wpt/resources/test-only-api.js +++ b/test/fixtures/wpt/resources/test-only-api.js @@ -29,72 +29,3 @@ function loadScript(path) { return p; } } - -/** - * A helper for Chromium-based browsers to load Mojo JS bindings - * - * This is an async function that works in both workers and windows. It first - * loads mojo_bindings.js, disables automatic dependency loading, and loads all - * resources sequentially. The promise resolves if everything loads - * successfully, or rejects if any exception is raised. If testharness.js is - * used, an uncaught exception will terminate the test with a harness error - * (unless `allow_uncaught_exception` is true), which is usually the desired - * behaviour. - * - * This function also works with Blink web tests loaded from file://, in which - * case file:// will be prepended to all '/gen/...' URLs. - * - * Only call this function if isChromiumBased === true. - * - * @param {Array.<string>} resources - A list of scripts to load: Mojo JS - * bindings should be of the form '/gen/../*.mojom.js' or - * '/gen/../*.mojom-lite.js' (requires `lite` to be true); the order does not - * matter. Do not include 'mojo_bindings.js' or 'mojo_bindings_lite.js'. - * @param {boolean=} lite - Whether the lite bindings (*.mojom-lite.js) are used - * (default is false). - * @returns {Promise} - */ -async function loadMojoResources(resources, lite = false) { - if (!isChromiumBased) { - throw new Error('MojoJS not enabled; start Chrome with --enable-blink-features=MojoJS,MojoJSTest'); - } - if (resources.length == 0) { - return; - } - - let genPrefix = ''; - if (self.location.pathname.includes('/web_tests/')) { - // Blink internal web tests - genPrefix = 'file://'; - } - - for (const path of resources) { - // We want to load mojo_bindings.js separately to set mojo.config. - if (path.endsWith('/mojo_bindings.js')) { - throw new Error('Do not load mojo_bindings.js explicitly.'); - } - if (path.endsWith('/mojo_bindings_lite.js')) { - throw new Error('Do not load mojo_bindings_lite.js explicitly.'); - } - if (lite) { - if (! /^\/gen\/.*\.mojom-lite\.js$/.test(path)) { - throw new Error(`Unrecognized resource path: ${path}`); - } - } else { - if (! /^\/gen\/.*\.mojom\.js$/.test(path)) { - throw new Error(`Unrecognized resource path: ${path}`); - } - } - } - - if (lite) { - await loadScript(genPrefix + '/gen/layout_test_data/mojo/public/js/mojo_bindings_lite.js'); - } else { - await loadScript(genPrefix + '/gen/layout_test_data/mojo/public/js/mojo_bindings.js'); - mojo.config.autoLoadMojomDeps = false; - } - - for (const path of resources) { - await loadScript(genPrefix + path); - } -} diff --git a/test/fixtures/wpt/resources/testharness.js b/test/fixtures/wpt/resources/testharness.js index f7fe7531710d23..c62f0917c173b0 100644 --- a/test/fixtures/wpt/resources/testharness.js +++ b/test/fixtures/wpt/resources/testharness.js @@ -15,7 +15,6 @@ policies and contribution forms [3]. (function (global_scope) { - var debug = false; // default timeout is 10 seconds, test can override if needed var settings = { output:true, @@ -24,7 +23,8 @@ policies and contribution forms [3]. "long":60000 }, test_timeout:null, - message_events: ["start", "test_state", "result", "completion"] + message_events: ["start", "test_state", "result", "completion"], + debug: false, }; var xhtml_ns = "http://www.w3.org/1999/xhtml"; @@ -87,14 +87,15 @@ policies and contribution forms [3]. test: test.structured_clone()}); }], completion: [add_completion_callback, remove_completion_callback, - function (tests, harness_status) { + function (tests, harness_status, asserts) { var cloned_tests = map(tests, function(test) { return test.structured_clone(); }); this_obj._dispatch("completion_callback", [tests, harness_status], {type: "complete", tests: cloned_tests, - status: harness_status.structured_clone()}); + status: harness_status.structured_clone(), + asserts: asserts.map(assert => assert.structured_clone())}); }] } @@ -131,11 +132,7 @@ policies and contribution forms [3]. if (has_selector) { try { w[selector].apply(undefined, callback_args); - } catch (e) { - if (debug) { - throw e; - } - } + } catch (e) {} } } if (supports_post_message(w) && w !== self) { @@ -192,8 +189,8 @@ policies and contribution forms [3]. this_obj.output_handler.show_status(); }); - add_completion_callback(function (tests, harness_status) { - this_obj.output_handler.show_results(tests, harness_status); + add_completion_callback(function (tests, harness_status, asserts_run) { + this_obj.output_handler.show_results(tests, harness_status, asserts_run); }); this.setup_messages(settings.message_events); }; @@ -314,14 +311,15 @@ policies and contribution forms [3]. }); }); add_completion_callback( - function(tests, harness_status) { + function(tests, harness_status, asserts) { this_obj._dispatch({ type: "complete", tests: map(tests, function(test) { return test.structured_clone(); }), - status: harness_status.structured_clone() + status: harness_status.structured_clone(), + asserts: asserts.map(assert => assert.structured_clone()), }); }); }; @@ -500,11 +498,7 @@ policies and contribution forms [3]. return new DedicatedWorkerTestEnvironment(); } - if (!('location' in global_scope)) { - return new ShellTestEnvironment(); - } - - throw new Error("Unsupported test environment"); + return new ShellTestEnvironment(); } var test_environment = create_test_environment(); @@ -1187,19 +1181,53 @@ policies and contribution forms [3]. * Assertions */ + function expose_assert(f, name) { + function assert_wrapper(...args) { + let status = Test.statuses.TIMEOUT; + let stack = null; + try { + if (settings.debug) { + console.debug("ASSERT", name, tests.current_test && tests.current_test.name, args); + } + if (tests.output) { + tests.set_assert(name, ...args); + } + const rv = f(...args); + status = Test.statuses.PASS; + return rv; + } catch(e) { + if (e instanceof AssertionError) { + status = Test.statuses.FAIL; + stack = e.stack; + } else { + status = Test.statuses.ERROR; + } + throw e; + } finally { + if (tests.output && !stack) { + stack = get_stack(); + } + if (tests.output) { + tests.set_assert_status(status, stack); + } + } + } + expose(assert_wrapper, name); + } + function assert_true(actual, description) { assert(actual === true, "assert_true", description, "expected true got ${actual}", {actual:actual}); } - expose(assert_true, "assert_true"); + expose_assert(assert_true, "assert_true"); function assert_false(actual, description) { assert(actual === false, "assert_false", description, "expected false got ${actual}", {actual:actual}); } - expose(assert_false, "assert_false"); + expose_assert(assert_false, "assert_false"); function same_value(x, y) { if (y !== y) { @@ -1229,7 +1257,7 @@ policies and contribution forms [3]. "expected ${expected} but got ${actual}", {expected:expected, actual:actual}); } - expose(assert_equals, "assert_equals"); + expose_assert(assert_equals, "assert_equals"); function assert_not_equals(actual, expected, description) { @@ -1241,7 +1269,7 @@ policies and contribution forms [3]. "got disallowed value ${actual}", {actual:actual}); } - expose(assert_not_equals, "assert_not_equals"); + expose_assert(assert_not_equals, "assert_not_equals"); function assert_in_array(actual, expected, description) { @@ -1249,7 +1277,7 @@ policies and contribution forms [3]. "value ${actual} not in array ${expected}", {actual:actual, expected:expected}); } - expose(assert_in_array, "assert_in_array"); + expose_assert(assert_in_array, "assert_in_array"); // This function was deprecated in July of 2015. // See https://github.com/web-platform-tests/wpt/issues/2033 @@ -1287,7 +1315,7 @@ policies and contribution forms [3]. } check_equal(actual, expected, []); } - expose(assert_object_equals, "assert_object_equals"); + expose_assert(assert_object_equals, "assert_object_equals"); function assert_array_equals(actual, expected, description) { @@ -1344,7 +1372,7 @@ policies and contribution forms [3]. arrayExpected:shorten_array(expected, i), arrayActual:shorten_array(actual, i)}); } } - expose(assert_array_equals, "assert_array_equals"); + expose_assert(assert_array_equals, "assert_array_equals"); function assert_array_approx_equals(actual, expected, epsilon, description) { @@ -1372,7 +1400,7 @@ policies and contribution forms [3]. {i:i, expected:expected[i], actual:actual[i], epsilon:epsilon}); } } - expose(assert_array_approx_equals, "assert_array_approx_equals"); + expose_assert(assert_array_approx_equals, "assert_array_approx_equals"); function assert_approx_equals(actual, expected, epsilon, description) { @@ -1395,7 +1423,7 @@ policies and contribution forms [3]. assert_equals(actual, expected); } } - expose(assert_approx_equals, "assert_approx_equals"); + expose_assert(assert_approx_equals, "assert_approx_equals"); function assert_less_than(actual, expected, description) { @@ -1412,7 +1440,7 @@ policies and contribution forms [3]. "expected a number less than ${expected} but got ${actual}", {expected:expected, actual:actual}); } - expose(assert_less_than, "assert_less_than"); + expose_assert(assert_less_than, "assert_less_than"); function assert_greater_than(actual, expected, description) { @@ -1429,7 +1457,7 @@ policies and contribution forms [3]. "expected a number greater than ${expected} but got ${actual}", {expected:expected, actual:actual}); } - expose(assert_greater_than, "assert_greater_than"); + expose_assert(assert_greater_than, "assert_greater_than"); function assert_between_exclusive(actual, lower, upper, description) { @@ -1447,7 +1475,7 @@ policies and contribution forms [3]. "and less than ${upper} but got ${actual}", {lower:lower, upper:upper, actual:actual}); } - expose(assert_between_exclusive, "assert_between_exclusive"); + expose_assert(assert_between_exclusive, "assert_between_exclusive"); function assert_less_than_equal(actual, expected, description) { @@ -1464,7 +1492,7 @@ policies and contribution forms [3]. "expected a number less than or equal to ${expected} but got ${actual}", {expected:expected, actual:actual}); } - expose(assert_less_than_equal, "assert_less_than_equal"); + expose_assert(assert_less_than_equal, "assert_less_than_equal"); function assert_greater_than_equal(actual, expected, description) { @@ -1481,7 +1509,7 @@ policies and contribution forms [3]. "expected a number greater than or equal to ${expected} but got ${actual}", {expected:expected, actual:actual}); } - expose(assert_greater_than_equal, "assert_greater_than_equal"); + expose_assert(assert_greater_than_equal, "assert_greater_than_equal"); function assert_between_inclusive(actual, lower, upper, description) { @@ -1499,7 +1527,7 @@ policies and contribution forms [3]. "and less than or equal to ${upper} but got ${actual}", {lower:lower, upper:upper, actual:actual}); } - expose(assert_between_inclusive, "assert_between_inclusive"); + expose_assert(assert_between_inclusive, "assert_between_inclusive"); function assert_regexp_match(actual, expected, description) { /* @@ -1510,7 +1538,7 @@ policies and contribution forms [3]. "expected ${expected} but got ${actual}", {expected:expected, actual:actual}); } - expose(assert_regexp_match, "assert_regexp_match"); + expose_assert(assert_regexp_match, "assert_regexp_match"); function assert_class_string(object, class_string, description) { var actual = {}.toString.call(object); @@ -1519,22 +1547,21 @@ policies and contribution forms [3]. "expected ${expected} but got ${actual}", {expected:expected, actual:actual}); } - expose(assert_class_string, "assert_class_string"); - + expose_assert(assert_class_string, "assert_class_string"); function assert_own_property(object, property_name, description) { assert(object.hasOwnProperty(property_name), "assert_own_property", description, "expected property ${p} missing", {p:property_name}); } - expose(assert_own_property, "assert_own_property"); + expose_assert(assert_own_property, "assert_own_property"); function assert_not_own_property(object, property_name, description) { assert(!object.hasOwnProperty(property_name), "assert_not_own_property", description, "unexpected property ${p} is found on object", {p:property_name}); } - expose(assert_not_own_property, "assert_not_own_property"); + expose_assert(assert_not_own_property, "assert_not_own_property"); function _assert_inherits(name) { return function (object, property_name, description) @@ -1560,8 +1587,8 @@ policies and contribution forms [3]. {p:property_name}); }; } - expose(_assert_inherits("assert_inherits"), "assert_inherits"); - expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute"); + expose_assert(_assert_inherits("assert_inherits"), "assert_inherits"); + expose_assert(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute"); function assert_readonly(object, property_name, description) { @@ -1578,7 +1605,7 @@ policies and contribution forms [3]. object[property_name] = initial_value; } } - expose(assert_readonly, "assert_readonly"); + expose_assert(assert_readonly, "assert_readonly"); /** * Assert a JS Error with the expected constructor is thrown. @@ -1592,7 +1619,7 @@ policies and contribution forms [3]. assert_throws_js_impl(constructor, func, description, "assert_throws_js"); } - expose(assert_throws_js, "assert_throws_js"); + expose_assert(assert_throws_js, "assert_throws_js"); /** * Like assert_throws_js but allows specifying the assertion type @@ -1690,7 +1717,7 @@ policies and contribution forms [3]. } assert_throws_dom_impl(type, func, description, "assert_throws_dom", constructor) } - expose(assert_throws_dom, "assert_throws_dom"); + expose_assert(assert_throws_dom, "assert_throws_dom"); /** * Similar to assert_throws_dom but allows specifying the assertion type @@ -1853,7 +1880,7 @@ policies and contribution forms [3]. assert_throws_exactly_impl(exception, func, description, "assert_throws_exactly"); } - expose(assert_throws_exactly, "assert_throws_exactly"); + expose_assert(assert_throws_exactly, "assert_throws_exactly"); /** * Like assert_throws_exactly but allows specifying the assertion type @@ -1881,7 +1908,7 @@ policies and contribution forms [3]. assert(false, "assert_unreached", description, "Reached unreachable code"); } - expose(assert_unreached, "assert_unreached"); + expose_assert(assert_unreached, "assert_unreached"); function assert_any(assert_func, actual, expected_array) { @@ -1902,7 +1929,7 @@ policies and contribution forms [3]. throw new AssertionError(errors.join("\n\n")); } } - expose(assert_any, "assert_any"); + expose_assert(assert_any, "assert_any"); /** * Assert that a feature is implemented, based on a 'truthy' condition. @@ -1919,7 +1946,7 @@ policies and contribution forms [3]. function assert_implements(condition, description) { assert(!!condition, "assert_implements", description); } - expose(assert_implements, "assert_implements") + expose_assert(assert_implements, "assert_implements") /** * Assert that an optional feature is implemented, based on a 'truthy' condition. @@ -1939,7 +1966,7 @@ policies and contribution forms [3]. throw new OptionalFeatureUnsupportedError(description); } } - expose(assert_implements_optional, "assert_implements_optional") + expose_assert(assert_implements_optional, "assert_implements_optional") function Test(name, properties) { @@ -1999,6 +2026,18 @@ policies and contribution forms [3]. COMPLETE:4 }; + Test.prototype.status_formats = { + 0: "Pass", + 1: "Fail", + 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() { if (!this._structured_clone) { @@ -2023,11 +2062,16 @@ policies and contribution forms [3]. if (this.phase > this.phases.STARTED) { return; } + + if (settings.debug && this.phase !== this.phases.STARTED) { + console.log("TEST START", this.name); + } this.phase = this.phases.STARTED; //If we don't get a result before the harness times out that will be a test timeout this.set_status(this.TIMEOUT, "Test timed out"); tests.started = true; + tests.current_test = this; tests.notify_test_state(this); if (this.timeout_id === null) { @@ -2040,6 +2084,10 @@ policies and contribution forms [3]. this_obj = this; } + if (settings.debug) { + console.debug("TEST STEP", this.name); + } + try { return func.apply(this_obj, Array.prototype.slice.call(arguments, 2)); } catch (e) { @@ -2053,6 +2101,8 @@ policies and contribution forms [3]. this.set_status(status, message, stack); this.phase = this.phases.HAS_RESULT; this.done(); + } finally { + this.current_test = null; } }; @@ -2270,6 +2320,12 @@ policies and contribution forms [3]. clearTimeout(this.timeout_id); } + if (settings.debug) { + console.log("TEST DONE", + this.status, + this.name,) + } + this.cleanup(); }; @@ -2469,6 +2525,10 @@ policies and contribution forms [3]. }); } + RemoteTest.prototype.format_status = function() { + return Test.prototype.status_formats[this.status]; + } + /* * A RemoteContext listens for test events from a remote test context, such * as another window or a worker. These events are then used to construct @@ -2577,6 +2637,16 @@ policies and contribution forms [3]. tests.set_status(data.status.status, data.status.message, data.status.sack); } + for (let assert of data.asserts) { + var record = new AssertRecord(); + record.assert_name = assert.assert_name; + record.args = assert.args; + record.test = assert.test != null ? this.tests[assert.test.index] : null; + record.status = assert.status; + record.stack = assert.stack; + tests.asserts_run.push(record); + } + this.message_target.removeEventListener("message", this.message_handler); this.running = false; @@ -2626,6 +2696,14 @@ policies and contribution forms [3]. TestsStatus.prototype = merge({}, TestsStatus.statuses); + TestsStatus.prototype.formats = { + 0: "OK", + 1: "Error", + 2: "Timeout", + 3: "Optional Feature Unsupported" + } + + TestsStatus.prototype.structured_clone = function() { if (!this._structured_clone) { @@ -2640,6 +2718,27 @@ policies and contribution forms [3]. return this._structured_clone; }; + TestsStatus.prototype.format_status = function() { + return this.formats[this.status]; + } + + function AssertRecord(test, assert_name, ...args) { + this.assert_name = assert_name; + this.test = test; + // Avoid keeping complex objects alive + this.args = args.map(x => format_value(x).replace(/\n/g, " ")); + this.status = null; + } + + AssertRecord.prototype.structured_clone = function() { + return { + assert_name: this.assert_name, + test: this.test ? this.test.structured_clone() : null, + args: this.args, + status: this.status, + } + } + function Tests() { this.tests = []; @@ -2679,6 +2778,18 @@ policies and contribution forms [3]. this.hide_test_state = false; this.pending_remotes = []; + this.current_test = null; + this.asserts_run = []; + + // Track whether output is enabled, and thus whether or not we should + // track asserts. + // + // On workers we don't get properties set from testharnessreport.js, so + // we don't know whether or not to track asserts. To avoid the + // resulting performance hit, we assume we are not meant to. This means + // that assert tracking does not function on workers. + this.output = settings.output && 'document' in global_scope; + this.status = new TestsStatus(); var this_obj = this; @@ -2726,6 +2837,10 @@ policies and contribution forms [3]. } } else if (p == "hide_test_state") { this.hide_test_state = value; + } else if (p == "output") { + this.output = value; + } else if (p === "debug") { + settings.debug = value; } } } @@ -2750,7 +2865,7 @@ policies and contribution forms [3]. this.wait_for_finish = true; this.file_is_test = true; // Create the test, which will add it to the list of tests - async_test(); + tests.current_test = async_test(); }; Tests.prototype.set_status = function(status, message, stack) @@ -2914,6 +3029,16 @@ policies and contribution forms [3]. all_complete); }; + Tests.prototype.set_assert = function(assert_name, ...args) { + this.asserts_run.push(new AssertRecord(this.current_test, assert_name, ...args)) + } + + Tests.prototype.set_assert_status = function(status, stack) { + let assert_record = this.asserts_run[this.asserts_run.length - 1]; + assert_record.status = status; + assert_record.stack = stack; + } + /** * Update the harness status to reflect an unrecoverable harness error that * should cancel all further testing. Update all previously-defined tests @@ -3011,7 +3136,7 @@ policies and contribution forms [3]. forEach (this.all_done_callbacks, function(callback) { - callback(this_obj.tests, this_obj.status); + callback(this_obj.tests, this_obj.status, this_obj.asserts_run); }); }; @@ -3237,7 +3362,7 @@ policies and contribution forms [3]. } }; - Output.prototype.show_results = function (tests, harness_status) { + Output.prototype.show_results = function (tests, harness_status, asserts_run) { if (this.phase >= this.COMPLETE) { return; } @@ -3266,23 +3391,10 @@ policies and contribution forms [3]. heads[0].appendChild(stylesheet); } - var status_text_harness = {}; - status_text_harness[harness_status.OK] = "OK"; - status_text_harness[harness_status.ERROR] = "Error"; - status_text_harness[harness_status.TIMEOUT] = "Timeout"; - status_text_harness[harness_status.PRECONDITION_FAILED] = "Optional Feature Unsupported"; - - var status_text = {}; - status_text[Test.prototype.PASS] = "Pass"; - status_text[Test.prototype.FAIL] = "Fail"; - status_text[Test.prototype.TIMEOUT] = "Timeout"; - status_text[Test.prototype.NOTRUN] = "Not Run"; - status_text[Test.prototype.PRECONDITION_FAILED] = "Optional Feature Unsupported"; - var status_number = {}; forEach(tests, function(test) { - var status = status_text[test.status]; + var status = test.format_status(); if (status_number.hasOwnProperty(status)) { status_number[status] += 1; } else { @@ -3299,8 +3411,7 @@ policies and contribution forms [3]. ["h2", {}, "Summary"], function() { - - var status = status_text_harness[harness_status.status]; + var status = harness_status.format_status(); var rv = [["section", {}, ["p", {}, "Harness status: ", @@ -3322,13 +3433,14 @@ policies and contribution forms [3]. function() { var rv = [["div", {}]]; var i = 0; - while (status_text.hasOwnProperty(i)) { - if (status_number.hasOwnProperty(status_text[i])) { - var status = status_text[i]; - rv[0].push(["div", {"class":status_class(status)}, + while (Test.prototype.status_formats.hasOwnProperty(i)) { + if (status_number.hasOwnProperty(Test.prototype.status_formats[i])) { + var status = Test.prototype.status_formats[i]; + rv[0].push(["div", {}, ["label", {}, ["input", {type:"checkbox", checked:"checked"}], - status_number[status] + " " + status]]); + status_number[status] + " ", + ["span", {"class":status_class(status)}, status]]]); } i++; } @@ -3395,6 +3507,51 @@ policies and contribution forms [3]. return ''; } + var asserts_run_by_test = new Map(); + asserts_run.forEach(assert => { + if (!asserts_run_by_test.has(assert.test)) { + asserts_run_by_test.set(assert.test, []); + } + asserts_run_by_test.get(assert.test).push(assert); + }); + + function get_asserts_output(test) { + var asserts = asserts_run_by_test.get(test); + if (!asserts) { + return "No asserts ran"; + } + rv = "<table>"; + rv += asserts.map(assert => { + var output_fn = "<strong>" + escape_html(assert.assert_name) + "</strong>("; + var prefix_len = output_fn.length; + var output_args = assert.args; + var output_len = output_args.reduce((prev, current) => prev+current, prefix_len); + if (output_len[output_len.length - 1] > 50) { + output_args = output_args.map((x, i) => + (i > 0 ? " ".repeat(prefix_len) : "" )+ x + (i < output_args.length - 1 ? ",\n" : "")); + } else { + output_args = output_args.map((x, i) => x + (i < output_args.length - 1 ? ", " : "")); + } + output_fn += escape_html(output_args.join("")); + output_fn += ')'; + var output_location; + if (assert.stack) { + output_location = assert.stack.split("\n", 1)[0].replace(/@?\w+:\/\/[^ "\/]+(?::\d+)?/g, " "); + } + return "<tr><td class=" + + status_class(Test.prototype.status_formats[assert.status]) + ">" + + Test.prototype.status_formats[assert.status] + "</td>" + + "</td>" + + "<td><pre>" + + output_fn + + (output_location ? "\n" + escape_html(output_location) : "") + + "</pre></td></tr>"; + } + ).join("\n"); + rv += "</table>"; + return rv; + } + log.appendChild(document.createElementNS(xhtml_ns, "section")); var assertions = has_assertions(); var html = "<h2>Details</h2><table id='results' " + (assertions ? "class='assertions'" : "" ) + ">" + @@ -3403,19 +3560,23 @@ policies and contribution forms [3]. "<th>Message</th></tr></thead>" + "<tbody>"; for (var i = 0; i < tests.length; i++) { - html += '<tr class="' + - escape_html(status_class(status_text[tests[i].status])) + - '"><td>' + - escape_html(status_text[tests[i].status]) + + var test = tests[i]; + html += '<tr><td class="' + + status_class(test.format_status()) + + '">' + + test.format_status() + "</td><td>" + - escape_html(tests[i].name) + + escape_html(test.name) + "</td><td>" + - (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>" : "") + - escape_html(tests[i].message ? tests[i].message : " ") + + (assertions ? escape_html(get_assertion(test)) + "</td><td>" : "") + + escape_html(test.message ? tests[i].message : " ") + (tests[i].stack ? "<pre>" + escape_html(tests[i].stack) + - "</pre>": "") + - "</td></tr>"; + "</pre>": ""); + if (!(test instanceof RemoteTest)) { + html += "<details><summary>Asserts run</summary>" + get_asserts_output(test) + "</details>" + } + html += "</td></tr>"; } html += "</tbody></table>"; try { @@ -3610,13 +3771,13 @@ policies and contribution forms [3]. message = sanitize_unpaired_surrogates(message); } this.message = message; - this.stack = this.get_stack(); + this.stack = get_stack(); } expose(AssertionError, "AssertionError"); AssertionError.prototype = Object.create(Error.prototype); - AssertionError.prototype.get_stack = function() { + const get_stack = function() { var stack = new Error().stack; // IE11 does not initialize 'Error.stack' until the object is thrown. if (!stack) { @@ -3980,54 +4141,62 @@ table#results {\ width:100%;\ }\ \ -table#results th:first-child,\ -table#results td:first-child {\ +table#results > thead > tr > th:first-child,\ +table#results > tbody > tr > td:first-child {\ width:8em;\ }\ \ -table#results th:last-child,\ -table#results td:last-child {\ +table#results > thead > tr > th:last-child,\ +table#results > thead > tr > td:last-child {\ width:50%;\ }\ \ -table#results.assertions th:last-child,\ -table#results.assertions td:last-child {\ +table#results.assertions > thead > tr > th:last-child,\ +table#results.assertions > tbody > tr > td:last-child {\ width:35%;\ }\ \ -table#results th {\ +table#results > thead > > tr > th {\ padding:0;\ padding-bottom:0.5em;\ border-bottom:medium solid black;\ }\ \ -table#results td {\ +table#results > tbody > tr> td {\ padding:1em;\ padding-bottom:0.5em;\ border-bottom:thin solid black;\ }\ \ -tr.pass > td:first-child {\ +.pass {\ color:green;\ }\ \ -tr.fail > td:first-child {\ +.fail {\ color:red;\ }\ \ -tr.timeout > td:first-child {\ +tr.timeout {\ color:red;\ }\ \ -tr.notrun > td:first-child {\ +tr.notrun {\ color:blue;\ }\ \ -tr.optionalunsupported > td:first-child {\ +tr.optionalunsupported {\ color:blue;\ }\ \ -.pass > td:first-child, .fail > td:first-child, .timeout > td:first-child, .notrun > td:first-child, .optionalunsupported > td:first-child {\ +.ok {\ + color:green;\ +}\ +\ +.error {\ + color:red;\ +}\ +\ +.pass, .fail, .timeout, .notrun, .optionalunsupported .ok, .timeout, .error {\ font-variant:small-caps;\ }\ \ @@ -4044,22 +4213,6 @@ table#results span.actual {\ font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;\ white-space:pre;\ }\ -\ -span.ok {\ - color:green;\ -}\ -\ -tr.error {\ - color:red;\ -}\ -\ -span.timeout {\ - color:red;\ -}\ -\ -span.ok, span.timeout, span.error {\ - font-variant:small-caps;\ -}\ "; })(self); diff --git a/test/fixtures/wpt/url/url-constructor.any.js b/test/fixtures/wpt/url/url-constructor.any.js new file mode 100644 index 00000000000000..3f4af56d2a9654 --- /dev/null +++ b/test/fixtures/wpt/url/url-constructor.any.js @@ -0,0 +1,39 @@ +// META: timeout=long + +function bURL(url, base) { + return new URL(url, base || "about:blank") +} + +function runURLTests(urltests) { + for(var i = 0, l = urltests.length; i < l; i++) { + var expected = urltests[i] + if (typeof expected === "string") continue // skip comments + + test(function() { + if (expected.failure) { + assert_throws_js(TypeError, function() { + bURL(expected.input, expected.base) + }) + return + } + + var url = bURL(expected.input, expected.base) + assert_equals(url.href, expected.href, "href") + assert_equals(url.protocol, expected.protocol, "protocol") + assert_equals(url.username, expected.username, "username") + assert_equals(url.password, expected.password, "password") + assert_equals(url.host, expected.host, "host") + assert_equals(url.hostname, expected.hostname, "hostname") + assert_equals(url.port, expected.port, "port") + assert_equals(url.pathname, expected.pathname, "pathname") + assert_equals(url.search, expected.search, "search") + if ("searchParams" in expected) { + assert_true("searchParams" in url) + assert_equals(url.searchParams.toString(), expected.searchParams, "searchParams") + } + assert_equals(url.hash, expected.hash, "hash") + }, "Parsing: <" + expected.input + "> against <" + expected.base + ">") + } +} + +promise_test(() => fetch("resources/urltestdata.json").then(res => res.json()).then(runURLTests), "Loading data…"); diff --git a/test/fixtures/wpt/url/url-origin.any.js b/test/fixtures/wpt/url/url-origin.any.js new file mode 100644 index 00000000000000..d9ef64c73b8bcc --- /dev/null +++ b/test/fixtures/wpt/url/url-origin.any.js @@ -0,0 +1,17 @@ +promise_test(() => fetch("resources/urltestdata.json").then(res => res.json()).then(runURLTests), "Loading data…"); + +function bURL(url, base) { + return new URL(url, base || "about:blank") +} + +function runURLTests(urltests) { + for(var i = 0, l = urltests.length; i < l; i++) { + var expected = urltests[i] + if (typeof expected === "string" || !("origin" in expected)) continue + + test(function() { + var url = bURL(expected.input, expected.base) + assert_equals(url.origin, expected.origin, "origin") + }, "Origin parsing: <" + expected.input + "> against <" + expected.base + ">") + } +} diff --git a/test/fixtures/wpt/url/urlsearchparams-constructor.any.js b/test/fixtures/wpt/url/urlsearchparams-constructor.any.js index f9878373e5e067..d482911350ba0f 100644 --- a/test/fixtures/wpt/url/urlsearchparams-constructor.any.js +++ b/test/fixtures/wpt/url/urlsearchparams-constructor.any.js @@ -29,7 +29,7 @@ test(() => { test(() => { var params = new URLSearchParams(''); assert_true(params != null, 'constructor returned non-null value.'); - assert_equals(params.__proto__, URLSearchParams.prototype, 'expected URLSearchParams.prototype as prototype.'); + assert_equals(Object.getPrototypeOf(params), URLSearchParams.prototype, 'expected URLSearchParams.prototype as prototype.'); }, "URLSearchParams constructor, empty string as argument") test(() => { diff --git a/test/fixtures/wpt/url/urlsearchparams-foreach.any.js b/test/fixtures/wpt/url/urlsearchparams-foreach.any.js index 7969a0cb11a271..ff19643ac220d1 100644 --- a/test/fixtures/wpt/url/urlsearchparams-foreach.any.js +++ b/test/fixtures/wpt/url/urlsearchparams-foreach.any.js @@ -14,7 +14,7 @@ test(function() { let a = new URL("http://a.b/c?a=1&b=2&c=3&d=4"); let b = a.searchParams; var c = []; - for (i of b) { + for (const i of b) { a.search = "x=1&y=2&z=3"; c.push(i); } @@ -26,7 +26,7 @@ test(function() { test(function() { let a = new URL("http://a.b/c"); let b = a.searchParams; - for (i of b) { + for (const i of b) { assert_unreached(i); } }, "empty"); @@ -35,7 +35,7 @@ test(function() { const url = new URL("http://localhost/query?param0=0¶m1=1¶m2=2"); const searchParams = url.searchParams; const seen = []; - for (param of searchParams) { + for (const param of searchParams) { if (param[0] === 'param0') { searchParams.delete('param1'); } @@ -50,7 +50,7 @@ test(function() { const url = new URL("http://localhost/query?param0=0¶m1=1¶m2=2"); const searchParams = url.searchParams; const seen = []; - for (param of searchParams) { + for (const param of searchParams) { if (param[0] === 'param0') { searchParams.delete('param0'); // 'param1=1' is now in the first slot, so the next iteration will see 'param2=2'. @@ -66,7 +66,7 @@ test(function() { const url = new URL("http://localhost/query?param0=0¶m1=1¶m2=2"); const searchParams = url.searchParams; const seen = []; - for (param of searchParams) { + for (const param of searchParams) { seen.push(param[0]); searchParams.delete(param[0]); } diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index 91bb213072cb92..c2cd318c2b721c 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -1,46 +1,46 @@ { + "common": { + "commit": "3586ff740b00aa1fa82ab00cccbc36cca0bb8ccb", + "path": "common" + }, "console": { "commit": "3b1f72e99a91d31551edd2147dc7b564eaf25d72", "path": "console" }, + "dom/abort": { + "commit": "625e1310ce19e9dde25b01f9eda0452c6ec274da", + "path": "dom/abort" + }, "encoding": { - "commit": "3c9820d1cc5d9d2627c26ef1268b6d54a35adf22", + "commit": "35f70910d3753c8b650fdfd4c716caedfefe88c9", "path": "encoding" }, - "url": { - "commit": "1783c9bccf48c426ec1017f36e1ccfcf90a8af47", - "path": "url" - }, - "resources": { - "commit": "351a99782b9677706b5dc0dd78e85978fa4ab130", - "path": "resources" + "FileAPI": { + "commit": "3b279420d40afea32506e823f9ac005448f4f3d8", + "path": "FileAPI" }, - "interfaces": { - "commit": "b4be9a3fdf18459a924f88e49bc55d8b30faa93a", - "path": "interfaces" + "hr-time": { + "commit": "9910784394858a8e34d9eb4e5d00788765abf837", + "path": "hr-time" }, "html/webappapis/microtask-queuing": { "commit": "2c5c3c4c27d27a419c1fdba3e9879c2d22037074", "path": "html/webappapis/microtask-queuing" }, "html/webappapis/timers": { - "commit": "264f12bc7bf5db0c6dd064842a5d39ccbf9208c5", + "commit": "5873f2d8f1f7bbb9c64689e52d04498614632906", "path": "html/webappapis/timers" }, - "hr-time": { - "commit": "a5d1774ecf41751d1c9357c27c709ee33bf3e279", - "path": "hr-time" - }, - "common": { - "commit": "841a51412f236ad63655daca843d3b8e83e0777b", - "path": "common" + "interfaces": { + "commit": "8602e9c9a168c25838bc4b808e3d213ce94ccac1", + "path": "interfaces" }, - "dom/abort": { - "commit": "7caa3de7471cf19b78ee9efa313c7341a462b5e3", - "path": "dom/abort" + "resources": { + "commit": "e366371a194459f23f8d6115cfdb57e0bc851468", + "path": "resources" }, - "FileAPI": { - "commit": "3b279420d40afea32506e823f9ac005448f4f3d8", - "path": "FileAPI" + "url": { + "commit": "59d28c8f2d91d12533cfd0371acbe473b1825967", + "path": "url" } } \ No newline at end of file diff --git a/test/wpt/status/encoding.json b/test/wpt/status/encoding.json index 0b2c84051d0e1b..88373a1ee38fc5 100644 --- a/test/wpt/status/encoding.json +++ b/test/wpt/status/encoding.json @@ -57,5 +57,16 @@ }, "textdecoder-copy.any.js": { "fail": "WebAssembly.Memory does not support shared:true" + }, + "legacy-mb-schinese/gbk/gbk-decoder.any.js": { + "requires": ["full-icu"], + "skip": "The gbk encoding is not supported" + }, + "legacy-mb-schinese/gb18030/gb18030-decoder.any.js": { + "requires": ["full-icu"], + "skip": "The gb18030 encoding is not supported" + }, + "textdecoder-arguments.any.js": { + "fail": "Does not support flushing an incomplete sequence" } } diff --git a/test/wpt/status/hr-time.json b/test/wpt/status/hr-time.json index 12a239ab398107..4910d925b5be94 100644 --- a/test/wpt/status/hr-time.json +++ b/test/wpt/status/hr-time.json @@ -1,8 +1,11 @@ { - "window-worker-timeOrigin.window.js": { - "fail": "Blob is not defined" + "basic.any.js": { + "fail": "self.performance.addEventListener is not a function" }, "idlharness.any.js": { "skip": "TODO: update IDL parser" + }, + "window-worker-timeOrigin.window.js": { + "fail": "depends on URL.createObjectURL(blob)" } -} \ No newline at end of file +} diff --git a/test/wpt/status/html/webappapis/timers.json b/test/wpt/status/html/webappapis/timers.json index 0967ef424bce67..69d7095511f41c 100644 --- a/test/wpt/status/html/webappapis/timers.json +++ b/test/wpt/status/html/webappapis/timers.json @@ -1 +1,14 @@ -{} +{ + "negative-settimeout.any.js": { + "fail": "assert_unreached: Reached unreachable code" + }, + "type-long-setinterval.any.js": { + "fail": "assert_unreached: Reached unreachable code" + }, + "type-long-settimeout.any.js": { + "fail": "assert_unreached: Reached unreachable code" + }, + "negative-setinterval.any.js": { + "fail": "assert_unreached: Reached unreachable code" + } +} diff --git a/test/wpt/status/url.json b/test/wpt/status/url.json index 89601af0ca5814..36b3fd682b37f9 100644 --- a/test/wpt/status/url.json +++ b/test/wpt/status/url.json @@ -18,5 +18,11 @@ }, "urlsearchparams-constructor.any.js": { "fail": "FormData is not defined" + }, + "url-constructor.any.js": { + "fail": "TODO: support relative fetch()" + }, + "url-origin.any.js": { + "fail": "TODO: support relative fetch()" } } diff --git a/test/wpt/test-hr-time.js b/test/wpt/test-hr-time.js index d77ac0bbc6d9c0..6add176f5fb995 100644 --- a/test/wpt/test-hr-time.js +++ b/test/wpt/test-hr-time.js @@ -6,6 +6,9 @@ const { WPTRunner } = require('../common/wpt'); const runner = new WPTRunner('hr-time'); runner.setInitScript(` + const { Blob } = require('buffer'); + global.Blob = Blob; + const { performance, PerformanceObserver } = require('perf_hooks'); global.performance = performance; global.PerformanceObserver = PerformanceObserver;