in packages/jest-runner/src/runTest.ts [77:359]
async function runTestInternal(
path: string,
globalConfig: Config.GlobalConfig,
projectConfig: Config.ProjectConfig,
resolver: Resolver,
context?: TestRunnerContext,
sendMessageToJest?: TestFileEvent,
): Promise<RunTestInternalResult> {
const testSource = fs.readFileSync(path, 'utf8');
const docblockPragmas = docblock.parse(docblock.extract(testSource));
const customEnvironment = docblockPragmas['jest-environment'];
let testEnvironment = projectConfig.testEnvironment;
if (customEnvironment) {
if (Array.isArray(customEnvironment)) {
throw new Error(
`You can only define a single test environment through docblocks, got "${customEnvironment.join(
', ',
)}"`,
);
}
testEnvironment = resolveTestEnvironment({
...projectConfig,
requireResolveFunction: require.resolve,
testEnvironment: customEnvironment,
});
}
const cacheFS = new Map([[path, testSource]]);
const transformer = await createScriptTransformer(projectConfig, cacheFS);
const TestEnvironment: typeof JestEnvironment =
await transformer.requireAndTranspileModule(testEnvironment);
const testFramework: TestFramework =
await transformer.requireAndTranspileModule(
process.env.JEST_JASMINE === '1'
? require.resolve('jest-jasmine2')
: projectConfig.testRunner,
);
const Runtime: typeof RuntimeClass = interopRequireDefault(
projectConfig.runtime
? require(projectConfig.runtime)
: require('jest-runtime'),
).default;
const consoleOut = globalConfig.useStderr ? process.stderr : process.stdout;
const consoleFormatter = (type: LogType, message: LogMessage) =>
getConsoleOutput(
// 4 = the console call is buried 4 stack frames deep
BufferedConsole.write([], type, message, 4),
projectConfig,
globalConfig,
);
let testConsole;
if (globalConfig.silent) {
testConsole = new NullConsole(consoleOut, consoleOut, consoleFormatter);
} else if (globalConfig.verbose) {
testConsole = new CustomConsole(consoleOut, consoleOut, consoleFormatter);
} else {
testConsole = new BufferedConsole();
}
let extraTestEnvironmentOptions;
const docblockEnvironmentOptions =
docblockPragmas['jest-environment-options'];
if (typeof docblockEnvironmentOptions === 'string') {
extraTestEnvironmentOptions = JSON.parse(docblockEnvironmentOptions);
}
const environment = new TestEnvironment(
{
globalConfig,
projectConfig: extraTestEnvironmentOptions
? {
...projectConfig,
testEnvironmentOptions: {
...projectConfig.testEnvironmentOptions,
...extraTestEnvironmentOptions,
},
}
: projectConfig,
},
{
console: testConsole,
docblockPragmas,
testPath: path,
},
);
if (typeof environment.getVmContext !== 'function') {
console.error(
`Test environment found at "${testEnvironment}" does not export a "getVmContext" method, which is mandatory from Jest 27. This method is a replacement for "runScript".`,
);
process.exit(1);
}
const leakDetector = projectConfig.detectLeaks
? new LeakDetector(environment)
: null;
setGlobal(environment.global, 'console', testConsole);
const runtime = new Runtime(
projectConfig,
environment,
resolver,
transformer,
cacheFS,
{
changedFiles: context?.changedFiles,
collectCoverage: globalConfig.collectCoverage,
collectCoverageFrom: globalConfig.collectCoverageFrom,
collectCoverageOnlyFrom: globalConfig.collectCoverageOnlyFrom,
coverageProvider: globalConfig.coverageProvider,
sourcesRelatedToTestsInChangedFiles:
context?.sourcesRelatedToTestsInChangedFiles,
},
path,
);
const start = Date.now();
for (const path of projectConfig.setupFiles) {
const esm = runtime.unstable_shouldLoadAsEsm(path);
if (esm) {
await runtime.unstable_importModule(path);
} else {
const setupFile = runtime.requireModule(path);
if (typeof setupFile === 'function') {
await setupFile();
}
}
}
const sourcemapOptions: sourcemapSupport.Options = {
environment: 'node',
handleUncaughtExceptions: false,
retrieveSourceMap: source => {
const sourceMapSource = runtime.getSourceMaps()?.get(source);
if (sourceMapSource) {
try {
return {
map: JSON.parse(fs.readFileSync(sourceMapSource, 'utf8')),
url: source,
};
} catch {}
}
return null;
},
};
// For tests
runtime
.requireInternalModule<typeof import('source-map-support')>(
require.resolve('source-map-support'),
'source-map-support',
)
.install(sourcemapOptions);
// For runtime errors
sourcemapSupport.install(sourcemapOptions);
if (
environment.global &&
environment.global.process &&
environment.global.process.exit
) {
const realExit = environment.global.process.exit;
environment.global.process.exit = function exit(...args: Array<any>) {
const error = new ErrorWithStack(
`process.exit called with "${args.join(', ')}"`,
exit,
);
const formattedError = formatExecError(
error,
projectConfig,
{noStackTrace: false},
undefined,
true,
);
process.stderr.write(formattedError);
return realExit(...args);
};
}
// if we don't have `getVmContext` on the env skip coverage
const collectV8Coverage =
globalConfig.coverageProvider === 'v8' &&
typeof environment.getVmContext === 'function';
try {
await environment.setup();
let result: TestResult;
try {
if (collectV8Coverage) {
await runtime.collectV8Coverage();
}
result = await testFramework(
globalConfig,
projectConfig,
environment,
runtime,
path,
sendMessageToJest,
);
} catch (err: any) {
// Access stack before uninstalling sourcemaps
err.stack;
throw err;
} finally {
if (collectV8Coverage) {
await runtime.stopCollectingV8Coverage();
}
}
freezeConsole(testConsole, projectConfig);
const testCount =
result.numPassingTests +
result.numFailingTests +
result.numPendingTests +
result.numTodoTests;
const end = Date.now();
const testRuntime = end - start;
result.perfStats = {
end,
runtime: testRuntime,
slow: testRuntime / 1000 > projectConfig.slowTestThreshold,
start,
};
result.testFilePath = path;
result.console = testConsole.getBuffer();
result.skipped = testCount === result.numPendingTests;
result.displayName = projectConfig.displayName;
const coverage = runtime.getAllCoverageInfoCopy();
if (coverage) {
const coverageKeys = Object.keys(coverage);
if (coverageKeys.length) {
result.coverage = coverage;
}
}
if (collectV8Coverage) {
const v8Coverage = runtime.getAllV8CoverageInfoCopy();
if (v8Coverage && v8Coverage.length > 0) {
result.v8Coverage = v8Coverage;
}
}
if (globalConfig.logHeapUsage) {
// @ts-expect-error
globalThis.gc?.();
result.memoryUsage = process.memoryUsage().heapUsed;
}
// Delay the resolution to allow log messages to be output.
return new Promise(resolve => {
setImmediate(() => resolve({leakDetector, result}));
});
} finally {
runtime.teardown();
await environment.teardown();
sourcemapSupport.resetRetrieveHandlers();
}
}