Skip to content

Commit 3f33ad8

Browse files
committed
test(NODE-7345): custom vm.Context runner for mocha
1 parent bf75181 commit 3f33ad8

1 file changed

Lines changed: 207 additions & 0 deletions

File tree

test/tools/runner/vm_runner.js

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/* eslint-disable no-restricted-globals */
2+
3+
const fs = require('node:fs');
4+
const path = require('node:path');
5+
const vm = require('node:vm');
6+
const ts = require('typescript');
7+
const Mocha = require('mocha');
8+
9+
require('ts-node/register');
10+
require('source-map-support/register');
11+
12+
const mocha = new Mocha({
13+
extension: ['js', 'ts'],
14+
ui: 'test/tools/runner/metadata_ui.js',
15+
recursive: true,
16+
timeout: 60000,
17+
failZero: true,
18+
reporter: 'test/tools/reporter/mongodb_reporter.js',
19+
sort: true,
20+
color: true,
21+
ignore: [
22+
'test/integration/node-specific/examples/handler.js',
23+
'test/integration/node-specific/examples/handler.test.js',
24+
'test/integration/node-specific/examples/aws_handler.js',
25+
'test/integration/node-specific/examples/aws_handler.test.js',
26+
'test/integration/node-specific/examples/setup.js',
27+
'test/integration/node-specific/examples/transactions.test.js',
28+
'test/integration/node-specific/examples/versioned_api.js'
29+
]
30+
});
31+
mocha.suite.emit('pre-require', global, 'host-context', mocha);
32+
33+
require('./throw_rejections.cjs');
34+
require('./chai_addons.ts');
35+
require('./ee_checker.ts');
36+
37+
for (const path of ['./hooks/leak_checker.ts', './hooks/configuration.ts']) {
38+
const mod = require(path);
39+
const hooks = mod.mochaHooks;
40+
const register = (hookName, globalFn) => {
41+
if (hooks[hookName]) {
42+
const list = Array.isArray(hooks[hookName]) ? hooks[hookName] : [hooks[hookName]];
43+
list.forEach(fn => globalFn(fn));
44+
}
45+
};
46+
47+
register('beforeAll', global.before);
48+
register('afterAll', global.after);
49+
register('beforeEach', global.beforeEach);
50+
register('afterEach', global.afterEach);
51+
}
52+
53+
let compilerOptions = { module: ts.ModuleKind.CommonJS };
54+
const tsConfigPath = path.join(__dirname, '../../tsconfig.json');
55+
const configFile = ts.readConfigFile(tsConfigPath, ts.sys.readFile);
56+
if (!configFile.error) {
57+
const parsedConfig = ts.parseJsonConfigFileContent(
58+
configFile.config,
59+
ts.sys,
60+
path.dirname(tsConfigPath)
61+
);
62+
compilerOptions = {
63+
...parsedConfig.options,
64+
module: ts.ModuleKind.CommonJS,
65+
sourceMap: false,
66+
inlineSourceMap: false
67+
};
68+
}
69+
70+
const moduleCache = new Map();
71+
72+
function createSandboxContext(filename) {
73+
const exportsContainer = {};
74+
return {
75+
console: console,
76+
AbortController: AbortController,
77+
AbortSignal: AbortSignal,
78+
79+
context: global.context,
80+
describe: global.describe,
81+
xdescribe: global.xdescribe,
82+
it: global.it,
83+
xit: global.xit,
84+
before: global.before,
85+
after: global.after,
86+
beforeEach: global.beforeEach,
87+
afterEach: global.afterEach,
88+
89+
exports: exportsContainer,
90+
module: { exports: exportsContainer },
91+
__filename: filename,
92+
__dirname: path.dirname(filename),
93+
94+
// Buffer: Buffer,
95+
queueMicrotask: queueMicrotask
96+
};
97+
}
98+
99+
function createProxiedRequire(parentPath) {
100+
const parentDir = path.dirname(parentPath);
101+
102+
return function sandboxRequire(moduleIdentifier) {
103+
if (!moduleIdentifier.startsWith('.')) {
104+
return require(moduleIdentifier);
105+
}
106+
107+
const absolutePath = path.resolve(parentDir, moduleIdentifier);
108+
109+
let fullPath;
110+
try {
111+
fullPath = require.resolve(absolutePath);
112+
} catch (e) {
113+
if (e.code === 'MODULE_NOT_FOUND') {
114+
const alternatives = [absolutePath + '.ts', path.join(absolutePath, 'index.ts')];
115+
116+
for (const alt of alternatives) {
117+
try {
118+
fullPath = require.resolve(alt);
119+
break;
120+
} catch {}
121+
}
122+
123+
if (!fullPath) {
124+
return require(moduleIdentifier);
125+
}
126+
} else {
127+
throw e;
128+
}
129+
}
130+
131+
if (fullPath.includes('node_modules')) {
132+
return require(fullPath);
133+
}
134+
135+
if (fullPath.endsWith('.ts') || fullPath.endsWith('.js')) {
136+
return loadInSandbox(fullPath);
137+
}
138+
139+
return require(fullPath);
140+
};
141+
}
142+
143+
function loadInSandbox(filepath) {
144+
const realPath = fs.realpathSync(filepath);
145+
146+
if (moduleCache.has(realPath)) {
147+
return moduleCache.get(realPath);
148+
}
149+
150+
const content = fs.readFileSync(realPath, 'utf8');
151+
152+
const transpiled = ts.transpileModule(content, {
153+
compilerOptions: compilerOptions,
154+
filename: realPath
155+
});
156+
157+
const sandbox = createSandboxContext(realPath);
158+
sandbox.require = createProxiedRequire(realPath);
159+
160+
moduleCache.set(realPath, sandbox.module.exports);
161+
162+
try {
163+
const script = new vm.Script(transpiled.outputText, { filename: realPath });
164+
script.runInNewContext(sandbox);
165+
} catch (err) {
166+
console.error(`Error running ${realPath} in sandbox:`, err.message);
167+
throw err;
168+
}
169+
170+
moduleCache.set(realPath, sandbox.module.exports);
171+
return sandbox.module.exports;
172+
}
173+
174+
// use it similar to regular mocha:
175+
// mocha --config test/mocha_mongodb.js test/integration
176+
// node test/runner/vm_context.js test/integration
177+
const userArgs = process.argv.slice(2);
178+
const searchTargets = userArgs.length > 0 ? userArgs : ['test'];
179+
const testFiles = searchTargets.flatMap(target => {
180+
try {
181+
const stats = fs.statSync(target);
182+
if (stats.isDirectory()) {
183+
const pattern = path.join(target, '**/*.test.{ts,js}').replace(/\\/g, '/');
184+
return fs.globSync(pattern);
185+
}
186+
if (stats.isFile()) {
187+
return [target];
188+
}
189+
} catch {
190+
console.error(`Error: Could not find path "${target}"`);
191+
}
192+
return [];
193+
});
194+
195+
if (testFiles.length === 0) {
196+
console.log('No test files found.');
197+
process.exit(0);
198+
}
199+
200+
testFiles.forEach(file => {
201+
loadInSandbox(path.resolve(file));
202+
});
203+
204+
console.log('Running Tests...');
205+
mocha.run(failures => {
206+
process.exitCode = failures ? 1 : 0;
207+
});

0 commit comments

Comments
 (0)