UNPKG

13.1 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 onStart = this._dispatcher.onTestStart.bind(this._dispatcher);
144
145 const timings = [];
146 const contexts = new Set();
147 tests.forEach(test => {
148 contexts.add(test.context);
149
150 if (test.duration) {
151 timings.push(test.duration);
152 }
153 });
154 const aggregatedResults = createAggregatedResults(tests.length);
155 const estimatedTime = Math.ceil(
156 getEstimatedTime(timings, this._globalConfig.maxWorkers) / 1000
157 );
158 const runInBand = (0, _testSchedulerHelper.shouldRunInBand)(
159 tests,
160 timings,
161 this._globalConfig
162 );
163
164 const onResult = async (test, testResult) => {
165 if (watcher.isInterrupted()) {
166 return Promise.resolve();
167 }
168
169 if (testResult.testResults.length === 0) {
170 const message = 'Your test suite must contain at least one test.';
171 return onFailure(test, {
172 message,
173 stack: new Error(message).stack
174 });
175 } // Throws when the context is leaked after executing a test.
176
177 if (testResult.leaks) {
178 const message =
179 _chalk().default.red.bold('EXPERIMENTAL FEATURE!\n') +
180 'Your test suite is leaking memory. Please ensure all references are cleaned.\n' +
181 '\n' +
182 'There is a number of things that can leak memory:\n' +
183 ' - Async operations that have not finished (e.g. fs.readFile).\n' +
184 ' - Timers not properly mocked (e.g. setInterval, setTimeout).\n' +
185 ' - Keeping references to the global scope.';
186 return onFailure(test, {
187 message,
188 stack: new Error(message).stack
189 });
190 }
191
192 (0, _testResult().addResult)(aggregatedResults, testResult);
193 await this._dispatcher.onTestResult(test, testResult, aggregatedResults);
194 return this._bailIfNeeded(contexts, aggregatedResults, watcher);
195 };
196
197 const onFailure = async (test, error) => {
198 if (watcher.isInterrupted()) {
199 return;
200 }
201
202 const testResult = (0, _testResult().buildFailureTestResult)(
203 test.path,
204 error
205 );
206 testResult.failureMessage = (0, _jestMessageUtil().formatExecError)(
207 testResult.testExecError,
208 test.context.config,
209 this._globalConfig,
210 test.path
211 );
212 (0, _testResult().addResult)(aggregatedResults, testResult);
213 await this._dispatcher.onTestResult(test, testResult, aggregatedResults);
214 };
215
216 const updateSnapshotState = () => {
217 contexts.forEach(context => {
218 const status = _jestSnapshot().default.cleanup(
219 context.hasteFS,
220 this._globalConfig.updateSnapshot,
221 _jestSnapshot().default.buildSnapshotResolver(context.config),
222 context.config.testPathIgnorePatterns
223 );
224
225 aggregatedResults.snapshot.filesRemoved += status.filesRemoved;
226 aggregatedResults.snapshot.filesRemovedList = (
227 aggregatedResults.snapshot.filesRemovedList || []
228 ).concat(status.filesRemovedList);
229 });
230 const updateAll = this._globalConfig.updateSnapshot === 'all';
231 aggregatedResults.snapshot.didUpdate = updateAll;
232 aggregatedResults.snapshot.failure = !!(
233 !updateAll &&
234 (aggregatedResults.snapshot.unchecked ||
235 aggregatedResults.snapshot.unmatched ||
236 aggregatedResults.snapshot.filesRemoved)
237 );
238 };
239
240 await this._dispatcher.onRunStart(aggregatedResults, {
241 estimatedTime,
242 showStatus: !runInBand
243 });
244 const testRunners = Object.create(null);
245 contexts.forEach(({config}) => {
246 if (!testRunners[config.runner]) {
247 var _this$_context, _this$_context2;
248
249 const Runner = require(config.runner);
250
251 testRunners[config.runner] = new Runner(this._globalConfig, {
252 changedFiles:
253 (_this$_context = this._context) === null ||
254 _this$_context === void 0
255 ? void 0
256 : _this$_context.changedFiles,
257 sourcesRelatedToTestsInChangedFiles:
258 (_this$_context2 = this._context) === null ||
259 _this$_context2 === void 0
260 ? void 0
261 : _this$_context2.sourcesRelatedToTestsInChangedFiles
262 });
263 }
264 });
265
266 const testsByRunner = this._partitionTests(testRunners, tests);
267
268 if (testsByRunner) {
269 try {
270 for (const runner of Object.keys(testRunners)) {
271 await testRunners[runner].runTests(
272 testsByRunner[runner],
273 watcher,
274 onStart,
275 onResult,
276 onFailure,
277 {
278 serial: runInBand || Boolean(testRunners[runner].isSerial)
279 }
280 );
281 }
282 } catch (error) {
283 if (!watcher.isInterrupted()) {
284 throw error;
285 }
286 }
287 }
288
289 updateSnapshotState();
290 aggregatedResults.wasInterrupted = watcher.isInterrupted();
291 await this._dispatcher.onRunComplete(contexts, aggregatedResults);
292 const anyTestFailures = !(
293 aggregatedResults.numFailedTests === 0 &&
294 aggregatedResults.numRuntimeErrorTestSuites === 0
295 );
296
297 const anyReporterErrors = this._dispatcher.hasErrors();
298
299 aggregatedResults.success = !(
300 anyTestFailures ||
301 aggregatedResults.snapshot.failure ||
302 anyReporterErrors
303 );
304 return aggregatedResults;
305 }
306
307 _partitionTests(testRunners, tests) {
308 if (Object.keys(testRunners).length > 1) {
309 return tests.reduce((testRuns, test) => {
310 const runner = test.context.config.runner;
311
312 if (!testRuns[runner]) {
313 testRuns[runner] = [];
314 }
315
316 testRuns[runner].push(test);
317 return testRuns;
318 }, Object.create(null));
319 } else if (tests.length > 0 && tests[0] != null) {
320 // If there is only one runner, don't partition the tests.
321 return Object.assign(Object.create(null), {
322 [tests[0].context.config.runner]: tests
323 });
324 } else {
325 return null;
326 }
327 }
328
329 _shouldAddDefaultReporters(reporters) {
330 return (
331 !reporters ||
332 !!reporters.find(
333 reporter => this._getReporterProps(reporter).path === 'default'
334 )
335 );
336 }
337
338 _setupReporters() {
339 const {collectCoverage, notify, reporters} = this._globalConfig;
340
341 const isDefault = this._shouldAddDefaultReporters(reporters);
342
343 if (isDefault) {
344 this._setupDefaultReporters(collectCoverage);
345 }
346
347 if (!isDefault && collectCoverage) {
348 var _this$_context3, _this$_context4;
349
350 this.addReporter(
351 new (_reporters().CoverageReporter)(this._globalConfig, {
352 changedFiles:
353 (_this$_context3 = this._context) === null ||
354 _this$_context3 === void 0
355 ? void 0
356 : _this$_context3.changedFiles,
357 sourcesRelatedToTestsInChangedFiles:
358 (_this$_context4 = this._context) === null ||
359 _this$_context4 === void 0
360 ? void 0
361 : _this$_context4.sourcesRelatedToTestsInChangedFiles
362 })
363 );
364 }
365
366 if (notify) {
367 this.addReporter(
368 new (_reporters().NotifyReporter)(
369 this._globalConfig,
370 this._options.startRun,
371 this._context
372 )
373 );
374 }
375
376 if (reporters && Array.isArray(reporters)) {
377 this._addCustomReporters(reporters);
378 }
379 }
380
381 _setupDefaultReporters(collectCoverage) {
382 this.addReporter(
383 this._globalConfig.verbose
384 ? new (_reporters().VerboseReporter)(this._globalConfig)
385 : new (_reporters().DefaultReporter)(this._globalConfig)
386 );
387
388 if (collectCoverage) {
389 var _this$_context5, _this$_context6;
390
391 this.addReporter(
392 new (_reporters().CoverageReporter)(this._globalConfig, {
393 changedFiles:
394 (_this$_context5 = this._context) === null ||
395 _this$_context5 === void 0
396 ? void 0
397 : _this$_context5.changedFiles,
398 sourcesRelatedToTestsInChangedFiles:
399 (_this$_context6 = this._context) === null ||
400 _this$_context6 === void 0
401 ? void 0
402 : _this$_context6.sourcesRelatedToTestsInChangedFiles
403 })
404 );
405 }
406
407 this.addReporter(new (_reporters().SummaryReporter)(this._globalConfig));
408 }
409
410 _addCustomReporters(reporters) {
411 reporters.forEach(reporter => {
412 const {options, path} = this._getReporterProps(reporter);
413
414 if (path === 'default') return;
415
416 try {
417 // TODO: Use `requireAndTranspileModule` for Jest 26
418 const Reporter = (0, _jestUtil().interopRequireDefault)(require(path))
419 .default;
420 this.addReporter(new Reporter(this._globalConfig, options));
421 } catch (error) {
422 error.message =
423 'An error occurred while adding the reporter at path "' +
424 _chalk().default.bold(path) +
425 '".' +
426 error.message;
427 throw error;
428 }
429 });
430 }
431 /**
432 * Get properties of a reporter in an object
433 * to make dealing with them less painful.
434 */
435
436 _getReporterProps(reporter) {
437 if (typeof reporter === 'string') {
438 return {
439 options: this._options,
440 path: reporter
441 };
442 } else if (Array.isArray(reporter)) {
443 const [path, options] = reporter;
444 return {
445 options,
446 path
447 };
448 }
449
450 throw new Error('Reporter should be either a string or an array');
451 }
452
453 _bailIfNeeded(contexts, aggregatedResults, watcher) {
454 if (
455 this._globalConfig.bail !== 0 &&
456 aggregatedResults.numFailedTests >= this._globalConfig.bail
457 ) {
458 if (watcher.isWatchMode()) {
459 watcher.setState({
460 interrupted: true
461 });
462 } else {
463 const failureExit = () => (0, _exit().default)(1);
464
465 return this._dispatcher
466 .onRunComplete(contexts, aggregatedResults)
467 .then(failureExit)
468 .catch(failureExit);
469 }
470 }
471
472 return Promise.resolve();
473 }
474}
475
476exports.default = TestScheduler;
477
478const createAggregatedResults = numTotalTestSuites => {
479 const result = (0, _testResult().makeEmptyAggregatedTestResult)();
480 result.numTotalTestSuites = numTotalTestSuites;
481 result.startTime = Date.now();
482 result.success = false;
483 return result;
484};
485
486const getEstimatedTime = (timings, workers) => {
487 if (!timings.length) {
488 return 0;
489 }
490
491 const max = Math.max.apply(null, timings);
492 return timings.length <= workers
493 ? max
494 : Math.max(timings.reduce((sum, time) => sum + time) / workers, max);
495};