'use strict'; function _chalk() { const data = _interopRequireDefault(require('chalk')); _chalk = function () { return data; }; return data; } function _emittery() { const data = _interopRequireDefault(require('emittery')); _emittery = function () { return data; }; return data; } function _exit() { const data = _interopRequireDefault(require('exit')); _exit = function () { return data; }; return data; } function _throat() { const data = _interopRequireDefault(require('throat')); _throat = function () { return data; }; return data; } function _jestUtil() { const data = require('jest-util'); _jestUtil = 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'); class TestRunner { constructor(globalConfig, context) { _defineProperty(this, '_globalConfig', void 0); _defineProperty(this, '_context', void 0); _defineProperty(this, 'eventEmitter', new (_emittery().default.Typed)()); _defineProperty( this, '__PRIVATE_UNSTABLE_API_supportsEventEmitters__', true ); _defineProperty(this, 'isSerial', void 0); _defineProperty(this, 'on', this.eventEmitter.on.bind(this.eventEmitter)); 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(); } let sendMessageToJest; // Remove `if(onStart)` in Jest 27 if (onStart) { await onStart(test); return (0, _runTest.default)( test.path, this._globalConfig, test.context.config, test.context.resolver, this._context, undefined ); } else { // `deepCyclicCopy` used here to avoid mem-leak sendMessageToJest = (eventName, args) => this.eventEmitter.emit( eventName, (0, _jestUtil().deepCyclicCopy)(args, { keepPrototype: false }) ); await this.eventEmitter.emit('test-file-start', [test]); return (0, _runTest.default)( test.path, this._globalConfig, test.context.config, test.context.resolver, this._context, sendMessageToJest ); } }) .then(result => { if (onResult) { return onResult(test, result); } else { return this.eventEmitter.emit('test-file-success', [ test, result ]); } }) .catch(err => { if (onFailure) { return onFailure(test, err); } else { return this.eventEmitter.emit('test-file-failure', [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(); } // Remove `if(onStart)` in Jest 27 if (onStart) { await onStart(test); } else { await this.eventEmitter.emit('test-file-start', [test]); } const promise = worker.worker({ config: test.context.config, context: { ...this._context, changedFiles: this._context.changedFiles && Array.from(this._context.changedFiles), sourcesRelatedToTestsInChangedFiles: this._context.sourcesRelatedToTestsInChangedFiles && Array.from(this._context.sourcesRelatedToTestsInChangedFiles) }, globalConfig: this._globalConfig, path: test.path }); if (promise.UNSTABLE_onCustomMessage) { // TODO: Get appropriate type for `onCustomMessage` promise.UNSTABLE_onCustomMessage(([event, payload]) => { this.eventEmitter.emit(event, payload); }); } return promise; }); const onError = async (err, test) => { // Remove `if(onFailure)` in Jest 27 if (onFailure) { await onFailure(test, err); } else { await this.eventEmitter.emit('test-file-failure', [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(result => { if (onResult) { return onResult(test, result); } else { return this.eventEmitter.emit('test-file-success', [ test, result ]); } }) .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 --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;