mirror of
https://github.com/nodejs/node.git
synced 2025-12-28 07:50:41 +00:00
- Make sure that the vm.Module.evaluate() method is conditionally synchronous based on the specification. Previously, the returned promise is unconditionally pending (even for synthetic modules and source text modules without top-level await) instead of immediately fulfilled, making it harder to debug as it deviates from the specified behavior. - Clarify the synchronicity of this method in the documentation - Add more tests for the synchronicity of this method. PR-URL: https://github.com/nodejs/node/pull/60205 Refs: https://github.com/nodejs/node/issues/59656 Refs: https://github.com/nodejs/node/issues/37648 Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
168 lines
6.1 KiB
JavaScript
168 lines
6.1 KiB
JavaScript
// Flags: --experimental-vm-modules
|
|
'use strict';
|
|
|
|
// This tests the result of evaluating a vm.SourceTextModule.
|
|
const common = require('../common');
|
|
|
|
const assert = require('assert');
|
|
// To make testing easier we just use the public inspect API. If the output format
|
|
// changes, update this test accordingly.
|
|
const { inspect } = require('util');
|
|
const vm = require('vm');
|
|
|
|
globalThis.callCount = {};
|
|
common.allowGlobals(globalThis.callCount);
|
|
|
|
// Synchronous error during evaluation results in a synchronously rejected promise.
|
|
{
|
|
globalThis.callCount.syncError = 0;
|
|
const mod = new vm.SourceTextModule(`
|
|
globalThis.callCount.syncError++;
|
|
throw new Error("synchronous source text module");
|
|
export const a = 1;
|
|
`);
|
|
mod.linkRequests([]);
|
|
mod.instantiate();
|
|
const promise = mod.evaluate();
|
|
assert.strictEqual(globalThis.callCount.syncError, 1);
|
|
assert.match(inspect(promise), /rejected/);
|
|
assert(mod.error, 'Expected mod.error to be set');
|
|
assert.strictEqual(mod.error.message, 'synchronous source text module');
|
|
|
|
promise.catch(common.mustCall((err) => {
|
|
assert.strictEqual(err, mod.error);
|
|
// Calling evaluate() again results in the same rejection synchronously.
|
|
const promise2 = mod.evaluate();
|
|
assert.match(inspect(promise2), /rejected/);
|
|
promise2.catch(common.mustCall((err2) => {
|
|
assert.strictEqual(err, err2);
|
|
// The module is only evaluated once.
|
|
assert.strictEqual(globalThis.callCount.syncError, 1);
|
|
}));
|
|
}));
|
|
}
|
|
|
|
// Successful evaluation of a module without top-level await results in a
|
|
// promise synchronously resolved to undefined.
|
|
{
|
|
globalThis.callCount.syncNamedExports = 0;
|
|
const mod = new vm.SourceTextModule(`
|
|
globalThis.callCount.syncNamedExports++;
|
|
export const a = 1, b = 2;
|
|
`);
|
|
mod.linkRequests([]);
|
|
mod.instantiate();
|
|
const promise = mod.evaluate();
|
|
assert.match(inspect(promise), /Promise { undefined }/);
|
|
assert.strictEqual(mod.namespace.a, 1);
|
|
assert.strictEqual(mod.namespace.b, 2);
|
|
assert.strictEqual(globalThis.callCount.syncNamedExports, 1);
|
|
promise.then(common.mustCall((value) => {
|
|
assert.strictEqual(value, undefined);
|
|
|
|
// Calling evaluate() again results in the same resolved promise synchronously.
|
|
const promise2 = mod.evaluate();
|
|
assert.match(inspect(promise2), /Promise { undefined }/);
|
|
assert.strictEqual(mod.namespace.a, 1);
|
|
assert.strictEqual(mod.namespace.b, 2);
|
|
promise2.then(common.mustCall((value) => {
|
|
assert.strictEqual(value, undefined);
|
|
// The module is only evaluated once.
|
|
assert.strictEqual(globalThis.callCount.syncNamedExports, 1);
|
|
}));
|
|
}));
|
|
}
|
|
|
|
{
|
|
globalThis.callCount.syncDefaultExports = 0;
|
|
// Modules with either named and default exports have the same behaviors.
|
|
const mod = new vm.SourceTextModule(`
|
|
globalThis.callCount.syncDefaultExports++;
|
|
export default 42;
|
|
`);
|
|
mod.linkRequests([]);
|
|
mod.instantiate();
|
|
const promise = mod.evaluate();
|
|
assert.match(inspect(promise), /Promise { undefined }/);
|
|
assert.strictEqual(mod.namespace.default, 42);
|
|
assert.strictEqual(globalThis.callCount.syncDefaultExports, 1);
|
|
|
|
promise.then(common.mustCall((value) => {
|
|
assert.strictEqual(value, undefined);
|
|
|
|
// Calling evaluate() again results in the same resolved promise synchronously.
|
|
const promise2 = mod.evaluate();
|
|
assert.match(inspect(promise2), /Promise { undefined }/);
|
|
assert.strictEqual(mod.namespace.default, 42);
|
|
promise2.then(common.mustCall((value) => {
|
|
assert.strictEqual(value, undefined);
|
|
// The module is only evaluated once.
|
|
assert.strictEqual(globalThis.callCount.syncDefaultExports, 1);
|
|
}));
|
|
}));
|
|
}
|
|
|
|
// Successful evaluation of a module with top-level await results in a promise
|
|
// that is fulfilled asynchronously with undefined.
|
|
{
|
|
globalThis.callCount.asyncEvaluation = 0;
|
|
const mod = new vm.SourceTextModule(`
|
|
globalThis.callCount.asyncEvaluation++;
|
|
await Promise.resolve();
|
|
export const a = 1;
|
|
`);
|
|
mod.linkRequests([]);
|
|
mod.instantiate();
|
|
const promise = mod.evaluate();
|
|
assert.match(inspect(promise), /<pending>/);
|
|
// Accessing the namespace before the promise is fulfilled throws ReferenceError.
|
|
assert.throws(() => mod.namespace.a, { name: 'ReferenceError' });
|
|
assert.strictEqual(globalThis.callCount.asyncEvaluation, 1);
|
|
promise.then(common.mustCall((value) => {
|
|
assert.strictEqual(value, undefined);
|
|
assert.strictEqual(globalThis.callCount.asyncEvaluation, 1);
|
|
|
|
// Calling evaluate() again results in a promise synchronously resolved to undefined.
|
|
const promise2 = mod.evaluate();
|
|
assert.match(inspect(promise2), /Promise { undefined }/);
|
|
assert.strictEqual(mod.namespace.a, 1);
|
|
promise2.then(common.mustCall((value) => {
|
|
assert.strictEqual(value, undefined);
|
|
// The module is only evaluated once.
|
|
assert.strictEqual(globalThis.callCount.asyncEvaluation, 1);
|
|
}));
|
|
}));
|
|
}
|
|
|
|
// Rejection of a top-level await promise results in a promise that is
|
|
// rejected asynchronously with the same reason.
|
|
{
|
|
globalThis.callCount.asyncRejection = 0;
|
|
const mod = new vm.SourceTextModule(`
|
|
globalThis.callCount.asyncRejection++;
|
|
await Promise.reject(new Error("asynchronous source text module"));
|
|
export const a = 1;
|
|
`);
|
|
mod.linkRequests([]);
|
|
mod.instantiate();
|
|
const promise = mod.evaluate();
|
|
assert.match(inspect(promise), /<pending>/);
|
|
// Accessing the namespace before the promise is fulfilled throws ReferenceError.
|
|
assert.throws(() => mod.namespace.a, { name: 'ReferenceError' });
|
|
promise.catch(common.mustCall((err) => {
|
|
assert.strictEqual(err, mod.error);
|
|
assert.strictEqual(err.message, 'asynchronous source text module');
|
|
assert.strictEqual(globalThis.callCount.asyncRejection, 1);
|
|
|
|
// Calling evaluate() again results in a promise synchronously rejected
|
|
// with the same reason.
|
|
const promise2 = mod.evaluate();
|
|
assert.match(inspect(promise2), /rejected/);
|
|
promise2.catch(common.mustCall((err2) => {
|
|
assert.strictEqual(err, err2);
|
|
// The module is only evaluated once.
|
|
assert.strictEqual(globalThis.callCount.asyncRejection, 1);
|
|
}));
|
|
}));
|
|
}
|