UNPKG

5.19 kBJavaScriptView Raw
1const Exclude = require('test-exclude')
2const furi = require('furi')
3const libCoverage = require('istanbul-lib-coverage')
4const libReport = require('istanbul-lib-report')
5const reports = require('istanbul-reports')
6const { readdirSync, readFileSync } = require('fs')
7const { isAbsolute, resolve } = require('path')
8// TODO: switch back to @c88/v8-coverage once patch is landed.
9const { mergeProcessCovs } = require('@bcoe/v8-coverage')
10const v8toIstanbul = require('v8-to-istanbul')
11const isCjsEsmBridgeCov = require('./is-cjs-esm-bridge')
12
13class Report {
14 constructor ({
15 exclude,
16 include,
17 reporter,
18 reportsDirectory,
19 tempDirectory,
20 watermarks,
21 omitRelative,
22 wrapperLength,
23 resolve: resolvePaths
24 }) {
25 this.reporter = reporter
26 this.reportsDirectory = reportsDirectory
27 this.tempDirectory = tempDirectory
28 this.watermarks = watermarks
29 this.resolve = resolvePaths
30 this.exclude = Exclude({
31 exclude: exclude,
32 include: include
33 })
34 this.omitRelative = omitRelative
35 this.wrapperLength = wrapperLength
36 }
37 async run () {
38 const map = await this.getCoverageMapFromAllCoverageFiles()
39 var context = libReport.createContext({
40 dir: this.reportsDirectory,
41 watermarks: this.watermarks
42 })
43
44 const tree = libReport.summarizers.pkg(map)
45
46 this.reporter.forEach(function (_reporter) {
47 tree.visit(reports.create(_reporter), context)
48 })
49 }
50
51 async getCoverageMapFromAllCoverageFiles () {
52 // the merge process can be very expensive, and it's often the case that
53 // check-coverage is called immediately after a report. We memoize the
54 // result from getCoverageMapFromAllCoverageFiles() to address this
55 // use-case.
56 if (this._allCoverageFiles) return this._allCoverageFiles
57
58 const v8ProcessCov = this._getMergedProcessCov()
59 const map = libCoverage.createCoverageMap({})
60 const resultCountPerPath = new Map()
61 const possibleCjsEsmBridges = new Map()
62
63 for (const v8ScriptCov of v8ProcessCov.result) {
64 try {
65 const path = resolve(this.resolve, v8ScriptCov.url)
66 const converter = v8toIstanbul(path, this.wrapperLength)
67 await converter.load()
68
69 if (resultCountPerPath.has(path)) {
70 resultCountPerPath.set(path, resultCountPerPath.get(path) + 1)
71 } else {
72 resultCountPerPath.set(path, 0)
73 }
74
75 if (isCjsEsmBridgeCov(v8ScriptCov)) {
76 possibleCjsEsmBridges.set(converter, {
77 path,
78 functions: v8ScriptCov.functions
79 })
80 } else {
81 converter.applyCoverage(v8ScriptCov.functions)
82 map.merge(converter.toIstanbul())
83 }
84 } catch (err) {
85 console.warn(`file: ${v8ScriptCov.url} error: ${err.stack}`)
86 }
87 }
88
89 for (const [converter, { path, functions }] of possibleCjsEsmBridges) {
90 if (resultCountPerPath.get(path) <= 1) {
91 converter.applyCoverage(functions)
92 map.merge(converter.toIstanbul())
93 }
94 }
95
96 this._allCoverageFiles = map
97 return this._allCoverageFiles
98 }
99
100 /**
101 * Returns the merged V8 process coverage.
102 *
103 * The result is computed from the individual process coverages generated
104 * by Node. It represents the sum of their counts.
105 *
106 * @return {ProcessCov} Merged V8 process coverage.
107 * @private
108 */
109 _getMergedProcessCov () {
110 const v8ProcessCovs = []
111 for (const v8ProcessCov of this._loadReports()) {
112 v8ProcessCovs.push(this._normalizeProcessCov(v8ProcessCov))
113 }
114 return mergeProcessCovs(v8ProcessCovs)
115 }
116
117 /**
118 * Returns the list of V8 process coverages generated by Node.
119 *
120 * @return {ProcessCov[]} Process coverages generated by Node.
121 * @private
122 */
123 _loadReports () {
124 const files = readdirSync(this.tempDirectory)
125
126 return files.map((f) => {
127 try {
128 return JSON.parse(readFileSync(
129 resolve(this.tempDirectory, f),
130 'utf8'
131 ))
132 } catch (err) {
133 console.warn(`${err.stack}`)
134 }
135 })
136 }
137
138 /**
139 * Normalizes a process coverage.
140 *
141 * This function replaces file URLs (`url` property) by their corresponding
142 * system-dependent path and applies the current inclusion rules to filter out
143 * the excluded script coverages.
144 *
145 * The result is a copy of the input, with script coverages filtered based
146 * on their `url` and the current inclusion rules.
147 * There is no deep cloning.
148 *
149 * @param v8ProcessCov V8 process coverage to normalize.
150 * @return {v8ProcessCov} Normalized V8 process coverage.
151 * @private
152 */
153 _normalizeProcessCov (v8ProcessCov) {
154 const result = []
155 for (const v8ScriptCov of v8ProcessCov.result) {
156 if (/^file:\/\//.test(v8ScriptCov.url)) {
157 try {
158 v8ScriptCov.url = furi.toSysPath(v8ScriptCov.url)
159 } catch (err) {
160 console.warn(err)
161 continue
162 }
163 }
164 if (this.exclude.shouldInstrument(v8ScriptCov.url) &&
165 (!this.omitRelative || isAbsolute(v8ScriptCov.url))) {
166 result.push(v8ScriptCov)
167 }
168 }
169 return { result }
170 }
171}
172
173module.exports = function (opts) {
174 return new Report(opts)
175}