in packages/jest-core/src/TestScheduler.ts [90:314]
async scheduleTests(
tests: Array<Test>,
watcher: TestWatcher,
): Promise<AggregatedResult> {
const onTestFileStart = this._dispatcher.onTestFileStart.bind(
this._dispatcher,
);
const timings: Array<number> = [];
const contexts = new Set<Context>();
tests.forEach(test => {
contexts.add(test.context);
if (test.duration) {
timings.push(test.duration);
}
});
const aggregatedResults = createAggregatedResults(tests.length);
const estimatedTime = Math.ceil(
getEstimatedTime(timings, this._globalConfig.maxWorkers) / 1000,
);
const runInBand = shouldRunInBand(tests, timings, this._globalConfig);
const onResult = async (test: Test, testResult: TestResult) => {
if (watcher.isInterrupted()) {
return Promise.resolve();
}
if (testResult.testResults.length === 0) {
const message = 'Your test suite must contain at least one test.';
return onFailure(test, {
message,
stack: new Error(message).stack,
});
}
// Throws when the context is leaked after executing a test.
if (testResult.leaks) {
const message =
`${chalk.red.bold(
'EXPERIMENTAL FEATURE!\n',
)}Your test suite is leaking memory. Please ensure all references are cleaned.\n` +
'\n' +
'There is a number of things that can leak memory:\n' +
' - Async operations that have not finished (e.g. fs.readFile).\n' +
' - Timers not properly mocked (e.g. setInterval, setTimeout).\n' +
' - Keeping references to the global scope.';
return onFailure(test, {
message,
stack: new Error(message).stack,
});
}
addResult(aggregatedResults, testResult);
await this._dispatcher.onTestFileResult(
test,
testResult,
aggregatedResults,
);
return this._bailIfNeeded(contexts, aggregatedResults, watcher);
};
const onFailure = async (test: Test, error: SerializableError) => {
if (watcher.isInterrupted()) {
return;
}
const testResult = buildFailureTestResult(test.path, error);
testResult.failureMessage = formatExecError(
testResult.testExecError,
test.context.config,
this._globalConfig,
test.path,
);
addResult(aggregatedResults, testResult);
await this._dispatcher.onTestFileResult(
test,
testResult,
aggregatedResults,
);
};
const updateSnapshotState = async () => {
const contextsWithSnapshotResolvers = await Promise.all(
Array.from(contexts).map(
async context =>
[context, await buildSnapshotResolver(context.config)] as const,
),
);
contextsWithSnapshotResolvers.forEach(([context, snapshotResolver]) => {
const status = cleanupSnapshots(
context.hasteFS,
this._globalConfig.updateSnapshot,
snapshotResolver,
context.config.testPathIgnorePatterns,
);
aggregatedResults.snapshot.filesRemoved += status.filesRemoved;
aggregatedResults.snapshot.filesRemovedList = (
aggregatedResults.snapshot.filesRemovedList || []
).concat(status.filesRemovedList);
});
const updateAll = this._globalConfig.updateSnapshot === 'all';
aggregatedResults.snapshot.didUpdate = updateAll;
aggregatedResults.snapshot.failure = !!(
!updateAll &&
(aggregatedResults.snapshot.unchecked ||
aggregatedResults.snapshot.unmatched ||
aggregatedResults.snapshot.filesRemoved)
);
};
await this._dispatcher.onRunStart(aggregatedResults, {
estimatedTime,
showStatus: !runInBand,
});
const testRunners: {[key: string]: TestRunner} = Object.create(null);
const contextsByTestRunner = new WeakMap<TestRunner, Context>();
await Promise.all(
Array.from(contexts).map(async context => {
const {config} = context;
if (!testRunners[config.runner]) {
const transformer = await createScriptTransformer(config);
const Runner: typeof TestRunner =
await transformer.requireAndTranspileModule(config.runner);
const runner = new Runner(this._globalConfig, {
changedFiles: this._context?.changedFiles,
sourcesRelatedToTestsInChangedFiles:
this._context?.sourcesRelatedToTestsInChangedFiles,
});
testRunners[config.runner] = runner;
contextsByTestRunner.set(runner, context);
}
}),
);
const testsByRunner = this._partitionTests(testRunners, tests);
if (testsByRunner) {
try {
for (const runner of Object.keys(testRunners)) {
const testRunner = testRunners[runner];
const context = contextsByTestRunner.get(testRunner);
invariant(context);
const tests = testsByRunner[runner];
const testRunnerOptions = {
serial: runInBand || Boolean(testRunner.isSerial),
};
/**
* Test runners with event emitters are still not supported
* for third party test runners.
*/
if (testRunner.__PRIVATE_UNSTABLE_API_supportsEventEmitters__) {
const unsubscribes = [
testRunner.on('test-file-start', ([test]) =>
onTestFileStart(test),
),
testRunner.on('test-file-success', ([test, testResult]) =>
onResult(test, testResult),
),
testRunner.on('test-file-failure', ([test, error]) =>
onFailure(test, error),
),
testRunner.on(
'test-case-result',
([testPath, testCaseResult]) => {
const test: Test = {context, path: testPath};
this._dispatcher.onTestCaseResult(test, testCaseResult);
},
),
];
await testRunner.runTests(
tests,
watcher,
undefined,
undefined,
undefined,
testRunnerOptions,
);
unsubscribes.forEach(sub => sub());
} else {
await testRunner.runTests(
tests,
watcher,
onTestFileStart,
onResult,
onFailure,
testRunnerOptions,
);
}
}
} catch (error) {
if (!watcher.isInterrupted()) {
throw error;
}
}
}
await updateSnapshotState();
aggregatedResults.wasInterrupted = watcher.isInterrupted();
await this._dispatcher.onRunComplete(contexts, aggregatedResults);
const anyTestFailures = !(
aggregatedResults.numFailedTests === 0 &&
aggregatedResults.numRuntimeErrorTestSuites === 0
);
const anyReporterErrors = this._dispatcher.hasErrors();
aggregatedResults.success = !(
anyTestFailures ||
aggregatedResults.snapshot.failure ||
anyReporterErrors
);
return aggregatedResults;
}