UNPKG

18.4 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
178function _defineProperty(obj, key, value) {
179 if (key in obj) {
180 Object.defineProperty(obj, key, {
181 value: value,
182 enumerable: true,
183 configurable: true,
184 writable: true
185 });
186 } else {
187 obj[key] = value;
188 }
189 return obj;
190}
191
192const FAIL_COLOR = _chalk().default.bold.red;
193
194const RUNNING_TEST_COLOR = _chalk().default.bold.dim;
195
196class CoverageReporter extends _BaseReporter.default {
197 constructor(globalConfig, options) {
198 super();
199
200 _defineProperty(this, '_coverageMap', void 0);
201
202 _defineProperty(this, '_globalConfig', void 0);
203
204 _defineProperty(this, '_sourceMapStore', void 0);
205
206 _defineProperty(this, '_options', void 0);
207
208 _defineProperty(this, '_v8CoverageResults', void 0);
209
210 this._coverageMap = _istanbulLibCoverage().default.createCoverageMap({});
211 this._globalConfig = globalConfig;
212 this._sourceMapStore =
213 _istanbulLibSourceMaps().default.createSourceMapStore();
214 this._v8CoverageResults = [];
215 this._options = options || {};
216 }
217
218 onTestResult(_test, testResult) {
219 if (testResult.v8Coverage) {
220 this._v8CoverageResults.push(testResult.v8Coverage);
221
222 return;
223 }
224
225 if (testResult.coverage) {
226 this._coverageMap.merge(testResult.coverage);
227 }
228 }
229
230 async onRunComplete(contexts, aggregatedResults) {
231 await this._addUntestedFiles(contexts);
232 const {map, reportContext} = await this._getCoverageResult();
233
234 try {
235 const coverageReporters = this._globalConfig.coverageReporters || [];
236
237 if (!this._globalConfig.useStderr && coverageReporters.length < 1) {
238 coverageReporters.push('text-summary');
239 }
240
241 coverageReporters.forEach(reporter => {
242 let additionalOptions = {};
243
244 if (Array.isArray(reporter)) {
245 [reporter, additionalOptions] = reporter;
246 }
247
248 _istanbulReports()
249 .default.create(reporter, {
250 maxCols: process.stdout.columns || Infinity,
251 ...additionalOptions
252 }) // @ts-expect-error
253 .execute(reportContext);
254 });
255 aggregatedResults.coverageMap = map;
256 } catch (e) {
257 console.error(
258 _chalk().default.red(`
259 Failed to write coverage reports:
260 ERROR: ${e.toString()}
261 STACK: ${e.stack}
262 `)
263 );
264 }
265
266 this._checkThreshold(map);
267 }
268
269 async _addUntestedFiles(contexts) {
270 const files = [];
271 contexts.forEach(context => {
272 const config = context.config;
273
274 if (
275 this._globalConfig.collectCoverageFrom &&
276 this._globalConfig.collectCoverageFrom.length
277 ) {
278 context.hasteFS
279 .matchFilesWithGlob(
280 this._globalConfig.collectCoverageFrom,
281 config.rootDir
282 )
283 .forEach(filePath =>
284 files.push({
285 config,
286 path: filePath
287 })
288 );
289 }
290 });
291
292 if (!files.length) {
293 return;
294 }
295
296 if (_jestUtil().isInteractive) {
297 process.stderr.write(
298 RUNNING_TEST_COLOR('Running coverage on untested files...')
299 );
300 }
301
302 let worker;
303
304 if (this._globalConfig.maxWorkers <= 1) {
305 worker = require('./CoverageWorker');
306 } else {
307 worker = new (_jestWorker().Worker)(require.resolve('./CoverageWorker'), {
308 exposedMethods: ['worker'],
309 maxRetries: 2,
310 numWorkers: this._globalConfig.maxWorkers
311 });
312 }
313
314 const instrumentation = files.map(async fileObj => {
315 const filename = fileObj.path;
316 const config = fileObj.config;
317
318 const hasCoverageData = this._v8CoverageResults.some(v8Res =>
319 v8Res.some(innerRes => innerRes.result.url === filename)
320 );
321
322 if (
323 !hasCoverageData &&
324 !this._coverageMap.data[filename] &&
325 'worker' in worker
326 ) {
327 try {
328 const result = await worker.worker({
329 config,
330 globalConfig: this._globalConfig,
331 options: {
332 ...this._options,
333 changedFiles:
334 this._options.changedFiles &&
335 Array.from(this._options.changedFiles),
336 sourcesRelatedToTestsInChangedFiles:
337 this._options.sourcesRelatedToTestsInChangedFiles &&
338 Array.from(this._options.sourcesRelatedToTestsInChangedFiles)
339 },
340 path: filename
341 });
342
343 if (result) {
344 if (result.kind === 'V8Coverage') {
345 this._v8CoverageResults.push([
346 {
347 codeTransformResult: undefined,
348 result: result.result
349 }
350 ]);
351 } else {
352 this._coverageMap.addFileCoverage(result.coverage);
353 }
354 }
355 } catch (error) {
356 console.error(
357 _chalk().default.red(
358 [
359 `Failed to collect coverage from ${filename}`,
360 `ERROR: ${error.message}`,
361 `STACK: ${error.stack}`
362 ].join('\n')
363 )
364 );
365 }
366 }
367 });
368
369 try {
370 await Promise.all(instrumentation);
371 } catch {
372 // Do nothing; errors were reported earlier to the console.
373 }
374
375 if (_jestUtil().isInteractive) {
376 (0, _jestUtil().clearLine)(process.stderr);
377 }
378
379 if (worker && 'end' in worker && typeof worker.end === 'function') {
380 await worker.end();
381 }
382 }
383
384 _checkThreshold(map) {
385 const {coverageThreshold} = this._globalConfig;
386
387 if (coverageThreshold) {
388 function check(name, thresholds, actuals) {
389 return ['statements', 'branches', 'lines', 'functions'].reduce(
390 (errors, key) => {
391 const actual = actuals[key].pct;
392 const actualUncovered = actuals[key].total - actuals[key].covered;
393 const threshold = thresholds[key];
394
395 if (threshold !== undefined) {
396 if (threshold < 0) {
397 if (threshold * -1 < actualUncovered) {
398 errors.push(
399 `Jest: Uncovered count for ${key} (${actualUncovered}) ` +
400 `exceeds ${name} threshold (${-1 * threshold})`
401 );
402 }
403 } else if (actual < threshold) {
404 errors.push(
405 `Jest: "${name}" coverage threshold for ${key} (${threshold}%) not met: ${actual}%`
406 );
407 }
408 }
409
410 return errors;
411 },
412 []
413 );
414 }
415
416 const THRESHOLD_GROUP_TYPES = {
417 GLOB: 'glob',
418 GLOBAL: 'global',
419 PATH: 'path'
420 };
421 const coveredFiles = map.files();
422 const thresholdGroups = Object.keys(coverageThreshold);
423 const groupTypeByThresholdGroup = {};
424 const filesByGlob = {};
425 const coveredFilesSortedIntoThresholdGroup = coveredFiles.reduce(
426 (files, file) => {
427 const pathOrGlobMatches = thresholdGroups.reduce(
428 (agg, thresholdGroup) => {
429 const absoluteThresholdGroup = path().resolve(thresholdGroup); // The threshold group might be a path:
430
431 if (file.indexOf(absoluteThresholdGroup) === 0) {
432 groupTypeByThresholdGroup[thresholdGroup] =
433 THRESHOLD_GROUP_TYPES.PATH;
434 return agg.concat([[file, thresholdGroup]]);
435 } // If the threshold group is not a path it might be a glob:
436 // Note: glob.sync is slow. By memoizing the files matching each glob
437 // (rather than recalculating it for each covered file) we save a tonne
438 // of execution time.
439
440 if (filesByGlob[absoluteThresholdGroup] === undefined) {
441 filesByGlob[absoluteThresholdGroup] = _glob()
442 .default.sync(absoluteThresholdGroup)
443 .map(filePath => path().resolve(filePath));
444 }
445
446 if (filesByGlob[absoluteThresholdGroup].indexOf(file) > -1) {
447 groupTypeByThresholdGroup[thresholdGroup] =
448 THRESHOLD_GROUP_TYPES.GLOB;
449 return agg.concat([[file, thresholdGroup]]);
450 }
451
452 return agg;
453 },
454 []
455 );
456
457 if (pathOrGlobMatches.length > 0) {
458 return files.concat(pathOrGlobMatches);
459 } // Neither a glob or a path? Toss it in global if there's a global threshold:
460
461 if (thresholdGroups.indexOf(THRESHOLD_GROUP_TYPES.GLOBAL) > -1) {
462 groupTypeByThresholdGroup[THRESHOLD_GROUP_TYPES.GLOBAL] =
463 THRESHOLD_GROUP_TYPES.GLOBAL;
464 return files.concat([[file, THRESHOLD_GROUP_TYPES.GLOBAL]]);
465 } // A covered file that doesn't have a threshold:
466
467 return files.concat([[file, undefined]]);
468 },
469 []
470 );
471
472 const getFilesInThresholdGroup = thresholdGroup =>
473 coveredFilesSortedIntoThresholdGroup
474 .filter(fileAndGroup => fileAndGroup[1] === thresholdGroup)
475 .map(fileAndGroup => fileAndGroup[0]);
476
477 function combineCoverage(filePaths) {
478 return filePaths
479 .map(filePath => map.fileCoverageFor(filePath))
480 .reduce((combinedCoverage, nextFileCoverage) => {
481 if (combinedCoverage === undefined || combinedCoverage === null) {
482 return nextFileCoverage.toSummary();
483 }
484
485 return combinedCoverage.merge(nextFileCoverage.toSummary());
486 }, undefined);
487 }
488
489 let errors = [];
490 thresholdGroups.forEach(thresholdGroup => {
491 switch (groupTypeByThresholdGroup[thresholdGroup]) {
492 case THRESHOLD_GROUP_TYPES.GLOBAL: {
493 const coverage = combineCoverage(
494 getFilesInThresholdGroup(THRESHOLD_GROUP_TYPES.GLOBAL)
495 );
496
497 if (coverage) {
498 errors = errors.concat(
499 check(
500 thresholdGroup,
501 coverageThreshold[thresholdGroup],
502 coverage
503 )
504 );
505 }
506
507 break;
508 }
509
510 case THRESHOLD_GROUP_TYPES.PATH: {
511 const coverage = combineCoverage(
512 getFilesInThresholdGroup(thresholdGroup)
513 );
514
515 if (coverage) {
516 errors = errors.concat(
517 check(
518 thresholdGroup,
519 coverageThreshold[thresholdGroup],
520 coverage
521 )
522 );
523 }
524
525 break;
526 }
527
528 case THRESHOLD_GROUP_TYPES.GLOB:
529 getFilesInThresholdGroup(thresholdGroup).forEach(
530 fileMatchingGlob => {
531 errors = errors.concat(
532 check(
533 fileMatchingGlob,
534 coverageThreshold[thresholdGroup],
535 map.fileCoverageFor(fileMatchingGlob).toSummary()
536 )
537 );
538 }
539 );
540 break;
541
542 default:
543 // If the file specified by path is not found, error is returned.
544 if (thresholdGroup !== THRESHOLD_GROUP_TYPES.GLOBAL) {
545 errors = errors.concat(
546 `Jest: Coverage data for ${thresholdGroup} was not found.`
547 );
548 }
549
550 // Sometimes all files in the coverage data are matched by
551 // PATH and GLOB threshold groups in which case, don't error when
552 // the global threshold group doesn't match any files.
553 }
554 });
555 errors = errors.filter(
556 err => err !== undefined && err !== null && err.length > 0
557 );
558
559 if (errors.length > 0) {
560 this.log(`${FAIL_COLOR(errors.join('\n'))}`);
561
562 this._setError(new Error(errors.join('\n')));
563 }
564 }
565 }
566
567 async _getCoverageResult() {
568 if (this._globalConfig.coverageProvider === 'v8') {
569 const mergedCoverages = (0, _v8Coverage().mergeProcessCovs)(
570 this._v8CoverageResults.map(cov => ({
571 result: cov.map(r => r.result)
572 }))
573 );
574 const fileTransforms = new Map();
575
576 this._v8CoverageResults.forEach(res =>
577 res.forEach(r => {
578 if (r.codeTransformResult && !fileTransforms.has(r.result.url)) {
579 fileTransforms.set(r.result.url, r.codeTransformResult);
580 }
581 })
582 );
583
584 const transformedCoverage = await Promise.all(
585 mergedCoverages.result.map(async res => {
586 var _fileTransform$wrappe;
587
588 const fileTransform = fileTransforms.get(res.url);
589 let sourcemapContent = undefined;
590
591 if (
592 fileTransform !== null &&
593 fileTransform !== void 0 &&
594 fileTransform.sourceMapPath &&
595 fs().existsSync(fileTransform.sourceMapPath)
596 ) {
597 sourcemapContent = JSON.parse(
598 fs().readFileSync(fileTransform.sourceMapPath, 'utf8')
599 );
600 }
601
602 const converter = (0, _v8ToIstanbul().default)(
603 res.url,
604 (_fileTransform$wrappe =
605 fileTransform === null || fileTransform === void 0
606 ? void 0
607 : fileTransform.wrapperLength) !== null &&
608 _fileTransform$wrappe !== void 0
609 ? _fileTransform$wrappe
610 : 0,
611 fileTransform && sourcemapContent
612 ? {
613 originalSource: fileTransform.originalCode,
614 source: fileTransform.code,
615 sourceMap: {
616 sourcemap: {
617 file: res.url,
618 ...sourcemapContent
619 }
620 }
621 }
622 : {
623 source: fs().readFileSync(res.url, 'utf8')
624 }
625 );
626 await converter.load();
627 converter.applyCoverage(res.functions);
628 return converter.toIstanbul();
629 })
630 );
631
632 const map = _istanbulLibCoverage().default.createCoverageMap({});
633
634 transformedCoverage.forEach(res => map.merge(res));
635
636 const reportContext = _istanbulLibReport().default.createContext({
637 coverageMap: map,
638 dir: this._globalConfig.coverageDirectory,
639 watermarks: (0, _getWatermarks.default)(this._globalConfig)
640 });
641
642 return {
643 map,
644 reportContext
645 };
646 }
647
648 const map = await this._sourceMapStore.transformCoverage(this._coverageMap);
649
650 const reportContext = _istanbulLibReport().default.createContext({
651 coverageMap: map,
652 dir: this._globalConfig.coverageDirectory,
653 sourceFinder: this._sourceMapStore.sourceFinder,
654 watermarks: (0, _getWatermarks.default)(this._globalConfig)
655 });
656
657 return {
658 map,
659 reportContext
660 };
661 }
662}
663
664exports.default = CoverageReporter;
665
666_defineProperty(CoverageReporter, 'filename', __filename);