node/test/es-module/test-wasm-web-api.js
Nathan Baulch 28c7394319
test: fix typos
PR-URL: https://github.com/nodejs/node/pull/55063
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
2024-09-28 13:58:55 +00:00

248 lines
9.2 KiB
JavaScript

'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const events = require('events');
const fs = require('fs/promises');
const { createServer } = require('http');
assert.strictEqual(typeof WebAssembly.compileStreaming, 'function');
assert.strictEqual(typeof WebAssembly.instantiateStreaming, 'function');
const simpleWasmBytes = fixtures.readSync('simple.wasm');
// Sets up an HTTP server with the given response handler and calls fetch() to
// obtain a Response from the newly created server.
async function testRequest(handler) {
const server = createServer((_, res) => handler(res)).unref().listen(0);
await events.once(server, 'listening');
const { port } = server.address();
return fetch(`http://127.0.0.1:${port}/foo.wasm`);
}
// Runs the given function both with the promise itself and as a continuation
// of the promise. We use this to test that the API accepts not just a Response
// but also a Promise that resolves to a Response.
function withPromiseAndResolved(makePromise, consume) {
return Promise.all([
consume(makePromise()),
makePromise().then(consume),
]);
}
// The makeResponsePromise function must return a Promise that resolves to a
// Response. The checkResult function receives the Promise returned by
// WebAssembly.compileStreaming and must return a Promise itself.
function testCompileStreaming(makeResponsePromise, checkResult) {
return withPromiseAndResolved(
common.mustCall(makeResponsePromise, 2),
common.mustCall((response) => {
return checkResult(WebAssembly.compileStreaming(response));
}, 2)
);
}
function testCompileStreamingSuccess(makeResponsePromise) {
return testCompileStreaming(makeResponsePromise, async (modPromise) => {
const mod = await modPromise;
assert.strictEqual(mod.constructor, WebAssembly.Module);
});
}
function testCompileStreamingRejection(makeResponsePromise, rejection) {
return testCompileStreaming(makeResponsePromise, (modPromise) => {
assert.strictEqual(modPromise.constructor, Promise);
return assert.rejects(modPromise, rejection);
});
}
function testCompileStreamingSuccessUsingFetch(responseCallback) {
return testCompileStreamingSuccess(() => testRequest(responseCallback));
}
function testCompileStreamingRejectionUsingFetch(responseCallback, rejection) {
return testCompileStreamingRejection(() => testRequest(responseCallback),
rejection);
}
(async () => {
// A non-Response should cause a TypeError.
for (const invalid of [undefined, null, 0, true, 'foo', {}, [], Symbol()]) {
await withPromiseAndResolved(() => Promise.resolve(invalid), (arg) => {
return assert.rejects(() => WebAssembly.compileStreaming(arg), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
message: /^The "source" argument .*$/
});
});
}
// When given a Promise, any rejection should be propagated as-is.
{
const err = new RangeError('foo');
await assert.rejects(() => {
return WebAssembly.compileStreaming(Promise.reject(err));
}, (actualError) => actualError === err);
}
// A valid WebAssembly file with the correct MIME type.
await testCompileStreamingSuccessUsingFetch((res) => {
res.setHeader('Content-Type', 'application/wasm');
res.end(simpleWasmBytes);
});
// The same valid WebAssembly file with the same MIME type, but using a
// Response whose body is a Buffer instead of calling fetch().
await testCompileStreamingSuccess(() => {
return Promise.resolve(new Response(simpleWasmBytes, {
status: 200,
headers: { 'Content-Type': 'application/wasm' }
}));
});
// The same valid WebAssembly file with the same MIME type, but using a
// Response whose body is a ReadableStream instead of calling fetch().
await testCompileStreamingSuccess(async () => {
const handle = await fs.open(fixtures.path('simple.wasm'));
const stream = handle.readableWebStream();
return Promise.resolve(new Response(stream, {
status: 200,
headers: { 'Content-Type': 'application/wasm' }
}));
});
// A larger valid WebAssembly file with the correct MIME type that causes the
// client to pass it to the compiler in many separate chunks. For this, we use
// the same WebAssembly file as in the previous test but insert useless custom
// sections into the WebAssembly module to increase the file size without
// changing the relevant contents.
await testCompileStreamingSuccessUsingFetch((res) => {
res.setHeader('Content-Type', 'application/wasm');
// Send the WebAssembly magic and version first.
res.write(simpleWasmBytes.slice(0, 8), common.mustCall());
// Construct a 4KiB custom section.
const customSection = Buffer.concat([
Buffer.from([
0, // Custom section.
134, 32, // (134 & 0x7f) + 0x80 * 32 = 6 + 4096 bytes in this section.
5, // The length of the following section name.
]),
Buffer.from('?'.repeat(5)), // The section name
Buffer.from('\0'.repeat(4096)), // The actual section data
]);
// Now repeatedly send useless custom sections. These have no use for the
// WebAssembly compiler but they are syntactically valid. The client has to
// keep reading the stream until the very end to obtain the relevant
// sections within the module. This adds up to a few hundred kibibytes.
(function next(i) {
if (i < 100) {
while (res.write(customSection));
res.once('drain', () => next(i + 1));
} else {
// End the response body with the actual module contents.
res.end(simpleWasmBytes.slice(8));
}
})(0);
});
// A valid WebAssembly file with an empty parameter in the (otherwise valid)
// MIME type.
await testCompileStreamingRejectionUsingFetch((res) => {
res.setHeader('Content-Type', 'application/wasm;');
res.end(simpleWasmBytes);
}, {
name: 'TypeError',
code: 'ERR_WEBASSEMBLY_RESPONSE',
message: 'WebAssembly response has unsupported MIME type ' +
"'application/wasm;'"
});
// A valid WebAssembly file with an invalid MIME type.
await testCompileStreamingRejectionUsingFetch((res) => {
res.setHeader('Content-Type', 'application/octet-stream');
res.end(simpleWasmBytes);
}, {
name: 'TypeError',
code: 'ERR_WEBASSEMBLY_RESPONSE',
message: 'WebAssembly response has unsupported MIME type ' +
"'application/octet-stream'"
});
// HTTP status code indicating an error.
await testCompileStreamingRejectionUsingFetch((res) => {
res.statusCode = 418;
res.setHeader('Content-Type', 'application/wasm');
res.end(simpleWasmBytes);
}, {
name: 'TypeError',
code: 'ERR_WEBASSEMBLY_RESPONSE',
message: /^WebAssembly response has status code 418$/
});
// HTTP status code indicating an error, but using a Response whose body is
// a Buffer instead of calling fetch().
await testCompileStreamingSuccess(() => {
return Promise.resolve(new Response(simpleWasmBytes, {
status: 200,
headers: { 'Content-Type': 'application/wasm' }
}));
});
// Extra bytes after the WebAssembly file.
await testCompileStreamingRejectionUsingFetch((res) => {
res.setHeader('Content-Type', 'application/wasm');
res.end(Buffer.concat([simpleWasmBytes, Buffer.from('foo')]));
}, {
name: 'CompileError',
message: /^WebAssembly\.compileStreaming\(\): .*$/
});
// Missing bytes at the end of the WebAssembly file.
await testCompileStreamingRejectionUsingFetch((res) => {
res.setHeader('Content-Type', 'application/wasm');
res.end(simpleWasmBytes.subarray(0, simpleWasmBytes.length - 3));
}, {
name: 'CompileError',
message: /^WebAssembly\.compileStreaming\(\): .*$/
});
// Incomplete HTTP response body. The TypeError might come as a surprise, but
// it originates from within fetch().
await testCompileStreamingRejectionUsingFetch((res) => {
res.setHeader('Content-Length', simpleWasmBytes.length);
res.setHeader('Content-Type', 'application/wasm');
res.write(simpleWasmBytes.slice(0, 5), common.mustSucceed(() => {
res.destroy();
}));
}, {
name: 'TypeError',
message: /terminated/
});
// Test "Developer-Facing Display Conventions" described in the WebAssembly
// Web API specification.
await testCompileStreaming(() => testRequest((res) => {
// Respond with a WebAssembly module that only exports a single function,
// which only contains an 'unreachable' instruction.
res.setHeader('Content-Type', 'application/wasm');
res.end(fixtures.readSync('crash.wasm'));
}), async (modPromise) => {
// Call the WebAssembly function and check that the error stack contains the
// correct "WebAssembly location" as per the specification.
const mod = await modPromise;
const instance = new WebAssembly.Instance(mod);
assert.throws(() => instance.exports.crash(), (err) => {
const stack = err.stack.split(/\n/g);
assert.strictEqual(stack[0], 'RuntimeError: unreachable');
assert.match(stack[1],
/^\s*at http:\/\/127\.0\.0\.1:\d+\/foo\.wasm:wasm-function\[0\]:0x22$/);
return true;
});
});
})().then(common.mustCall());