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