UNPKG

7.73 kBJavaScriptView Raw
1const path = require('path');
2const libCoverage = require('istanbul-lib-coverage');
3const libSourceMaps = require('istanbul-lib-source-maps');
4const libReport = require('istanbul-lib-report');
5const libReports = require('istanbul-reports');
6const util = require('./util');
7
8const BROWSER_PLACEHOLDER = '%browser%';
9
10function checkThresholds(thresholds, summary) {
11 const failedTypes = [];
12
13 Object.keys(thresholds).forEach((key) => {
14 const coverage = summary[key].pct;
15 if (coverage < thresholds[key]) {
16 failedTypes.push(key);
17 }
18 });
19
20 return failedTypes;
21}
22
23function CoverageIstanbulReporter(baseReporterDecorator, logger, config) {
24 baseReporterDecorator(this);
25
26 // Copied from https://github.com/angular/angular-cli/pull/9529/files
27 // Fixes https://github.com/mattlewis92/karma-coverage-istanbul-reporter/issues/44
28 const reporterName = 'coverage-istanbul';
29 const hasTrailingReporters =
30 config.reporters.slice(-1).pop() !== reporterName;
31 if (hasTrailingReporters) {
32 this.writeCommonMsg = () => {};
33 }
34
35 const log = logger.create('reporter.coverage-istanbul');
36 const browserCoverage = new WeakMap();
37 const coverageConfig = Object.assign({}, config.coverageIstanbulReporter);
38
39 function addCoverage(coverageMap, browser) {
40 const coverage = browserCoverage.get(browser);
41 browserCoverage.delete(browser);
42
43 if (!coverage) {
44 return;
45 }
46
47 Object.keys(coverage).forEach((filename) => {
48 const fileCoverage = coverage[filename];
49 if (fileCoverage.inputSourceMap && coverageConfig.fixWebpackSourcePaths) {
50 fileCoverage.inputSourceMap = util.fixWebpackSourcePaths(
51 fileCoverage.inputSourceMap,
52 config.webpack
53 );
54 }
55
56 if (
57 coverageConfig.skipFilesWithNoCoverage &&
58 Object.keys(fileCoverage.statementMap).length === 0 &&
59 Object.keys(fileCoverage.fnMap).length === 0 &&
60 Object.keys(fileCoverage.branchMap).length === 0
61 ) {
62 log.debug(`File [${filename}] ignored, nothing could be mapped`);
63 } else {
64 coverageMap.addFileCoverage(fileCoverage);
65 }
66 });
67 }
68
69 function logThresholdMessage(thresholds, message) {
70 if (thresholds.emitWarning) {
71 log.warn(message);
72 } else {
73 log.error(message);
74 }
75 }
76
77 async function createReport(browserOrBrowsers, results) {
78 const reportConfigOverride =
79 !coverageConfig.combineBrowserReports && coverageConfig.dir
80 ? {
81 dir: coverageConfig.dir.replace(
82 BROWSER_PLACEHOLDER,
83 browserOrBrowsers.name
84 ),
85 }
86 : {};
87
88 const reportConfig = {
89 instrumentation: Object.assign({}, coverageConfig.instrumentation),
90 verbose: coverageConfig.verbose === true,
91 reporting: Object.assign({}, coverageConfig, reportConfigOverride),
92 summarizer: coverageConfig.summarizer,
93 };
94 const reportTypes = reportConfig.reporting.reports;
95
96 const coverageMap = libCoverage.createCoverageMap();
97 const sourceMapStore = libSourceMaps.createSourceMapStore();
98
99 if (coverageConfig.combineBrowserReports) {
100 browserOrBrowsers.forEach((browser) => addCoverage(coverageMap, browser));
101 } else {
102 addCoverage(coverageMap, browserOrBrowsers);
103 }
104
105 const remappedCoverageMap = await sourceMapStore.transformCoverage(
106 coverageMap
107 );
108 const { sourceFinder } = sourceMapStore;
109
110 if (!coverageConfig.skipFilesWithNoCoverage) {
111 // On Windows, istanbul returns files with mixed forward/backslashes in them
112 const fixedFilePaths = {};
113 remappedCoverageMap.files().forEach((path) => {
114 fixedFilePaths[util.fixPathSeparators(path)] = true;
115 });
116 coverageMap.files().forEach((path) => {
117 if (!(util.fixPathSeparators(path) in fixedFilePaths)) {
118 // Re-add empty coverage record
119 remappedCoverageMap.addFileCoverage(path);
120 }
121 });
122 }
123
124 log.debug('Writing coverage reports:', reportTypes);
125 writeReports(reportConfig, remappedCoverageMap, sourceFinder);
126
127 const userThresholds = coverageConfig.thresholds;
128
129 const thresholds = {
130 emitWarning: false,
131 global: {
132 statements: 0,
133 lines: 0,
134 branches: 0,
135 functions: 0,
136 },
137 each: {
138 statements: 0,
139 lines: 0,
140 branches: 0,
141 functions: 0,
142 overrides: {},
143 },
144 };
145
146 if (userThresholds) {
147 if (userThresholds.global || userThresholds.each) {
148 Object.assign(thresholds.global, userThresholds.global);
149 Object.assign(thresholds.each, userThresholds.each);
150 if (userThresholds.emitWarning === true) {
151 thresholds.emitWarning = true;
152 }
153 } else {
154 Object.assign(thresholds.global, userThresholds);
155 }
156 }
157
158 let thresholdCheckFailed = false;
159
160 // Adapted from https://github.com/istanbuljs/nyc/blob/98ebdff573be91e1098bb7259776a9082a5c1ce1/index.js#L463-L478
161 const globalSummary = remappedCoverageMap.getCoverageSummary();
162 const failedGlobalTypes = checkThresholds(thresholds.global, globalSummary);
163 failedGlobalTypes.forEach((type) => {
164 thresholdCheckFailed = true;
165 logThresholdMessage(
166 thresholds,
167 `Coverage for ${type} (${globalSummary[type].pct}%) does not meet global threshold (${thresholds.global[type]}%)`
168 );
169 });
170
171 remappedCoverageMap.files().forEach((file) => {
172 const fileThresholds = Object.assign(
173 {},
174 thresholds.each,
175 util.overrideThresholds(
176 file,
177 thresholds.each.overrides,
178 config.basePath
179 )
180 );
181 delete fileThresholds.overrides;
182 const fileSummary = remappedCoverageMap.fileCoverageFor(file).toSummary()
183 .data;
184 const failedFileTypes = checkThresholds(fileThresholds, fileSummary);
185
186 failedFileTypes.forEach((type) => {
187 thresholdCheckFailed = true;
188 if (coverageConfig.fixWebpackSourcePaths) {
189 file = util.fixWebpackFilePath(file);
190 }
191
192 logThresholdMessage(
193 thresholds,
194 `Coverage for ${type} (${fileSummary[type].pct}%) in file ${file} does not meet per file threshold (${fileThresholds[type]}%)`
195 );
196 });
197 });
198
199 if (thresholdCheckFailed && results && !thresholds.emitWarning) {
200 results.exitCode = 1;
201 }
202 }
203
204 function writeReports(config, coverageMap, sourceFinder) {
205 const dir = path.resolve(config.reporting.dir);
206 const contextOptions = {
207 dir,
208 watermarks: config.reporting.watermarks || [],
209 sourceFinder,
210 coverageMap,
211 };
212 const context = libReport.createContext(contextOptions);
213
214 const tree = context.getTree(config.summarizer);
215 config.reporting.reports.forEach((name) => {
216 const reportConfig = (config.reporting['report-config'] || [])[name];
217 const report = libReports.create(name, reportConfig);
218 tree.visit(report, context);
219 });
220 }
221
222 this.onBrowserComplete = function (browser, result) {
223 if (result && result.coverage) {
224 browserCoverage.set(browser, result.coverage);
225 }
226 };
227
228 const baseReporterOnRunComplete = this.onRunComplete;
229 this.onRunComplete = async function (browsers, results) {
230 Reflect.apply(baseReporterOnRunComplete, this, arguments);
231
232 if (coverageConfig.combineBrowserReports) {
233 await createReport(browsers, results);
234 } else {
235 await Promise.all(
236 browsers.map((browser) => createReport(browser, results))
237 );
238 }
239 };
240}
241
242CoverageIstanbulReporter.$inject = [
243 'baseReporterDecorator',
244 'logger',
245 'config',
246];
247
248module.exports = {
249 'reporter:coverage-istanbul': ['type', CoverageIstanbulReporter],
250};