UNPKG

15.4 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', {
4 value: true
5});
6exports.createTestScheduler = createTestScheduler;
7
8function _chalk() {
9 const data = _interopRequireDefault(require('chalk'));
10
11 _chalk = function () {
12 return data;
13 };
14
15 return data;
16}
17
18function _exit() {
19 const data = _interopRequireDefault(require('exit'));
20
21 _exit = function () {
22 return data;
23 };
24
25 return data;
26}
27
28function _reporters() {
29 const data = require('@jest/reporters');
30
31 _reporters = function () {
32 return data;
33 };
34
35 return data;
36}
37
38function _testResult() {
39 const data = require('@jest/test-result');
40
41 _testResult = function () {
42 return data;
43 };
44
45 return data;
46}
47
48function _transform() {
49 const data = require('@jest/transform');
50
51 _transform = function () {
52 return data;
53 };
54
55 return data;
56}
57
58function _jestMessageUtil() {
59 const data = require('jest-message-util');
60
61 _jestMessageUtil = function () {
62 return data;
63 };
64
65 return data;
66}
67
68function _jestSnapshot() {
69 const data = _interopRequireDefault(require('jest-snapshot'));
70
71 _jestSnapshot = 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
112async function createTestScheduler(globalConfig, options, context) {
113 const scheduler = new TestScheduler(globalConfig, options, context);
114 await scheduler._setupReporters();
115 return scheduler;
116}
117
118class TestScheduler {
119 constructor(globalConfig, options, context) {
120 _defineProperty(this, '_dispatcher', void 0);
121
122 _defineProperty(this, '_globalConfig', void 0);
123
124 _defineProperty(this, '_options', void 0);
125
126 _defineProperty(this, '_context', void 0);
127
128 this._dispatcher = new _ReporterDispatcher.default();
129 this._globalConfig = globalConfig;
130 this._options = options;
131 this._context = context;
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 = async () => {
227 const contextsWithSnapshotResolvers = await Promise.all(
228 Array.from(contexts).map(async context => [
229 context,
230 await _jestSnapshot().default.buildSnapshotResolver(context.config)
231 ])
232 );
233 contextsWithSnapshotResolvers.forEach(([context, snapshotResolver]) => {
234 const status = _jestSnapshot().default.cleanup(
235 context.hasteFS,
236 this._globalConfig.updateSnapshot,
237 snapshotResolver,
238 context.config.testPathIgnorePatterns
239 );
240
241 aggregatedResults.snapshot.filesRemoved += status.filesRemoved;
242 aggregatedResults.snapshot.filesRemovedList = (
243 aggregatedResults.snapshot.filesRemovedList || []
244 ).concat(status.filesRemovedList);
245 });
246 const updateAll = this._globalConfig.updateSnapshot === 'all';
247 aggregatedResults.snapshot.didUpdate = updateAll;
248 aggregatedResults.snapshot.failure = !!(
249 !updateAll &&
250 (aggregatedResults.snapshot.unchecked ||
251 aggregatedResults.snapshot.unmatched ||
252 aggregatedResults.snapshot.filesRemoved)
253 );
254 };
255
256 await this._dispatcher.onRunStart(aggregatedResults, {
257 estimatedTime,
258 showStatus: !runInBand
259 });
260 const testRunners = Object.create(null);
261 const contextsByTestRunner = new WeakMap();
262 await Promise.all(
263 Array.from(contexts).map(async context => {
264 const {config} = context;
265
266 if (!testRunners[config.runner]) {
267 var _this$_context, _this$_context2;
268
269 const transformer = await (0, _transform().createScriptTransformer)(
270 config
271 );
272 const Runner = await transformer.requireAndTranspileModule(
273 config.runner
274 );
275 const runner = new Runner(this._globalConfig, {
276 changedFiles:
277 (_this$_context = this._context) === null ||
278 _this$_context === void 0
279 ? void 0
280 : _this$_context.changedFiles,
281 sourcesRelatedToTestsInChangedFiles:
282 (_this$_context2 = this._context) === null ||
283 _this$_context2 === void 0
284 ? void 0
285 : _this$_context2.sourcesRelatedToTestsInChangedFiles
286 });
287 testRunners[config.runner] = runner;
288 contextsByTestRunner.set(runner, context);
289 }
290 })
291 );
292
293 const testsByRunner = this._partitionTests(testRunners, tests);
294
295 if (testsByRunner) {
296 try {
297 for (const runner of Object.keys(testRunners)) {
298 const testRunner = testRunners[runner];
299 const context = contextsByTestRunner.get(testRunner);
300 invariant(context);
301 const tests = testsByRunner[runner];
302 const testRunnerOptions = {
303 serial: runInBand || Boolean(testRunner.isSerial)
304 };
305 /**
306 * Test runners with event emitters are still not supported
307 * for third party test runners.
308 */
309
310 if (testRunner.__PRIVATE_UNSTABLE_API_supportsEventEmitters__) {
311 const unsubscribes = [
312 testRunner.on('test-file-start', ([test]) =>
313 onTestFileStart(test)
314 ),
315 testRunner.on('test-file-success', ([test, testResult]) =>
316 onResult(test, testResult)
317 ),
318 testRunner.on('test-file-failure', ([test, error]) =>
319 onFailure(test, error)
320 ),
321 testRunner.on(
322 'test-case-result',
323 ([testPath, testCaseResult]) => {
324 const test = {
325 context,
326 path: testPath
327 };
328
329 this._dispatcher.onTestCaseResult(test, testCaseResult);
330 }
331 )
332 ];
333 await testRunner.runTests(
334 tests,
335 watcher,
336 undefined,
337 undefined,
338 undefined,
339 testRunnerOptions
340 );
341 unsubscribes.forEach(sub => sub());
342 } else {
343 await testRunner.runTests(
344 tests,
345 watcher,
346 onTestFileStart,
347 onResult,
348 onFailure,
349 testRunnerOptions
350 );
351 }
352 }
353 } catch (error) {
354 if (!watcher.isInterrupted()) {
355 throw error;
356 }
357 }
358 }
359
360 await updateSnapshotState();
361 aggregatedResults.wasInterrupted = watcher.isInterrupted();
362 await this._dispatcher.onRunComplete(contexts, aggregatedResults);
363 const anyTestFailures = !(
364 aggregatedResults.numFailedTests === 0 &&
365 aggregatedResults.numRuntimeErrorTestSuites === 0
366 );
367
368 const anyReporterErrors = this._dispatcher.hasErrors();
369
370 aggregatedResults.success = !(
371 anyTestFailures ||
372 aggregatedResults.snapshot.failure ||
373 anyReporterErrors
374 );
375 return aggregatedResults;
376 }
377
378 _partitionTests(testRunners, tests) {
379 if (Object.keys(testRunners).length > 1) {
380 return tests.reduce((testRuns, test) => {
381 const runner = test.context.config.runner;
382
383 if (!testRuns[runner]) {
384 testRuns[runner] = [];
385 }
386
387 testRuns[runner].push(test);
388 return testRuns;
389 }, Object.create(null));
390 } else if (tests.length > 0 && tests[0] != null) {
391 // If there is only one runner, don't partition the tests.
392 return Object.assign(Object.create(null), {
393 [tests[0].context.config.runner]: tests
394 });
395 } else {
396 return null;
397 }
398 }
399
400 _shouldAddDefaultReporters(reporters) {
401 return (
402 !reporters ||
403 !!reporters.find(
404 reporter => this._getReporterProps(reporter).path === 'default'
405 )
406 );
407 }
408
409 async _setupReporters() {
410 const {collectCoverage, notify, reporters} = this._globalConfig;
411
412 const isDefault = this._shouldAddDefaultReporters(reporters);
413
414 if (isDefault) {
415 this._setupDefaultReporters(collectCoverage);
416 }
417
418 if (!isDefault && collectCoverage) {
419 var _this$_context3, _this$_context4;
420
421 this.addReporter(
422 new (_reporters().CoverageReporter)(this._globalConfig, {
423 changedFiles:
424 (_this$_context3 = this._context) === null ||
425 _this$_context3 === void 0
426 ? void 0
427 : _this$_context3.changedFiles,
428 sourcesRelatedToTestsInChangedFiles:
429 (_this$_context4 = this._context) === null ||
430 _this$_context4 === void 0
431 ? void 0
432 : _this$_context4.sourcesRelatedToTestsInChangedFiles
433 })
434 );
435 }
436
437 if (notify) {
438 this.addReporter(
439 new (_reporters().NotifyReporter)(
440 this._globalConfig,
441 this._options.startRun,
442 this._context
443 )
444 );
445 }
446
447 if (reporters && Array.isArray(reporters)) {
448 await this._addCustomReporters(reporters);
449 }
450 }
451
452 _setupDefaultReporters(collectCoverage) {
453 this.addReporter(
454 this._globalConfig.verbose
455 ? new (_reporters().VerboseReporter)(this._globalConfig)
456 : new (_reporters().DefaultReporter)(this._globalConfig)
457 );
458
459 if (collectCoverage) {
460 var _this$_context5, _this$_context6;
461
462 this.addReporter(
463 new (_reporters().CoverageReporter)(this._globalConfig, {
464 changedFiles:
465 (_this$_context5 = this._context) === null ||
466 _this$_context5 === void 0
467 ? void 0
468 : _this$_context5.changedFiles,
469 sourcesRelatedToTestsInChangedFiles:
470 (_this$_context6 = this._context) === null ||
471 _this$_context6 === void 0
472 ? void 0
473 : _this$_context6.sourcesRelatedToTestsInChangedFiles
474 })
475 );
476 }
477
478 this.addReporter(new (_reporters().SummaryReporter)(this._globalConfig));
479 }
480
481 async _addCustomReporters(reporters) {
482 for (const reporter of reporters) {
483 const {options, path} = this._getReporterProps(reporter);
484
485 if (path === 'default') continue;
486
487 try {
488 const Reporter = await (0, _jestUtil().requireOrImportModule)(
489 path,
490 true
491 );
492 this.addReporter(new Reporter(this._globalConfig, options));
493 } catch (error) {
494 error.message =
495 'An error occurred while adding the reporter at path "' +
496 _chalk().default.bold(path) +
497 '".' +
498 error.message;
499 throw error;
500 }
501 }
502 }
503 /**
504 * Get properties of a reporter in an object
505 * to make dealing with them less painful.
506 */
507
508 _getReporterProps(reporter) {
509 if (typeof reporter === 'string') {
510 return {
511 options: this._options,
512 path: reporter
513 };
514 } else if (Array.isArray(reporter)) {
515 const [path, options] = reporter;
516 return {
517 options,
518 path
519 };
520 }
521
522 throw new Error('Reporter should be either a string or an array');
523 }
524
525 async _bailIfNeeded(contexts, aggregatedResults, watcher) {
526 if (
527 this._globalConfig.bail !== 0 &&
528 aggregatedResults.numFailedTests >= this._globalConfig.bail
529 ) {
530 if (watcher.isWatchMode()) {
531 await watcher.setState({
532 interrupted: true
533 });
534 return;
535 }
536
537 try {
538 await this._dispatcher.onRunComplete(contexts, aggregatedResults);
539 } finally {
540 const exitCode = this._globalConfig.testFailureExitCode;
541 (0, _exit().default)(exitCode);
542 }
543 }
544 }
545}
546
547function invariant(condition, message) {
548 if (!condition) {
549 throw new Error(message);
550 }
551}
552
553const createAggregatedResults = numTotalTestSuites => {
554 const result = (0, _testResult().makeEmptyAggregatedTestResult)();
555 result.numTotalTestSuites = numTotalTestSuites;
556 result.startTime = Date.now();
557 result.success = false;
558 return result;
559};
560
561const getEstimatedTime = (timings, workers) => {
562 if (timings.length === 0) {
563 return 0;
564 }
565
566 const max = Math.max(...timings);
567 return timings.length <= workers
568 ? max
569 : Math.max(timings.reduce((sum, time) => sum + time) / workers, max);
570};