monocart-coverage-reports 2.0.7 → 2.0.9
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 +125 -21
- package/lib/converter/ast.js +2 -3
- package/lib/converter/branches.js +24 -50
- package/lib/converter/converter.js +432 -458
- package/lib/converter/find-in-ranges.js +39 -0
- package/lib/converter/ignore.js +130 -0
- package/lib/converter/info-branch.js +60 -15
- package/lib/converter/info-function.js +30 -7
- package/lib/default/options.js +3 -0
- package/lib/generate.js +64 -65
- package/lib/index.d.ts +3 -0
- package/lib/index.js +1 -5
- package/lib/istanbul/istanbul-summary.js +1 -2
- package/lib/istanbul/istanbul.js +2 -2
- package/lib/packages/monocart-coverage-v8.js +1 -1
- package/lib/packages/monocart-coverage-vendor.js +28 -28
- package/lib/utils/dedupe.js +0 -5
- package/lib/utils/util.js +13 -0
- package/lib/v8/v8-summary.js +85 -34
- package/lib/v8/v8.js +7 -68
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* [Default Options](#default-options)
|
|
11
11
|
* [Available Reports](#available-reports)
|
|
12
12
|
* [Multiprocessing Support](#multiprocessing-support)
|
|
13
|
+
* [onEnd Hook](#onend-hook)
|
|
13
14
|
* [Compare Reports](#compare-reports)
|
|
14
15
|
* [Compare Workflows](#compare-workflows)
|
|
15
16
|
* [Collecting Istanbul Coverage Data](#collecting-istanbul-coverage-data)
|
|
@@ -19,7 +20,9 @@
|
|
|
19
20
|
* [How to convert V8 to Istanbul](#how-to-convert-v8-to-istanbul)
|
|
20
21
|
- [Using `v8-to-istanbul`](#using-v8-to-istanbul)
|
|
21
22
|
- [How Monocart Works](#how-monocart-works)
|
|
23
|
+
* [Ignoring Uncovered Lines](#ignoring-uncovered-lines)
|
|
22
24
|
* [Chromium Coverage API](#chromium-coverage-api)
|
|
25
|
+
* [V8 Coverage Data Format](#v8-coverage-data-format)
|
|
23
26
|
* [Istanbul Introduction](#istanbul-introduction)
|
|
24
27
|
* [Thanks](#thanks)
|
|
25
28
|
|
|
@@ -128,11 +131,42 @@ const coverageResults = await coverageReport.generate();
|
|
|
128
131
|
console.log(coverageResults.summary);
|
|
129
132
|
```
|
|
130
133
|
|
|
134
|
+
## onEnd Hook
|
|
135
|
+
For example, checking thresholds:
|
|
136
|
+
```js
|
|
137
|
+
const EC = require('eight-colors');
|
|
138
|
+
const coverageOptions = {
|
|
139
|
+
name: 'My Coverage Report',
|
|
140
|
+
outputDir: './coverage-reports',
|
|
141
|
+
onEnd: (coverageResults) => {
|
|
142
|
+
const thresholds = {
|
|
143
|
+
bytes: 80,
|
|
144
|
+
lines: 60
|
|
145
|
+
};
|
|
146
|
+
console.log('check thresholds ...', thresholds);
|
|
147
|
+
const errors = [];
|
|
148
|
+
const { summary } = coverageResults;
|
|
149
|
+
Object.keys(thresholds).forEach((k) => {
|
|
150
|
+
const pct = summary[k].pct;
|
|
151
|
+
if (pct < thresholds[k]) {
|
|
152
|
+
errors.push(`Coverage threshold for ${k} (${pct} %) not met: ${thresholds[k]} %`);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
if (errors.length) {
|
|
156
|
+
const errMsg = errors.join('\n');
|
|
157
|
+
console.log(EC.red(errMsg));
|
|
158
|
+
// throw new Error(errMsg);
|
|
159
|
+
// process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
131
165
|
## Compare Reports
|
|
132
166
|
| | Istanbul | V8 | V8 to Istanbul |
|
|
133
167
|
| :--------------| :------ | :------ | :---------------------- |
|
|
134
|
-
| Coverage data | Istanbul (Object) | V8 (Array) | V8 (Array) |
|
|
135
|
-
| Output | [Istanbul reports](#
|
|
168
|
+
| 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) |
|
|
169
|
+
| Output | [Istanbul reports](#available-reports) | [V8 reports](#available-reports) | [Istanbul reports](#available-reports) |
|
|
136
170
|
| - Bytes | ❌ | ✅ | ❌ |
|
|
137
171
|
| - Statements | ✅ | ❌ | ☑️❔ |
|
|
138
172
|
| - Branches | ✅ | ☑️❔ | ☑️❔ |
|
|
@@ -166,8 +200,9 @@ console.log(coverageResults.summary);
|
|
|
166
200
|
|
|
167
201
|
## Collecting V8 Coverage Data
|
|
168
202
|
- For source code: enable `sourcemap` and do not compress/minify:
|
|
169
|
-
- Webpack: with `source-map` [devtool](https://webpack.js.org/configuration/devtool/) and `development` [mode](https://webpack.js.org/configuration/mode/), example [webpack.config-v8.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/webpack.config-v8.js)
|
|
170
|
-
- Rollup: [options](https://rollupjs.org/configuration-options/) `sourcemap: true`
|
|
203
|
+
- Webpack: build with `source-map` [devtool](https://webpack.js.org/configuration/devtool/) and `development` [mode](https://webpack.js.org/configuration/mode/), example [webpack.config-v8.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/webpack.config-v8.js)
|
|
204
|
+
- Rollup: build with [options](https://rollupjs.org/configuration-options/) `sourcemap: true`
|
|
205
|
+
- Vite: build with [options](https://vitejs.dev/config/build-options.html) `sourcemap: true` and `minify: false`
|
|
171
206
|
- Browser (Chromium Only)
|
|
172
207
|
> Collecting coverage data with [Chromium Coverage API](#chromium-coverage-api), see [example](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-v8.js)
|
|
173
208
|
- Node.js
|
|
@@ -179,12 +214,15 @@ console.log(coverageResults.summary);
|
|
|
179
214
|
- 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
|
|
180
215
|
- Read the json file(s) from the `dir` and generate coverage report
|
|
181
216
|
- example:
|
|
182
|
-
> 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)
|
|
217
|
+
> 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)
|
|
183
218
|
|
|
184
|
-
- Using [V8](https://nodejs.org/docs/latest/api/v8.html#v8takecoverage) API
|
|
219
|
+
- Using [V8](https://nodejs.org/docs/latest/api/v8.html#v8takecoverage) API + NODE_V8_COVERAGE
|
|
185
220
|
- Writing the coverage started by NODE_V8_COVERAGE to disk on demand with `v8.takeCoverage()` and stopping with `v8.stopCoverage()`.
|
|
186
221
|
- example:
|
|
187
|
-
> cross-env NODE_V8_COVERAGE=`.temp/v8-coverage` node [./test/test-node-api.js](./test/test-node-api.js)
|
|
222
|
+
> cross-env NODE_V8_COVERAGE=`.temp/v8-coverage-api` node [./test/test-node-api.js](./test/test-node-api.js)
|
|
223
|
+
|
|
224
|
+
- Using [foreground-child](https://github.com/tapjs/foreground-child) + NODE_V8_COVERAGE
|
|
225
|
+
- example: [./test/test-node-fgc.js](./test/test-node-fgc.js)
|
|
188
226
|
|
|
189
227
|
- Using [Inspector](https://nodejs.org/docs/latest/api/inspector.html) API (or module [collect-v8-coverage](https://github.com/SimenB/collect-v8-coverage))
|
|
190
228
|
- Connecting to the V8 inspector and enable V8 coverage.
|
|
@@ -199,24 +237,24 @@ console.log(coverageResults.summary);
|
|
|
199
237
|
## Using `entryFilter` and `sourceFilter` to filter the results for V8 report
|
|
200
238
|
When V8 coverage data collected, it actually contains the data of all entry files, for example:
|
|
201
239
|
```
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
240
|
+
dist/main.js
|
|
241
|
+
dist/vendor.js
|
|
242
|
+
dist/something-else.js
|
|
205
243
|
```
|
|
206
244
|
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.
|
|
207
245
|
```
|
|
208
|
-
|
|
246
|
+
dist/main.js
|
|
209
247
|
```
|
|
210
248
|
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`.
|
|
211
249
|
```
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
250
|
+
> src/index.js
|
|
251
|
+
> src/components/app.js
|
|
252
|
+
> node_modules/dependency/dist/dependency.js
|
|
215
253
|
```
|
|
216
254
|
We can use `sourceFilter` to filter the source files. For example, we should remove `dependency.js` if it is not in our coverage scope.
|
|
217
255
|
```
|
|
218
|
-
|
|
219
|
-
|
|
256
|
+
> src/index.js
|
|
257
|
+
> src/components/app.js
|
|
220
258
|
```
|
|
221
259
|
Example:
|
|
222
260
|
```js
|
|
@@ -243,15 +281,15 @@ And also there are some problems about counting the functions and branches:
|
|
|
243
281
|
functions.forEach(block => {
|
|
244
282
|
block.ranges.forEach((range, i) => {
|
|
245
283
|
if (block.isBlockCoverage) {
|
|
246
|
-
// v8-to-istanbul:
|
|
284
|
+
// v8-to-istanbul: new CovBranch()
|
|
247
285
|
// Problem: not every block is branch, and the first block is actually function.
|
|
248
286
|
if (block.functionName && i === 0) {
|
|
249
|
-
// v8-to-istanbul:
|
|
250
|
-
// Problem:
|
|
287
|
+
// v8-to-istanbul: new CovFunction()
|
|
288
|
+
// Problem: no anonymous function
|
|
251
289
|
}
|
|
252
290
|
} else if (block.functionName) {
|
|
253
|
-
// v8-to-istanbul:
|
|
254
|
-
// Problem:
|
|
291
|
+
// v8-to-istanbul: new CovFunction()
|
|
292
|
+
// Problem: no anonymous function
|
|
255
293
|
}
|
|
256
294
|
}
|
|
257
295
|
});
|
|
@@ -272,12 +310,78 @@ We have removed `v8-to-istanbul` because of the two major problems and implement
|
|
|
272
310
|
| LogicalExpression | ✔ Not Perfect |
|
|
273
311
|
| SwitchStatement | ✔ Not Perfect |
|
|
274
312
|
|
|
313
|
+
## Ignoring Uncovered Codes
|
|
314
|
+
To ignore codes, use the special comment which starts with `v8 ignore `:
|
|
315
|
+
- ignoring all until told
|
|
316
|
+
```js
|
|
317
|
+
/* v8 ignore start */
|
|
318
|
+
function uncovered() {
|
|
319
|
+
}
|
|
320
|
+
/* v8 ignore stop */
|
|
321
|
+
```
|
|
322
|
+
- ignoring the next line or next N lines
|
|
323
|
+
```js
|
|
324
|
+
/* v8 ignore next */
|
|
325
|
+
const os = platform === 'wind32' ? 'Windows' : 'Other';
|
|
326
|
+
|
|
327
|
+
const os = platform === 'wind32' ? 'Windows' /* v8 ignore next */ : 'Other';
|
|
328
|
+
|
|
329
|
+
/* v8 ignore next 3 */
|
|
330
|
+
if (platform === 'linux') {
|
|
331
|
+
console.info('hello linux');
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
275
335
|
## Chromium Coverage API
|
|
276
336
|
- [V8 coverage report](https://v8.dev/blog/javascript-code-coverage) - Native support for JavaScript code coverage to V8. (Chromium only)
|
|
277
337
|
- [Puppeteer Coverage Class](https://pptr.dev/api/puppeteer.coverage)
|
|
278
338
|
- [Playwright Coverage Class](https://playwright.dev/docs/api/class-coverage)
|
|
279
339
|
- [DevTools Protocol for Coverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#method-startPreciseCoverage)
|
|
280
340
|
|
|
341
|
+
## V8 Coverage Data Format
|
|
342
|
+
```js
|
|
343
|
+
// Coverage data for a source range.
|
|
344
|
+
export interface CoverageRange {
|
|
345
|
+
// JavaScript script source offset for the range start.
|
|
346
|
+
startOffset: integer;
|
|
347
|
+
// JavaScript script source offset for the range end.
|
|
348
|
+
endOffset: integer;
|
|
349
|
+
// Collected execution count of the source range.
|
|
350
|
+
count: integer;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Coverage data for a JavaScript function.
|
|
354
|
+
/**
|
|
355
|
+
* @functionName can be an empty string.
|
|
356
|
+
* @ranges is always non-empty. The first range is called the "root range".
|
|
357
|
+
* @isBlockCoverage indicates if the function has block coverage information.
|
|
358
|
+
If this is false, it usually means that the functions was never called.
|
|
359
|
+
It seems to be equivalent to ranges.length === 1 && ranges[0].count === 0.
|
|
360
|
+
*/
|
|
361
|
+
export interface FunctionCoverage {
|
|
362
|
+
// JavaScript function name.
|
|
363
|
+
functionName: string;
|
|
364
|
+
// Source ranges inside the function with coverage data.
|
|
365
|
+
ranges: CoverageRange[];
|
|
366
|
+
// Whether coverage data for this function has block granularity.
|
|
367
|
+
isBlockCoverage: boolean;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Coverage data for a JavaScript script.
|
|
371
|
+
export interface ScriptCoverage {
|
|
372
|
+
// JavaScript script id.
|
|
373
|
+
scriptId: Runtime.ScriptId;
|
|
374
|
+
// JavaScript script name or url.
|
|
375
|
+
url: string;
|
|
376
|
+
// Functions contained in the script that has coverage data.
|
|
377
|
+
functions: FunctionCoverage[];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export type V8CoverageData = ScriptCoverage[];
|
|
381
|
+
```
|
|
382
|
+
see devtools-protocol [ScriptCoverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-ScriptCoverage)
|
|
383
|
+
|
|
384
|
+
|
|
281
385
|
## Istanbul Introduction
|
|
282
386
|
- [Istanbul coverage report](https://istanbul.js.org/) - Instrumenting source codes and generating coverage reports
|
|
283
387
|
- [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul)
|
package/lib/converter/ast.js
CHANGED
|
@@ -3,9 +3,8 @@ const {
|
|
|
3
3
|
} = require('../packages/monocart-coverage-vendor.js');
|
|
4
4
|
const Util = require('../utils/util.js');
|
|
5
5
|
|
|
6
|
-
const {
|
|
7
|
-
|
|
8
|
-
} = require('./branches.js');
|
|
6
|
+
const { createBranches, collectBranches } = require('./branches.js');
|
|
7
|
+
const findInRanges = require('./find-in-ranges.js');
|
|
9
8
|
|
|
10
9
|
const getFunctionRange = (start, end, state) => {
|
|
11
10
|
|
|
@@ -1,40 +1,5 @@
|
|
|
1
1
|
const Util = require('../utils/util.js');
|
|
2
|
-
|
|
3
|
-
const quickFindRange = (position, ranges) => {
|
|
4
|
-
let start = 0;
|
|
5
|
-
let end = ranges.length - 1;
|
|
6
|
-
while (end - start > 1) {
|
|
7
|
-
const i = Math.floor((start + end) * 0.5);
|
|
8
|
-
const item = ranges[i];
|
|
9
|
-
if (position < item.startOffset) {
|
|
10
|
-
end = i;
|
|
11
|
-
continue;
|
|
12
|
-
}
|
|
13
|
-
if (position > item.endOffset) {
|
|
14
|
-
start = i;
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
return ranges[i];
|
|
18
|
-
}
|
|
19
|
-
// last two items, less is start
|
|
20
|
-
const endItem = ranges[end];
|
|
21
|
-
if (position < endItem.startOffset) {
|
|
22
|
-
return ranges[start];
|
|
23
|
-
}
|
|
24
|
-
return ranges[end];
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const findInRanges = (start, end, ranges) => {
|
|
29
|
-
if (!Util.isList(ranges)) {
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const range = quickFindRange(start, ranges);
|
|
33
|
-
if (start >= range.startOffset && end <= range.endOffset) {
|
|
34
|
-
return range;
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
|
|
2
|
+
const findInRanges = require('./find-in-ranges.js');
|
|
38
3
|
|
|
39
4
|
const findBranchBlock = (start, end, functionInfo) => {
|
|
40
5
|
const { range } = functionInfo;
|
|
@@ -65,11 +30,11 @@ const createBranchGroup = (type, branchStart, parents, branchMap, functionInfo)
|
|
|
65
30
|
const reverseParents = [].concat(parents).reverse();
|
|
66
31
|
const group = {
|
|
67
32
|
type,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
33
|
+
|
|
34
|
+
start: branchStart,
|
|
35
|
+
// could be updated if multiple locations
|
|
36
|
+
end: branchStart,
|
|
37
|
+
|
|
73
38
|
locations: [],
|
|
74
39
|
reverseParents,
|
|
75
40
|
functionInfo
|
|
@@ -81,7 +46,7 @@ const createBranchGroup = (type, branchStart, parents, branchMap, functionInfo)
|
|
|
81
46
|
const addBranch = (group, node, locationMap, functionInfo) => {
|
|
82
47
|
const { start, end } = node;
|
|
83
48
|
|
|
84
|
-
const branchStart = group.
|
|
49
|
+
const branchStart = group.start;
|
|
85
50
|
|
|
86
51
|
// the block is not exact correct, if there is a block wrapped
|
|
87
52
|
// [x8]{ var a = b || [x4]c }, b is no block, but it can be found in x8 block
|
|
@@ -101,7 +66,7 @@ const addBranch = (group, node, locationMap, functionInfo) => {
|
|
|
101
66
|
group.locations.push(branchInfo);
|
|
102
67
|
|
|
103
68
|
// update group end
|
|
104
|
-
group.
|
|
69
|
+
group.end = end;
|
|
105
70
|
|
|
106
71
|
locationMap.set(start, branchInfo);
|
|
107
72
|
|
|
@@ -259,12 +224,12 @@ const createBranches = (ast, functionInfo, branchMap) => {
|
|
|
259
224
|
// first, left a and right c
|
|
260
225
|
// then, left a and right b
|
|
261
226
|
if (prevLocation) {
|
|
262
|
-
const {
|
|
227
|
+
const { locations } = group;
|
|
263
228
|
locations.sort((a, b) => {
|
|
264
229
|
return a.start - b.start;
|
|
265
230
|
});
|
|
266
231
|
// update group end after sorted
|
|
267
|
-
|
|
232
|
+
group.end = locations[locations.length - 1].end;
|
|
268
233
|
}
|
|
269
234
|
},
|
|
270
235
|
|
|
@@ -307,7 +272,7 @@ const IfStatement = (group, parentCount, noBlockList, blockCount) => {
|
|
|
307
272
|
if (!noBlockList.length) {
|
|
308
273
|
return;
|
|
309
274
|
}
|
|
310
|
-
// console.log(parentCount, 'uncovered list', noBlockList.length, group.
|
|
275
|
+
// console.log(parentCount, 'uncovered list', noBlockList.length, group.start);
|
|
311
276
|
let count = parentCount - blockCount;
|
|
312
277
|
noBlockList.forEach((item) => {
|
|
313
278
|
item.count = count;
|
|
@@ -326,7 +291,7 @@ const LogicalExpression = (group, parentCount, noBlockList, blockCount) => {
|
|
|
326
291
|
item.count = count;
|
|
327
292
|
});
|
|
328
293
|
|
|
329
|
-
// const start = group.
|
|
294
|
+
// const start = group.start;
|
|
330
295
|
// if (start > 7874 && start < 7922) {
|
|
331
296
|
// console.log(group.type, parentCount, 'locations', group.locations);
|
|
332
297
|
// }
|
|
@@ -416,23 +381,32 @@ const collectBranches = (branchMap) => {
|
|
|
416
381
|
// init branches
|
|
417
382
|
const branches = [];
|
|
418
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
|
+
|
|
419
393
|
branches.push({
|
|
420
394
|
type: group.type,
|
|
421
|
-
|
|
395
|
+
start: group.start,
|
|
396
|
+
end: group.end,
|
|
422
397
|
locations: group.locations
|
|
423
398
|
});
|
|
424
399
|
});
|
|
425
400
|
|
|
426
401
|
// sort branches
|
|
427
402
|
branches.sort((a, b) => {
|
|
428
|
-
return a.
|
|
403
|
+
return a.start - b.start;
|
|
429
404
|
});
|
|
430
405
|
|
|
431
406
|
return branches;
|
|
432
407
|
};
|
|
433
408
|
|
|
434
409
|
module.exports = {
|
|
435
|
-
findInRanges,
|
|
436
410
|
createBranches,
|
|
437
411
|
collectBranches
|
|
438
412
|
};
|