Skip to content

Commit

Permalink
feat: add minesniff WPTs (nodejs#1702)
Browse files Browse the repository at this point in the history
  • Loading branch information
KhafraDev authored and crysmags committed Feb 27, 2024
1 parent 5de0f6f commit e80ec42
Show file tree
Hide file tree
Showing 10 changed files with 4,023 additions and 6 deletions.
10 changes: 8 additions & 2 deletions lib/fetch/body.js
Expand Up @@ -14,7 +14,7 @@ const { isErrored } = require('../core/util')
const { isUint8Array, isArrayBuffer } = require('util/types')
const { File } = require('./file')
const { StringDecoder } = require('string_decoder')
const { parseMIMEType } = require('./dataURL')
const { parseMIMEType, serializeAMimeType } = require('./dataURL')

/** @type {globalThis['ReadableStream']} */
let ReadableStream
Expand Down Expand Up @@ -516,9 +516,15 @@ function packageData ({ bytes, size }, type, mimeType) {
return uint8.buffer
}
case 'Blob': {
if (mimeType === 'failure') {
mimeType = ''
} else if (mimeType) {
mimeType = serializeAMimeType(mimeType)
}

// Return a Blob whose contents are bytes and type attribute
// is mimeType.
return new Blob(bytes, { type: mimeType?.essence })
return new Blob(bytes, { type: mimeType })
}
case 'JSON': {
// Return the result of running parse JSON from bytes on bytes.
Expand Down
2 changes: 1 addition & 1 deletion lib/fetch/dataURL.js
Expand Up @@ -305,7 +305,7 @@ function parseMIMEType (input) {
)

// 8. Remove any trailing HTTP whitespace from subtype.
subtype = subtype.trim()
subtype = subtype.trimEnd()

// 9. If subtype is the empty string or does not solely
// contain HTTP token code points, then return failure.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -53,7 +53,7 @@
"test:tap": "tap test/*.js test/diagnostics-channel/*.js",
"test:tdd": "tap test/*.js test/diagnostics-channel/*.js -w",
"test:typescript": "tsd",
"test:wpt": "node scripts/verifyVersion 18 || (node test/wpt/start-fetch.mjs && node test/wpt/start-FileAPI.mjs)",
"test:wpt": "node scripts/verifyVersion 18 || (node test/wpt/start-fetch.mjs && node test/wpt/start-FileAPI.mjs && node test/wpt/start-mimesniff.mjs)",
"coverage": "nyc --reporter=text --reporter=html npm run test",
"coverage:ci": "nyc --reporter=lcov npm run test",
"bench": "PORT=3042 concurrently -k -s first npm:bench:server npm:bench:run",
Expand Down
4 changes: 2 additions & 2 deletions test/wpt/runner/runner/runner.mjs
Expand Up @@ -120,15 +120,15 @@ export class WPTRunner extends EventEmitter {
* Called after a test has succeeded or failed.
*/
handleIndividualTestCompletion (message, fileName) {
const { fail } = this.#status[fileName] ?? {}
const { fail, allowUnexpectedFailures } = this.#status[fileName] ?? {}

if (message.type === 'result') {
this.#stats.completed += 1

if (message.result.status === 1) {
this.#stats.failed += 1

if (fail && fail.includes(message.result.name)) {
if (allowUnexpectedFailures || (fail && fail.includes(message.result.name))) {
this.#stats.expectedFailures += 1
} else {
process.exitCode = 1
Expand Down
2 changes: 2 additions & 0 deletions test/wpt/server/server.mjs
Expand Up @@ -30,6 +30,8 @@ const server = createServer(async (req, res) => {
const fullUrl = new URL(req.url, `http://localhost:${server.address().port}`)

switch (fullUrl.pathname) {
case '/mimesniff/mime-types/resources/generated-mime-types.json':
case '/mimesniff/mime-types/resources/mime-types.json':
case '/interfaces/dom.idl':
case '/interfaces/html.idl':
case '/interfaces/fetch.idl':
Expand Down
24 changes: 24 additions & 0 deletions test/wpt/start-mimesniff.mjs
@@ -0,0 +1,24 @@
import { WPTRunner } from './runner/runner/runner.mjs'
import { join } from 'path'
import { fileURLToPath } from 'url'
import { fork } from 'child_process'
import { on } from 'events'

const serverPath = fileURLToPath(join(import.meta.url, '../server/server.mjs'))

const child = fork(serverPath, [], {
stdio: ['pipe', 'pipe', 'pipe', 'ipc']
})

for await (const [message] of on(child, 'message')) {
if (message.server) {
const runner = new WPTRunner('mimesniff', message.server)
runner.run()

runner.once('completion', () => {
child.send('shutdown')
})
} else if (message.message === 'shutdown') {
process.exit()
}
}
5 changes: 5 additions & 0 deletions test/wpt/status/mimesniff.status.json
@@ -0,0 +1,5 @@
{
"parsing.any.js": {
"allowUnexpectedFailures": true
}
}
57 changes: 57 additions & 0 deletions test/wpt/tests/mimesniff/mime-types/parsing.any.js
@@ -0,0 +1,57 @@
// META: timeout=long

promise_test(() => {
return Promise.all([
fetch("resources/mime-types.json"),
fetch("resources/generated-mime-types.json")
]).then(([res, res2]) => res.json().then(runTests).then(() => res2.json().then(runTests)));
}, "Loading data…");

function isByteCompatible(str) {
// see https://fetch.spec.whatwg.org/#concept-header-value-normalize
if(/^[\u0009\u0020\u000A\u000D]+|[\u0009\u0020\u000A\u000D]+$/.test(str)) {
return "header-value-incompatible";
}

for(let i = 0; i < str.length; i++) {
const charCode = str.charCodeAt(i);
// See https://fetch.spec.whatwg.org/#concept-header-value
if(charCode > 0xFF) {
return "incompatible";
} else if(charCode === 0x00 || charCode === 0x0A || charCode === 0x0D) {
return "header-value-error";
}
}
return "compatible";
}

function runTests(tests) {
tests.forEach(val => {
if(typeof val === "string") {
return;
}
const output = val.output === null ? "" : val.output
test(() => {
assert_equals(new Blob([], { type: val.input}).type, output, "Blob");
assert_equals(new File([], "noname", { type: val.input}).type, output, "File");
}, val.input + " (Blob/File)");

const compatibleNess = isByteCompatible(val.input);
if(compatibleNess === "header-value-incompatible") {
return;
}

promise_test(() => {
if(compatibleNess === "incompatible" || compatibleNess === "header-value-error") {
assert_throws_js(TypeError, () => new Request("about:blank", { headers: [["Content-Type", val.input]] }));
assert_throws_js(TypeError, () => new Response(null, { headers: [["Content-Type", val.input]] }));
return Promise.resolve();
} else {
return Promise.all([
new Request("about:blank", { headers: [["Content-Type", val.input]] }).blob().then(blob => assert_equals(blob.type, output)),
new Response(null, { headers: [["Content-Type", val.input]] }).blob().then(blob => assert_equals(blob.type, output))
]);
}
}, val.input + " (Request/Response)");
});
}

0 comments on commit e80ec42

Please sign in to comment.