monocart-coverage-reports 2.2.0 → 2.2.2
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 +138 -126
- package/lib/cli.js +1 -1
- package/lib/converter/converter.js +13 -0
- package/lib/generate.js +34 -39
- package/lib/index.d.ts +3 -0
- package/lib/istanbul/istanbul.js +3 -3
- package/lib/utils/source-path.js +2 -2
- package/lib/v8/v8.js +98 -23
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* [Usage](#usage)
|
|
12
12
|
* [Default Options](#default-options)
|
|
13
13
|
* [Available Reports](#available-reports)
|
|
14
|
-
* [
|
|
14
|
+
* [Using `entryFilter` and `sourceFilter` to filter the results for V8 report](#using-entryfilter-and-sourcefilter-to-filter-the-results-for-v8-report)
|
|
15
15
|
* [onEnd Hook](#onend-hook)
|
|
16
16
|
* [`mcr` CLI](#mcr-cli)
|
|
17
17
|
* [Compare Reports](#compare-reports)
|
|
@@ -19,13 +19,14 @@
|
|
|
19
19
|
* [Collecting Istanbul Coverage Data](#collecting-istanbul-coverage-data)
|
|
20
20
|
* [Collecting V8 Coverage Data](#collecting-v8-coverage-data)
|
|
21
21
|
* [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
|
|
22
|
-
* [
|
|
22
|
+
* [Multiprocessing Support](#multiprocessing-support)
|
|
23
|
+
* [Integration](#integration)
|
|
24
|
+
* [Ignoring Uncovered Codes](#ignoring-uncovered-codes)
|
|
25
|
+
* [Chromium Coverage API](#chromium-coverage-api)
|
|
26
|
+
* [V8 Coverage Data Format](#v8-coverage-data-format)
|
|
23
27
|
* [How to convert V8 to Istanbul](#how-to-convert-v8-to-istanbul)
|
|
24
28
|
- [Using `v8-to-istanbul`](#using-v8-to-istanbul)
|
|
25
29
|
- [How Monocart Works](#how-monocart-works)
|
|
26
|
-
* [Ignoring Uncovered Lines](#ignoring-uncovered-lines)
|
|
27
|
-
* [Chromium Coverage API](#chromium-coverage-api)
|
|
28
|
-
* [V8 Coverage Data Format](#v8-coverage-data-format)
|
|
29
30
|
* [Istanbul Introduction](#istanbul-introduction)
|
|
30
31
|
* [Thanks](#thanks)
|
|
31
32
|
|
|
@@ -54,6 +55,7 @@ console.log(coverageResults.summary);
|
|
|
54
55
|
- Build with webpack: [V8](https://cenfun.github.io/monocart-coverage-reports/v8) and [V8 Minify](https://cenfun.github.io/monocart-coverage-reports/v8-minify)
|
|
55
56
|
- Build with [Rollup](https://cenfun.github.io/monocart-coverage-reports/v8-rollup) and [Esbuild](https://cenfun.github.io/monocart-coverage-reports/v8-esbuild)
|
|
56
57
|
- Collect with [puppeteer](https://cenfun.github.io/monocart-coverage-reports/puppeteer/)
|
|
58
|
+
- [anonymous](https://cenfun.github.io/monocart-coverage-reports/anonymous/) and [css](https://cenfun.github.io/monocart-coverage-reports/css/)
|
|
57
59
|
- Test in Node.js
|
|
58
60
|
- Collect with [env](https://cenfun.github.io/monocart-coverage-reports/v8-node-env), and also V8 [API](https://cenfun.github.io/monocart-coverage-reports/v8-node-api), [Inspector](https://cenfun.github.io/monocart-coverage-reports/v8-node-ins) and [CDP](https://cenfun.github.io/monocart-coverage-reports/v8-node-cdp)
|
|
59
61
|
- Web server example: [koa](https://cenfun.github.io/monocart-coverage-reports/v8-node-koa/)
|
|
@@ -62,6 +64,8 @@ console.log(coverageResults.summary);
|
|
|
62
64
|
|
|
63
65
|
- `v8-json` (V8 data only)
|
|
64
66
|
- [V8 coverage-report.json](https://cenfun.github.io/monocart-coverage-reports/v8-and-istanbul/coverage-report.json)
|
|
67
|
+
- `codecov` (V8 data only)
|
|
68
|
+
- coverage data for [Codecov](https://docs.codecov.com/docs/codecov-custom-coverage-format), see [example](https://app.codecov.io/github/cenfun/monocart-coverage-reports)
|
|
65
69
|
- `console-summary` shows coverage summary in console
|
|
66
70
|
|
|
67
71
|

|
|
@@ -105,37 +109,34 @@ const options = {
|
|
|
105
109
|
const coverageReport = MCR(options);
|
|
106
110
|
```
|
|
107
111
|
|
|
108
|
-
##
|
|
109
|
-
|
|
110
|
-
- [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage) - Integration with [Vitest](https://github.com/vitest-dev/vitest) coverage
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
## Multiprocessing Support
|
|
114
|
-
The data will be added to `[outputDir]/.cache`, and the cache will be removed after reports generated.
|
|
115
|
-
- sub process 1
|
|
116
|
-
```js
|
|
117
|
-
const MCR = require('monocart-coverage-reports');
|
|
118
|
-
const options = require('path-to/same-options.js');
|
|
119
|
-
const coverageReport = MCR(options);
|
|
120
|
-
await coverageReport.add(coverageData1);
|
|
112
|
+
## Using `entryFilter` and `sourceFilter` to filter the results for V8 report
|
|
113
|
+
When V8 coverage data collected, it actually contains the data of all entry files, for example:
|
|
121
114
|
```
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const MCR = require('monocart-coverage-reports');
|
|
126
|
-
const options = require('path-to/same-options.js');
|
|
127
|
-
const coverageReport = MCR(options);
|
|
128
|
-
await coverageReport.add(coverageData2);
|
|
115
|
+
dist/main.js
|
|
116
|
+
dist/vendor.js
|
|
117
|
+
dist/something-else.js
|
|
129
118
|
```
|
|
130
|
-
|
|
131
|
-
|
|
119
|
+
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.
|
|
120
|
+
```
|
|
121
|
+
dist/main.js
|
|
122
|
+
```
|
|
123
|
+
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`.
|
|
124
|
+
```
|
|
125
|
+
> src/index.js
|
|
126
|
+
> src/components/app.js
|
|
127
|
+
> node_modules/dependency/dist/dependency.js
|
|
128
|
+
```
|
|
129
|
+
We can use `sourceFilter` to filter the source files. For example, we should remove `dependency.js` if it is not in our coverage scope.
|
|
130
|
+
```
|
|
131
|
+
> src/index.js
|
|
132
|
+
> src/components/app.js
|
|
133
|
+
```
|
|
134
|
+
Example:
|
|
132
135
|
```js
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const coverageResults = await coverageReport.generate();
|
|
138
|
-
console.log(coverageResults.summary);
|
|
136
|
+
const coverageOptions = {
|
|
137
|
+
entryFilter: (entry) => entry.url.indexOf("main.js") !== -1,
|
|
138
|
+
sourceFilter: (sourcePath) => sourcePath.search(/src\//) !== -1
|
|
139
|
+
};
|
|
139
140
|
```
|
|
140
141
|
|
|
141
142
|
## onEnd Hook
|
|
@@ -170,6 +171,7 @@ const coverageOptions = {
|
|
|
170
171
|
```
|
|
171
172
|
|
|
172
173
|
## `mcr` CLI
|
|
174
|
+
> The CLI will run the program as a [child process](https://nodejs.org/docs/latest/api/child_process.html) with `NODE_V8_COVERAGE=dir` until it exits gracefully, and generate the coverage report with the coverage data from the `dir`.
|
|
173
175
|
- Global mode
|
|
174
176
|
```sh
|
|
175
177
|
npm i monocart-coverage-reports -g
|
|
@@ -201,7 +203,7 @@ Options:
|
|
|
201
203
|
--lcov generate lcov.info file
|
|
202
204
|
-h, --help display help for command
|
|
203
205
|
```
|
|
204
|
-
- Using configuration file for more options
|
|
206
|
+
- Using configuration file for more [options](#default-options)
|
|
205
207
|
```sh
|
|
206
208
|
mcr "node ./test/test-node-env.js" -c test/cli-options.js
|
|
207
209
|
```
|
|
@@ -234,139 +236,103 @@ mcr "node ./test/test-node-env.js" -c test/cli-options.js
|
|
|
234
236
|
## Collecting Istanbul Coverage Data
|
|
235
237
|
- Instrumenting source code
|
|
236
238
|
> Before collecting Istanbul coverage data, It requires your source code is instrumented with Istanbul
|
|
237
|
-
-
|
|
238
|
-
-
|
|
239
|
-
-
|
|
239
|
+
- webpack: [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul), example: [webpack.config-istanbul.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/webpack.config-istanbul.js)
|
|
240
|
+
- rollup: [rollup-plugin-istanbul](https://github.com/artberri/rollup-plugin-istanbul)
|
|
241
|
+
- vite: [vite-plugin-istanbul](https://github.com/ifaxity/vite-plugin-istanbul)
|
|
240
242
|
- Browser
|
|
241
|
-
|
|
243
|
+
- Collecting coverage data from `window.__coverage__`, example: [test-istanbul.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-istanbul.js)
|
|
242
244
|
- Node.js
|
|
243
|
-
|
|
245
|
+
- Collecting coverage data from `global.__coverage__`
|
|
244
246
|
|
|
245
247
|
## Collecting V8 Coverage Data
|
|
246
248
|
- For source code: enable `sourcemap` and do not compress/minify:
|
|
247
|
-
-
|
|
248
|
-
-
|
|
249
|
-
-
|
|
249
|
+
- [webpack](https://webpack.js.org/configuration/): `devtool: source-map` and `mode: development`, example [webpack.config-v8.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/webpack.config-v8.js)
|
|
250
|
+
- [rollup](https://rollupjs.org/configuration-options/): `sourcemap: true`
|
|
251
|
+
- [vite](https://vitejs.dev/config/build-options.html): `sourcemap: true` and `minify: false`
|
|
252
|
+
- [esbuild](https://esbuild.github.io/api/): `sourcemap: true` and `minify: false`
|
|
250
253
|
- Browser (Chromium Only)
|
|
251
|
-
> Collecting coverage data with [Chromium Coverage API](#chromium-coverage-api)
|
|
254
|
+
> Collecting coverage data with [Chromium Coverage API](#chromium-coverage-api):
|
|
255
|
+
- [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)
|
|
256
|
+
- [Puppeteer example](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-puppeteer.js)
|
|
252
257
|
- Node.js
|
|
253
|
-
|
|
258
|
+
- see following [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
|
|
254
259
|
|
|
255
260
|
|
|
256
261
|
## Node.js V8 Coverage Report for Server Side
|
|
257
262
|
Possible solutions:
|
|
258
|
-
-
|
|
259
|
-
-
|
|
260
|
-
- Read the
|
|
261
|
-
-
|
|
263
|
+
- [NODE_V8_COVERAGE](https://nodejs.org/docs/latest/api/cli.html#node_v8_coveragedir)=`dir`
|
|
264
|
+
- Sets Node.js env `NODE_V8_COVERAGE`=`dir` before the program running, the coverage data will be saved to the `dir` after the program exits gracefully.
|
|
265
|
+
- Read the JSON file(s) from the `dir` and generate coverage report.
|
|
266
|
+
- Example:
|
|
262
267
|
> 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)
|
|
263
268
|
|
|
264
269
|
- [V8](https://nodejs.org/docs/latest/api/v8.html#v8takecoverage) API + NODE_V8_COVERAGE
|
|
265
|
-
- Writing the coverage started by NODE_V8_COVERAGE to disk on demand with `v8.takeCoverage()
|
|
266
|
-
-
|
|
270
|
+
- Writing the coverage started by NODE_V8_COVERAGE to disk on demand with `v8.takeCoverage()`, it does not require waiting until the program exits gracefully.
|
|
271
|
+
- Example:
|
|
267
272
|
> cross-env NODE_V8_COVERAGE=`.temp/v8-coverage-api` node [./test/test-node-api.js](./test/test-node-api.js)
|
|
268
273
|
|
|
269
|
-
- [Inspector](https://nodejs.org/docs/latest/api/inspector.html) API
|
|
274
|
+
- [Inspector](https://nodejs.org/docs/latest/api/inspector.html) API
|
|
270
275
|
- Connecting to the V8 inspector and enable V8 coverage.
|
|
271
|
-
- Taking coverage data and adding to the report
|
|
272
|
-
-
|
|
276
|
+
- Taking coverage data and adding it to the report.
|
|
277
|
+
- Example:
|
|
278
|
+
> node [./test/test-node-ins.js](./test/test-node-ins.js)
|
|
273
279
|
|
|
274
280
|
- [CDP](https://chromedevtools.github.io/devtools-protocol/) API
|
|
275
|
-
- Enabling [Node Debugging](https://nodejs.org/en/guides/debugging-getting-started/)
|
|
281
|
+
- Enabling [Node Debugging](https://nodejs.org/en/guides/debugging-getting-started/).
|
|
276
282
|
- Collecting coverage data with CDP API.
|
|
277
|
-
-
|
|
283
|
+
- Example:
|
|
284
|
+
> node --inspect=9229 [./test/test-node-cdp.js](./test/test-node-cdp.js)
|
|
278
285
|
|
|
279
|
-
- [
|
|
280
|
-
|
|
281
|
-
-
|
|
286
|
+
- [Node Debugging](https://nodejs.org/en/guides/debugging-getting-started) + CDP + NODE_V8_COVERAGE + V8 API
|
|
287
|
+
- When the program starts a server, it will not exit on its own, thus requiring a manual invocation of the `v8.takeCoverage()` interface to manually collect coverage data. Remote invocation of the `v8.takeCoverage()` interface can be accomplished through the `Runtime.evaluate` of the CDP.
|
|
288
|
+
- Example for [koa](https://github.com/koajs/koa) web server:
|
|
282
289
|
> node [./test/test-node-koa.js](./test/test-node-koa.js)
|
|
283
290
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
```
|
|
291
|
-
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.
|
|
292
|
-
```
|
|
293
|
-
dist/main.js
|
|
294
|
-
```
|
|
295
|
-
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`.
|
|
296
|
-
```
|
|
297
|
-
> src/index.js
|
|
298
|
-
> src/components/app.js
|
|
299
|
-
> node_modules/dependency/dist/dependency.js
|
|
300
|
-
```
|
|
301
|
-
We can use `sourceFilter` to filter the source files. For example, we should remove `dependency.js` if it is not in our coverage scope.
|
|
302
|
-
```
|
|
303
|
-
> src/index.js
|
|
304
|
-
> src/components/app.js
|
|
305
|
-
```
|
|
306
|
-
Example:
|
|
291
|
+
- [Child Process](https://nodejs.org/docs/latest/api/child_process.html) + NODE_V8_COVERAGE
|
|
292
|
+
- see [`mcr` CLI](#mcr-cli)
|
|
293
|
+
|
|
294
|
+
## Multiprocessing Support
|
|
295
|
+
The data will be added to `[outputDir]/.cache`, and the cache will be removed after reports generated.
|
|
296
|
+
- sub process 1
|
|
307
297
|
```js
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
298
|
+
const MCR = require('monocart-coverage-reports');
|
|
299
|
+
const options = require('path-to/same-options.js');
|
|
300
|
+
const coverageReport = MCR(options);
|
|
301
|
+
await coverageReport.add(coverageData1);
|
|
312
302
|
```
|
|
313
303
|
|
|
314
|
-
|
|
315
|
-
### Using [v8-to-istanbul](https://github.com/istanbuljs/v8-to-istanbul)
|
|
316
|
-
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:
|
|
317
|
-
- 1, The source mapping does not work well if the position is between the two consecutive mappings. for example:
|
|
304
|
+
- sub process 2
|
|
318
305
|
```js
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
306
|
+
const MCR = require('monocart-coverage-reports');
|
|
307
|
+
const options = require('path-to/same-options.js');
|
|
308
|
+
const coverageReport = MCR(options);
|
|
309
|
+
await coverageReport.add(coverageData2);
|
|
322
310
|
```
|
|
323
|
-
> `m1` and `m2` are two consecutive mappings, `p` is the position we looking for. However, we can only get the position of the `m1` if we don't fix it to `p`. Especially the generated code is different from the original code, such as minified, compressed or converted, then it becomes very difficult to find the middle position between two mappings.
|
|
324
311
|
|
|
325
|
-
-
|
|
326
|
-
And also there are some problems about counting the functions and branches:
|
|
312
|
+
- main process
|
|
327
313
|
```js
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
// v8-to-istanbul: new CovFunction()
|
|
335
|
-
// Problem: no anonymous function
|
|
336
|
-
}
|
|
337
|
-
} else if (block.functionName) {
|
|
338
|
-
// v8-to-istanbul: new CovFunction()
|
|
339
|
-
// Problem: no anonymous function
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
// Problem: When the function or parent-level function is uncovered, then its sub-level functions will never be counted.
|
|
314
|
+
// after all sub processes finished
|
|
315
|
+
const MCR = require('monocart-coverage-reports');
|
|
316
|
+
const options = require('path-to/same-options.js');
|
|
317
|
+
const coverageReport = MCR(options);
|
|
318
|
+
const coverageResults = await coverageReport.generate();
|
|
319
|
+
console.log(coverageResults.summary);
|
|
344
320
|
```
|
|
345
|
-
see source code [v8-to-istanbul.js](https://github.com/istanbuljs/v8-to-istanbul/blob/master/lib/v8-to-istanbul.js)
|
|
346
|
-
|
|
347
|
-
### How Monocart Works
|
|
348
|
-
We have removed `v8-to-istanbul` because of the two major problems and implemented new converter:
|
|
349
|
-
- 1, Trying to fix the middle position if not found the exact mapping.
|
|
350
|
-
- 2, Finding all functions and branches by parsing the source code [AST](https://github.com/acornjs/acorn), however the V8 cannot provide effective branch coverage information, so the branches is still not perfect.
|
|
351
321
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
| ConditionalExpression | ✔ Not Perfect |
|
|
356
|
-
| IfStatement | ✔ Not Perfect |
|
|
357
|
-
| LogicalExpression | ✔ Not Perfect |
|
|
358
|
-
| SwitchStatement | ✔ Not Perfect |
|
|
322
|
+
## Integration
|
|
323
|
+
- [monocart-reporter](https://cenfun.github.io/monocart-reporter/) - Test reporter for [Playwright](https://github.com/microsoft/playwright)
|
|
324
|
+
- [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage) - Integration with [Vitest](https://github.com/vitest-dev/vitest) coverage
|
|
359
325
|
|
|
360
326
|
## Ignoring Uncovered Codes
|
|
361
327
|
To ignore codes, use the special comment which starts with `v8 ignore `:
|
|
362
|
-
-
|
|
328
|
+
- Ignoring all until stop
|
|
363
329
|
```js
|
|
364
330
|
/* v8 ignore start */
|
|
365
331
|
function uncovered() {
|
|
366
332
|
}
|
|
367
333
|
/* v8 ignore stop */
|
|
368
334
|
```
|
|
369
|
-
-
|
|
335
|
+
- Ignoring the next line or next N lines
|
|
370
336
|
```js
|
|
371
337
|
/* v8 ignore next */
|
|
372
338
|
const os = platform === 'wind32' ? 'Windows' : 'Other';
|
|
@@ -448,6 +414,52 @@ const rawV8CoverageData = jsCoverage.map((it) => {
|
|
|
448
414
|
```
|
|
449
415
|
see example: [./test/test-puppeteer.js](./test/test-puppeteer.js)
|
|
450
416
|
|
|
417
|
+
## How to convert V8 to Istanbul
|
|
418
|
+
### Using [v8-to-istanbul](https://github.com/istanbuljs/v8-to-istanbul)
|
|
419
|
+
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:
|
|
420
|
+
- 1, The source mapping does not work well if the position is between the two consecutive mappings. for example:
|
|
421
|
+
```js
|
|
422
|
+
const a = tf ? 'true' : 'false';
|
|
423
|
+
^ ^ ^
|
|
424
|
+
m1 p m2
|
|
425
|
+
```
|
|
426
|
+
> `m1` and `m2` are two consecutive mappings, `p` is the position we looking for. However, we can only get the position of the `m1` or `m2` if we don't fix it to `p`. Especially the generated code is different from the original code, such as the code was minified, compressed or converted, it is difficult to find the exact position.
|
|
427
|
+
|
|
428
|
+
- 2, The coverage of functions and branches is incorrect. V8 only provided coverage at functions and it's blocks. But if a function is uncovered (count = 0), there is no information for it's blocks and sub-level functions.
|
|
429
|
+
And also there are some problems about counting the functions and branches:
|
|
430
|
+
```js
|
|
431
|
+
// Problem: When the function or parent-level function is uncovered, then its sub-level functions will never be counted.
|
|
432
|
+
functions.forEach(block => {
|
|
433
|
+
block.ranges.forEach((range, i) => {
|
|
434
|
+
if (block.isBlockCoverage) {
|
|
435
|
+
// v8-to-istanbul: new CovBranch()
|
|
436
|
+
// Problem: not every block is branch, and the first block could be function.
|
|
437
|
+
if (block.functionName && i === 0) {
|
|
438
|
+
// v8-to-istanbul: new CovFunction()
|
|
439
|
+
// Problem: no anonymous function
|
|
440
|
+
}
|
|
441
|
+
} else if (block.functionName) {
|
|
442
|
+
// v8-to-istanbul: new CovFunction()
|
|
443
|
+
// Problem: no anonymous function
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
see source code [v8-to-istanbul.js](https://github.com/istanbuljs/v8-to-istanbul/blob/master/lib/v8-to-istanbul.js)
|
|
449
|
+
|
|
450
|
+
### How Monocart Works
|
|
451
|
+
We implemented new converter instead of v8-to-istanbul:
|
|
452
|
+
- 1, Trying to fix the middle position if not found the exact mapping for the position.
|
|
453
|
+
- 2, Finding all functions and branches by parsing the source code [AST](https://github.com/acornjs/acorn), however the V8 cannot provide effective branch coverage information, so the branches is still not perfect but close to reality.
|
|
454
|
+
|
|
455
|
+
| AST | V8 |
|
|
456
|
+
| :---------------------| :------------- |
|
|
457
|
+
| AssignmentPattern | 🛇 Not Support |
|
|
458
|
+
| ConditionalExpression | ✔ Not Perfect |
|
|
459
|
+
| IfStatement | ✔ Not Perfect |
|
|
460
|
+
| LogicalExpression | ✔ Not Perfect |
|
|
461
|
+
| SwitchStatement | ✔ Not Perfect |
|
|
462
|
+
|
|
451
463
|
## Istanbul Introduction
|
|
452
464
|
- [Istanbul coverage report](https://istanbul.js.org/) - Instrumenting source codes and generating coverage reports
|
|
453
465
|
- [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul)
|
package/lib/cli.js
CHANGED
|
@@ -313,9 +313,21 @@ const collectFileCoverage = (item, state, coverageData, options) => {
|
|
|
313
313
|
b: {}
|
|
314
314
|
};
|
|
315
315
|
|
|
316
|
+
// for codecov https://docs.codecov.com/docs/codecov-custom-coverage-format
|
|
317
|
+
data.lines = {};
|
|
318
|
+
|
|
316
319
|
lines.filter((it) => !it.ignored).forEach((line, index) => {
|
|
317
320
|
istanbulCoverage.statementMap[`${index}`] = line.generate();
|
|
318
321
|
istanbulCoverage.s[`${index}`] = line.count;
|
|
322
|
+
|
|
323
|
+
let count = 0;
|
|
324
|
+
if (line.covered) {
|
|
325
|
+
count = line.count;
|
|
326
|
+
} else if (!line.covered && line.count > 0) {
|
|
327
|
+
count = '1/2';
|
|
328
|
+
}
|
|
329
|
+
// 1-base
|
|
330
|
+
data.lines[`${line.line}`] = count;
|
|
319
331
|
});
|
|
320
332
|
|
|
321
333
|
functions.filter((it) => !it.ignored).forEach((fn, index) => {
|
|
@@ -987,6 +999,7 @@ const convertV8List = async (v8list, options) => {
|
|
|
987
999
|
if (sourceList.length) {
|
|
988
1000
|
sourceList.forEach((item) => {
|
|
989
1001
|
|
|
1002
|
+
// second time filter for empty source
|
|
990
1003
|
// exists same id, mark previous item as dedupe
|
|
991
1004
|
const prevItem = v8list.find((it) => it.id === item.id);
|
|
992
1005
|
if (prevItem) {
|
package/lib/generate.js
CHANGED
|
@@ -67,59 +67,50 @@ const getReportGroup = (reports, lcov, dataType) => {
|
|
|
67
67
|
reportMap[defaultReport] = {};
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
const allSupportedReports =
|
|
70
|
+
const allSupportedReports = {
|
|
71
71
|
|
|
72
72
|
// v8
|
|
73
|
-
'v8',
|
|
74
|
-
'v8-json',
|
|
73
|
+
'v8': 'v8',
|
|
74
|
+
'v8-json': 'v8',
|
|
75
|
+
'codecov': 'v8',
|
|
75
76
|
|
|
76
77
|
// both
|
|
77
|
-
'console-summary',
|
|
78
|
+
'console-summary': 'both',
|
|
78
79
|
|
|
79
80
|
// istanbul
|
|
80
|
-
'clover',
|
|
81
|
-
'cobertura',
|
|
82
|
-
'html',
|
|
83
|
-
'html-spa',
|
|
84
|
-
'json',
|
|
85
|
-
'json-summary',
|
|
86
|
-
'lcov',
|
|
87
|
-
'lcovonly',
|
|
88
|
-
'none',
|
|
89
|
-
'teamcity',
|
|
90
|
-
'text',
|
|
91
|
-
'text-lcov',
|
|
92
|
-
'text-summary'
|
|
93
|
-
|
|
81
|
+
'clover': 'istanbul',
|
|
82
|
+
'cobertura': 'istanbul',
|
|
83
|
+
'html': 'istanbul',
|
|
84
|
+
'html-spa': 'istanbul',
|
|
85
|
+
'json': 'istanbul',
|
|
86
|
+
'json-summary': 'istanbul',
|
|
87
|
+
'lcov': 'istanbul',
|
|
88
|
+
'lcovonly': 'istanbul',
|
|
89
|
+
'none': 'istanbul',
|
|
90
|
+
'teamcity': 'istanbul',
|
|
91
|
+
'text': 'istanbul',
|
|
92
|
+
'text-lcov': 'istanbul',
|
|
93
|
+
'text-summary': 'istanbul'
|
|
94
|
+
};
|
|
94
95
|
|
|
95
96
|
// group v8 and istanbul
|
|
96
97
|
const reportGroup = {};
|
|
97
98
|
Object.keys(reportMap).forEach((k) => {
|
|
98
|
-
if (!allSupportedReports.includes(k)) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
99
|
|
|
102
|
-
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
if (!reportGroup.v8) {
|
|
106
|
-
reportGroup.v8 = {};
|
|
107
|
-
}
|
|
108
|
-
reportGroup.v8[k] = reportMap[k];
|
|
100
|
+
const groupName = allSupportedReports[k];
|
|
101
|
+
if (!groupName) {
|
|
102
|
+
Util.logError(`Unsupported report: ${k}`);
|
|
109
103
|
return;
|
|
110
104
|
}
|
|
111
105
|
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
106
|
+
let group = reportGroup[groupName];
|
|
107
|
+
if (!group) {
|
|
108
|
+
group = {};
|
|
109
|
+
reportGroup[groupName] = group;
|
|
116
110
|
}
|
|
117
111
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
reportGroup.istanbul = {};
|
|
121
|
-
}
|
|
122
|
-
reportGroup.istanbul[k] = reportMap[k];
|
|
112
|
+
group[k] = reportMap[k];
|
|
113
|
+
|
|
123
114
|
});
|
|
124
115
|
|
|
125
116
|
return reportGroup;
|
|
@@ -156,8 +147,12 @@ const nFormatter = (v) => {
|
|
|
156
147
|
|
|
157
148
|
const showConsoleSummary = (coverageResults, options) => {
|
|
158
149
|
|
|
159
|
-
const
|
|
160
|
-
|
|
150
|
+
const bothGroup = options.reportGroup.both;
|
|
151
|
+
if (!bothGroup) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const consoleOptions = bothGroup['console-summary'];
|
|
161
156
|
if (!consoleOptions) {
|
|
162
157
|
return;
|
|
163
158
|
}
|
package/lib/index.d.ts
CHANGED
|
@@ -34,6 +34,9 @@ declare namespace MCR {
|
|
|
34
34
|
['v8-json'] | ["v8-json", {
|
|
35
35
|
outputFile?: string;
|
|
36
36
|
}] |
|
|
37
|
+
['codecov'] | ["codecov", {
|
|
38
|
+
outputFile?: string;
|
|
39
|
+
}] |
|
|
37
40
|
['console-summary'] | ['console-summary', {
|
|
38
41
|
metrics?: Array<"bytes" | "functions" | "branches" | "lines" | "statements">;
|
|
39
42
|
}] |
|
package/lib/istanbul/istanbul.js
CHANGED
|
@@ -87,9 +87,9 @@ const saveIstanbulReports = (coverageData, fileSources, options) => {
|
|
|
87
87
|
// create a context for report generation
|
|
88
88
|
const context = istanbulLibReport.createContext(contextOptions);
|
|
89
89
|
|
|
90
|
-
const
|
|
91
|
-
Object.keys(
|
|
92
|
-
const report = istanbulReports.create(reportName,
|
|
90
|
+
const istanbulGroup = options.reportGroup.istanbul;
|
|
91
|
+
Object.keys(istanbulGroup).forEach((reportName) => {
|
|
92
|
+
const report = istanbulReports.create(reportName, istanbulGroup[reportName]);
|
|
93
93
|
report.execute(context);
|
|
94
94
|
});
|
|
95
95
|
|
package/lib/utils/source-path.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
-
const { fileURLToPath } = require('url');
|
|
2
|
+
const { fileURLToPath, pathToFileURL } = require('url');
|
|
3
3
|
const Util = require('./util.js');
|
|
4
4
|
|
|
5
5
|
const resolveUrl = (input) => {
|
|
@@ -36,7 +36,7 @@ const getSourcePath = (url, index = '', type = '') => {
|
|
|
36
36
|
|
|
37
37
|
if (!url) {
|
|
38
38
|
// anonymous scripts will have __playwright_evaluation_script__ as their URL.
|
|
39
|
-
url = ['
|
|
39
|
+
url = pathToFileURL(['anonymous', index ? `-${index}` : '', type ? `.${type}` : ''].join('')).toString();
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
if (url.startsWith('file:')) {
|
package/lib/v8/v8.js
CHANGED
|
@@ -21,9 +21,18 @@ const initV8ListAndSourcemap = async (v8list, options) => {
|
|
|
21
21
|
|
|
22
22
|
v8list = v8list.filter((item) => {
|
|
23
23
|
if (typeof item.source === 'string' && item.functions) {
|
|
24
|
+
item.type = 'js';
|
|
24
25
|
return true;
|
|
25
26
|
}
|
|
26
27
|
if (typeof item.text === 'string' && item.ranges) {
|
|
28
|
+
item.type = 'css';
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
// allow empty coverage
|
|
32
|
+
if (item.empty) {
|
|
33
|
+
if (!['js', 'css'].includes(item.type)) {
|
|
34
|
+
item.type = 'js';
|
|
35
|
+
}
|
|
27
36
|
return true;
|
|
28
37
|
}
|
|
29
38
|
// 404 css, text will be empty
|
|
@@ -39,22 +48,30 @@ const initV8ListAndSourcemap = async (v8list, options) => {
|
|
|
39
48
|
// init id, sourcePath
|
|
40
49
|
v8list = v8list.map((item, i) => {
|
|
41
50
|
|
|
51
|
+
const type = item.type;
|
|
52
|
+
|
|
42
53
|
const data = {
|
|
43
|
-
url: item.url
|
|
54
|
+
url: item.url,
|
|
55
|
+
type
|
|
44
56
|
};
|
|
45
57
|
|
|
46
|
-
if (
|
|
47
|
-
data.type = 'js';
|
|
58
|
+
if (type === 'js') {
|
|
48
59
|
data.functions = item.functions;
|
|
49
60
|
data.source = item.source;
|
|
50
61
|
// could be existed
|
|
51
62
|
data.sourceMap = item.sourceMap;
|
|
52
|
-
} else
|
|
53
|
-
|
|
63
|
+
} else {
|
|
64
|
+
// css
|
|
54
65
|
data.ranges = item.ranges;
|
|
55
66
|
data.source = item.text;
|
|
56
67
|
}
|
|
57
68
|
|
|
69
|
+
|
|
70
|
+
// no functions and ranges
|
|
71
|
+
if (item.empty) {
|
|
72
|
+
data.empty = true;
|
|
73
|
+
}
|
|
74
|
+
|
|
58
75
|
const idList = [];
|
|
59
76
|
// could be existed
|
|
60
77
|
const distFile = item.distFile;
|
|
@@ -122,17 +139,17 @@ const mergeJsFunctions = (itemList) => {
|
|
|
122
139
|
|
|
123
140
|
const mergeV8Coverage = async (dataList, options) => {
|
|
124
141
|
|
|
125
|
-
let
|
|
142
|
+
let allList = [];
|
|
126
143
|
dataList.forEach((d) => {
|
|
127
|
-
|
|
144
|
+
allList = allList.concat(d.data);
|
|
128
145
|
});
|
|
129
146
|
|
|
130
|
-
|
|
147
|
+
const coverageList = allList.filter((it) => !it.empty);
|
|
131
148
|
|
|
132
149
|
// connect all functions and ranges
|
|
133
150
|
const itemMap = {};
|
|
134
151
|
const mergeMap = {};
|
|
135
|
-
|
|
152
|
+
coverageList.forEach((item) => {
|
|
136
153
|
const { id } = item;
|
|
137
154
|
const prev = itemMap[id];
|
|
138
155
|
if (prev) {
|
|
@@ -158,11 +175,17 @@ const mergeV8Coverage = async (dataList, options) => {
|
|
|
158
175
|
}
|
|
159
176
|
}
|
|
160
177
|
|
|
161
|
-
|
|
178
|
+
// first time filter for empty, (not for sources)
|
|
179
|
+
const emptyList = allList.filter((it) => it.empty).filter((it) => !itemMap[it.id]);
|
|
180
|
+
|
|
181
|
+
// empty list + merged list
|
|
182
|
+
const mergedList = emptyList.concat(Object.values(itemMap));
|
|
162
183
|
|
|
163
184
|
// try to load coverage and source by id
|
|
164
|
-
for (const item of
|
|
165
|
-
const {
|
|
185
|
+
for (const item of mergedList) {
|
|
186
|
+
const {
|
|
187
|
+
id, type, empty
|
|
188
|
+
} = item;
|
|
166
189
|
const sourcePath = Util.resolveCacheSourcePath(options.cacheDir, id);
|
|
167
190
|
const content = await Util.readFile(sourcePath);
|
|
168
191
|
if (content) {
|
|
@@ -173,24 +196,63 @@ const mergeV8Coverage = async (dataList, options) => {
|
|
|
173
196
|
Util.logError(`failed to read source: ${item.url}`);
|
|
174
197
|
item.source = '';
|
|
175
198
|
}
|
|
199
|
+
|
|
200
|
+
// add empty coverage after merged
|
|
201
|
+
if (empty) {
|
|
202
|
+
if (type === 'js') {
|
|
203
|
+
item.functions = [{
|
|
204
|
+
functionName: '',
|
|
205
|
+
ranges: [{
|
|
206
|
+
startOffset: 0,
|
|
207
|
+
endOffset: item.source.length,
|
|
208
|
+
count: 0
|
|
209
|
+
}]
|
|
210
|
+
}];
|
|
211
|
+
} else {
|
|
212
|
+
// empty css
|
|
213
|
+
item.ranges = [];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
176
217
|
}
|
|
177
218
|
|
|
178
|
-
return
|
|
219
|
+
return mergedList;
|
|
179
220
|
};
|
|
180
221
|
|
|
181
222
|
// ============================================================
|
|
182
223
|
|
|
224
|
+
const saveCodecovReport = async (reportData, options, codecovOptions) => {
|
|
225
|
+
const mergedOptions = {
|
|
226
|
+
outputFile: 'codecov.json',
|
|
227
|
+
... codecovOptions
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// console.log(mergedOptions);
|
|
231
|
+
const jsonPath = path.resolve(options.outputDir, mergedOptions.outputFile);
|
|
232
|
+
|
|
233
|
+
// https://docs.codecov.com/docs/codecov-custom-coverage-format
|
|
234
|
+
const coverage = {};
|
|
235
|
+
reportData.files.forEach((item) => {
|
|
236
|
+
const { sourcePath, data } = item;
|
|
237
|
+
coverage[sourcePath] = data.lines;
|
|
238
|
+
});
|
|
239
|
+
const codecovData = {
|
|
240
|
+
coverage
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
await Util.writeFile(jsonPath, JSON.stringify(codecovData));
|
|
244
|
+
return Util.relativePath(jsonPath);
|
|
245
|
+
};
|
|
246
|
+
|
|
183
247
|
const saveV8JsonReport = async (reportData, options, v8JsonOptions) => {
|
|
184
|
-
const
|
|
248
|
+
const mergedOptions = {
|
|
185
249
|
outputFile: 'coverage-report.json',
|
|
186
250
|
... v8JsonOptions
|
|
187
251
|
};
|
|
188
252
|
|
|
189
|
-
// console.log(
|
|
190
|
-
const jsonPath = path.resolve(options.outputDir,
|
|
191
|
-
// console.log(reportPath);
|
|
253
|
+
// console.log(mergedOptions);
|
|
254
|
+
const jsonPath = path.resolve(options.outputDir, mergedOptions.outputFile);
|
|
192
255
|
await Util.writeFile(jsonPath, JSON.stringify(reportData));
|
|
193
|
-
|
|
194
256
|
return Util.relativePath(jsonPath);
|
|
195
257
|
};
|
|
196
258
|
|
|
@@ -255,10 +317,10 @@ const saveV8Report = async (v8list, options) => {
|
|
|
255
317
|
let outputPath;
|
|
256
318
|
|
|
257
319
|
// v8 reports
|
|
258
|
-
const
|
|
320
|
+
const v8Group = options.reportGroup.v8;
|
|
259
321
|
|
|
260
322
|
// v8 html
|
|
261
|
-
const v8Options =
|
|
323
|
+
const v8Options = v8Group.v8;
|
|
262
324
|
if (v8Options) {
|
|
263
325
|
|
|
264
326
|
// V8 only options, merged with root options
|
|
@@ -276,12 +338,25 @@ const saveV8Report = async (v8list, options) => {
|
|
|
276
338
|
outputPath = await saveV8HtmlReport(reportData, options, v8HtmlOptions);
|
|
277
339
|
}
|
|
278
340
|
|
|
279
|
-
// v8 json
|
|
280
|
-
const v8JsonOptions =
|
|
341
|
+
// v8 json (after html for metrics data)
|
|
342
|
+
const v8JsonOptions = v8Group['v8-json'];
|
|
281
343
|
if (v8JsonOptions) {
|
|
282
|
-
|
|
344
|
+
const jsonPath = await saveV8JsonReport(reportData, options, v8JsonOptions);
|
|
345
|
+
if (!outputPath) {
|
|
346
|
+
outputPath = jsonPath;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// codecov
|
|
351
|
+
const codecovOptions = v8Group.codecov;
|
|
352
|
+
if (codecovOptions) {
|
|
353
|
+
const jsonPath = await saveCodecovReport(reportData, options, codecovOptions);
|
|
354
|
+
if (!outputPath) {
|
|
355
|
+
outputPath = jsonPath;
|
|
356
|
+
}
|
|
283
357
|
}
|
|
284
358
|
|
|
359
|
+
// outputPath last one is html
|
|
285
360
|
const reportPath = Util.resolveReportPath(options, () => {
|
|
286
361
|
return outputPath;
|
|
287
362
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "monocart-coverage-reports",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.2",
|
|
4
4
|
"description": "Monocart coverage reports",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"test-node-cdp": "node --inspect=9229 ./test/test-node-cdp.js",
|
|
29
29
|
"test-node-koa": "node ./test/test-node-koa.js",
|
|
30
30
|
"test-browser": "node ./test/test.js",
|
|
31
|
-
"test-cli": "npx mcr \"node ./test/test-node-env.js\" -o docs/cli -
|
|
31
|
+
"test-cli": "npx mcr \"node ./test/test-node-env.js\" -o docs/cli -c test/cli-options.js",
|
|
32
32
|
"test": "npm run test-browser && npm run test-node && npm run test-cli && npm run build-docs",
|
|
33
33
|
"dev": "sf d v8",
|
|
34
34
|
"open": "node ./scripts/open.js",
|