'use strict'; function _exit() { const data = _interopRequireDefault(require('exit')); _exit = function () { return data; }; return data; } function _chalk() { const data = _interopRequireDefault(require('chalk')); _chalk = function () { return data; }; return data; } function _throat() { const data = _interopRequireDefault(require('throat')); _throat = function () { return data; }; return data; } function _jestWorker() { const data = _interopRequireDefault(require('jest-worker')); _jestWorker = function () { return data; }; return data; } var _runTest = _interopRequireDefault(require('./runTest')); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj}; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } const TEST_WORKER_PATH = require.resolve('./testWorker'); /* eslint-disable-next-line no-redeclare */ class TestRunner { constructor(globalConfig, context) { _defineProperty(this, '_globalConfig', void 0); _defineProperty(this, '_context', void 0); _defineProperty(this, 'isSerial', void 0); this._globalConfig = globalConfig; this._context = context || {}; } async runTests(tests, watcher, onStart, onResult, onFailure, options) { return await (options.serial ? this._createInBandTestRun(tests, watcher, onStart, onResult, onFailure) : this._createParallelTestRun( tests, watcher, onStart, onResult, onFailure )); } async _createInBandTestRun(tests, watcher, onStart, onResult, onFailure) { process.env.JEST_WORKER_ID = '1'; const mutex = (0, _throat().default)(1); return tests.reduce( (promise, test) => mutex(() => promise .then(async () => { if (watcher.isInterrupted()) { throw new CancelRun(); } await onStart(test); return (0, _runTest.default)( test.path, this._globalConfig, test.context.config, test.context.resolver, this._context ); }) .then(result => onResult(test, result)) .catch(err => onFailure(test, err)) ), Promise.resolve() ); } async _createParallelTestRun(tests, watcher, onStart, onResult, onFailure) { const resolvers = new Map(); for (const test of tests) { if (!resolvers.has(test.context.config.name)) { resolvers.set(test.context.config.name, { config: test.context.config, serializableModuleMap: test.context.moduleMap.toJSON() }); } } const worker = new (_jestWorker().default)(TEST_WORKER_PATH, { exposedMethods: ['worker'], forkOptions: { stdio: 'pipe' }, maxRetries: 3, numWorkers: this._globalConfig.maxWorkers, setupArgs: [ { serializableResolvers: Array.from(resolvers.values()) } ] }); if (worker.getStdout()) worker.getStdout().pipe(process.stdout); if (worker.getStderr()) worker.getStderr().pipe(process.stderr); const mutex = (0, _throat().default)(this._globalConfig.maxWorkers); // Send test suites to workers continuously instead of all at once to track // the start time of individual tests. const runTestInWorker = test => mutex(async () => { if (watcher.isInterrupted()) { return Promise.reject(); } await onStart(test); return worker.worker({ config: test.context.config, context: { ...this._context, changedFiles: this._context.changedFiles && Array.from(this._context.changedFiles) }, globalConfig: this._globalConfig, path: test.path }); }); const onError = async (err, test) => { await onFailure(test, err); if (err.type === 'ProcessTerminatedError') { console.error( 'A worker process has quit unexpectedly! ' + 'Most likely this is an initialization error.' ); (0, _exit().default)(1); } }; const onInterrupt = new Promise((_, reject) => { watcher.on('change', state => { if (state.interrupted) { reject(new CancelRun()); } }); }); const runAllTests = Promise.all( tests.map(test => runTestInWorker(test) .then(testResult => onResult(test, testResult)) .catch(error => onError(error, test)) ) ); const cleanup = async () => { const {forceExited} = await worker.end(); if (forceExited) { console.error( _chalk().default.yellow( 'A worker process has failed to exit gracefully and has been force exited. ' + 'This is likely caused by tests leaking due to improper teardown. ' + 'Try running with --runInBand --detectOpenHandles to find leaks.' ) ); } }; return Promise.race([runAllTests, onInterrupt]).then(cleanup, cleanup); } } class CancelRun extends Error { constructor(message) { super(message); this.name = 'CancelRun'; } } module.exports = TestRunner;