1'use strict'; 2 3// Flags: --experimental-vm-modules 4 5const common = require('../common'); 6 7const assert = require('assert'); 8 9const { SourceTextModule, createContext, Module } = require('vm'); 10 11async function createEmptyLinkedModule() { 12 const m = new SourceTextModule(''); 13 await m.link(common.mustNotCall()); 14 return m; 15} 16 17async function checkArgType() { 18 assert.throws(() => { 19 new SourceTextModule(); 20 }, { 21 code: 'ERR_INVALID_ARG_TYPE', 22 name: 'TypeError' 23 }); 24 25 for (const invalidOptions of [ 26 0, 1, null, true, 'str', () => {}, { identifier: 0 }, Symbol.iterator, 27 { context: null }, { context: 'hucairz' }, { context: {} }, 28 ]) { 29 assert.throws(() => { 30 new SourceTextModule('', invalidOptions); 31 }, { 32 code: 'ERR_INVALID_ARG_TYPE', 33 name: 'TypeError' 34 }); 35 } 36 37 for (const invalidLinker of [ 38 0, 1, undefined, null, true, 'str', {}, Symbol.iterator, 39 ]) { 40 await assert.rejects(async () => { 41 const m = new SourceTextModule(''); 42 await m.link(invalidLinker); 43 }, { 44 code: 'ERR_INVALID_ARG_TYPE', 45 name: 'TypeError' 46 }); 47 } 48} 49 50// Check methods/properties can only be used under a specific state. 51async function checkModuleState() { 52 await assert.rejects(async () => { 53 const m = new SourceTextModule(''); 54 await m.link(common.mustNotCall()); 55 assert.strictEqual(m.status, 'linked'); 56 await m.link(common.mustNotCall()); 57 }, { 58 code: 'ERR_VM_MODULE_ALREADY_LINKED' 59 }); 60 61 await assert.rejects(async () => { 62 const m = new SourceTextModule(''); 63 m.link(common.mustNotCall()); 64 assert.strictEqual(m.status, 'linking'); 65 await m.link(common.mustNotCall()); 66 }, { 67 code: 'ERR_VM_MODULE_STATUS' 68 }); 69 70 await assert.rejects(async () => { 71 const m = new SourceTextModule(''); 72 await m.evaluate(); 73 }, { 74 code: 'ERR_VM_MODULE_STATUS', 75 message: 'Module status must be one of linked, evaluated, or errored' 76 }); 77 78 await assert.rejects(async () => { 79 const m = new SourceTextModule(''); 80 await m.evaluate(false); 81 }, { 82 code: 'ERR_INVALID_ARG_TYPE', 83 message: 'The "options" argument must be of type object. ' + 84 'Received type boolean (false)' 85 }); 86 87 assert.throws(() => { 88 const m = new SourceTextModule(''); 89 m.error; // eslint-disable-line no-unused-expressions 90 }, { 91 code: 'ERR_VM_MODULE_STATUS', 92 message: 'Module status must be errored' 93 }); 94 95 await assert.rejects(async () => { 96 const m = await createEmptyLinkedModule(); 97 await m.evaluate(); 98 m.error; // eslint-disable-line no-unused-expressions 99 }, { 100 code: 'ERR_VM_MODULE_STATUS', 101 message: 'Module status must be errored' 102 }); 103 104 assert.throws(() => { 105 const m = new SourceTextModule(''); 106 m.namespace; // eslint-disable-line no-unused-expressions 107 }, { 108 code: 'ERR_VM_MODULE_STATUS', 109 message: 'Module status must not be unlinked or linking' 110 }); 111} 112 113// Check link() fails when the returned module is not valid. 114async function checkLinking() { 115 await assert.rejects(async () => { 116 const m = new SourceTextModule('import "foo";'); 117 try { 118 await m.link(common.mustCall(() => ({}))); 119 } catch (err) { 120 assert.strictEqual(m.status, 'errored'); 121 throw err; 122 } 123 }, { 124 code: 'ERR_VM_MODULE_NOT_MODULE' 125 }); 126 127 await assert.rejects(async () => { 128 const c = createContext({ a: 1 }); 129 const foo = new SourceTextModule('', { context: c }); 130 await foo.link(common.mustNotCall()); 131 const bar = new SourceTextModule('import "foo";'); 132 try { 133 await bar.link(common.mustCall(() => foo)); 134 } catch (err) { 135 assert.strictEqual(bar.status, 'errored'); 136 throw err; 137 } 138 }, { 139 code: 'ERR_VM_MODULE_DIFFERENT_CONTEXT' 140 }); 141 142 const error = new Error(); 143 await assert.rejects(async () => { 144 globalThis.error = error; 145 const erroredModule = new SourceTextModule('throw error;'); 146 await erroredModule.link(common.mustNotCall()); 147 try { 148 await erroredModule.evaluate(); 149 } catch { 150 // ignored 151 } 152 delete globalThis.error; 153 154 assert.strictEqual(erroredModule.status, 'errored'); 155 156 const rootModule = new SourceTextModule('import "errored";'); 157 await rootModule.link(common.mustCall(() => erroredModule)); 158 }, { 159 code: 'ERR_VM_MODULE_LINK_FAILURE', 160 cause: error, 161 }); 162} 163 164assert.throws(() => { 165 new SourceTextModule('', { 166 importModuleDynamically: 'hucairz' 167 }); 168}, { 169 code: 'ERR_INVALID_ARG_TYPE', 170 name: 'TypeError', 171 message: 'The "options.importModuleDynamically" property must be of type ' + 172 "function. Received type string ('hucairz')" 173}); 174 175// Check the JavaScript engine deals with exceptions correctly 176async function checkExecution() { 177 await (async () => { 178 const m = new SourceTextModule('import { nonexistent } from "module";'); 179 180 // There is no code for this exception since it is thrown by the JavaScript 181 // engine. 182 await assert.rejects(() => { 183 return m.link(common.mustCall(() => new SourceTextModule(''))); 184 }, SyntaxError); 185 })(); 186 187 await (async () => { 188 const m = new SourceTextModule('throw new Error();'); 189 await m.link(common.mustNotCall()); 190 try { 191 await m.evaluate(); 192 } catch (err) { 193 assert.strictEqual(m.error, err); 194 assert.strictEqual(m.status, 'errored'); 195 return; 196 } 197 assert.fail('Missing expected exception'); 198 })(); 199} 200 201// Check for error thrown when breakOnSigint is not a boolean for evaluate() 202async function checkInvalidOptionForEvaluate() { 203 await assert.rejects(async () => { 204 const m = new SourceTextModule('export const a = 1; export let b = 2'); 205 await m.evaluate({ breakOnSigint: 'a-string' }); 206 }, { 207 name: 'TypeError', 208 message: 209 'The "options.breakOnSigint" property must be of type boolean. ' + 210 "Received type string ('a-string')", 211 code: 'ERR_INVALID_ARG_TYPE' 212 }); 213 214 { 215 ['link', 'evaluate'].forEach(async (method) => { 216 await assert.rejects(async () => { 217 await Module.prototype[method](); 218 }, { 219 code: 'ERR_VM_MODULE_NOT_MODULE', 220 message: /Provided module is not an instance of Module/ 221 }); 222 }); 223 } 224} 225 226function checkInvalidCachedData() { 227 [true, false, 'foo', {}, Array, function() {}].forEach((invalidArg) => { 228 const message = 'The "options.cachedData" property must be an ' + 229 'instance of Buffer, TypedArray, or DataView.' + 230 common.invalidArgTypeHelper(invalidArg); 231 assert.throws( 232 () => new SourceTextModule('import "foo";', { cachedData: invalidArg }), 233 { 234 code: 'ERR_INVALID_ARG_TYPE', 235 name: 'TypeError', 236 message, 237 } 238 ); 239 }); 240} 241 242function checkGettersErrors() { 243 const expectedError = { 244 code: 'ERR_VM_MODULE_NOT_MODULE', 245 message: /Provided module is not an instance of Module/ 246 }; 247 const getters = ['identifier', 'context', 'namespace', 'status', 'error']; 248 getters.forEach((getter) => { 249 assert.throws(() => { 250 // eslint-disable-next-line no-unused-expressions 251 Module.prototype[getter]; 252 }, expectedError); 253 assert.throws(() => { 254 // eslint-disable-next-line no-unused-expressions 255 SourceTextModule.prototype[getter]; 256 }, expectedError); 257 }); 258 // `dependencySpecifiers` getter is just part of SourceTextModule 259 assert.throws(() => { 260 // eslint-disable-next-line no-unused-expressions 261 SourceTextModule.prototype.dependencySpecifiers; 262 }, expectedError); 263} 264 265const finished = common.mustCall(); 266 267(async function main() { 268 await checkArgType(); 269 await checkModuleState(); 270 await checkLinking(); 271 await checkExecution(); 272 await checkInvalidOptionForEvaluate(); 273 checkInvalidCachedData(); 274 checkGettersErrors(); 275 finished(); 276})().then(common.mustCall()); 277