// 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), //); // 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), //); // 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); })); })); }