TestScheduler.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  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 _exit() {
  14. const data = _interopRequireDefault(require('exit'));
  15. _exit = function () {
  16. return data;
  17. };
  18. return data;
  19. }
  20. function _reporters() {
  21. const data = require('@jest/reporters');
  22. _reporters = function () {
  23. return data;
  24. };
  25. return data;
  26. }
  27. function _testResult() {
  28. const data = require('@jest/test-result');
  29. _testResult = function () {
  30. return data;
  31. };
  32. return data;
  33. }
  34. function _jestMessageUtil() {
  35. const data = require('jest-message-util');
  36. _jestMessageUtil = function () {
  37. return data;
  38. };
  39. return data;
  40. }
  41. function _jestRunner() {
  42. const data = _interopRequireDefault(require('jest-runner'));
  43. _jestRunner = function () {
  44. return data;
  45. };
  46. return data;
  47. }
  48. function _jestSnapshot() {
  49. const data = _interopRequireDefault(require('jest-snapshot'));
  50. _jestSnapshot = function () {
  51. return data;
  52. };
  53. return data;
  54. }
  55. function _jestUtil() {
  56. const data = require('jest-util');
  57. _jestUtil = function () {
  58. return data;
  59. };
  60. return data;
  61. }
  62. var _ReporterDispatcher = _interopRequireDefault(
  63. require('./ReporterDispatcher')
  64. );
  65. var _testSchedulerHelper = require('./testSchedulerHelper');
  66. function _interopRequireDefault(obj) {
  67. return obj && obj.__esModule ? obj : {default: obj};
  68. }
  69. function _defineProperty(obj, key, value) {
  70. if (key in obj) {
  71. Object.defineProperty(obj, key, {
  72. value: value,
  73. enumerable: true,
  74. configurable: true,
  75. writable: true
  76. });
  77. } else {
  78. obj[key] = value;
  79. }
  80. return obj;
  81. }
  82. // The default jest-runner is required because it is the default test runner
  83. // and required implicitly through the `runner` ProjectConfig option.
  84. _jestRunner().default;
  85. class TestScheduler {
  86. constructor(globalConfig, options, context) {
  87. _defineProperty(this, '_dispatcher', void 0);
  88. _defineProperty(this, '_globalConfig', void 0);
  89. _defineProperty(this, '_options', void 0);
  90. _defineProperty(this, '_context', void 0);
  91. this._dispatcher = new _ReporterDispatcher.default();
  92. this._globalConfig = globalConfig;
  93. this._options = options;
  94. this._context = context;
  95. this._setupReporters();
  96. }
  97. addReporter(reporter) {
  98. this._dispatcher.register(reporter);
  99. }
  100. removeReporter(ReporterClass) {
  101. this._dispatcher.unregister(ReporterClass);
  102. }
  103. async scheduleTests(tests, watcher) {
  104. const onTestFileStart = this._dispatcher.onTestFileStart.bind(
  105. this._dispatcher
  106. );
  107. const timings = [];
  108. const contexts = new Set();
  109. tests.forEach(test => {
  110. contexts.add(test.context);
  111. if (test.duration) {
  112. timings.push(test.duration);
  113. }
  114. });
  115. const aggregatedResults = createAggregatedResults(tests.length);
  116. const estimatedTime = Math.ceil(
  117. getEstimatedTime(timings, this._globalConfig.maxWorkers) / 1000
  118. );
  119. const runInBand = (0, _testSchedulerHelper.shouldRunInBand)(
  120. tests,
  121. timings,
  122. this._globalConfig
  123. );
  124. const onResult = async (test, testResult) => {
  125. if (watcher.isInterrupted()) {
  126. return Promise.resolve();
  127. }
  128. if (testResult.testResults.length === 0) {
  129. const message = 'Your test suite must contain at least one test.';
  130. return onFailure(test, {
  131. message,
  132. stack: new Error(message).stack
  133. });
  134. } // Throws when the context is leaked after executing a test.
  135. if (testResult.leaks) {
  136. const message =
  137. _chalk().default.red.bold('EXPERIMENTAL FEATURE!\n') +
  138. 'Your test suite is leaking memory. Please ensure all references are cleaned.\n' +
  139. '\n' +
  140. 'There is a number of things that can leak memory:\n' +
  141. ' - Async operations that have not finished (e.g. fs.readFile).\n' +
  142. ' - Timers not properly mocked (e.g. setInterval, setTimeout).\n' +
  143. ' - Keeping references to the global scope.';
  144. return onFailure(test, {
  145. message,
  146. stack: new Error(message).stack
  147. });
  148. }
  149. (0, _testResult().addResult)(aggregatedResults, testResult);
  150. await this._dispatcher.onTestFileResult(
  151. test,
  152. testResult,
  153. aggregatedResults
  154. );
  155. return this._bailIfNeeded(contexts, aggregatedResults, watcher);
  156. };
  157. const onFailure = async (test, error) => {
  158. if (watcher.isInterrupted()) {
  159. return;
  160. }
  161. const testResult = (0, _testResult().buildFailureTestResult)(
  162. test.path,
  163. error
  164. );
  165. testResult.failureMessage = (0, _jestMessageUtil().formatExecError)(
  166. testResult.testExecError,
  167. test.context.config,
  168. this._globalConfig,
  169. test.path
  170. );
  171. (0, _testResult().addResult)(aggregatedResults, testResult);
  172. await this._dispatcher.onTestFileResult(
  173. test,
  174. testResult,
  175. aggregatedResults
  176. );
  177. };
  178. const updateSnapshotState = () => {
  179. contexts.forEach(context => {
  180. const status = _jestSnapshot().default.cleanup(
  181. context.hasteFS,
  182. this._globalConfig.updateSnapshot,
  183. _jestSnapshot().default.buildSnapshotResolver(context.config),
  184. context.config.testPathIgnorePatterns
  185. );
  186. aggregatedResults.snapshot.filesRemoved += status.filesRemoved;
  187. aggregatedResults.snapshot.filesRemovedList = (
  188. aggregatedResults.snapshot.filesRemovedList || []
  189. ).concat(status.filesRemovedList);
  190. });
  191. const updateAll = this._globalConfig.updateSnapshot === 'all';
  192. aggregatedResults.snapshot.didUpdate = updateAll;
  193. aggregatedResults.snapshot.failure = !!(
  194. !updateAll &&
  195. (aggregatedResults.snapshot.unchecked ||
  196. aggregatedResults.snapshot.unmatched ||
  197. aggregatedResults.snapshot.filesRemoved)
  198. );
  199. };
  200. await this._dispatcher.onRunStart(aggregatedResults, {
  201. estimatedTime,
  202. showStatus: !runInBand
  203. });
  204. const testRunners = Object.create(null);
  205. const contextsByTestRunner = new WeakMap();
  206. contexts.forEach(context => {
  207. const {config} = context;
  208. if (!testRunners[config.runner]) {
  209. var _this$_context, _this$_context2;
  210. const Runner = require(config.runner);
  211. const runner = new Runner(this._globalConfig, {
  212. changedFiles:
  213. (_this$_context = this._context) === null ||
  214. _this$_context === void 0
  215. ? void 0
  216. : _this$_context.changedFiles,
  217. sourcesRelatedToTestsInChangedFiles:
  218. (_this$_context2 = this._context) === null ||
  219. _this$_context2 === void 0
  220. ? void 0
  221. : _this$_context2.sourcesRelatedToTestsInChangedFiles
  222. });
  223. testRunners[config.runner] = runner;
  224. contextsByTestRunner.set(runner, context);
  225. }
  226. });
  227. const testsByRunner = this._partitionTests(testRunners, tests);
  228. if (testsByRunner) {
  229. try {
  230. for (const runner of Object.keys(testRunners)) {
  231. const testRunner = testRunners[runner];
  232. const context = contextsByTestRunner.get(testRunner);
  233. invariant(context);
  234. const tests = testsByRunner[runner];
  235. const testRunnerOptions = {
  236. serial: runInBand || Boolean(testRunner.isSerial)
  237. };
  238. /**
  239. * Test runners with event emitters are still not supported
  240. * for third party test runners.
  241. */
  242. if (testRunner.__PRIVATE_UNSTABLE_API_supportsEventEmitters__) {
  243. const unsubscribes = [
  244. testRunner.on('test-file-start', ([test]) =>
  245. onTestFileStart(test)
  246. ),
  247. testRunner.on('test-file-success', ([test, testResult]) =>
  248. onResult(test, testResult)
  249. ),
  250. testRunner.on('test-file-failure', ([test, error]) =>
  251. onFailure(test, error)
  252. ),
  253. testRunner.on(
  254. 'test-case-result',
  255. ([testPath, testCaseResult]) => {
  256. const test = {
  257. context,
  258. path: testPath
  259. };
  260. this._dispatcher.onTestCaseResult(test, testCaseResult);
  261. }
  262. )
  263. ];
  264. await testRunner.runTests(
  265. tests,
  266. watcher,
  267. undefined,
  268. undefined,
  269. undefined,
  270. testRunnerOptions
  271. );
  272. unsubscribes.forEach(sub => sub());
  273. } else {
  274. await testRunner.runTests(
  275. tests,
  276. watcher,
  277. onTestFileStart,
  278. onResult,
  279. onFailure,
  280. testRunnerOptions
  281. );
  282. }
  283. }
  284. } catch (error) {
  285. if (!watcher.isInterrupted()) {
  286. throw error;
  287. }
  288. }
  289. }
  290. updateSnapshotState();
  291. aggregatedResults.wasInterrupted = watcher.isInterrupted();
  292. await this._dispatcher.onRunComplete(contexts, aggregatedResults);
  293. const anyTestFailures = !(
  294. aggregatedResults.numFailedTests === 0 &&
  295. aggregatedResults.numRuntimeErrorTestSuites === 0
  296. );
  297. const anyReporterErrors = this._dispatcher.hasErrors();
  298. aggregatedResults.success = !(
  299. anyTestFailures ||
  300. aggregatedResults.snapshot.failure ||
  301. anyReporterErrors
  302. );
  303. return aggregatedResults;
  304. }
  305. _partitionTests(testRunners, tests) {
  306. if (Object.keys(testRunners).length > 1) {
  307. return tests.reduce((testRuns, test) => {
  308. const runner = test.context.config.runner;
  309. if (!testRuns[runner]) {
  310. testRuns[runner] = [];
  311. }
  312. testRuns[runner].push(test);
  313. return testRuns;
  314. }, Object.create(null));
  315. } else if (tests.length > 0 && tests[0] != null) {
  316. // If there is only one runner, don't partition the tests.
  317. return Object.assign(Object.create(null), {
  318. [tests[0].context.config.runner]: tests
  319. });
  320. } else {
  321. return null;
  322. }
  323. }
  324. _shouldAddDefaultReporters(reporters) {
  325. return (
  326. !reporters ||
  327. !!reporters.find(
  328. reporter => this._getReporterProps(reporter).path === 'default'
  329. )
  330. );
  331. }
  332. _setupReporters() {
  333. const {collectCoverage, notify, reporters} = this._globalConfig;
  334. const isDefault = this._shouldAddDefaultReporters(reporters);
  335. if (isDefault) {
  336. this._setupDefaultReporters(collectCoverage);
  337. }
  338. if (!isDefault && collectCoverage) {
  339. var _this$_context3, _this$_context4;
  340. this.addReporter(
  341. new (_reporters().CoverageReporter)(this._globalConfig, {
  342. changedFiles:
  343. (_this$_context3 = this._context) === null ||
  344. _this$_context3 === void 0
  345. ? void 0
  346. : _this$_context3.changedFiles,
  347. sourcesRelatedToTestsInChangedFiles:
  348. (_this$_context4 = this._context) === null ||
  349. _this$_context4 === void 0
  350. ? void 0
  351. : _this$_context4.sourcesRelatedToTestsInChangedFiles
  352. })
  353. );
  354. }
  355. if (notify) {
  356. this.addReporter(
  357. new (_reporters().NotifyReporter)(
  358. this._globalConfig,
  359. this._options.startRun,
  360. this._context
  361. )
  362. );
  363. }
  364. if (reporters && Array.isArray(reporters)) {
  365. this._addCustomReporters(reporters);
  366. }
  367. }
  368. _setupDefaultReporters(collectCoverage) {
  369. this.addReporter(
  370. this._globalConfig.verbose
  371. ? new (_reporters().VerboseReporter)(this._globalConfig)
  372. : new (_reporters().DefaultReporter)(this._globalConfig)
  373. );
  374. if (collectCoverage) {
  375. var _this$_context5, _this$_context6;
  376. this.addReporter(
  377. new (_reporters().CoverageReporter)(this._globalConfig, {
  378. changedFiles:
  379. (_this$_context5 = this._context) === null ||
  380. _this$_context5 === void 0
  381. ? void 0
  382. : _this$_context5.changedFiles,
  383. sourcesRelatedToTestsInChangedFiles:
  384. (_this$_context6 = this._context) === null ||
  385. _this$_context6 === void 0
  386. ? void 0
  387. : _this$_context6.sourcesRelatedToTestsInChangedFiles
  388. })
  389. );
  390. }
  391. this.addReporter(new (_reporters().SummaryReporter)(this._globalConfig));
  392. }
  393. _addCustomReporters(reporters) {
  394. reporters.forEach(reporter => {
  395. const {options, path} = this._getReporterProps(reporter);
  396. if (path === 'default') return;
  397. try {
  398. // TODO: Use `requireAndTranspileModule` for Jest 26
  399. const Reporter = (0, _jestUtil().interopRequireDefault)(require(path))
  400. .default;
  401. this.addReporter(new Reporter(this._globalConfig, options));
  402. } catch (error) {
  403. error.message =
  404. 'An error occurred while adding the reporter at path "' +
  405. _chalk().default.bold(path) +
  406. '".' +
  407. error.message;
  408. throw error;
  409. }
  410. });
  411. }
  412. /**
  413. * Get properties of a reporter in an object
  414. * to make dealing with them less painful.
  415. */
  416. _getReporterProps(reporter) {
  417. if (typeof reporter === 'string') {
  418. return {
  419. options: this._options,
  420. path: reporter
  421. };
  422. } else if (Array.isArray(reporter)) {
  423. const [path, options] = reporter;
  424. return {
  425. options,
  426. path
  427. };
  428. }
  429. throw new Error('Reporter should be either a string or an array');
  430. }
  431. _bailIfNeeded(contexts, aggregatedResults, watcher) {
  432. if (
  433. this._globalConfig.bail !== 0 &&
  434. aggregatedResults.numFailedTests >= this._globalConfig.bail
  435. ) {
  436. if (watcher.isWatchMode()) {
  437. watcher.setState({
  438. interrupted: true
  439. });
  440. } else {
  441. const failureExit = () => (0, _exit().default)(1);
  442. return this._dispatcher
  443. .onRunComplete(contexts, aggregatedResults)
  444. .then(failureExit)
  445. .catch(failureExit);
  446. }
  447. }
  448. return Promise.resolve();
  449. }
  450. }
  451. exports.default = TestScheduler;
  452. function invariant(condition, message) {
  453. if (!condition) {
  454. throw new Error(message);
  455. }
  456. }
  457. const createAggregatedResults = numTotalTestSuites => {
  458. const result = (0, _testResult().makeEmptyAggregatedTestResult)();
  459. result.numTotalTestSuites = numTotalTestSuites;
  460. result.startTime = Date.now();
  461. result.success = false;
  462. return result;
  463. };
  464. const getEstimatedTime = (timings, workers) => {
  465. if (!timings.length) {
  466. return 0;
  467. }
  468. const max = Math.max.apply(null, timings);
  469. return timings.length <= workers
  470. ? max
  471. : Math.max(timings.reduce((sum, time) => sum + time) / workers, max);
  472. };