From c6855eb88fbb92d5b58839e01367f99f9e61f911 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Wed, 17 Mar 2021 11:27:27 -0700 Subject: [PATCH] test: app atob web platform tests Signed-off-by: James M Snell PR-URL: https://github.com/nodejs/node/pull/37529 Fixes: https://github.com/nodejs/node/issues/3462 Reviewed-By: Anna Henningsen Reviewed-By: Matteo Collina --- test/fixtures/wpt/README.md | 1 + .../wpt/html/webappapis/atob/base64.any.js | 163 ++++++++++++++++++ test/fixtures/wpt/versions.json | 4 + test/wpt/status/html/webappapis/atob.json | 5 + test/wpt/test-atob.js | 19 ++ 5 files changed, 192 insertions(+) create mode 100644 test/fixtures/wpt/html/webappapis/atob/base64.any.js create mode 100644 test/wpt/status/html/webappapis/atob.json create mode 100644 test/wpt/test-atob.js diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index 52d4ccc8294a6c..9474d8a1963b7e 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -16,6 +16,7 @@ Last update: - 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/atob: https://github.com/web-platform-tests/wpt/tree/f267e1dca6/html/webappapis/atob - 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/79fa4cf76e/interfaces diff --git a/test/fixtures/wpt/html/webappapis/atob/base64.any.js b/test/fixtures/wpt/html/webappapis/atob/base64.any.js new file mode 100644 index 00000000000000..7f433f4d8a9ee2 --- /dev/null +++ b/test/fixtures/wpt/html/webappapis/atob/base64.any.js @@ -0,0 +1,163 @@ +/** + * btoa() as defined by the HTML5 spec, which mostly just references RFC4648. + */ +function mybtoa(s) { + // String conversion as required by WebIDL. + s = String(s); + + // "The btoa() method must throw an INVALID_CHARACTER_ERR exception if the + // method's first argument contains any character whose code point is + // greater than U+00FF." + for (var i = 0; i < s.length; i++) { + if (s.charCodeAt(i) > 255) { + return "INVALID_CHARACTER_ERR"; + } + } + + var out = ""; + for (var i = 0; i < s.length; i += 3) { + var groupsOfSix = [undefined, undefined, undefined, undefined]; + groupsOfSix[0] = s.charCodeAt(i) >> 2; + groupsOfSix[1] = (s.charCodeAt(i) & 0x03) << 4; + if (s.length > i + 1) { + groupsOfSix[1] |= s.charCodeAt(i + 1) >> 4; + groupsOfSix[2] = (s.charCodeAt(i + 1) & 0x0f) << 2; + } + if (s.length > i + 2) { + groupsOfSix[2] |= s.charCodeAt(i + 2) >> 6; + groupsOfSix[3] = s.charCodeAt(i + 2) & 0x3f; + } + for (var j = 0; j < groupsOfSix.length; j++) { + if (typeof groupsOfSix[j] == "undefined") { + out += "="; + } else { + out += btoaLookup(groupsOfSix[j]); + } + } + } + return out; +} + +/** + * Lookup table for mybtoa(), which converts a six-bit number into the + * corresponding ASCII character. + */ +function btoaLookup(idx) { + if (idx < 26) { + return String.fromCharCode(idx + 'A'.charCodeAt(0)); + } + if (idx < 52) { + return String.fromCharCode(idx - 26 + 'a'.charCodeAt(0)); + } + if (idx < 62) { + return String.fromCharCode(idx - 52 + '0'.charCodeAt(0)); + } + if (idx == 62) { + return '+'; + } + if (idx == 63) { + return '/'; + } + // Throw INVALID_CHARACTER_ERR exception here -- won't be hit in the tests. +} + +function btoaException(input) { + input = String(input); + for (var i = 0; i < input.length; i++) { + if (input.charCodeAt(i) > 255) { + return true; + } + } + return false; +} + +function testBtoa(input) { + // "The btoa() method must throw an INVALID_CHARACTER_ERR exception if the + // method's first argument contains any character whose code point is + // greater than U+00FF." + var normalizedInput = String(input); + for (var i = 0; i < normalizedInput.length; i++) { + if (normalizedInput.charCodeAt(i) > 255) { + assert_throws_dom("InvalidCharacterError", function() { btoa(input); }, + "Code unit " + i + " has value " + normalizedInput.charCodeAt(i) + ", which is greater than 255"); + return; + } + } + assert_equals(btoa(input), mybtoa(input)); + assert_equals(atob(btoa(input)), String(input), "atob(btoa(input)) must be the same as String(input)"); +} + +var tests = ["עברית", "", "ab", "abc", "abcd", "abcde", + // This one is thrown in because IE9 seems to fail atob(btoa()) on it. Or + // possibly to fail btoa(). I actually can't tell what's happening here, + // but it doesn't hurt. + "\xff\xff\xc0", + // Is your DOM implementation binary-safe? + "\0a", "a\0b", + // WebIDL tests. + undefined, null, 7, 12, 1.5, true, false, NaN, +Infinity, -Infinity, 0, -0, + {toString: function() { return "foo" }}, +]; +for (var i = 0; i < 258; i++) { + tests.push(String.fromCharCode(i)); +} +tests.push(String.fromCharCode(10000)); +tests.push(String.fromCharCode(65534)); +tests.push(String.fromCharCode(65535)); + +// This is supposed to be U+10000. +tests.push(String.fromCharCode(0xd800, 0xdc00)); +tests = tests.map( + function(elem) { + var expected = mybtoa(elem); + if (expected === "INVALID_CHARACTER_ERR") { + return ["btoa(" + format_value(elem) + ") must raise INVALID_CHARACTER_ERR", elem]; + } + return ["btoa(" + format_value(elem) + ") == " + format_value(mybtoa(elem)), elem]; + } +); + +var everything = ""; +for (var i = 0; i < 256; i++) { + everything += String.fromCharCode(i); +} +tests.push(["btoa(first 256 code points concatenated)", everything]); + +generate_tests(testBtoa, tests); + +promise_test(() => fetch("../../../fetch/data-urls/resources/base64.json").then(res => res.json()).then(runAtobTests), "atob() setup."); + +const idlTests = [ + [undefined, null], + [null, [158, 233, 101]], + [7, null], + [12, [215]], + [1.5, null], + [true, [182, 187]], + [false, null], + [NaN, [53, 163]], + [+Infinity, [34, 119, 226, 158, 43, 114]], + [-Infinity, null], + [0, null], + [-0, null], + [{toString: function() { return "foo" }}, [126, 138]], + [{toString: function() { return "abcd" }}, [105, 183, 29]] +]; + +function runAtobTests(tests) { + const allTests = tests.concat(idlTests); + for(let i = 0; i < allTests.length; i++) { + const input = allTests[i][0], + output = allTests[i][1]; + test(() => { + if(output === null) { + assert_throws_dom("InvalidCharacterError", () => globalThis.atob(input)); + } else { + const result = globalThis.atob(input); + for(let ii = 0; ii < output.length; ii++) { + assert_equals(result.charCodeAt(ii), output[ii]); + } + } + }, "atob(" + format_value(input) + ")"); + } +} diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index c533088b4693b8..800f07c607bf4b 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -23,6 +23,10 @@ "commit": "9910784394858a8e34d9eb4e5d00788765abf837", "path": "hr-time" }, + "html/webappapis/atob": { + "commit": "f267e1dca6f57a9f4d69f32a6920adfdb3268656", + "path": "html/webappapis/atob" + }, "html/webappapis/microtask-queuing": { "commit": "2c5c3c4c27d27a419c1fdba3e9879c2d22037074", "path": "html/webappapis/microtask-queuing" diff --git a/test/wpt/status/html/webappapis/atob.json b/test/wpt/status/html/webappapis/atob.json new file mode 100644 index 00000000000000..65deda7312d426 --- /dev/null +++ b/test/wpt/status/html/webappapis/atob.json @@ -0,0 +1,5 @@ +{ + "base64.any.js": { + "fail": "promise_test: Unhandled rejection with value: object \"Error: ENOENT: no such file or directory, open '/root/node/node/fetch/data-urls/resources/base64.json'\"" + } +} diff --git a/test/wpt/test-atob.js b/test/wpt/test-atob.js new file mode 100644 index 00000000000000..51fa27e36d3f2f --- /dev/null +++ b/test/wpt/test-atob.js @@ -0,0 +1,19 @@ +'use strict'; + +require('../common'); +const { WPTRunner } = require('../common/wpt'); + +const runner = new WPTRunner('html/webappapis/atob'); + +// Needed to access to DOMException. +runner.setFlags(['--expose-internals']); + +// Set a script that will be executed in the worker before running the tests. +runner.setInitScript(` + const { internalBinding } = require('internal/test/binding'); + const { atob, btoa } = require('buffer'); + const { DOMException } = internalBinding('messaging'); + global.DOMException = DOMException; +`); + +runner.runJsTests();