diff options
author | Gus Caplan <me@gus.host> | 2018-01-13 23:35:51 -0800 |
---|---|---|
committer | Timothy Gu <timothygu99@gmail.com> | 2018-01-30 17:00:57 -0800 |
commit | 0993fbe5b213d0fe746c3162bcda85f6c66bb552 (patch) | |
tree | f0a1ffef2ab24f1da963a23a43f3dd58eed5bba0 /test | |
parent | 2033a9f4362ee46b0fbddf9caf9cf6238391561e (diff) | |
download | android-node-v8-0993fbe5b213d0fe746c3162bcda85f6c66bb552.tar.gz android-node-v8-0993fbe5b213d0fe746c3162bcda85f6c66bb552.tar.bz2 android-node-v8-0993fbe5b213d0fe746c3162bcda85f6c66bb552.zip |
vm: add modules
Adds vm.Module, which wraps around ModuleWrap to provide an interface
for developers to work with modules in a more reflective manner.
Co-authored-by: Timothy Gu <timothygu99@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/17560
Reviewed-By: Michaƫl Zasso <targos@protonmail.com>
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
Diffstat (limited to 'test')
-rw-r--r-- | test/parallel/test-vm-module-basic.js | 54 | ||||
-rw-r--r-- | test/parallel/test-vm-module-dynamic-import.js | 27 | ||||
-rw-r--r-- | test/parallel/test-vm-module-errors.js | 264 | ||||
-rw-r--r-- | test/parallel/test-vm-module-link.js | 135 | ||||
-rw-r--r-- | test/parallel/test-vm-module-reevaluate.js | 49 |
5 files changed, 529 insertions, 0 deletions
diff --git a/test/parallel/test-vm-module-basic.js b/test/parallel/test-vm-module-basic.js new file mode 100644 index 0000000000..4bbe0a95ee --- /dev/null +++ b/test/parallel/test-vm-module-basic.js @@ -0,0 +1,54 @@ +'use strict'; + +// Flags: --experimental-vm-modules + +const common = require('../common'); +const assert = require('assert'); +const { Module, createContext } = require('vm'); + +common.crashOnUnhandledRejection(); + +(async function test1() { + const context = createContext({ + foo: 'bar', + baz: undefined, + typeofProcess: undefined, + }); + const m = new Module( + 'baz = foo; typeofProcess = typeof process; typeof Object;', + { context } + ); + assert.strictEqual(m.status, 'uninstantiated'); + await m.link(common.mustNotCall()); + m.instantiate(); + assert.strictEqual(m.status, 'instantiated'); + const result = await m.evaluate(); + assert.strictEqual(m.status, 'evaluated'); + assert.strictEqual(Object.getPrototypeOf(result), null); + assert.deepStrictEqual(context, { + foo: 'bar', + baz: 'bar', + typeofProcess: 'undefined' + }); + assert.strictEqual(result.result, 'function'); +}()); + +(async () => { + const m = new Module( + 'global.vmResult = "foo"; Object.prototype.toString.call(process);' + ); + await m.link(common.mustNotCall()); + m.instantiate(); + const { result } = await m.evaluate(); + assert.strictEqual(global.vmResult, 'foo'); + assert.strictEqual(result, '[object process]'); + delete global.vmResult; +})(); + +(async () => { + const m = new Module('while (true) {}'); + await m.link(common.mustNotCall()); + m.instantiate(); + await m.evaluate({ timeout: 500 }) + .then(() => assert(false), () => {}); +})(); diff --git a/test/parallel/test-vm-module-dynamic-import.js b/test/parallel/test-vm-module-dynamic-import.js new file mode 100644 index 0000000000..ca4dceb5de --- /dev/null +++ b/test/parallel/test-vm-module-dynamic-import.js @@ -0,0 +1,27 @@ +'use strict'; + +// Flags: --experimental-vm-modules --experimental-modules --harmony-dynamic-import + +const common = require('../common'); +common.crashOnUnhandledRejection(); + +const assert = require('assert'); +const { Module, createContext } = require('vm'); + +const finished = common.mustCall(); + +(async function() { + const m = new Module('import("foo")', { context: createContext() }); + await m.link(common.mustNotCall()); + m.instantiate(); + const { result } = await m.evaluate(); + let threw = false; + try { + await result; + } catch (err) { + threw = true; + assert.strictEqual(err.message, 'import() called outside of main context'); + } + assert(threw); + finished(); +}()); diff --git a/test/parallel/test-vm-module-errors.js b/test/parallel/test-vm-module-errors.js new file mode 100644 index 0000000000..8bcb101ccc --- /dev/null +++ b/test/parallel/test-vm-module-errors.js @@ -0,0 +1,264 @@ +'use strict'; + +// Flags: --experimental-vm-modules + +const common = require('../common'); +common.crashOnUnhandledRejection(); + +const assert = require('assert'); + +const { Module, createContext } = require('vm'); + +async function expectsRejection(fn, settings) { + const validateError = common.expectsError(settings); + // Retain async context. + const storedError = new Error('Thrown from:'); + try { + await fn(); + } catch (err) { + try { + validateError(err); + } catch (validationError) { + console.error(validationError); + console.error('Original error:'); + console.error(err); + throw storedError; + } + return; + } + assert.fail('Missing expected exception'); +} + +async function createEmptyLinkedModule() { + const m = new Module(''); + await m.link(common.mustNotCall()); + return m; +} + +async function checkArgType() { + common.expectsError(() => { + new Module(); + }, { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError + }); + + for (const invalidOptions of [ + 0, 1, null, true, 'str', () => {}, Symbol.iterator + ]) { + common.expectsError(() => { + new Module('', invalidOptions); + }, { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError + }); + } + + for (const invalidLinker of [ + 0, 1, undefined, null, true, 'str', {}, Symbol.iterator + ]) { + await expectsRejection(async () => { + const m = new Module(''); + await m.link(invalidLinker); + }, { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError + }); + } +} + +// Check methods/properties can only be used under a specific state. +async function checkModuleState() { + await expectsRejection(async () => { + const m = new Module(''); + await m.link(common.mustNotCall()); + assert.strictEqual(m.linkingStatus, 'linked'); + await m.link(common.mustNotCall()); + }, { + code: 'ERR_VM_MODULE_ALREADY_LINKED' + }); + + await expectsRejection(async () => { + const m = new Module(''); + m.link(common.mustNotCall()); + assert.strictEqual(m.linkingStatus, 'linking'); + await m.link(common.mustNotCall()); + }, { + code: 'ERR_VM_MODULE_ALREADY_LINKED' + }); + + common.expectsError(() => { + const m = new Module(''); + m.instantiate(); + }, { + code: 'ERR_VM_MODULE_NOT_LINKED' + }); + + await expectsRejection(async () => { + const m = new Module('import "foo";'); + try { + await m.link(common.mustCall(() => ({}))); + } catch (err) { + assert.strictEqual(m.linkingStatus, 'errored'); + m.instantiate(); + } + assert.fail('Unreachable'); + }, { + code: 'ERR_VM_MODULE_NOT_LINKED' + }); + + { + const m = new Module('import "foo";'); + await m.link(common.mustCall(async (module, specifier) => { + assert.strictEqual(module, m); + assert.strictEqual(specifier, 'foo'); + assert.strictEqual(m.linkingStatus, 'linking'); + common.expectsError(() => { + m.instantiate(); + }, { + code: 'ERR_VM_MODULE_NOT_LINKED' + }); + return new Module(''); + })); + m.instantiate(); + await m.evaluate(); + } + + await expectsRejection(async () => { + const m = new Module(''); + await m.evaluate(); + }, { + code: 'ERR_VM_MODULE_STATUS', + message: 'Module status must be one of instantiated, evaluated, and errored' + }); + + await expectsRejection(async () => { + const m = await createEmptyLinkedModule(); + await m.evaluate(); + }, { + code: 'ERR_VM_MODULE_STATUS', + message: 'Module status must be one of instantiated, evaluated, and errored' + }); + + common.expectsError(() => { + const m = new Module(''); + m.error; + }, { + code: 'ERR_VM_MODULE_STATUS', + message: 'Module status must be errored' + }); + + await expectsRejection(async () => { + const m = await createEmptyLinkedModule(); + m.instantiate(); + await m.evaluate(); + m.error; + }, { + code: 'ERR_VM_MODULE_STATUS', + message: 'Module status must be errored' + }); + + common.expectsError(() => { + const m = new Module(''); + m.namespace; + }, { + code: 'ERR_VM_MODULE_STATUS', + message: 'Module status must not be uninstantiated or instantiating' + }); + + await expectsRejection(async () => { + const m = await createEmptyLinkedModule(); + m.namespace; + }, { + code: 'ERR_VM_MODULE_STATUS', + message: 'Module status must not be uninstantiated or instantiating' + }); +} + +// Check link() fails when the returned module is not valid. +async function checkLinking() { + await expectsRejection(async () => { + const m = new Module('import "foo";'); + try { + await m.link(common.mustCall(() => ({}))); + } catch (err) { + assert.strictEqual(m.linkingStatus, 'errored'); + throw err; + } + assert.fail('Unreachable'); + }, { + code: 'ERR_VM_MODULE_NOT_MODULE' + }); + + await expectsRejection(async () => { + const c = createContext({ a: 1 }); + const foo = new Module('', { context: c }); + await foo.link(common.mustNotCall()); + const bar = new Module('import "foo";'); + try { + await bar.link(common.mustCall(() => foo)); + } catch (err) { + assert.strictEqual(bar.linkingStatus, 'errored'); + throw err; + } + assert.fail('Unreachable'); + }, { + code: 'ERR_VM_MODULE_DIFFERENT_CONTEXT' + }); + + await expectsRejection(async () => { + const erroredModule = new Module('import "foo";'); + try { + await erroredModule.link(common.mustCall(() => ({}))); + } catch (err) { + // ignored + } finally { + assert.strictEqual(erroredModule.linkingStatus, 'errored'); + } + + const rootModule = new Module('import "errored";'); + await rootModule.link(common.mustCall(() => erroredModule)); + }, { + code: 'ERR_VM_MODULE_LINKING_ERRORED' + }); +} + +// Check the JavaScript engine deals with exceptions correctly +async function checkExecution() { + await (async () => { + const m = new Module('import { nonexistent } from "module";'); + await m.link(common.mustCall(() => new Module(''))); + + // There is no code for this exception since it is thrown by the JavaScript + // engine. + assert.throws(() => { + m.instantiate(); + }, SyntaxError); + })(); + + await (async () => { + const m = new Module('throw new Error();'); + await m.link(common.mustNotCall()); + m.instantiate(); + const evaluatePromise = m.evaluate(); + await evaluatePromise.catch(() => {}); + assert.strictEqual(m.status, 'errored'); + try { + await evaluatePromise; + } catch (err) { + assert.strictEqual(m.error, err); + return; + } + assert.fail('Missing expected exception'); + })(); +} + +const finished = common.mustCall(); + +(async function main() { + await checkArgType(); + await checkModuleState(); + await checkLinking(); + await checkExecution(); + finished(); +})(); diff --git a/test/parallel/test-vm-module-link.js b/test/parallel/test-vm-module-link.js new file mode 100644 index 0000000000..870427e91b --- /dev/null +++ b/test/parallel/test-vm-module-link.js @@ -0,0 +1,135 @@ +'use strict'; + +// Flags: --experimental-vm-modules + +const common = require('../common'); +common.crashOnUnhandledRejection(); + +const assert = require('assert'); +const { URL } = require('url'); + +const { Module } = require('vm'); + +async function simple() { + const foo = new Module('export default 5;'); + await foo.link(common.mustNotCall()); + + const bar = new Module('import five from "foo"; five'); + + assert.deepStrictEqual(bar.dependencySpecifiers, ['foo']); + + await bar.link(common.mustCall((module, specifier) => { + assert.strictEqual(module, bar); + assert.strictEqual(specifier, 'foo'); + return foo; + })); + + bar.instantiate(); + + assert.strictEqual((await bar.evaluate()).result, 5); +} + +async function depth() { + const foo = new Module('export default 5'); + await foo.link(common.mustNotCall()); + + async function getProxy(parentName, parentModule) { + const mod = new Module(` + import ${parentName} from '${parentName}'; + export default ${parentName}; + `); + await mod.link(common.mustCall((module, specifier) => { + assert.strictEqual(module, mod); + assert.strictEqual(specifier, parentName); + return parentModule; + })); + return mod; + } + + const bar = await getProxy('foo', foo); + const baz = await getProxy('bar', bar); + const barz = await getProxy('baz', baz); + + barz.instantiate(); + await barz.evaluate(); + + assert.strictEqual(barz.namespace.default, 5); +} + +async function circular() { + const foo = new Module(` + import getFoo from 'bar'; + export let foo = 42; + export default getFoo(); + `); + const bar = new Module(` + import { foo } from 'foo'; + export default function getFoo() { + return foo; + } + `); + await foo.link(common.mustCall(async (fooModule, fooSpecifier) => { + assert.strictEqual(fooModule, foo); + assert.strictEqual(fooSpecifier, 'bar'); + await bar.link(common.mustCall((barModule, barSpecifier) => { + assert.strictEqual(barModule, bar); + assert.strictEqual(barSpecifier, 'foo'); + assert.strictEqual(foo.linkingStatus, 'linking'); + return foo; + })); + assert.strictEqual(bar.linkingStatus, 'linked'); + return bar; + })); + + foo.instantiate(); + await foo.evaluate(); + assert.strictEqual(foo.namespace.default, 42); +} + +async function circular2() { + const sourceMap = { + root: ` + import * as a from './a.mjs'; + import * as b from './b.mjs'; + if (!('fromA' in a)) + throw new Error(); + if (!('fromB' in a)) + throw new Error(); + if (!('fromA' in b)) + throw new Error(); + if (!('fromB' in b)) + throw new Error(); + `, + './a.mjs': ` + export * from './b.mjs'; + export var fromA; + `, + './b.mjs': ` + export * from './a.mjs'; + export var fromB; + ` + }; + const moduleMap = new Map(); + const rootModule = new Module(sourceMap.root, { url: 'vm:root' }); + async function link(referencingModule, specifier) { + if (moduleMap.has(specifier)) { + return moduleMap.get(specifier); + } + const mod = new Module(sourceMap[specifier], { url: new URL(specifier, 'file:///').href }); + moduleMap.set(specifier, mod); + return mod; + } + await rootModule.link(link); + rootModule.instantiate(); + await rootModule.evaluate(); +} + +const finished = common.mustCall(); + +(async function main() { + await simple(); + await depth(); + await circular(); + await circular2(); + finished(); +})(); diff --git a/test/parallel/test-vm-module-reevaluate.js b/test/parallel/test-vm-module-reevaluate.js new file mode 100644 index 0000000000..e4f5858800 --- /dev/null +++ b/test/parallel/test-vm-module-reevaluate.js @@ -0,0 +1,49 @@ +'use strict'; + +// Flags: --experimental-vm-modules + +const common = require('../common'); +common.crashOnUnhandledRejection(); + +const assert = require('assert'); + +const { Module } = require('vm'); + +const finished = common.mustCall(); + +(async function main() { + { + const m = new Module('1'); + await m.link(common.mustNotCall()); + m.instantiate(); + assert.strictEqual((await m.evaluate()).result, 1); + assert.strictEqual((await m.evaluate()).result, undefined); + assert.strictEqual((await m.evaluate()).result, undefined); + } + + { + const m = new Module('throw new Error()'); + await m.link(common.mustNotCall()); + m.instantiate(); + + let threw = false; + try { + await m.evaluate(); + } catch (err) { + assert(err instanceof Error); + threw = true; + } + assert(threw); + + threw = false; + try { + await m.evaluate(); + } catch (err) { + assert(err instanceof Error); + threw = true; + } + assert(threw); + } + + finished(); +})(); |