UNPKG

14.5 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', {
4 value: true
5});
6exports.createTestScheduler = createTestScheduler;
7function _chalk() {
8 const data = _interopRequireDefault(require('chalk'));
9 _chalk = function () {
10 return data;
11 };
12 return data;
13}
14function _ciInfo() {
15 const data = require('ci-info');
16 _ciInfo = function () {
17 return data;
18 };
19 return data;
20}
21function _exit() {
22 const data = _interopRequireDefault(require('exit'));
23 _exit = function () {
24 return data;
25 };
26 return data;
27}
28function _reporters() {
29 const data = require('@jest/reporters');
30 _reporters = function () {
31 return data;
32 };
33 return data;
34}
35function _testResult() {
36 const data = require('@jest/test-result');
37 _testResult = function () {
38 return data;
39 };
40 return data;
41}
42function _transform() {
43 const data = require('@jest/transform');
44 _transform = function () {
45 return data;
46 };
47 return data;
48}
49function _jestMessageUtil() {
50 const data = require('jest-message-util');
51 _jestMessageUtil = function () {
52 return data;
53 };
54 return data;
55}
56function _jestSnapshot() {
57 const data = require('jest-snapshot');
58 _jestSnapshot = function () {
59 return data;
60 };
61 return data;
62}
63function _jestUtil() {
64 const data = require('jest-util');
65 _jestUtil = function () {
66 return data;
67 };
68 return data;
69}
70var _ReporterDispatcher = _interopRequireDefault(
71 require('./ReporterDispatcher')
72);
73var _testSchedulerHelper = require('./testSchedulerHelper');
74function _interopRequireDefault(obj) {
75 return obj && obj.__esModule ? obj : {default: obj};
76}
77/**
78 * Copyright (c) Meta Platforms, Inc. and affiliates.
79 *
80 * This source code is licensed under the MIT license found in the
81 * LICENSE file in the root directory of this source tree.
82 */
83
84async function createTestScheduler(globalConfig, context) {
85 const scheduler = new TestScheduler(globalConfig, context);
86 await scheduler._setupReporters();
87 return scheduler;
88}
89class 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 // Throws when the context is leaked after executing a test.
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 // If there is only one runner, don't partition the tests.
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}
419const 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};
426const 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};
435const 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};
451const 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};