monocart-coverage-reports 2.0.8 → 2.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -18
- package/lib/converter/branches.js +23 -13
- package/lib/converter/converter.js +97 -145
- package/lib/converter/info-branch.js +60 -15
- package/lib/converter/info-function.js +30 -7
- package/lib/packages/monocart-coverage-v8.js +1 -1
- package/lib/packages/monocart-coverage-vendor.js +15 -15
- package/lib/utils/util.js +13 -0
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/monocart-coverage-reports)
|
|
4
4
|
[](https://www.npmjs.com/package/monocart-coverage-reports)
|
|
5
5
|

|
|
6
|
+

|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
> Code coverage tool to generate native [V8](https://v8.dev/blog/javascript-code-coverage) reports or [Istanbul](https://istanbul.js.org/) reports.
|
|
8
10
|
|
|
@@ -10,6 +12,7 @@
|
|
|
10
12
|
* [Default Options](#default-options)
|
|
11
13
|
* [Available Reports](#available-reports)
|
|
12
14
|
* [Multiprocessing Support](#multiprocessing-support)
|
|
15
|
+
* [onEnd Hook](#onend-hook)
|
|
13
16
|
* [Compare Reports](#compare-reports)
|
|
14
17
|
* [Compare Workflows](#compare-workflows)
|
|
15
18
|
* [Collecting Istanbul Coverage Data](#collecting-istanbul-coverage-data)
|
|
@@ -21,6 +24,7 @@
|
|
|
21
24
|
- [How Monocart Works](#how-monocart-works)
|
|
22
25
|
* [Ignoring Uncovered Lines](#ignoring-uncovered-lines)
|
|
23
26
|
* [Chromium Coverage API](#chromium-coverage-api)
|
|
27
|
+
* [V8 Coverage Data Format](#v8-coverage-data-format)
|
|
24
28
|
* [Istanbul Introduction](#istanbul-introduction)
|
|
25
29
|
* [Thanks](#thanks)
|
|
26
30
|
|
|
@@ -129,11 +133,42 @@ const coverageResults = await coverageReport.generate();
|
|
|
129
133
|
console.log(coverageResults.summary);
|
|
130
134
|
```
|
|
131
135
|
|
|
136
|
+
## onEnd Hook
|
|
137
|
+
For example, checking thresholds:
|
|
138
|
+
```js
|
|
139
|
+
const EC = require('eight-colors');
|
|
140
|
+
const coverageOptions = {
|
|
141
|
+
name: 'My Coverage Report',
|
|
142
|
+
outputDir: './coverage-reports',
|
|
143
|
+
onEnd: (coverageResults) => {
|
|
144
|
+
const thresholds = {
|
|
145
|
+
bytes: 80,
|
|
146
|
+
lines: 60
|
|
147
|
+
};
|
|
148
|
+
console.log('check thresholds ...', thresholds);
|
|
149
|
+
const errors = [];
|
|
150
|
+
const { summary } = coverageResults;
|
|
151
|
+
Object.keys(thresholds).forEach((k) => {
|
|
152
|
+
const pct = summary[k].pct;
|
|
153
|
+
if (pct < thresholds[k]) {
|
|
154
|
+
errors.push(`Coverage threshold for ${k} (${pct} %) not met: ${thresholds[k]} %`);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
if (errors.length) {
|
|
158
|
+
const errMsg = errors.join('\n');
|
|
159
|
+
console.log(EC.red(errMsg));
|
|
160
|
+
// throw new Error(errMsg);
|
|
161
|
+
// process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
132
167
|
## Compare Reports
|
|
133
168
|
| | Istanbul | V8 | V8 to Istanbul |
|
|
134
169
|
| :--------------| :------ | :------ | :---------------------- |
|
|
135
|
-
| Coverage data | Istanbul (Object) | V8 (Array) | V8 (Array) |
|
|
136
|
-
| Output | [Istanbul reports](#
|
|
170
|
+
| Coverage data | [Istanbul](https://github.com/gotwarlost/istanbul/blob/master/coverage.json.md) (Object) | [V8](#v8-coverage-data-format) (Array) | [V8](#v8-coverage-data-format) (Array) |
|
|
171
|
+
| Output | [Istanbul reports](#available-reports) | [V8 reports](#available-reports) | [Istanbul reports](#available-reports) |
|
|
137
172
|
| - Bytes | ❌ | ✅ | ❌ |
|
|
138
173
|
| - Statements | ✅ | ❌ | ☑️❔ |
|
|
139
174
|
| - Branches | ✅ | ☑️❔ | ☑️❔ |
|
|
@@ -181,12 +216,12 @@ console.log(coverageResults.summary);
|
|
|
181
216
|
- Before running your Node.js application, set env `NODE_V8_COVERAGE`=`dir`. After the application runs and exits, the coverage data will be saved to the `dir` directory in JSON file format
|
|
182
217
|
- Read the json file(s) from the `dir` and generate coverage report
|
|
183
218
|
- example:
|
|
184
|
-
> cross-env NODE_V8_COVERAGE=`.temp/v8-coverage` node [./test/test-node-env.js](./test/test-node-env.js) && node [./test/generate-node-report.js](./test/generate-node-report.js)
|
|
219
|
+
> cross-env NODE_V8_COVERAGE=`.temp/v8-coverage-env` node [./test/test-node-env.js](./test/test-node-env.js) && node [./test/generate-node-report.js](./test/generate-node-report.js)
|
|
185
220
|
|
|
186
|
-
- Using [V8](https://nodejs.org/docs/latest/api/v8.html#v8takecoverage) API
|
|
221
|
+
- Using [V8](https://nodejs.org/docs/latest/api/v8.html#v8takecoverage) API + NODE_V8_COVERAGE
|
|
187
222
|
- Writing the coverage started by NODE_V8_COVERAGE to disk on demand with `v8.takeCoverage()` and stopping with `v8.stopCoverage()`.
|
|
188
223
|
- example:
|
|
189
|
-
> cross-env NODE_V8_COVERAGE=`.temp/v8-coverage` node [./test/test-node-api.js](./test/test-node-api.js)
|
|
224
|
+
> cross-env NODE_V8_COVERAGE=`.temp/v8-coverage-api` node [./test/test-node-api.js](./test/test-node-api.js)
|
|
190
225
|
|
|
191
226
|
- Using [Inspector](https://nodejs.org/docs/latest/api/inspector.html) API (or module [collect-v8-coverage](https://github.com/SimenB/collect-v8-coverage))
|
|
192
227
|
- Connecting to the V8 inspector and enable V8 coverage.
|
|
@@ -201,24 +236,24 @@ console.log(coverageResults.summary);
|
|
|
201
236
|
## Using `entryFilter` and `sourceFilter` to filter the results for V8 report
|
|
202
237
|
When V8 coverage data collected, it actually contains the data of all entry files, for example:
|
|
203
238
|
```
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
239
|
+
dist/main.js
|
|
240
|
+
dist/vendor.js
|
|
241
|
+
dist/something-else.js
|
|
207
242
|
```
|
|
208
243
|
We can use `entryFilter` to filter the entry files. For example, we should remove `vendor.js` and `something-else.js` if they are not in our coverage scope.
|
|
209
244
|
```
|
|
210
|
-
|
|
245
|
+
dist/main.js
|
|
211
246
|
```
|
|
212
247
|
When inline or linked sourcemap exists to the entry file, the source files will be extracted from the sourcemap for the entry file, and the entry file will be removed if `logging` is not `debug`.
|
|
213
248
|
```
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
249
|
+
> src/index.js
|
|
250
|
+
> src/components/app.js
|
|
251
|
+
> node_modules/dependency/dist/dependency.js
|
|
217
252
|
```
|
|
218
253
|
We can use `sourceFilter` to filter the source files. For example, we should remove `dependency.js` if it is not in our coverage scope.
|
|
219
254
|
```
|
|
220
|
-
|
|
221
|
-
|
|
255
|
+
> src/index.js
|
|
256
|
+
> src/components/app.js
|
|
222
257
|
```
|
|
223
258
|
Example:
|
|
224
259
|
```js
|
|
@@ -245,13 +280,15 @@ And also there are some problems about counting the functions and branches:
|
|
|
245
280
|
functions.forEach(block => {
|
|
246
281
|
block.ranges.forEach((range, i) => {
|
|
247
282
|
if (block.isBlockCoverage) {
|
|
248
|
-
// v8-to-istanbul:
|
|
283
|
+
// v8-to-istanbul: new CovBranch()
|
|
249
284
|
// Problem: not every block is branch, and the first block is actually function.
|
|
250
285
|
if (block.functionName && i === 0) {
|
|
251
|
-
// v8-to-istanbul:
|
|
286
|
+
// v8-to-istanbul: new CovFunction()
|
|
287
|
+
// Problem: no anonymous function
|
|
252
288
|
}
|
|
253
289
|
} else if (block.functionName) {
|
|
254
|
-
// v8-to-istanbul:
|
|
290
|
+
// v8-to-istanbul: new CovFunction()
|
|
291
|
+
// Problem: no anonymous function
|
|
255
292
|
}
|
|
256
293
|
}
|
|
257
294
|
});
|
|
@@ -288,7 +325,7 @@ const os = platform === 'wind32' ? 'Windows' : 'Other';
|
|
|
288
325
|
|
|
289
326
|
const os = platform === 'wind32' ? 'Windows' /* v8 ignore next */ : 'Other';
|
|
290
327
|
|
|
291
|
-
|
|
328
|
+
// v8 ignore next 3
|
|
292
329
|
if (platform === 'linux') {
|
|
293
330
|
console.info('hello linux');
|
|
294
331
|
}
|
|
@@ -300,6 +337,50 @@ if (platform === 'linux') {
|
|
|
300
337
|
- [Playwright Coverage Class](https://playwright.dev/docs/api/class-coverage)
|
|
301
338
|
- [DevTools Protocol for Coverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#method-startPreciseCoverage)
|
|
302
339
|
|
|
340
|
+
## V8 Coverage Data Format
|
|
341
|
+
```js
|
|
342
|
+
// Coverage data for a source range.
|
|
343
|
+
export interface CoverageRange {
|
|
344
|
+
// JavaScript script source offset for the range start.
|
|
345
|
+
startOffset: integer;
|
|
346
|
+
// JavaScript script source offset for the range end.
|
|
347
|
+
endOffset: integer;
|
|
348
|
+
// Collected execution count of the source range.
|
|
349
|
+
count: integer;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Coverage data for a JavaScript function.
|
|
353
|
+
/**
|
|
354
|
+
* @functionName can be an empty string.
|
|
355
|
+
* @ranges is always non-empty. The first range is called the "root range".
|
|
356
|
+
* @isBlockCoverage indicates if the function has block coverage information.
|
|
357
|
+
If this is false, it usually means that the functions was never called.
|
|
358
|
+
It seems to be equivalent to ranges.length === 1 && ranges[0].count === 0.
|
|
359
|
+
*/
|
|
360
|
+
export interface FunctionCoverage {
|
|
361
|
+
// JavaScript function name.
|
|
362
|
+
functionName: string;
|
|
363
|
+
// Source ranges inside the function with coverage data.
|
|
364
|
+
ranges: CoverageRange[];
|
|
365
|
+
// Whether coverage data for this function has block granularity.
|
|
366
|
+
isBlockCoverage: boolean;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Coverage data for a JavaScript script.
|
|
370
|
+
export interface ScriptCoverage {
|
|
371
|
+
// JavaScript script id.
|
|
372
|
+
scriptId: Runtime.ScriptId;
|
|
373
|
+
// JavaScript script name or url.
|
|
374
|
+
url: string;
|
|
375
|
+
// Functions contained in the script that has coverage data.
|
|
376
|
+
functions: FunctionCoverage[];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export type V8CoverageData = ScriptCoverage[];
|
|
380
|
+
```
|
|
381
|
+
see devtools-protocol [ScriptCoverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-ScriptCoverage) and [v8-coverage](https://github.com/bcoe/v8-coverage)
|
|
382
|
+
|
|
383
|
+
|
|
303
384
|
## Istanbul Introduction
|
|
304
385
|
- [Istanbul coverage report](https://istanbul.js.org/) - Instrumenting source codes and generating coverage reports
|
|
305
386
|
- [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul)
|
|
@@ -30,11 +30,11 @@ const createBranchGroup = (type, branchStart, parents, branchMap, functionInfo)
|
|
|
30
30
|
const reverseParents = [].concat(parents).reverse();
|
|
31
31
|
const group = {
|
|
32
32
|
type,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
|
|
34
|
+
start: branchStart,
|
|
35
|
+
// could be updated if multiple locations
|
|
36
|
+
end: branchStart,
|
|
37
|
+
|
|
38
38
|
locations: [],
|
|
39
39
|
reverseParents,
|
|
40
40
|
functionInfo
|
|
@@ -46,7 +46,7 @@ const createBranchGroup = (type, branchStart, parents, branchMap, functionInfo)
|
|
|
46
46
|
const addBranch = (group, node, locationMap, functionInfo) => {
|
|
47
47
|
const { start, end } = node;
|
|
48
48
|
|
|
49
|
-
const branchStart = group.
|
|
49
|
+
const branchStart = group.start;
|
|
50
50
|
|
|
51
51
|
// the block is not exact correct, if there is a block wrapped
|
|
52
52
|
// [x8]{ var a = b || [x4]c }, b is no block, but it can be found in x8 block
|
|
@@ -66,7 +66,7 @@ const addBranch = (group, node, locationMap, functionInfo) => {
|
|
|
66
66
|
group.locations.push(branchInfo);
|
|
67
67
|
|
|
68
68
|
// update group end
|
|
69
|
-
group.
|
|
69
|
+
group.end = end;
|
|
70
70
|
|
|
71
71
|
locationMap.set(start, branchInfo);
|
|
72
72
|
|
|
@@ -224,12 +224,12 @@ const createBranches = (ast, functionInfo, branchMap) => {
|
|
|
224
224
|
// first, left a and right c
|
|
225
225
|
// then, left a and right b
|
|
226
226
|
if (prevLocation) {
|
|
227
|
-
const {
|
|
227
|
+
const { locations } = group;
|
|
228
228
|
locations.sort((a, b) => {
|
|
229
229
|
return a.start - b.start;
|
|
230
230
|
});
|
|
231
231
|
// update group end after sorted
|
|
232
|
-
|
|
232
|
+
group.end = locations[locations.length - 1].end;
|
|
233
233
|
}
|
|
234
234
|
},
|
|
235
235
|
|
|
@@ -272,7 +272,7 @@ const IfStatement = (group, parentCount, noBlockList, blockCount) => {
|
|
|
272
272
|
if (!noBlockList.length) {
|
|
273
273
|
return;
|
|
274
274
|
}
|
|
275
|
-
// console.log(parentCount, 'uncovered list', noBlockList.length, group.
|
|
275
|
+
// console.log(parentCount, 'uncovered list', noBlockList.length, group.start);
|
|
276
276
|
let count = parentCount - blockCount;
|
|
277
277
|
noBlockList.forEach((item) => {
|
|
278
278
|
item.count = count;
|
|
@@ -291,7 +291,7 @@ const LogicalExpression = (group, parentCount, noBlockList, blockCount) => {
|
|
|
291
291
|
item.count = count;
|
|
292
292
|
});
|
|
293
293
|
|
|
294
|
-
// const start = group.
|
|
294
|
+
// const start = group.start;
|
|
295
295
|
// if (start > 7874 && start < 7922) {
|
|
296
296
|
// console.log(group.type, parentCount, 'locations', group.locations);
|
|
297
297
|
// }
|
|
@@ -381,16 +381,26 @@ const collectBranches = (branchMap) => {
|
|
|
381
381
|
// init branches
|
|
382
382
|
const branches = [];
|
|
383
383
|
branchMap.forEach((group) => {
|
|
384
|
+
|
|
385
|
+
// add start/end for none with group start/end
|
|
386
|
+
group.locations.forEach((item) => {
|
|
387
|
+
if (item.none) {
|
|
388
|
+
item.start = group.start;
|
|
389
|
+
item.end = group.end;
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
384
393
|
branches.push({
|
|
385
394
|
type: group.type,
|
|
386
|
-
|
|
395
|
+
start: group.start,
|
|
396
|
+
end: group.end,
|
|
387
397
|
locations: group.locations
|
|
388
398
|
});
|
|
389
399
|
});
|
|
390
400
|
|
|
391
401
|
// sort branches
|
|
392
402
|
branches.sort((a, b) => {
|
|
393
|
-
return a.
|
|
403
|
+
return a.start - b.start;
|
|
394
404
|
});
|
|
395
405
|
|
|
396
406
|
return branches;
|