Skip to content

Commit d8ae8e6

Browse files
authored
feat: add minesniff WPTs (nodejs#1702)
1 parent 2c775e6 commit d8ae8e6

File tree

10 files changed

+4023
-6
lines changed

10 files changed

+4023
-6
lines changed

lib/fetch/body.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const { isErrored } = require('../core/util')
1414
const { isUint8Array, isArrayBuffer } = require('util/types')
1515
const { File } = require('./file')
1616
const { StringDecoder } = require('string_decoder')
17-
const { parseMIMEType } = require('./dataURL')
17+
const { parseMIMEType, serializeAMimeType } = require('./dataURL')
1818

1919
/** @type {globalThis['ReadableStream']} */
2020
let ReadableStream
@@ -516,9 +516,15 @@ function packageData ({ bytes, size }, type, mimeType) {
516516
return uint8.buffer
517517
}
518518
case 'Blob': {
519+
if (mimeType === 'failure') {
520+
mimeType = ''
521+
} else if (mimeType) {
522+
mimeType = serializeAMimeType(mimeType)
523+
}
524+
519525
// Return a Blob whose contents are bytes and type attribute
520526
// is mimeType.
521-
return new Blob(bytes, { type: mimeType?.essence })
527+
return new Blob(bytes, { type: mimeType })
522528
}
523529
case 'JSON': {
524530
// Return the result of running parse JSON from bytes on bytes.

lib/fetch/dataURL.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ function parseMIMEType (input) {
305305
)
306306

307307
// 8. Remove any trailing HTTP whitespace from subtype.
308-
subtype = subtype.trim()
308+
subtype = subtype.trimEnd()
309309

310310
// 9. If subtype is the empty string or does not solely
311311
// contain HTTP token code points, then return failure.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"test:tap": "tap test/*.js test/diagnostics-channel/*.js",
5454
"test:tdd": "tap test/*.js test/diagnostics-channel/*.js -w",
5555
"test:typescript": "tsd",
56-
"test:wpt": "node scripts/verifyVersion 18 || (node test/wpt/start-fetch.mjs && node test/wpt/start-FileAPI.mjs)",
56+
"test:wpt": "node scripts/verifyVersion 18 || (node test/wpt/start-fetch.mjs && node test/wpt/start-FileAPI.mjs && node test/wpt/start-mimesniff.mjs)",
5757
"coverage": "nyc --reporter=text --reporter=html npm run test",
5858
"coverage:ci": "nyc --reporter=lcov npm run test",
5959
"bench": "PORT=3042 concurrently -k -s first npm:bench:server npm:bench:run",

test/wpt/runner/runner/runner.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,15 @@ export class WPTRunner extends EventEmitter {
120120
* Called after a test has succeeded or failed.
121121
*/
122122
handleIndividualTestCompletion (message, fileName) {
123-
const { fail } = this.#status[fileName] ?? {}
123+
const { fail, allowUnexpectedFailures } = this.#status[fileName] ?? {}
124124

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

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

131-
if (fail && fail.includes(message.result.name)) {
131+
if (allowUnexpectedFailures || (fail && fail.includes(message.result.name))) {
132132
this.#stats.expectedFailures += 1
133133
} else {
134134
process.exitCode = 1

test/wpt/server/server.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ const server = createServer(async (req, res) => {
3030
const fullUrl = new URL(req.url, `http://localhost:${server.address().port}`)
3131

3232
switch (fullUrl.pathname) {
33+
case '/mimesniff/mime-types/resources/generated-mime-types.json':
34+
case '/mimesniff/mime-types/resources/mime-types.json':
3335
case '/interfaces/dom.idl':
3436
case '/interfaces/html.idl':
3537
case '/interfaces/fetch.idl':

test/wpt/start-mimesniff.mjs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { WPTRunner } from './runner/runner/runner.mjs'
2+
import { join } from 'path'
3+
import { fileURLToPath } from 'url'
4+
import { fork } from 'child_process'
5+
import { on } from 'events'
6+
7+
const serverPath = fileURLToPath(join(import.meta.url, '../server/server.mjs'))
8+
9+
const child = fork(serverPath, [], {
10+
stdio: ['pipe', 'pipe', 'pipe', 'ipc']
11+
})
12+
13+
for await (const [message] of on(child, 'message')) {
14+
if (message.server) {
15+
const runner = new WPTRunner('mimesniff', message.server)
16+
runner.run()
17+
18+
runner.once('completion', () => {
19+
child.send('shutdown')
20+
})
21+
} else if (message.message === 'shutdown') {
22+
process.exit()
23+
}
24+
}

test/wpt/status/mimesniff.status.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"parsing.any.js": {
3+
"allowUnexpectedFailures": true
4+
}
5+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// META: timeout=long
2+
3+
promise_test(() => {
4+
return Promise.all([
5+
fetch("resources/mime-types.json"),
6+
fetch("resources/generated-mime-types.json")
7+
]).then(([res, res2]) => res.json().then(runTests).then(() => res2.json().then(runTests)));
8+
}, "Loading data…");
9+
10+
function isByteCompatible(str) {
11+
// see https://fetch.spec.whatwg.org/#concept-header-value-normalize
12+
if(/^[\u0009\u0020\u000A\u000D]+|[\u0009\u0020\u000A\u000D]+$/.test(str)) {
13+
return "header-value-incompatible";
14+
}
15+
16+
for(let i = 0; i < str.length; i++) {
17+
const charCode = str.charCodeAt(i);
18+
// See https://fetch.spec.whatwg.org/#concept-header-value
19+
if(charCode > 0xFF) {
20+
return "incompatible";
21+
} else if(charCode === 0x00 || charCode === 0x0A || charCode === 0x0D) {
22+
return "header-value-error";
23+
}
24+
}
25+
return "compatible";
26+
}
27+
28+
function runTests(tests) {
29+
tests.forEach(val => {
30+
if(typeof val === "string") {
31+
return;
32+
}
33+
const output = val.output === null ? "" : val.output
34+
test(() => {
35+
assert_equals(new Blob([], { type: val.input}).type, output, "Blob");
36+
assert_equals(new File([], "noname", { type: val.input}).type, output, "File");
37+
}, val.input + " (Blob/File)");
38+
39+
const compatibleNess = isByteCompatible(val.input);
40+
if(compatibleNess === "header-value-incompatible") {
41+
return;
42+
}
43+
44+
promise_test(() => {
45+
if(compatibleNess === "incompatible" || compatibleNess === "header-value-error") {
46+
assert_throws_js(TypeError, () => new Request("about:blank", { headers: [["Content-Type", val.input]] }));
47+
assert_throws_js(TypeError, () => new Response(null, { headers: [["Content-Type", val.input]] }));
48+
return Promise.resolve();
49+
} else {
50+
return Promise.all([
51+
new Request("about:blank", { headers: [["Content-Type", val.input]] }).blob().then(blob => assert_equals(blob.type, output)),
52+
new Response(null, { headers: [["Content-Type", val.input]] }).blob().then(blob => assert_equals(blob.type, output))
53+
]);
54+
}
55+
}, val.input + " (Request/Response)");
56+
});
57+
}

0 commit comments

Comments
 (0)