monocart-coverage-reports 2.2.1 → 2.3.0
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 +142 -26
- package/lib/cli.js +0 -6
- package/lib/converter/ast.js +26 -5
- package/lib/converter/collect-source-maps.js +15 -3
- package/lib/converter/converter.js +318 -119
- package/lib/converter/find-original-range.js +5 -0
- package/lib/converter/info-branch.js +13 -5
- package/lib/default/options.js +3 -0
- package/lib/generate.js +151 -89
- package/lib/index.d.ts +15 -2
- package/lib/index.js +45 -15
- package/lib/istanbul/istanbul.js +9 -9
- package/lib/packages/monocart-coverage-v8.js +1 -1
- package/lib/platform/share.js +13 -0
- package/lib/utils/dedupe.js +21 -8
- package/lib/utils/source-path.js +5 -0
- package/lib/utils/util.js +30 -1
- package/lib/v8/v8.js +149 -75
- package/package.json +6 -4
- package/lib/utils/decode-mappings-worker.js +0 -9
- package/lib/utils/decode-mappings.js +0 -35
package/README.md
CHANGED
|
@@ -18,8 +18,11 @@
|
|
|
18
18
|
* [Compare Workflows](#compare-workflows)
|
|
19
19
|
* [Collecting Istanbul Coverage Data](#collecting-istanbul-coverage-data)
|
|
20
20
|
* [Collecting V8 Coverage Data](#collecting-v8-coverage-data)
|
|
21
|
+
* [Manually Resolve the Sourcemap](#manually-resolve-the-sourcemap)
|
|
22
|
+
* [Collecting Raw V8 Coverage Data with Puppeteer](#collecting-raw-v8-coverage-data-with-puppeteer)
|
|
21
23
|
* [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
|
|
22
24
|
* [Multiprocessing Support](#multiprocessing-support)
|
|
25
|
+
* [Merge Coverage Reports](#merge-coverage-reports)
|
|
23
26
|
* [Integration](#integration)
|
|
24
27
|
* [Ignoring Uncovered Codes](#ignoring-uncovered-codes)
|
|
25
28
|
* [Chromium Coverage API](#chromium-coverage-api)
|
|
@@ -64,11 +67,12 @@ console.log(coverageResults.summary);
|
|
|
64
67
|
|
|
65
68
|
- `v8-json` (V8 data only)
|
|
66
69
|
- [V8 coverage-report.json](https://cenfun.github.io/monocart-coverage-reports/v8-and-istanbul/coverage-report.json)
|
|
67
|
-
- `
|
|
70
|
+
- `codecov` (V8 data only)
|
|
71
|
+
- coverage data for [Codecov](https://docs.codecov.com/docs/codecov-custom-coverage-format), see [example](https://app.codecov.io/github/cenfun/monocart-coverage-reports)
|
|
68
72
|
|
|
69
73
|

|
|
70
74
|
|
|
71
|
-
>
|
|
75
|
+
> Istanbul [build-in reports](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib)
|
|
72
76
|
- `clover`
|
|
73
77
|
- `cobertura`
|
|
74
78
|
- `html`
|
|
@@ -88,11 +92,41 @@ console.log(coverageResults.summary);
|
|
|
88
92
|
- `text-lcov`
|
|
89
93
|
- `text-summary`
|
|
90
94
|
|
|
95
|
+
> Other reports
|
|
96
|
+
- `console-summary` shows coverage summary in console
|
|
97
|
+
- `raw` only keep all original data, which can be used for other reports input with `inputDir`
|
|
98
|
+
- see [Merge Coverage Reports](#merge-coverage-reports)
|
|
99
|
+
|
|
100
|
+
- Custom Reporter
|
|
101
|
+
```js
|
|
102
|
+
{
|
|
103
|
+
reports: [
|
|
104
|
+
[path.resolve('./test/custom-istanbul-reporter.js'), {
|
|
105
|
+
type: 'istanbul',
|
|
106
|
+
file: 'custom-istanbul-coverage.text'
|
|
107
|
+
}],
|
|
108
|
+
[path.resolve('./test/custom-v8-reporter.js'), {
|
|
109
|
+
type: 'v8',
|
|
110
|
+
outputFile: 'custom-v8-coverage.json'
|
|
111
|
+
}],
|
|
112
|
+
[path.resolve('./test/custom-v8-reporter.mjs'), {
|
|
113
|
+
type: 'both'
|
|
114
|
+
}]
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
- istanbul custom reporter
|
|
119
|
+
> example: [./test/custom-istanbul-reporter.js](./test/custom-istanbul-reporter.js), see [istanbul built-in reporters' implementation](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib) for reference,
|
|
120
|
+
- v8 custom reporter
|
|
121
|
+
> example: [./test/custom-v8-reporter.js](./test/custom-v8-reporter.js)
|
|
122
|
+
|
|
123
|
+
### Multiple Reports:
|
|
91
124
|
```js
|
|
92
125
|
const MCR = require('monocart-coverage-reports');
|
|
93
126
|
const options = {
|
|
94
127
|
outputDir: './coverage-reports',
|
|
95
128
|
reports: [
|
|
129
|
+
// build-in reports
|
|
96
130
|
['console-summary'],
|
|
97
131
|
['v8'],
|
|
98
132
|
['html', {
|
|
@@ -101,7 +135,18 @@ const options = {
|
|
|
101
135
|
['json', {
|
|
102
136
|
file: 'my-json-file.json'
|
|
103
137
|
}],
|
|
104
|
-
'lcovonly'
|
|
138
|
+
'lcovonly',
|
|
139
|
+
|
|
140
|
+
// custom reports
|
|
141
|
+
// Specify reporter name with the NPM package
|
|
142
|
+
["custom-reporter-1"],
|
|
143
|
+
["custom-reporter-2", {
|
|
144
|
+
type: "istanbul",
|
|
145
|
+
option: "value"
|
|
146
|
+
}],
|
|
147
|
+
// Specify reporter name with local path
|
|
148
|
+
['/absolute/path/to/custom-reporter.js']
|
|
149
|
+
|
|
105
150
|
]
|
|
106
151
|
}
|
|
107
152
|
const coverageReport = MCR(options);
|
|
@@ -248,13 +293,48 @@ mcr "node ./test/test-node-env.js" -c test/cli-options.js
|
|
|
248
293
|
- [rollup](https://rollupjs.org/configuration-options/): `sourcemap: true`
|
|
249
294
|
- [vite](https://vitejs.dev/config/build-options.html): `sourcemap: true` and `minify: false`
|
|
250
295
|
- [esbuild](https://esbuild.github.io/api/): `sourcemap: true` and `minify: false`
|
|
296
|
+
- [Manually Resolve the Sourcemap](#manually-resolve-the-sourcemap)
|
|
251
297
|
- Browser (Chromium Only)
|
|
252
298
|
> Collecting coverage data with [Chromium Coverage API](#chromium-coverage-api):
|
|
253
299
|
- [Playwright example](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-v8.js), and [anonymous](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-anonymous.js), [css](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-css.js)
|
|
254
|
-
- [Puppeteer
|
|
300
|
+
- see [Collecting Raw V8 Coverage Data with Puppeteer](#collecting-raw-v8-coverage-data-with-puppeteer)
|
|
255
301
|
- Node.js
|
|
256
|
-
- see
|
|
302
|
+
- see [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
|
|
257
303
|
|
|
304
|
+
## Manually Resolve the Sourcemap
|
|
305
|
+
> If the `js` file is loaded with `addScriptTag` [API](https://playwright.dev/docs/api/class-page#page-add-script-tag), then its sourcemap file may not work. You can try to manually read the sourcemap file before the coverage data is added to the report.
|
|
306
|
+
```js
|
|
307
|
+
const jsCoverage = await page.coverage.stopJSCoverage();
|
|
308
|
+
jsCoverage.forEach((entry) => {
|
|
309
|
+
// read sourcemap for the my-dist.js manually
|
|
310
|
+
if (entry.url.endsWith('my-dist.js')) {
|
|
311
|
+
entry.sourceMap = JSON.parse(fs.readFileSync('dist/my-dist.js.map').toString('utf-8'));
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
await MCR(coverageOptions).add(jsCoverage);
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Collecting Raw V8 Coverage Data with Puppeteer
|
|
320
|
+
> Puppeteer does not provide raw v8 coverage data by default. A simple conversion is required, see example: [./test/test-puppeteer.js](./test/test-puppeteer.js)
|
|
321
|
+
```js
|
|
322
|
+
await page.coverage.startJSCoverage({
|
|
323
|
+
// provide raw v8 coverage data
|
|
324
|
+
includeRawScriptCoverage: true
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
await page.goto(url);
|
|
328
|
+
|
|
329
|
+
const jsCoverage = await page.coverage.stopJSCoverage();
|
|
330
|
+
const rawV8CoverageData = jsCoverage.map((it) => {
|
|
331
|
+
// Convert to raw v8 coverage format
|
|
332
|
+
return {
|
|
333
|
+
source: it.text,
|
|
334
|
+
... it.rawScriptCoverage
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
```
|
|
258
338
|
|
|
259
339
|
## Node.js V8 Coverage Report for Server Side
|
|
260
340
|
Possible solutions:
|
|
@@ -317,9 +397,65 @@ const coverageResults = await coverageReport.generate();
|
|
|
317
397
|
console.log(coverageResults.summary);
|
|
318
398
|
```
|
|
319
399
|
|
|
400
|
+
## Merge Coverage Reports
|
|
401
|
+
The following usage scenarios may require merging coverage reports:
|
|
402
|
+
- When the code is executed in different environments, like Node.js Server Side and browser Client Side (Next.js for instance). Each environment may generate its own coverage report. Merging them can give a more comprehensive view of the test coverage.
|
|
403
|
+
- When the code is subjected to different kinds of testing. For example, unit tests with Jest might cover certain parts of the code, while end-to-end tests with Playwright might cover other parts. Merging these different coverage reports can provide a holistic view of what code has been tested.
|
|
404
|
+
- When tests are run on different machines or different shards, each might produce its own coverage report. Merging these can give a complete picture of the test coverage across all machines or shards.
|
|
405
|
+
|
|
406
|
+
First, using the `raw` report to export the original coverage data to the specified directory.
|
|
407
|
+
```js
|
|
408
|
+
const coverageOptions = {
|
|
409
|
+
name: 'My Unit Test Coverage Report',
|
|
410
|
+
outputDir: "./coverage-reports/unit",
|
|
411
|
+
reports: [
|
|
412
|
+
['raw', {
|
|
413
|
+
// relative path will be "./coverage-reports/unit/raw"
|
|
414
|
+
outputDir: "raw"
|
|
415
|
+
}],
|
|
416
|
+
['v8'],
|
|
417
|
+
['console-summary']
|
|
418
|
+
]
|
|
419
|
+
};
|
|
420
|
+
```
|
|
421
|
+
Then, after all the tests are completed, generate a merged report with option `inputDir`:
|
|
422
|
+
```js
|
|
423
|
+
import {CoverageReport} from 'monocart-coverage-reports';
|
|
424
|
+
const coverageOptions = {
|
|
425
|
+
name: 'My Merged Coverage Report',
|
|
426
|
+
inputDir: [
|
|
427
|
+
'./coverage-reports/unit/raw',
|
|
428
|
+
'./coverage-reports/e2e/raw'
|
|
429
|
+
],
|
|
430
|
+
outputDir: './coverage-reports/merged',
|
|
431
|
+
reports: [
|
|
432
|
+
['v8'],
|
|
433
|
+
['console-summary']
|
|
434
|
+
]
|
|
435
|
+
};
|
|
436
|
+
await new CoverageReport(coverageOptions).generate();
|
|
437
|
+
```
|
|
438
|
+
If the source file comes from the sourcemap, then its path is a virtual path. Using the `sourcePath` option to convert it.
|
|
439
|
+
```js
|
|
440
|
+
const coverageOptions = {
|
|
441
|
+
sourcePath: (filePath) => {
|
|
442
|
+
// Remove the virtual prefix
|
|
443
|
+
const list = ['my-dist-file1/', 'my-dist-file2/'];
|
|
444
|
+
for (const str of list) {
|
|
445
|
+
if (filePath.startsWith(str)) {
|
|
446
|
+
return filePath.slice(str.length);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return filePath;
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
```
|
|
453
|
+
see example: [./test/test-merge.js](./test/test-merge.js)
|
|
454
|
+
|
|
320
455
|
## Integration
|
|
321
456
|
- [monocart-reporter](https://cenfun.github.io/monocart-reporter/) - Test reporter for [Playwright](https://github.com/microsoft/playwright)
|
|
322
|
-
- [
|
|
457
|
+
- [jest-monocart-coverage](https://github.com/cenfun/jest-monocart-coverage) - Integration with [Jest](https://github.com/jestjs/jest/) for coverage reports
|
|
458
|
+
- [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage) - Integration with [Vitest](https://github.com/vitest-dev/vitest) for coverage reports
|
|
323
459
|
|
|
324
460
|
## Ignoring Uncovered Codes
|
|
325
461
|
To ignore codes, use the special comment which starts with `v8 ignore `:
|
|
@@ -392,26 +528,6 @@ export type V8CoverageData = ScriptCoverage[];
|
|
|
392
528
|
```
|
|
393
529
|
see devtools-protocol [ScriptCoverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-ScriptCoverage) and [v8-coverage](https://github.com/bcoe/v8-coverage)
|
|
394
530
|
|
|
395
|
-
### Collect raw v8 coverage data with Puppeteer
|
|
396
|
-
```js
|
|
397
|
-
await page.coverage.startJSCoverage({
|
|
398
|
-
// provide raw v8 coverage data
|
|
399
|
-
includeRawScriptCoverage: true
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
await page.goto(url);
|
|
403
|
-
|
|
404
|
-
const jsCoverage = await page.coverage.stopJSCoverage();
|
|
405
|
-
const rawV8CoverageData = jsCoverage.map((it) => {
|
|
406
|
-
// Convert to raw v8 coverage format
|
|
407
|
-
return {
|
|
408
|
-
source: it.text,
|
|
409
|
-
... it.rawScriptCoverage
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
```
|
|
413
|
-
see example: [./test/test-puppeteer.js](./test/test-puppeteer.js)
|
|
414
|
-
|
|
415
531
|
## How to convert V8 to Istanbul
|
|
416
532
|
### Using [v8-to-istanbul](https://github.com/istanbuljs/v8-to-istanbul)
|
|
417
533
|
It is a popular library which is used to convert V8 coverage format to istanbul's coverage format. Most test frameworks are using it, such as [Jest](https://github.com/jestjs/jest/), [Vitest](https://github.com/vitest-dev/vitest), but it has two major problems:
|
package/lib/cli.js
CHANGED
|
@@ -36,12 +36,6 @@ const initOptions = (cliOptions) => {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// init reports to list with `,`
|
|
40
|
-
const reports = cliOptions.reports;
|
|
41
|
-
if (reports) {
|
|
42
|
-
cliOptions.reports = reports.split(',');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
39
|
// report options
|
|
46
40
|
const options = {
|
|
47
41
|
... defaultOptions
|
package/lib/converter/ast.js
CHANGED
|
@@ -36,6 +36,17 @@ const collectAstInfo = (ast, state) => {
|
|
|
36
36
|
const functions = [];
|
|
37
37
|
const branchMap = new Map();
|
|
38
38
|
|
|
39
|
+
// root function branches
|
|
40
|
+
const functionRoot = state.functionRoot;
|
|
41
|
+
if (functionRoot) {
|
|
42
|
+
const rootFunctionInfo = {
|
|
43
|
+
count: functionRoot.count,
|
|
44
|
+
range: functionRoot
|
|
45
|
+
};
|
|
46
|
+
createBranches(ast, rootFunctionInfo, branchMap);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// functions branches
|
|
39
50
|
Util.visitAst(ast, {
|
|
40
51
|
|
|
41
52
|
VariableDeclarator(node, parents) {
|
|
@@ -101,15 +112,15 @@ const collectAstInfo = (ast, state) => {
|
|
|
101
112
|
const generateCountState = (coverageList) => {
|
|
102
113
|
const functionRanges = [];
|
|
103
114
|
const functionNameMap = new Map();
|
|
115
|
+
let functionRoot;
|
|
104
116
|
coverageList.forEach((block) => {
|
|
105
|
-
const {
|
|
117
|
+
const {
|
|
118
|
+
functionName, ranges, root
|
|
119
|
+
} = block;
|
|
106
120
|
|
|
107
121
|
// first one is function coverage info
|
|
108
122
|
const functionRange = ranges[0];
|
|
109
123
|
functionRange.functionName = functionName;
|
|
110
|
-
if (functionName) {
|
|
111
|
-
functionNameMap.set(functionRange.startOffset + functionName.length, functionRange);
|
|
112
|
-
}
|
|
113
124
|
|
|
114
125
|
// blocks
|
|
115
126
|
const len = ranges.length;
|
|
@@ -128,6 +139,15 @@ const generateCountState = (coverageList) => {
|
|
|
128
139
|
functionRange.blockRanges = blockRanges;
|
|
129
140
|
}
|
|
130
141
|
|
|
142
|
+
// root function
|
|
143
|
+
if (root) {
|
|
144
|
+
functionRoot = functionRange;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (functionName) {
|
|
149
|
+
functionNameMap.set(functionRange.startOffset + functionName.length, functionRange);
|
|
150
|
+
}
|
|
131
151
|
functionRanges.push(functionRange);
|
|
132
152
|
|
|
133
153
|
});
|
|
@@ -146,7 +166,8 @@ const generateCountState = (coverageList) => {
|
|
|
146
166
|
return {
|
|
147
167
|
functionMap,
|
|
148
168
|
functionNameMap,
|
|
149
|
-
functionRanges
|
|
169
|
+
functionRanges,
|
|
170
|
+
functionRoot
|
|
150
171
|
};
|
|
151
172
|
|
|
152
173
|
};
|
|
@@ -81,6 +81,14 @@ const resolveSourceMap = (data) => {
|
|
|
81
81
|
|
|
82
82
|
const saveSourceFile = async (filePath, data) => {
|
|
83
83
|
await Util.writeFile(filePath, JSON.stringify(data));
|
|
84
|
+
|
|
85
|
+
// save source and sourcemap file for debug
|
|
86
|
+
// https://evanw.github.io/source-map-visualization
|
|
87
|
+
// if (data.sourceMap) {
|
|
88
|
+
// await Util.writeFile(`${filePath}.js`, data.source);
|
|
89
|
+
// await Util.writeFile(`${filePath}.js.map`, JSON.stringify(data.sourceMap));
|
|
90
|
+
// }
|
|
91
|
+
|
|
84
92
|
};
|
|
85
93
|
|
|
86
94
|
const collectSourceMaps = async (v8list, options) => {
|
|
@@ -105,15 +113,19 @@ const collectSourceMaps = async (v8list, options) => {
|
|
|
105
113
|
continue;
|
|
106
114
|
}
|
|
107
115
|
|
|
116
|
+
// save source and sourceMap to separated json file
|
|
108
117
|
const sourceData = {
|
|
109
118
|
url,
|
|
110
119
|
id,
|
|
111
|
-
|
|
112
|
-
source: convertSourceMap.removeComments(source),
|
|
113
|
-
// could be existed
|
|
120
|
+
source,
|
|
114
121
|
sourceMap
|
|
115
122
|
};
|
|
116
123
|
|
|
124
|
+
// remove comments if not debug
|
|
125
|
+
if (Util.loggingType !== 'debug') {
|
|
126
|
+
sourceData.source = convertSourceMap.removeComments(source);
|
|
127
|
+
}
|
|
128
|
+
|
|
117
129
|
// check sourceMap only for js
|
|
118
130
|
if (type === 'js' && !sourceData.sourceMap) {
|
|
119
131
|
// from inline sync
|