1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, '__esModule', {
|
4 | value: true
|
5 | });
|
6 | exports.createTestScheduler = createTestScheduler;
|
7 |
|
8 | function _chalk() {
|
9 | const data = _interopRequireDefault(require('chalk'));
|
10 |
|
11 | _chalk = function () {
|
12 | return data;
|
13 | };
|
14 |
|
15 | return data;
|
16 | }
|
17 |
|
18 | function _exit() {
|
19 | const data = _interopRequireDefault(require('exit'));
|
20 |
|
21 | _exit = function () {
|
22 | return data;
|
23 | };
|
24 |
|
25 | return data;
|
26 | }
|
27 |
|
28 | function _reporters() {
|
29 | const data = require('@jest/reporters');
|
30 |
|
31 | _reporters = function () {
|
32 | return data;
|
33 | };
|
34 |
|
35 | return data;
|
36 | }
|
37 |
|
38 | function _testResult() {
|
39 | const data = require('@jest/test-result');
|
40 |
|
41 | _testResult = function () {
|
42 | return data;
|
43 | };
|
44 |
|
45 | return data;
|
46 | }
|
47 |
|
48 | function _transform() {
|
49 | const data = require('@jest/transform');
|
50 |
|
51 | _transform = function () {
|
52 | return data;
|
53 | };
|
54 |
|
55 | return data;
|
56 | }
|
57 |
|
58 | function _jestMessageUtil() {
|
59 | const data = require('jest-message-util');
|
60 |
|
61 | _jestMessageUtil = function () {
|
62 | return data;
|
63 | };
|
64 |
|
65 | return data;
|
66 | }
|
67 |
|
68 | function _jestSnapshot() {
|
69 | const data = _interopRequireDefault(require('jest-snapshot'));
|
70 |
|
71 | _jestSnapshot = function () {
|
72 | return data;
|
73 | };
|
74 |
|
75 | return data;
|
76 | }
|
77 |
|
78 | function _jestUtil() {
|
79 | const data = require('jest-util');
|
80 |
|
81 | _jestUtil = function () {
|
82 | return data;
|
83 | };
|
84 |
|
85 | return data;
|
86 | }
|
87 |
|
88 | var _ReporterDispatcher = _interopRequireDefault(
|
89 | require('./ReporterDispatcher')
|
90 | );
|
91 |
|
92 | var _testSchedulerHelper = require('./testSchedulerHelper');
|
93 |
|
94 | function _interopRequireDefault(obj) {
|
95 | return obj && obj.__esModule ? obj : {default: obj};
|
96 | }
|
97 |
|
98 | function _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 | async function createTestScheduler(globalConfig, options, context) {
|
113 | const scheduler = new TestScheduler(globalConfig, options, context);
|
114 | await scheduler._setupReporters();
|
115 | return scheduler;
|
116 | }
|
117 |
|
118 | class 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 | }
|
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 |
|
307 |
|
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 |
|
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 |
|
505 |
|
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 |
|
547 | function invariant(condition, message) {
|
548 | if (!condition) {
|
549 | throw new Error(message);
|
550 | }
|
551 | }
|
552 |
|
553 | const 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 |
|
561 | const 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 | };
|