UNPKG

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