TestScheduler.js 15 KB

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