index.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. function _chalk() {
  7. const data = _interopRequireDefault(require('chalk'));
  8. _chalk = function () {
  9. return data;
  10. };
  11. return data;
  12. }
  13. function _emittery() {
  14. const data = _interopRequireDefault(require('emittery'));
  15. _emittery = function () {
  16. return data;
  17. };
  18. return data;
  19. }
  20. function _throat() {
  21. const data = _interopRequireDefault(require('throat'));
  22. _throat = function () {
  23. return data;
  24. };
  25. return data;
  26. }
  27. function _jestUtil() {
  28. const data = require('jest-util');
  29. _jestUtil = function () {
  30. return data;
  31. };
  32. return data;
  33. }
  34. function _jestWorker() {
  35. const data = require('jest-worker');
  36. _jestWorker = function () {
  37. return data;
  38. };
  39. return data;
  40. }
  41. var _runTest = _interopRequireDefault(require('./runTest'));
  42. function _interopRequireDefault(obj) {
  43. return obj && obj.__esModule ? obj : {default: obj};
  44. }
  45. function _defineProperty(obj, key, value) {
  46. if (key in obj) {
  47. Object.defineProperty(obj, key, {
  48. value: value,
  49. enumerable: true,
  50. configurable: true,
  51. writable: true
  52. });
  53. } else {
  54. obj[key] = value;
  55. }
  56. return obj;
  57. }
  58. const TEST_WORKER_PATH = require.resolve('./testWorker');
  59. class TestRunner {
  60. constructor(globalConfig, context) {
  61. _defineProperty(this, '_globalConfig', void 0);
  62. _defineProperty(this, '_context', void 0);
  63. _defineProperty(this, 'eventEmitter', new (_emittery().default)());
  64. _defineProperty(
  65. this,
  66. '__PRIVATE_UNSTABLE_API_supportsEventEmitters__',
  67. true
  68. );
  69. _defineProperty(this, 'isSerial', void 0);
  70. this._globalConfig = globalConfig;
  71. this._context = context || {};
  72. }
  73. async runTests(tests, watcher, onStart, onResult, onFailure, options) {
  74. return await (options.serial
  75. ? this._createInBandTestRun(tests, watcher, onStart, onResult, onFailure)
  76. : this._createParallelTestRun(
  77. tests,
  78. watcher,
  79. onStart,
  80. onResult,
  81. onFailure
  82. ));
  83. }
  84. async _createInBandTestRun(tests, watcher, onStart, onResult, onFailure) {
  85. process.env.JEST_WORKER_ID = '1';
  86. const mutex = (0, _throat().default)(1);
  87. return tests.reduce(
  88. (promise, test) =>
  89. mutex(() =>
  90. promise
  91. .then(async () => {
  92. if (watcher.isInterrupted()) {
  93. throw new CancelRun();
  94. } // Remove `if(onStart)` in Jest 27
  95. if (onStart) {
  96. await onStart(test);
  97. return (0, _runTest.default)(
  98. test.path,
  99. this._globalConfig,
  100. test.context.config,
  101. test.context.resolver,
  102. this._context,
  103. undefined
  104. );
  105. } // `deepCyclicCopy` used here to avoid mem-leak
  106. const sendMessageToJest = (eventName, args) =>
  107. this.eventEmitter.emit(
  108. eventName,
  109. (0, _jestUtil().deepCyclicCopy)(args, {
  110. keepPrototype: false
  111. })
  112. );
  113. await this.eventEmitter.emit('test-file-start', [test]);
  114. return (0, _runTest.default)(
  115. test.path,
  116. this._globalConfig,
  117. test.context.config,
  118. test.context.resolver,
  119. this._context,
  120. sendMessageToJest
  121. );
  122. })
  123. .then(result => {
  124. if (onResult) {
  125. return onResult(test, result);
  126. }
  127. return this.eventEmitter.emit('test-file-success', [
  128. test,
  129. result
  130. ]);
  131. })
  132. .catch(err => {
  133. if (onFailure) {
  134. return onFailure(test, err);
  135. }
  136. return this.eventEmitter.emit('test-file-failure', [test, err]);
  137. })
  138. ),
  139. Promise.resolve()
  140. );
  141. }
  142. async _createParallelTestRun(tests, watcher, onStart, onResult, onFailure) {
  143. const resolvers = new Map();
  144. for (const test of tests) {
  145. if (!resolvers.has(test.context.config.name)) {
  146. resolvers.set(test.context.config.name, {
  147. config: test.context.config,
  148. serializableModuleMap: test.context.moduleMap.toJSON()
  149. });
  150. }
  151. }
  152. const worker = new (_jestWorker().Worker)(TEST_WORKER_PATH, {
  153. exposedMethods: ['worker'],
  154. forkOptions: {
  155. stdio: 'pipe'
  156. },
  157. maxRetries: 3,
  158. numWorkers: this._globalConfig.maxWorkers,
  159. setupArgs: [
  160. {
  161. serializableResolvers: Array.from(resolvers.values())
  162. }
  163. ]
  164. });
  165. if (worker.getStdout()) worker.getStdout().pipe(process.stdout);
  166. if (worker.getStderr()) worker.getStderr().pipe(process.stderr);
  167. const mutex = (0, _throat().default)(this._globalConfig.maxWorkers); // Send test suites to workers continuously instead of all at once to track
  168. // the start time of individual tests.
  169. const runTestInWorker = test =>
  170. mutex(async () => {
  171. if (watcher.isInterrupted()) {
  172. return Promise.reject();
  173. } // Remove `if(onStart)` in Jest 27
  174. if (onStart) {
  175. await onStart(test);
  176. } else {
  177. await this.eventEmitter.emit('test-file-start', [test]);
  178. }
  179. const promise = worker.worker({
  180. config: test.context.config,
  181. context: {
  182. ...this._context,
  183. changedFiles:
  184. this._context.changedFiles &&
  185. Array.from(this._context.changedFiles),
  186. sourcesRelatedToTestsInChangedFiles:
  187. this._context.sourcesRelatedToTestsInChangedFiles &&
  188. Array.from(this._context.sourcesRelatedToTestsInChangedFiles)
  189. },
  190. globalConfig: this._globalConfig,
  191. path: test.path
  192. });
  193. if (promise.UNSTABLE_onCustomMessage) {
  194. // TODO: Get appropriate type for `onCustomMessage`
  195. promise.UNSTABLE_onCustomMessage(([event, payload]) => {
  196. this.eventEmitter.emit(event, payload);
  197. });
  198. }
  199. return promise;
  200. });
  201. const onInterrupt = new Promise((_, reject) => {
  202. watcher.on('change', state => {
  203. if (state.interrupted) {
  204. reject(new CancelRun());
  205. }
  206. });
  207. });
  208. const runAllTests = Promise.all(
  209. tests.map(test =>
  210. runTestInWorker(test)
  211. .then(result => {
  212. if (onResult) {
  213. return onResult(test, result);
  214. }
  215. return this.eventEmitter.emit('test-file-success', [test, result]);
  216. })
  217. .catch(error => {
  218. if (onFailure) {
  219. return onFailure(test, error);
  220. }
  221. return this.eventEmitter.emit('test-file-failure', [test, error]);
  222. })
  223. )
  224. );
  225. const cleanup = async () => {
  226. const {forceExited} = await worker.end();
  227. if (forceExited) {
  228. console.error(
  229. _chalk().default.yellow(
  230. 'A worker process has failed to exit gracefully and has been force exited. ' +
  231. 'This is likely caused by tests leaking due to improper teardown. ' +
  232. 'Try running with --detectOpenHandles to find leaks. ' +
  233. 'Active timers can also cause this, ensure that .unref() was called on them.'
  234. )
  235. );
  236. }
  237. };
  238. return Promise.race([runAllTests, onInterrupt]).then(cleanup, cleanup);
  239. }
  240. on(eventName, listener) {
  241. return this.eventEmitter.on(eventName, listener);
  242. }
  243. }
  244. exports.default = TestRunner;
  245. class CancelRun extends Error {
  246. constructor(message) {
  247. super(message);
  248. this.name = 'CancelRun';
  249. }
  250. }