| '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 _emittery() { |
| const data = _interopRequireDefault(require('emittery')); |
| |
| _emittery = 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; |
| } |
| |
| function _jestUtil() { |
| const data = require('jest-util'); |
| |
| _jestUtil = 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 --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; |