| 'use strict'; |
| |
| // Flags: --experimental-vm-modules |
| |
| const common = require('../common'); |
| |
| const assert = require('assert'); |
| |
| const { SourceTextModule, createContext, Module } = require('vm'); |
| |
| async function createEmptyLinkedModule() { |
| const m = new SourceTextModule(''); |
| await m.link(common.mustNotCall()); |
| return m; |
| } |
| |
| async function checkArgType() { |
| assert.throws(() => { |
| new SourceTextModule(); |
| }, { |
| code: 'ERR_INVALID_ARG_TYPE', |
| name: 'TypeError' |
| }); |
| |
| for (const invalidOptions of [ |
| 0, 1, null, true, 'str', () => {}, { identifier: 0 }, Symbol.iterator, |
| { context: null }, { context: 'hucairz' }, { context: {} }, |
| ]) { |
| assert.throws(() => { |
| new SourceTextModule('', invalidOptions); |
| }, { |
| code: 'ERR_INVALID_ARG_TYPE', |
| name: 'TypeError' |
| }); |
| } |
| |
| for (const invalidLinker of [ |
| 0, 1, undefined, null, true, 'str', {}, Symbol.iterator, |
| ]) { |
| await assert.rejects(async () => { |
| const m = new SourceTextModule(''); |
| await m.link(invalidLinker); |
| }, { |
| code: 'ERR_INVALID_ARG_TYPE', |
| name: 'TypeError' |
| }); |
| } |
| } |
| |
| // Check methods/properties can only be used under a specific state. |
| async function checkModuleState() { |
| await assert.rejects(async () => { |
| const m = new SourceTextModule(''); |
| await m.link(common.mustNotCall()); |
| assert.strictEqual(m.status, 'linked'); |
| await m.link(common.mustNotCall()); |
| }, { |
| code: 'ERR_VM_MODULE_ALREADY_LINKED' |
| }); |
| |
| await assert.rejects(async () => { |
| const m = new SourceTextModule(''); |
| m.link(common.mustNotCall()); |
| assert.strictEqual(m.status, 'linking'); |
| await m.link(common.mustNotCall()); |
| }, { |
| code: 'ERR_VM_MODULE_STATUS' |
| }); |
| |
| await assert.rejects(async () => { |
| const m = new SourceTextModule(''); |
| await m.evaluate(); |
| }, { |
| code: 'ERR_VM_MODULE_STATUS', |
| message: 'Module status must be one of linked, evaluated, or errored' |
| }); |
| |
| await assert.rejects(async () => { |
| const m = new SourceTextModule(''); |
| await m.evaluate(false); |
| }, { |
| code: 'ERR_INVALID_ARG_TYPE', |
| message: 'The "options" argument must be of type object. ' + |
| 'Received type boolean (false)' |
| }); |
| |
| assert.throws(() => { |
| const m = new SourceTextModule(''); |
| m.error; // eslint-disable-line no-unused-expressions |
| }, { |
| code: 'ERR_VM_MODULE_STATUS', |
| message: 'Module status must be errored' |
| }); |
| |
| await assert.rejects(async () => { |
| const m = await createEmptyLinkedModule(); |
| await m.evaluate(); |
| m.error; // eslint-disable-line no-unused-expressions |
| }, { |
| code: 'ERR_VM_MODULE_STATUS', |
| message: 'Module status must be errored' |
| }); |
| |
| assert.throws(() => { |
| const m = new SourceTextModule(''); |
| m.namespace; // eslint-disable-line no-unused-expressions |
| }, { |
| code: 'ERR_VM_MODULE_STATUS', |
| message: 'Module status must not be unlinked or linking' |
| }); |
| } |
| |
| // Check link() fails when the returned module is not valid. |
| async function checkLinking() { |
| await assert.rejects(async () => { |
| const m = new SourceTextModule('import "foo";'); |
| try { |
| await m.link(common.mustCall(() => ({}))); |
| } catch (err) { |
| assert.strictEqual(m.status, 'errored'); |
| throw err; |
| } |
| }, { |
| code: 'ERR_VM_MODULE_NOT_MODULE' |
| }); |
| |
| await assert.rejects(async () => { |
| const c = createContext({ a: 1 }); |
| const foo = new SourceTextModule('', { context: c }); |
| await foo.link(common.mustNotCall()); |
| const bar = new SourceTextModule('import "foo";'); |
| try { |
| await bar.link(common.mustCall(() => foo)); |
| } catch (err) { |
| assert.strictEqual(bar.status, 'errored'); |
| throw err; |
| } |
| }, { |
| code: 'ERR_VM_MODULE_DIFFERENT_CONTEXT' |
| }); |
| |
| const error = new Error(); |
| await assert.rejects(async () => { |
| globalThis.error = error; |
| const erroredModule = new SourceTextModule('throw error;'); |
| await erroredModule.link(common.mustNotCall()); |
| try { |
| await erroredModule.evaluate(); |
| } catch { |
| // ignored |
| } |
| delete globalThis.error; |
| |
| assert.strictEqual(erroredModule.status, 'errored'); |
| |
| const rootModule = new SourceTextModule('import "errored";'); |
| await rootModule.link(common.mustCall(() => erroredModule)); |
| }, { |
| code: 'ERR_VM_MODULE_LINK_FAILURE', |
| cause: error, |
| }); |
| } |
| |
| assert.throws(() => { |
| new SourceTextModule('', { |
| importModuleDynamically: 'hucairz' |
| }); |
| }, { |
| code: 'ERR_INVALID_ARG_TYPE', |
| name: 'TypeError', |
| message: 'The "options.importModuleDynamically" property must be of type ' + |
| "function. Received type string ('hucairz')" |
| }); |
| |
| // Check the JavaScript engine deals with exceptions correctly |
| async function checkExecution() { |
| await (async () => { |
| const m = new SourceTextModule('import { nonexistent } from "module";'); |
| |
| // There is no code for this exception since it is thrown by the JavaScript |
| // engine. |
| await assert.rejects(() => { |
| return m.link(common.mustCall(() => new SourceTextModule(''))); |
| }, SyntaxError); |
| })(); |
| |
| await (async () => { |
| const m = new SourceTextModule('throw new Error();'); |
| await m.link(common.mustNotCall()); |
| try { |
| await m.evaluate(); |
| } catch (err) { |
| assert.strictEqual(m.error, err); |
| assert.strictEqual(m.status, 'errored'); |
| return; |
| } |
| assert.fail('Missing expected exception'); |
| })(); |
| } |
| |
| // Check for error thrown when breakOnSigint is not a boolean for evaluate() |
| async function checkInvalidOptionForEvaluate() { |
| await assert.rejects(async () => { |
| const m = new SourceTextModule('export const a = 1; export let b = 2'); |
| await m.evaluate({ breakOnSigint: 'a-string' }); |
| }, { |
| name: 'TypeError', |
| message: |
| 'The "options.breakOnSigint" property must be of type boolean. ' + |
| "Received type string ('a-string')", |
| code: 'ERR_INVALID_ARG_TYPE' |
| }); |
| |
| { |
| ['link', 'evaluate'].forEach(async (method) => { |
| await assert.rejects(async () => { |
| await Module.prototype[method](); |
| }, { code: 'ERR_INVALID_THIS' }); |
| }); |
| } |
| } |
| |
| function checkInvalidCachedData() { |
| [true, false, 'foo', {}, Array, function() {}].forEach((invalidArg) => { |
| const message = 'The "options.cachedData" property must be an ' + |
| 'instance of Buffer, TypedArray, or DataView.' + |
| common.invalidArgTypeHelper(invalidArg); |
| assert.throws( |
| () => new SourceTextModule('import "foo";', { cachedData: invalidArg }), |
| { |
| code: 'ERR_INVALID_ARG_TYPE', |
| name: 'TypeError', |
| message, |
| } |
| ); |
| }); |
| } |
| |
| function checkGettersErrors() { |
| const expectedError = { name: 'TypeError' }; |
| const getters = ['identifier', 'context', 'namespace', 'status', 'error']; |
| getters.forEach((getter) => { |
| assert.throws(() => { |
| // eslint-disable-next-line no-unused-expressions |
| Module.prototype[getter]; |
| }, expectedError, `Module.prototype.${getter} should throw`); |
| assert.throws(() => { |
| // eslint-disable-next-line no-unused-expressions |
| SourceTextModule.prototype[getter]; |
| }, expectedError, `SourceTextModule.prototype.${getter} should throw`); |
| }); |
| |
| const sourceTextModuleGetters = [ |
| 'moduleRequests', |
| 'dependencySpecifiers', |
| ]; |
| sourceTextModuleGetters.forEach((getter) => { |
| assert.throws(() => { |
| // eslint-disable-next-line no-unused-expressions |
| SourceTextModule.prototype[getter]; |
| }, expectedError, `SourceTextModule.prototype.${getter} should throw`); |
| }); |
| } |
| |
| const finished = common.mustCall(); |
| |
| (async function main() { |
| await checkArgType(); |
| await checkModuleState(); |
| await checkLinking(); |
| await checkExecution(); |
| await checkInvalidOptionForEvaluate(); |
| checkInvalidCachedData(); |
| checkGettersErrors(); |
| finished(); |
| })().then(common.mustCall()); |