1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, '__esModule', {
|
4 | value: true
|
5 | });
|
6 | exports.createTestScheduler = createTestScheduler;
|
7 | function _chalk() {
|
8 | const data = _interopRequireDefault(require('chalk'));
|
9 | _chalk = function () {
|
10 | return data;
|
11 | };
|
12 | return data;
|
13 | }
|
14 | function _ciInfo() {
|
15 | const data = require('ci-info');
|
16 | _ciInfo = function () {
|
17 | return data;
|
18 | };
|
19 | return data;
|
20 | }
|
21 | function _exit() {
|
22 | const data = _interopRequireDefault(require('exit'));
|
23 | _exit = function () {
|
24 | return data;
|
25 | };
|
26 | return data;
|
27 | }
|
28 | function _reporters() {
|
29 | const data = require('@jest/reporters');
|
30 | _reporters = function () {
|
31 | return data;
|
32 | };
|
33 | return data;
|
34 | }
|
35 | function _testResult() {
|
36 | const data = require('@jest/test-result');
|
37 | _testResult = function () {
|
38 | return data;
|
39 | };
|
40 | return data;
|
41 | }
|
42 | function _transform() {
|
43 | const data = require('@jest/transform');
|
44 | _transform = function () {
|
45 | return data;
|
46 | };
|
47 | return data;
|
48 | }
|
49 | function _jestMessageUtil() {
|
50 | const data = require('jest-message-util');
|
51 | _jestMessageUtil = function () {
|
52 | return data;
|
53 | };
|
54 | return data;
|
55 | }
|
56 | function _jestSnapshot() {
|
57 | const data = require('jest-snapshot');
|
58 | _jestSnapshot = function () {
|
59 | return data;
|
60 | };
|
61 | return data;
|
62 | }
|
63 | function _jestUtil() {
|
64 | const data = require('jest-util');
|
65 | _jestUtil = function () {
|
66 | return data;
|
67 | };
|
68 | return data;
|
69 | }
|
70 | var _ReporterDispatcher = _interopRequireDefault(
|
71 | require('./ReporterDispatcher')
|
72 | );
|
73 | var _testSchedulerHelper = require('./testSchedulerHelper');
|
74 | function _interopRequireDefault(obj) {
|
75 | return obj && obj.__esModule ? obj : {default: obj};
|
76 | }
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 | async function createTestScheduler(globalConfig, context) {
|
85 | const scheduler = new TestScheduler(globalConfig, context);
|
86 | await scheduler._setupReporters();
|
87 | return scheduler;
|
88 | }
|
89 | class TestScheduler {
|
90 | _context;
|
91 | _dispatcher;
|
92 | _globalConfig;
|
93 | constructor(globalConfig, context) {
|
94 | this._context = context;
|
95 | this._dispatcher = new _ReporterDispatcher.default();
|
96 | this._globalConfig = globalConfig;
|
97 | }
|
98 | addReporter(reporter) {
|
99 | this._dispatcher.register(reporter);
|
100 | }
|
101 | removeReporter(reporterConstructor) {
|
102 | this._dispatcher.unregister(reporterConstructor);
|
103 | }
|
104 | async scheduleTests(tests, watcher) {
|
105 | const onTestFileStart = this._dispatcher.onTestFileStart.bind(
|
106 | this._dispatcher
|
107 | );
|
108 | const timings = [];
|
109 | const testContexts = new Set();
|
110 | tests.forEach(test => {
|
111 | testContexts.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 | }
|
136 |
|
137 |
|
138 | if (testResult.leaks) {
|
139 | const message =
|
140 | `${_chalk().default.red.bold(
|
141 | 'EXPERIMENTAL FEATURE!\n'
|
142 | )}Your test suite is leaking memory. Please ensure all references are cleaned.\n` +
|
143 | '\n' +
|
144 | 'There is a number of things that can leak memory:\n' +
|
145 | ' - Async operations that have not finished (e.g. fs.readFile).\n' +
|
146 | ' - Timers not properly mocked (e.g. setInterval, setTimeout).\n' +
|
147 | ' - Keeping references to the global scope.';
|
148 | return onFailure(test, {
|
149 | message,
|
150 | stack: new Error(message).stack
|
151 | });
|
152 | }
|
153 | (0, _testResult().addResult)(aggregatedResults, testResult);
|
154 | await this._dispatcher.onTestFileResult(
|
155 | test,
|
156 | testResult,
|
157 | aggregatedResults
|
158 | );
|
159 | return this._bailIfNeeded(testContexts, aggregatedResults, watcher);
|
160 | };
|
161 | const onFailure = async (test, error) => {
|
162 | if (watcher.isInterrupted()) {
|
163 | return;
|
164 | }
|
165 | const testResult = (0, _testResult().buildFailureTestResult)(
|
166 | test.path,
|
167 | error
|
168 | );
|
169 | testResult.failureMessage = (0, _jestMessageUtil().formatExecError)(
|
170 | testResult.testExecError,
|
171 | test.context.config,
|
172 | this._globalConfig,
|
173 | test.path
|
174 | );
|
175 | (0, _testResult().addResult)(aggregatedResults, testResult);
|
176 | await this._dispatcher.onTestFileResult(
|
177 | test,
|
178 | testResult,
|
179 | aggregatedResults
|
180 | );
|
181 | };
|
182 | const updateSnapshotState = async () => {
|
183 | const contextsWithSnapshotResolvers = await Promise.all(
|
184 | Array.from(testContexts).map(async context => [
|
185 | context,
|
186 | await (0, _jestSnapshot().buildSnapshotResolver)(context.config)
|
187 | ])
|
188 | );
|
189 | contextsWithSnapshotResolvers.forEach(([context, snapshotResolver]) => {
|
190 | const status = (0, _jestSnapshot().cleanup)(
|
191 | context.hasteFS,
|
192 | this._globalConfig.updateSnapshot,
|
193 | snapshotResolver,
|
194 | context.config.testPathIgnorePatterns
|
195 | );
|
196 | aggregatedResults.snapshot.filesRemoved += status.filesRemoved;
|
197 | aggregatedResults.snapshot.filesRemovedList = (
|
198 | aggregatedResults.snapshot.filesRemovedList || []
|
199 | ).concat(status.filesRemovedList);
|
200 | });
|
201 | const updateAll = this._globalConfig.updateSnapshot === 'all';
|
202 | aggregatedResults.snapshot.didUpdate = updateAll;
|
203 | aggregatedResults.snapshot.failure = !!(
|
204 | !updateAll &&
|
205 | (aggregatedResults.snapshot.unchecked ||
|
206 | aggregatedResults.snapshot.unmatched ||
|
207 | aggregatedResults.snapshot.filesRemoved)
|
208 | );
|
209 | };
|
210 | await this._dispatcher.onRunStart(aggregatedResults, {
|
211 | estimatedTime,
|
212 | showStatus: !runInBand
|
213 | });
|
214 | const testRunners = Object.create(null);
|
215 | const contextsByTestRunner = new WeakMap();
|
216 | try {
|
217 | await Promise.all(
|
218 | Array.from(testContexts).map(async context => {
|
219 | const {config} = context;
|
220 | if (!testRunners[config.runner]) {
|
221 | const transformer = await (0, _transform().createScriptTransformer)(
|
222 | config
|
223 | );
|
224 | const Runner = await transformer.requireAndTranspileModule(
|
225 | config.runner
|
226 | );
|
227 | const runner = new Runner(this._globalConfig, {
|
228 | changedFiles: this._context.changedFiles,
|
229 | sourcesRelatedToTestsInChangedFiles:
|
230 | this._context.sourcesRelatedToTestsInChangedFiles
|
231 | });
|
232 | testRunners[config.runner] = runner;
|
233 | contextsByTestRunner.set(runner, context);
|
234 | }
|
235 | })
|
236 | );
|
237 | const testsByRunner = this._partitionTests(testRunners, tests);
|
238 | if (testsByRunner) {
|
239 | try {
|
240 | for (const runner of Object.keys(testRunners)) {
|
241 | const testRunner = testRunners[runner];
|
242 | const context = contextsByTestRunner.get(testRunner);
|
243 | (0, _jestUtil().invariant)(context);
|
244 | const tests = testsByRunner[runner];
|
245 | const testRunnerOptions = {
|
246 | serial: runInBand || Boolean(testRunner.isSerial)
|
247 | };
|
248 | if (testRunner.supportsEventEmitters) {
|
249 | const unsubscribes = [
|
250 | testRunner.on('test-file-start', ([test]) =>
|
251 | onTestFileStart(test)
|
252 | ),
|
253 | testRunner.on('test-file-success', ([test, testResult]) =>
|
254 | onResult(test, testResult)
|
255 | ),
|
256 | testRunner.on('test-file-failure', ([test, error]) =>
|
257 | onFailure(test, error)
|
258 | ),
|
259 | testRunner.on(
|
260 | 'test-case-start',
|
261 | ([testPath, testCaseStartInfo]) => {
|
262 | const test = {
|
263 | context,
|
264 | path: testPath
|
265 | };
|
266 | this._dispatcher.onTestCaseStart(test, testCaseStartInfo);
|
267 | }
|
268 | ),
|
269 | testRunner.on(
|
270 | 'test-case-result',
|
271 | ([testPath, testCaseResult]) => {
|
272 | const test = {
|
273 | context,
|
274 | path: testPath
|
275 | };
|
276 | this._dispatcher.onTestCaseResult(test, testCaseResult);
|
277 | }
|
278 | )
|
279 | ];
|
280 | await testRunner.runTests(tests, watcher, testRunnerOptions);
|
281 | unsubscribes.forEach(sub => sub());
|
282 | } else {
|
283 | await testRunner.runTests(
|
284 | tests,
|
285 | watcher,
|
286 | onTestFileStart,
|
287 | onResult,
|
288 | onFailure,
|
289 | testRunnerOptions
|
290 | );
|
291 | }
|
292 | }
|
293 | } catch (error) {
|
294 | if (!watcher.isInterrupted()) {
|
295 | throw error;
|
296 | }
|
297 | }
|
298 | }
|
299 | } catch (error) {
|
300 | aggregatedResults.runExecError = buildExecError(error);
|
301 | await this._dispatcher.onRunComplete(testContexts, aggregatedResults);
|
302 | throw error;
|
303 | }
|
304 | await updateSnapshotState();
|
305 | aggregatedResults.wasInterrupted = watcher.isInterrupted();
|
306 | await this._dispatcher.onRunComplete(testContexts, 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 |
|
331 | return Object.assign(Object.create(null), {
|
332 | [tests[0].context.config.runner]: tests
|
333 | });
|
334 | } else {
|
335 | return null;
|
336 | }
|
337 | }
|
338 | async _setupReporters() {
|
339 | const {collectCoverage: coverage, notify, verbose} = this._globalConfig;
|
340 | const reporters = this._globalConfig.reporters || [['default', {}]];
|
341 | let summaryOptions = null;
|
342 | for (const [reporter, options] of reporters) {
|
343 | switch (reporter) {
|
344 | case 'default':
|
345 | summaryOptions = options;
|
346 | verbose
|
347 | ? this.addReporter(
|
348 | new (_reporters().VerboseReporter)(this._globalConfig)
|
349 | )
|
350 | : this.addReporter(
|
351 | new (_reporters().DefaultReporter)(this._globalConfig)
|
352 | );
|
353 | break;
|
354 | case 'github-actions':
|
355 | _ciInfo().GITHUB_ACTIONS &&
|
356 | this.addReporter(
|
357 | new (_reporters().GitHubActionsReporter)(
|
358 | this._globalConfig,
|
359 | options
|
360 | )
|
361 | );
|
362 | break;
|
363 | case 'summary':
|
364 | summaryOptions = options;
|
365 | break;
|
366 | default:
|
367 | await this._addCustomReporter(reporter, options);
|
368 | }
|
369 | }
|
370 | if (notify) {
|
371 | this.addReporter(
|
372 | new (_reporters().NotifyReporter)(this._globalConfig, this._context)
|
373 | );
|
374 | }
|
375 | if (coverage) {
|
376 | this.addReporter(
|
377 | new (_reporters().CoverageReporter)(this._globalConfig, this._context)
|
378 | );
|
379 | }
|
380 | if (summaryOptions != null) {
|
381 | this.addReporter(
|
382 | new (_reporters().SummaryReporter)(this._globalConfig, summaryOptions)
|
383 | );
|
384 | }
|
385 | }
|
386 | async _addCustomReporter(reporter, options) {
|
387 | try {
|
388 | const Reporter = await (0, _jestUtil().requireOrImportModule)(reporter);
|
389 | this.addReporter(
|
390 | new Reporter(this._globalConfig, options, this._context)
|
391 | );
|
392 | } catch (error) {
|
393 | error.message = `An error occurred while adding the reporter at path "${_chalk().default.bold(
|
394 | reporter
|
395 | )}".\n${error instanceof Error ? error.message : ''}`;
|
396 | throw error;
|
397 | }
|
398 | }
|
399 | async _bailIfNeeded(testContexts, aggregatedResults, watcher) {
|
400 | if (
|
401 | this._globalConfig.bail !== 0 &&
|
402 | aggregatedResults.numFailedTests >= this._globalConfig.bail
|
403 | ) {
|
404 | if (watcher.isWatchMode()) {
|
405 | await watcher.setState({
|
406 | interrupted: true
|
407 | });
|
408 | return;
|
409 | }
|
410 | try {
|
411 | await this._dispatcher.onRunComplete(testContexts, aggregatedResults);
|
412 | } finally {
|
413 | const exitCode = this._globalConfig.testFailureExitCode;
|
414 | (0, _exit().default)(exitCode);
|
415 | }
|
416 | }
|
417 | }
|
418 | }
|
419 | const createAggregatedResults = numTotalTestSuites => {
|
420 | const result = (0, _testResult().makeEmptyAggregatedTestResult)();
|
421 | result.numTotalTestSuites = numTotalTestSuites;
|
422 | result.startTime = Date.now();
|
423 | result.success = false;
|
424 | return result;
|
425 | };
|
426 | const getEstimatedTime = (timings, workers) => {
|
427 | if (timings.length === 0) {
|
428 | return 0;
|
429 | }
|
430 | const max = Math.max(...timings);
|
431 | return timings.length <= workers
|
432 | ? max
|
433 | : Math.max(timings.reduce((sum, time) => sum + time) / workers, max);
|
434 | };
|
435 | const strToError = errString => {
|
436 | const {message, stack} = (0, _jestMessageUtil().separateMessageFromStack)(
|
437 | errString
|
438 | );
|
439 | if (stack.length > 0) {
|
440 | return {
|
441 | message,
|
442 | stack
|
443 | };
|
444 | }
|
445 | const error = new (_jestUtil().ErrorWithStack)(message, buildExecError);
|
446 | return {
|
447 | message,
|
448 | stack: error.stack || ''
|
449 | };
|
450 | };
|
451 | const buildExecError = err => {
|
452 | if (typeof err === 'string' || err == null) {
|
453 | return strToError(err || 'Error');
|
454 | }
|
455 | const anyErr = err;
|
456 | if (typeof anyErr.message === 'string') {
|
457 | if (typeof anyErr.stack === 'string' && anyErr.stack.length > 0) {
|
458 | return anyErr;
|
459 | }
|
460 | return strToError(anyErr.message);
|
461 | }
|
462 | return strToError(JSON.stringify(err));
|
463 | };
|