You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
300 lines
7.7 KiB
300 lines
7.7 KiB
'use strict'; |
|
|
|
Object.defineProperty(exports, '__esModule', { |
|
value: true |
|
}); |
|
exports.default = void 0; |
|
|
|
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 _jestUtil() { |
|
const data = require('jest-util'); |
|
|
|
_jestUtil = function () { |
|
return data; |
|
}; |
|
|
|
return data; |
|
} |
|
|
|
function _jestWorker() { |
|
const data = 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)()); |
|
|
|
_defineProperty( |
|
this, |
|
'__PRIVATE_UNSTABLE_API_supportsEventEmitters__', |
|
true |
|
); |
|
|
|
_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(); |
|
} // 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 |
|
); |
|
} // `deepCyclicCopy` used here to avoid mem-leak |
|
|
|
const 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); |
|
} |
|
|
|
return this.eventEmitter.emit('test-file-success', [ |
|
test, |
|
result |
|
]); |
|
}) |
|
.catch(err => { |
|
if (onFailure) { |
|
return onFailure(test, err); |
|
} |
|
|
|
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().Worker)(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 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); |
|
} |
|
|
|
return this.eventEmitter.emit('test-file-success', [test, result]); |
|
}) |
|
.catch(error => { |
|
if (onFailure) { |
|
return onFailure(test, error); |
|
} |
|
|
|
return this.eventEmitter.emit('test-file-failure', [test, error]); |
|
}) |
|
) |
|
); |
|
|
|
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. ' + |
|
'Active timers can also cause this, ensure that .unref() was called on them.' |
|
) |
|
); |
|
} |
|
}; |
|
|
|
return Promise.race([runAllTests, onInterrupt]).then(cleanup, cleanup); |
|
} |
|
|
|
on(eventName, listener) { |
|
return this.eventEmitter.on(eventName, listener); |
|
} |
|
} |
|
|
|
exports.default = TestRunner; |
|
|
|
class CancelRun extends Error { |
|
constructor(message) { |
|
super(message); |
|
this.name = 'CancelRun'; |
|
} |
|
}
|
|
|