UNPKG

18.8 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', {
4 value: true
5});
6exports.default = void 0;
7
8function path() {
9 const data = _interopRequireWildcard(require('path'));
10
11 path = function () {
12 return data;
13 };
14
15 return data;
16}
17
18function _v8Coverage() {
19 const data = require('@bcoe/v8-coverage');
20
21 _v8Coverage = function () {
22 return data;
23 };
24
25 return data;
26}
27
28function _chalk() {
29 const data = _interopRequireDefault(require('chalk'));
30
31 _chalk = function () {
32 return data;
33 };
34
35 return data;
36}
37
38function _glob() {
39 const data = _interopRequireDefault(require('glob'));
40
41 _glob = function () {
42 return data;
43 };
44
45 return data;
46}
47
48function fs() {
49 const data = _interopRequireWildcard(require('graceful-fs'));
50
51 fs = function () {
52 return data;
53 };
54
55 return data;
56}
57
58function _istanbulLibCoverage() {
59 const data = _interopRequireDefault(require('istanbul-lib-coverage'));
60
61 _istanbulLibCoverage = function () {
62 return data;
63 };
64
65 return data;
66}
67
68function _istanbulLibReport() {
69 const data = _interopRequireDefault(require('istanbul-lib-report'));
70
71 _istanbulLibReport = function () {
72 return data;
73 };
74
75 return data;
76}
77
78function _istanbulLibSourceMaps() {
79 const data = _interopRequireDefault(require('istanbul-lib-source-maps'));
80
81 _istanbulLibSourceMaps = function () {
82 return data;
83 };
84
85 return data;
86}
87
88function _istanbulReports() {
89 const data = _interopRequireDefault(require('istanbul-reports'));
90
91 _istanbulReports = function () {
92 return data;
93 };
94
95 return data;
96}
97
98function _v8ToIstanbul() {
99 const data = _interopRequireDefault(require('v8-to-istanbul'));
100
101 _v8ToIstanbul = function () {
102 return data;
103 };
104
105 return data;
106}
107
108function _jestUtil() {
109 const data = require('jest-util');
110
111 _jestUtil = function () {
112 return data;
113 };
114
115 return data;
116}
117
118function _jestWorker() {
119 const data = require('jest-worker');
120
121 _jestWorker = function () {
122 return data;
123 };
124
125 return data;
126}
127
128var _BaseReporter = _interopRequireDefault(require('./BaseReporter'));
129
130var _getWatermarks = _interopRequireDefault(require('./getWatermarks'));
131
132function _interopRequireDefault(obj) {
133 return obj && obj.__esModule ? obj : {default: obj};
134}
135
136function _getRequireWildcardCache(nodeInterop) {
137 if (typeof WeakMap !== 'function') return null;
138 var cacheBabelInterop = new WeakMap();
139 var cacheNodeInterop = new WeakMap();
140 return (_getRequireWildcardCache = function (nodeInterop) {
141 return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
142 })(nodeInterop);
143}
144
145function _interopRequireWildcard(obj, nodeInterop) {
146 if (!nodeInterop && obj && obj.__esModule) {
147 return obj;
148 }
149 if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
150 return {default: obj};
151 }
152 var cache = _getRequireWildcardCache(nodeInterop);
153 if (cache && cache.has(obj)) {
154 return cache.get(obj);
155 }
156 var newObj = {};
157 var hasPropertyDescriptor =
158 Object.defineProperty && Object.getOwnPropertyDescriptor;
159 for (var key in obj) {
160 if (key !== 'default' && Object.prototype.hasOwnProperty.call(obj, key)) {
161 var desc = hasPropertyDescriptor
162 ? Object.getOwnPropertyDescriptor(obj, key)
163 : null;
164 if (desc && (desc.get || desc.set)) {
165 Object.defineProperty(newObj, key, desc);
166 } else {
167 newObj[key] = obj[key];
168 }
169 }
170 }
171 newObj.default = obj;
172 if (cache) {
173 cache.set(obj, newObj);
174 }
175 return newObj;
176}
177
178/**
179 * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
180 *
181 * This source code is licensed under the MIT license found in the
182 * LICENSE file in the root directory of this source tree.
183 */
184const FAIL_COLOR = _chalk().default.bold.red;
185
186const RUNNING_TEST_COLOR = _chalk().default.bold.dim;
187
188class CoverageReporter extends _BaseReporter.default {
189 _context;
190 _coverageMap;
191 _globalConfig;
192 _sourceMapStore;
193 _v8CoverageResults;
194 static filename = __filename;
195
196 constructor(globalConfig, context) {
197 super();
198 this._context = context;
199 this._coverageMap = _istanbulLibCoverage().default.createCoverageMap({});
200 this._globalConfig = globalConfig;
201 this._sourceMapStore =
202 _istanbulLibSourceMaps().default.createSourceMapStore();
203 this._v8CoverageResults = [];
204 }
205
206 onTestResult(_test, testResult) {
207 if (testResult.v8Coverage) {
208 this._v8CoverageResults.push(testResult.v8Coverage);
209
210 return;
211 }
212
213 if (testResult.coverage) {
214 this._coverageMap.merge(testResult.coverage);
215 }
216 }
217
218 async onRunComplete(testContexts, aggregatedResults) {
219 await this._addUntestedFiles(testContexts);
220 const {map, reportContext} = await this._getCoverageResult();
221
222 try {
223 const coverageReporters = this._globalConfig.coverageReporters || [];
224
225 if (!this._globalConfig.useStderr && coverageReporters.length < 1) {
226 coverageReporters.push('text-summary');
227 }
228
229 coverageReporters.forEach(reporter => {
230 let additionalOptions = {};
231
232 if (Array.isArray(reporter)) {
233 [reporter, additionalOptions] = reporter;
234 }
235
236 _istanbulReports()
237 .default.create(reporter, {
238 maxCols: process.stdout.columns || Infinity,
239 ...additionalOptions
240 })
241 .execute(reportContext);
242 });
243 aggregatedResults.coverageMap = map;
244 } catch (e) {
245 console.error(
246 _chalk().default.red(`
247 Failed to write coverage reports:
248 ERROR: ${e.toString()}
249 STACK: ${e.stack}
250 `)
251 );
252 }
253
254 this._checkThreshold(map);
255 }
256
257 async _addUntestedFiles(testContexts) {
258 const files = [];
259 testContexts.forEach(context => {
260 const config = context.config;
261
262 if (
263 this._globalConfig.collectCoverageFrom &&
264 this._globalConfig.collectCoverageFrom.length
265 ) {
266 context.hasteFS
267 .matchFilesWithGlob(
268 this._globalConfig.collectCoverageFrom,
269 config.rootDir
270 )
271 .forEach(filePath =>
272 files.push({
273 config,
274 path: filePath
275 })
276 );
277 }
278 });
279
280 if (!files.length) {
281 return;
282 }
283
284 if (_jestUtil().isInteractive) {
285 process.stderr.write(
286 RUNNING_TEST_COLOR('Running coverage on untested files...')
287 );
288 }
289
290 let worker;
291
292 if (this._globalConfig.maxWorkers <= 1) {
293 worker = require('./CoverageWorker');
294 } else {
295 worker = new (_jestWorker().Worker)(require.resolve('./CoverageWorker'), {
296 exposedMethods: ['worker'],
297 // @ts-expect-error: option does not exist on the node 12 types
298 forkOptions: {
299 serialization: 'json'
300 },
301 maxRetries: 2,
302 numWorkers: this._globalConfig.maxWorkers
303 });
304 }
305
306 const instrumentation = files.map(async fileObj => {
307 const filename = fileObj.path;
308 const config = fileObj.config;
309
310 const hasCoverageData = this._v8CoverageResults.some(v8Res =>
311 v8Res.some(innerRes => innerRes.result.url === filename)
312 );
313
314 if (
315 !hasCoverageData &&
316 !this._coverageMap.data[filename] &&
317 'worker' in worker
318 ) {
319 try {
320 const result = await worker.worker({
321 config,
322 context: {
323 changedFiles:
324 this._context.changedFiles &&
325 Array.from(this._context.changedFiles),
326 sourcesRelatedToTestsInChangedFiles:
327 this._context.sourcesRelatedToTestsInChangedFiles &&
328 Array.from(this._context.sourcesRelatedToTestsInChangedFiles)
329 },
330 globalConfig: this._globalConfig,
331 path: filename
332 });
333
334 if (result) {
335 if (result.kind === 'V8Coverage') {
336 this._v8CoverageResults.push([
337 {
338 codeTransformResult: undefined,
339 result: result.result
340 }
341 ]);
342 } else {
343 this._coverageMap.addFileCoverage(result.coverage);
344 }
345 }
346 } catch (error) {
347 console.error(
348 _chalk().default.red(
349 [
350 `Failed to collect coverage from ${filename}`,
351 `ERROR: ${error.message}`,
352 `STACK: ${error.stack}`
353 ].join('\n')
354 )
355 );
356 }
357 }
358 });
359
360 try {
361 await Promise.all(instrumentation);
362 } catch {
363 // Do nothing; errors were reported earlier to the console.
364 }
365
366 if (_jestUtil().isInteractive) {
367 (0, _jestUtil().clearLine)(process.stderr);
368 }
369
370 if (worker && 'end' in worker && typeof worker.end === 'function') {
371 await worker.end();
372 }
373 }
374
375 _checkThreshold(map) {
376 const {coverageThreshold} = this._globalConfig;
377
378 if (coverageThreshold) {
379 function check(name, thresholds, actuals) {
380 return ['statements', 'branches', 'lines', 'functions'].reduce(
381 (errors, key) => {
382 const actual = actuals[key].pct;
383 const actualUncovered = actuals[key].total - actuals[key].covered;
384 const threshold = thresholds[key];
385
386 if (threshold !== undefined) {
387 if (threshold < 0) {
388 if (threshold * -1 < actualUncovered) {
389 errors.push(
390 `Jest: Uncovered count for ${key} (${actualUncovered}) ` +
391 `exceeds ${name} threshold (${-1 * threshold})`
392 );
393 }
394 } else if (actual < threshold) {
395 errors.push(
396 `Jest: "${name}" coverage threshold for ${key} (${threshold}%) not met: ${actual}%`
397 );
398 }
399 }
400
401 return errors;
402 },
403 []
404 );
405 }
406
407 const THRESHOLD_GROUP_TYPES = {
408 GLOB: 'glob',
409 GLOBAL: 'global',
410 PATH: 'path'
411 };
412 const coveredFiles = map.files();
413 const thresholdGroups = Object.keys(coverageThreshold);
414 const groupTypeByThresholdGroup = {};
415 const filesByGlob = {};
416 const coveredFilesSortedIntoThresholdGroup = coveredFiles.reduce(
417 (files, file) => {
418 const pathOrGlobMatches = thresholdGroups.reduce(
419 (agg, thresholdGroup) => {
420 // Preserve trailing slash, but not required if root dir
421 // See https://github.com/facebook/jest/issues/12703
422 const resolvedThresholdGroup = path().resolve(thresholdGroup);
423 const suffix =
424 (thresholdGroup.endsWith(path().sep) ||
425 (process.platform === 'win32' &&
426 thresholdGroup.endsWith('/'))) &&
427 !resolvedThresholdGroup.endsWith(path().sep)
428 ? path().sep
429 : '';
430 const absoluteThresholdGroup = `${resolvedThresholdGroup}${suffix}`; // The threshold group might be a path:
431
432 if (file.indexOf(absoluteThresholdGroup) === 0) {
433 groupTypeByThresholdGroup[thresholdGroup] =
434 THRESHOLD_GROUP_TYPES.PATH;
435 return agg.concat([[file, thresholdGroup]]);
436 } // If the threshold group is not a path it might be a glob:
437 // Note: glob.sync is slow. By memoizing the files matching each glob
438 // (rather than recalculating it for each covered file) we save a tonne
439 // of execution time.
440
441 if (filesByGlob[absoluteThresholdGroup] === undefined) {
442 filesByGlob[absoluteThresholdGroup] = _glob()
443 .default.sync(absoluteThresholdGroup)
444 .map(filePath => path().resolve(filePath));
445 }
446
447 if (filesByGlob[absoluteThresholdGroup].indexOf(file) > -1) {
448 groupTypeByThresholdGroup[thresholdGroup] =
449 THRESHOLD_GROUP_TYPES.GLOB;
450 return agg.concat([[file, thresholdGroup]]);
451 }
452
453 return agg;
454 },
455 []
456 );
457
458 if (pathOrGlobMatches.length > 0) {
459 return files.concat(pathOrGlobMatches);
460 } // Neither a glob or a path? Toss it in global if there's a global threshold:
461
462 if (thresholdGroups.indexOf(THRESHOLD_GROUP_TYPES.GLOBAL) > -1) {
463 groupTypeByThresholdGroup[THRESHOLD_GROUP_TYPES.GLOBAL] =
464 THRESHOLD_GROUP_TYPES.GLOBAL;
465 return files.concat([[file, THRESHOLD_GROUP_TYPES.GLOBAL]]);
466 } // A covered file that doesn't have a threshold:
467
468 return files.concat([[file, undefined]]);
469 },
470 []
471 );
472
473 const getFilesInThresholdGroup = thresholdGroup =>
474 coveredFilesSortedIntoThresholdGroup
475 .filter(fileAndGroup => fileAndGroup[1] === thresholdGroup)
476 .map(fileAndGroup => fileAndGroup[0]);
477
478 function combineCoverage(filePaths) {
479 return filePaths
480 .map(filePath => map.fileCoverageFor(filePath))
481 .reduce((combinedCoverage, nextFileCoverage) => {
482 if (combinedCoverage === undefined || combinedCoverage === null) {
483 return nextFileCoverage.toSummary();
484 }
485
486 return combinedCoverage.merge(nextFileCoverage.toSummary());
487 }, undefined);
488 }
489
490 let errors = [];
491 thresholdGroups.forEach(thresholdGroup => {
492 switch (groupTypeByThresholdGroup[thresholdGroup]) {
493 case THRESHOLD_GROUP_TYPES.GLOBAL: {
494 const coverage = combineCoverage(
495 getFilesInThresholdGroup(THRESHOLD_GROUP_TYPES.GLOBAL)
496 );
497
498 if (coverage) {
499 errors = errors.concat(
500 check(
501 thresholdGroup,
502 coverageThreshold[thresholdGroup],
503 coverage
504 )
505 );
506 }
507
508 break;
509 }
510
511 case THRESHOLD_GROUP_TYPES.PATH: {
512 const coverage = combineCoverage(
513 getFilesInThresholdGroup(thresholdGroup)
514 );
515
516 if (coverage) {
517 errors = errors.concat(
518 check(
519 thresholdGroup,
520 coverageThreshold[thresholdGroup],
521 coverage
522 )
523 );
524 }
525
526 break;
527 }
528
529 case THRESHOLD_GROUP_TYPES.GLOB:
530 getFilesInThresholdGroup(thresholdGroup).forEach(
531 fileMatchingGlob => {
532 errors = errors.concat(
533 check(
534 fileMatchingGlob,
535 coverageThreshold[thresholdGroup],
536 map.fileCoverageFor(fileMatchingGlob).toSummary()
537 )
538 );
539 }
540 );
541 break;
542
543 default:
544 // If the file specified by path is not found, error is returned.
545 if (thresholdGroup !== THRESHOLD_GROUP_TYPES.GLOBAL) {
546 errors = errors.concat(
547 `Jest: Coverage data for ${thresholdGroup} was not found.`
548 );
549 }
550
551 // Sometimes all files in the coverage data are matched by
552 // PATH and GLOB threshold groups in which case, don't error when
553 // the global threshold group doesn't match any files.
554 }
555 });
556 errors = errors.filter(
557 err => err !== undefined && err !== null && err.length > 0
558 );
559
560 if (errors.length > 0) {
561 this.log(`${FAIL_COLOR(errors.join('\n'))}`);
562
563 this._setError(new Error(errors.join('\n')));
564 }
565 }
566 }
567
568 async _getCoverageResult() {
569 if (this._globalConfig.coverageProvider === 'v8') {
570 const mergedCoverages = (0, _v8Coverage().mergeProcessCovs)(
571 this._v8CoverageResults.map(cov => ({
572 result: cov.map(r => r.result)
573 }))
574 );
575 const fileTransforms = new Map();
576
577 this._v8CoverageResults.forEach(res =>
578 res.forEach(r => {
579 if (r.codeTransformResult && !fileTransforms.has(r.result.url)) {
580 fileTransforms.set(r.result.url, r.codeTransformResult);
581 }
582 })
583 );
584
585 const transformedCoverage = await Promise.all(
586 mergedCoverages.result.map(async res => {
587 var _fileTransform$wrappe;
588
589 const fileTransform = fileTransforms.get(res.url);
590 let sourcemapContent = undefined;
591
592 if (
593 fileTransform !== null &&
594 fileTransform !== void 0 &&
595 fileTransform.sourceMapPath &&
596 fs().existsSync(fileTransform.sourceMapPath)
597 ) {
598 sourcemapContent = JSON.parse(
599 fs().readFileSync(fileTransform.sourceMapPath, 'utf8')
600 );
601 }
602
603 const converter = (0, _v8ToIstanbul().default)(
604 res.url,
605 (_fileTransform$wrappe =
606 fileTransform === null || fileTransform === void 0
607 ? void 0
608 : fileTransform.wrapperLength) !== null &&
609 _fileTransform$wrappe !== void 0
610 ? _fileTransform$wrappe
611 : 0,
612 fileTransform && sourcemapContent
613 ? {
614 originalSource: fileTransform.originalCode,
615 source: fileTransform.code,
616 sourceMap: {
617 sourcemap: {
618 file: res.url,
619 ...sourcemapContent
620 }
621 }
622 }
623 : {
624 source: fs().readFileSync(res.url, 'utf8')
625 }
626 );
627 await converter.load();
628 converter.applyCoverage(res.functions);
629 const istanbulData = converter.toIstanbul();
630 return istanbulData;
631 })
632 );
633
634 const map = _istanbulLibCoverage().default.createCoverageMap({});
635
636 transformedCoverage.forEach(res => map.merge(res));
637
638 const reportContext = _istanbulLibReport().default.createContext({
639 coverageMap: map,
640 dir: this._globalConfig.coverageDirectory,
641 watermarks: (0, _getWatermarks.default)(this._globalConfig)
642 });
643
644 return {
645 map,
646 reportContext
647 };
648 }
649
650 const map = await this._sourceMapStore.transformCoverage(this._coverageMap);
651
652 const reportContext = _istanbulLibReport().default.createContext({
653 coverageMap: map,
654 dir: this._globalConfig.coverageDirectory,
655 sourceFinder: this._sourceMapStore.sourceFinder,
656 watermarks: (0, _getWatermarks.default)(this._globalConfig)
657 });
658
659 return {
660 map,
661 reportContext
662 };
663 }
664}
665
666exports.default = CoverageReporter;