Skip to content

Commit 0158e19

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

1 file changed

Lines changed: 206 additions & 0 deletions

File tree

test/tools/runner/vm_runner.js

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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+
};
96+
}
97+
98+
function createProxiedRequire(parentPath) {
99+
const parentDir = path.dirname(parentPath);
100+
101+
return function sandboxRequire(moduleIdentifier) {
102+
if (!moduleIdentifier.startsWith('.')) {
103+
return require(moduleIdentifier);
104+
}
105+
106+
const absolutePath = path.resolve(parentDir, moduleIdentifier);
107+
108+
let fullPath;
109+
try {
110+
fullPath = require.resolve(absolutePath);
111+
} catch (e) {
112+
if (e.code === 'MODULE_NOT_FOUND') {
113+
const alternatives = [absolutePath + '.ts', path.join(absolutePath, 'index.ts')];
114+
115+
for (const alt of alternatives) {
116+
try {
117+
fullPath = require.resolve(alt);
118+
break;
119+
} catch {}
120+
}
121+
122+
if (!fullPath) {
123+
return require(moduleIdentifier);
124+
}
125+
} else {
126+
throw e;
127+
}
128+
}
129+
130+
if (fullPath.includes('node_modules')) {
131+
return require(fullPath);
132+
}
133+
134+
if (fullPath.endsWith('.ts') || fullPath.endsWith('.js')) {
135+
return loadInSandbox(fullPath);
136+
}
137+
138+
return require(fullPath);
139+
};
140+
}
141+
142+
function loadInSandbox(filepath) {
143+
const realPath = fs.realpathSync(filepath);
144+
145+
if (moduleCache.has(realPath)) {
146+
return moduleCache.get(realPath);
147+
}
148+
149+
const content = fs.readFileSync(realPath, 'utf8');
150+
151+
const transpiled = ts.transpileModule(content, {
152+
compilerOptions: compilerOptions,
153+
filename: realPath
154+
});
155+
156+
const sandbox = createSandboxContext(realPath);
157+
sandbox.require = createProxiedRequire(realPath);
158+
159+
moduleCache.set(realPath, sandbox.module.exports);
160+
161+
try {
162+
const script = new vm.Script(transpiled.outputText, { filename: realPath });
163+
script.runInNewContext(sandbox);
164+
} catch (err) {
165+
console.error(`Error running ${realPath} in sandbox:`, err.message);
166+
throw err;
167+
}
168+
169+
moduleCache.set(realPath, sandbox.module.exports);
170+
return sandbox.module.exports;
171+
}
172+
173+
// use it similar to regular mocha:
174+
// mocha --config test/mocha_mongodb.js test/integration
175+
// node test/runner/vm_context.js test/integration
176+
const userArgs = process.argv.slice(2);
177+
const searchTargets = userArgs.length > 0 ? userArgs : ['test'];
178+
const testFiles = searchTargets.flatMap(target => {
179+
try {
180+
const stats = fs.statSync(target);
181+
if (stats.isDirectory()) {
182+
const pattern = path.join(target, '**/*.test.{ts,js}').replace(/\\/g, '/');
183+
return fs.globSync(pattern);
184+
}
185+
if (stats.isFile()) {
186+
return [target];
187+
}
188+
} catch {
189+
console.error(`Error: Could not find path "${target}"`);
190+
}
191+
return [];
192+
});
193+
194+
if (testFiles.length === 0) {
195+
console.log('No test files found.');
196+
process.exit(0);
197+
}
198+
199+
testFiles.forEach(file => {
200+
loadInSandbox(path.resolve(file));
201+
});
202+
203+
console.log('Running Tests...');
204+
mocha.run(failures => {
205+
process.exitCode = failures ? 1 : 0;
206+
});

0 commit comments

Comments
 (0)