monocart-coverage-reports 2.1.0 → 2.2.1
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 +205 -128
- package/lib/cli.js +147 -0
- package/lib/converter/ast.js +1 -1
- package/lib/converter/converter.js +14 -5
- package/lib/index.d.ts +8 -6
- package/lib/packages/monocart-coverage-v8.js +1 -1
- package/lib/packages/monocart-coverage-vendor.js +79 -38
- package/lib/utils/source-path.js +14 -9
- package/lib/v8/v8.js +12 -1
- package/package.json +12 -6
package/README.md
CHANGED
|
@@ -11,20 +11,22 @@
|
|
|
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
|
+
* [`mcr` CLI](#mcr-cli)
|
|
16
17
|
* [Compare Reports](#compare-reports)
|
|
17
18
|
* [Compare Workflows](#compare-workflows)
|
|
18
19
|
* [Collecting Istanbul Coverage Data](#collecting-istanbul-coverage-data)
|
|
19
20
|
* [Collecting V8 Coverage Data](#collecting-v8-coverage-data)
|
|
20
21
|
* [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
|
|
21
|
-
* [
|
|
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)
|
|
22
27
|
* [How to convert V8 to Istanbul](#how-to-convert-v8-to-istanbul)
|
|
23
28
|
- [Using `v8-to-istanbul`](#using-v8-to-istanbul)
|
|
24
29
|
- [How Monocart Works](#how-monocart-works)
|
|
25
|
-
* [Ignoring Uncovered Lines](#ignoring-uncovered-lines)
|
|
26
|
-
* [Chromium Coverage API](#chromium-coverage-api)
|
|
27
|
-
* [V8 Coverage Data Format](#v8-coverage-data-format)
|
|
28
30
|
* [Istanbul Introduction](#istanbul-introduction)
|
|
29
31
|
* [Thanks](#thanks)
|
|
30
32
|
|
|
@@ -49,9 +51,14 @@ console.log(coverageResults.summary);
|
|
|
49
51
|
|
|
50
52
|
## Available Reports
|
|
51
53
|
- `v8` (V8 data only)
|
|
52
|
-
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
- Test in browser
|
|
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)
|
|
56
|
+
- Build with [Rollup](https://cenfun.github.io/monocart-coverage-reports/v8-rollup) and [Esbuild](https://cenfun.github.io/monocart-coverage-reports/v8-esbuild)
|
|
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/)
|
|
59
|
+
- Test in Node.js
|
|
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)
|
|
61
|
+
- Web server example: [koa](https://cenfun.github.io/monocart-coverage-reports/v8-node-koa/)
|
|
55
62
|
|
|
56
63
|

|
|
57
64
|
|
|
@@ -100,37 +107,34 @@ const options = {
|
|
|
100
107
|
const coverageReport = MCR(options);
|
|
101
108
|
```
|
|
102
109
|
|
|
103
|
-
##
|
|
104
|
-
|
|
105
|
-
- [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage) - Integration with [Vitest](https://github.com/vitest-dev/vitest) coverage
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
## Multiprocessing Support
|
|
109
|
-
The data will be added to `[outputDir]/.cache`, and the cache will be removed after reports generated.
|
|
110
|
-
- sub process 1
|
|
111
|
-
```js
|
|
112
|
-
const MCR = require('monocart-coverage-reports');
|
|
113
|
-
const options = require('path-to/same-options.js');
|
|
114
|
-
const coverageReport = MCR(options);
|
|
115
|
-
await coverageReport.add(coverageData1);
|
|
110
|
+
## Using `entryFilter` and `sourceFilter` to filter the results for V8 report
|
|
111
|
+
When V8 coverage data collected, it actually contains the data of all entry files, for example:
|
|
116
112
|
```
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const MCR = require('monocart-coverage-reports');
|
|
121
|
-
const options = require('path-to/same-options.js');
|
|
122
|
-
const coverageReport = MCR(options);
|
|
123
|
-
await coverageReport.add(coverageData2);
|
|
113
|
+
dist/main.js
|
|
114
|
+
dist/vendor.js
|
|
115
|
+
dist/something-else.js
|
|
124
116
|
```
|
|
125
|
-
|
|
126
|
-
|
|
117
|
+
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.
|
|
118
|
+
```
|
|
119
|
+
dist/main.js
|
|
120
|
+
```
|
|
121
|
+
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`.
|
|
122
|
+
```
|
|
123
|
+
> src/index.js
|
|
124
|
+
> src/components/app.js
|
|
125
|
+
> node_modules/dependency/dist/dependency.js
|
|
126
|
+
```
|
|
127
|
+
We can use `sourceFilter` to filter the source files. For example, we should remove `dependency.js` if it is not in our coverage scope.
|
|
128
|
+
```
|
|
129
|
+
> src/index.js
|
|
130
|
+
> src/components/app.js
|
|
131
|
+
```
|
|
132
|
+
Example:
|
|
127
133
|
```js
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const coverageResults = await coverageReport.generate();
|
|
133
|
-
console.log(coverageResults.summary);
|
|
134
|
+
const coverageOptions = {
|
|
135
|
+
entryFilter: (entry) => entry.url.indexOf("main.js") !== -1,
|
|
136
|
+
sourceFilter: (sourcePath) => sourcePath.search(/src\//) !== -1
|
|
137
|
+
};
|
|
134
138
|
```
|
|
135
139
|
|
|
136
140
|
## onEnd Hook
|
|
@@ -164,6 +168,44 @@ const coverageOptions = {
|
|
|
164
168
|
}
|
|
165
169
|
```
|
|
166
170
|
|
|
171
|
+
## `mcr` CLI
|
|
172
|
+
> 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
|
+
- Global mode
|
|
174
|
+
```sh
|
|
175
|
+
npm i monocart-coverage-reports -g
|
|
176
|
+
mcr "node ./test/test-node-env.js" -r v8,console-summary --lcov
|
|
177
|
+
```
|
|
178
|
+
- Current working directory mode
|
|
179
|
+
```sh
|
|
180
|
+
npm i monocart-coverage-reports
|
|
181
|
+
npx mcr "node ./test/test-node-env.js" -r v8,console-summary --lcov
|
|
182
|
+
```
|
|
183
|
+
- CLI Options
|
|
184
|
+
```sh
|
|
185
|
+
Usage: mcr [options] <command>
|
|
186
|
+
|
|
187
|
+
CLI to generate coverage reports
|
|
188
|
+
|
|
189
|
+
Arguments:
|
|
190
|
+
command command to execute
|
|
191
|
+
|
|
192
|
+
Options:
|
|
193
|
+
-V, --version output the version number
|
|
194
|
+
-c, --config <path> config path for options
|
|
195
|
+
-o, --outputDir <dir> output dir for reports
|
|
196
|
+
-r, --reports <name[,name]> coverage reports to use
|
|
197
|
+
-n, --name <name> report name for title
|
|
198
|
+
--outputFile <path> output file for v8 report
|
|
199
|
+
--inline inline html for v8 report
|
|
200
|
+
--assetsPath <path> assets path if not inline
|
|
201
|
+
--lcov generate lcov.info file
|
|
202
|
+
-h, --help display help for command
|
|
203
|
+
```
|
|
204
|
+
- Using configuration file for more [options](#default-options)
|
|
205
|
+
```sh
|
|
206
|
+
mcr "node ./test/test-node-env.js" -c test/cli-options.js
|
|
207
|
+
```
|
|
208
|
+
|
|
167
209
|
## Compare Reports
|
|
168
210
|
| | Istanbul | V8 | V8 to Istanbul |
|
|
169
211
|
| :--------------| :------ | :------ | :---------------------- |
|
|
@@ -192,133 +234,103 @@ const coverageOptions = {
|
|
|
192
234
|
## Collecting Istanbul Coverage Data
|
|
193
235
|
- Instrumenting source code
|
|
194
236
|
> Before collecting Istanbul coverage data, It requires your source code is instrumented with Istanbul
|
|
195
|
-
-
|
|
196
|
-
-
|
|
197
|
-
-
|
|
237
|
+
- 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)
|
|
238
|
+
- rollup: [rollup-plugin-istanbul](https://github.com/artberri/rollup-plugin-istanbul)
|
|
239
|
+
- vite: [vite-plugin-istanbul](https://github.com/ifaxity/vite-plugin-istanbul)
|
|
198
240
|
- Browser
|
|
199
|
-
|
|
241
|
+
- Collecting coverage data from `window.__coverage__`, example: [test-istanbul.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-istanbul.js)
|
|
200
242
|
- Node.js
|
|
201
|
-
|
|
243
|
+
- Collecting coverage data from `global.__coverage__`
|
|
202
244
|
|
|
203
245
|
## Collecting V8 Coverage Data
|
|
204
246
|
- For source code: enable `sourcemap` and do not compress/minify:
|
|
205
|
-
-
|
|
206
|
-
-
|
|
207
|
-
-
|
|
247
|
+
- [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)
|
|
248
|
+
- [rollup](https://rollupjs.org/configuration-options/): `sourcemap: true`
|
|
249
|
+
- [vite](https://vitejs.dev/config/build-options.html): `sourcemap: true` and `minify: false`
|
|
250
|
+
- [esbuild](https://esbuild.github.io/api/): `sourcemap: true` and `minify: false`
|
|
208
251
|
- Browser (Chromium Only)
|
|
209
|
-
> Collecting coverage data with [Chromium Coverage API](#chromium-coverage-api)
|
|
252
|
+
> Collecting coverage data with [Chromium Coverage API](#chromium-coverage-api):
|
|
253
|
+
- [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 example](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-puppeteer.js)
|
|
210
255
|
- Node.js
|
|
211
|
-
|
|
256
|
+
- see following [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
|
|
212
257
|
|
|
213
258
|
|
|
214
259
|
## Node.js V8 Coverage Report for Server Side
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
-
|
|
218
|
-
-
|
|
260
|
+
Possible solutions:
|
|
261
|
+
- [NODE_V8_COVERAGE](https://nodejs.org/docs/latest/api/cli.html#node_v8_coveragedir)=`dir`
|
|
262
|
+
- 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.
|
|
263
|
+
- Read the JSON file(s) from the `dir` and generate coverage report.
|
|
264
|
+
- Example:
|
|
219
265
|
> 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)
|
|
220
266
|
|
|
221
|
-
-
|
|
222
|
-
- Writing the coverage started by NODE_V8_COVERAGE to disk on demand with `v8.takeCoverage()
|
|
223
|
-
-
|
|
267
|
+
- [V8](https://nodejs.org/docs/latest/api/v8.html#v8takecoverage) API + NODE_V8_COVERAGE
|
|
268
|
+
- 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.
|
|
269
|
+
- Example:
|
|
224
270
|
> cross-env NODE_V8_COVERAGE=`.temp/v8-coverage-api` node [./test/test-node-api.js](./test/test-node-api.js)
|
|
225
271
|
|
|
226
|
-
-
|
|
272
|
+
- [Inspector](https://nodejs.org/docs/latest/api/inspector.html) API
|
|
227
273
|
- Connecting to the V8 inspector and enable V8 coverage.
|
|
228
|
-
- Taking coverage data and adding to the report
|
|
229
|
-
-
|
|
274
|
+
- Taking coverage data and adding it to the report.
|
|
275
|
+
- Example:
|
|
276
|
+
> node [./test/test-node-ins.js](./test/test-node-ins.js)
|
|
230
277
|
|
|
231
|
-
-
|
|
232
|
-
- Enabling [Node Debugging](https://nodejs.org/en/guides/debugging-getting-started/)
|
|
278
|
+
- [CDP](https://chromedevtools.github.io/devtools-protocol/) API
|
|
279
|
+
- Enabling [Node Debugging](https://nodejs.org/en/guides/debugging-getting-started/).
|
|
233
280
|
- Collecting coverage data with CDP API.
|
|
234
|
-
-
|
|
281
|
+
- Example:
|
|
282
|
+
> node --inspect=9229 [./test/test-node-cdp.js](./test/test-node-cdp.js)
|
|
235
283
|
|
|
236
|
-
|
|
237
|
-
When
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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`.
|
|
248
|
-
```
|
|
249
|
-
> src/index.js
|
|
250
|
-
> src/components/app.js
|
|
251
|
-
> node_modules/dependency/dist/dependency.js
|
|
252
|
-
```
|
|
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.
|
|
254
|
-
```
|
|
255
|
-
> src/index.js
|
|
256
|
-
> src/components/app.js
|
|
257
|
-
```
|
|
258
|
-
Example:
|
|
284
|
+
- [Node Debugging](https://nodejs.org/en/guides/debugging-getting-started) + CDP + NODE_V8_COVERAGE + V8 API
|
|
285
|
+
- 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.
|
|
286
|
+
- Example for [koa](https://github.com/koajs/koa) web server:
|
|
287
|
+
> node [./test/test-node-koa.js](./test/test-node-koa.js)
|
|
288
|
+
|
|
289
|
+
- [Child Process](https://nodejs.org/docs/latest/api/child_process.html) + NODE_V8_COVERAGE
|
|
290
|
+
- see [`mcr` CLI](#mcr-cli)
|
|
291
|
+
|
|
292
|
+
## Multiprocessing Support
|
|
293
|
+
The data will be added to `[outputDir]/.cache`, and the cache will be removed after reports generated.
|
|
294
|
+
- sub process 1
|
|
259
295
|
```js
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
296
|
+
const MCR = require('monocart-coverage-reports');
|
|
297
|
+
const options = require('path-to/same-options.js');
|
|
298
|
+
const coverageReport = MCR(options);
|
|
299
|
+
await coverageReport.add(coverageData1);
|
|
264
300
|
```
|
|
265
301
|
|
|
266
|
-
|
|
267
|
-
### Using [v8-to-istanbul](https://github.com/istanbuljs/v8-to-istanbul)
|
|
268
|
-
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:
|
|
269
|
-
- 1, The source mapping does not work well if the position is between the two consecutive mappings. for example:
|
|
302
|
+
- sub process 2
|
|
270
303
|
```js
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
304
|
+
const MCR = require('monocart-coverage-reports');
|
|
305
|
+
const options = require('path-to/same-options.js');
|
|
306
|
+
const coverageReport = MCR(options);
|
|
307
|
+
await coverageReport.add(coverageData2);
|
|
274
308
|
```
|
|
275
|
-
> `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.
|
|
276
309
|
|
|
277
|
-
-
|
|
278
|
-
And also there are some problems about counting the functions and branches:
|
|
310
|
+
- main process
|
|
279
311
|
```js
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
// v8-to-istanbul: new CovFunction()
|
|
287
|
-
// Problem: no anonymous function
|
|
288
|
-
}
|
|
289
|
-
} else if (block.functionName) {
|
|
290
|
-
// v8-to-istanbul: new CovFunction()
|
|
291
|
-
// Problem: no anonymous function
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
// Problem: When the function or parent-level function is uncovered, then its sub-level functions will never be counted.
|
|
312
|
+
// after all sub processes finished
|
|
313
|
+
const MCR = require('monocart-coverage-reports');
|
|
314
|
+
const options = require('path-to/same-options.js');
|
|
315
|
+
const coverageReport = MCR(options);
|
|
316
|
+
const coverageResults = await coverageReport.generate();
|
|
317
|
+
console.log(coverageResults.summary);
|
|
296
318
|
```
|
|
297
|
-
see source code [v8-to-istanbul.js](https://github.com/istanbuljs/v8-to-istanbul/blob/master/lib/v8-to-istanbul.js)
|
|
298
319
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
-
|
|
302
|
-
- 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.
|
|
303
|
-
|
|
304
|
-
| AST | V8 |
|
|
305
|
-
| :--------------| :------ |
|
|
306
|
-
| AssignmentPattern | 🛇 Not Support |
|
|
307
|
-
| ConditionalExpression | ✔ Not Perfect |
|
|
308
|
-
| IfStatement | ✔ Not Perfect |
|
|
309
|
-
| LogicalExpression | ✔ Not Perfect |
|
|
310
|
-
| SwitchStatement | ✔ Not Perfect |
|
|
320
|
+
## Integration
|
|
321
|
+
- [monocart-reporter](https://cenfun.github.io/monocart-reporter/) - Test reporter for [Playwright](https://github.com/microsoft/playwright)
|
|
322
|
+
- [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage) - Integration with [Vitest](https://github.com/vitest-dev/vitest) coverage
|
|
311
323
|
|
|
312
324
|
## Ignoring Uncovered Codes
|
|
313
325
|
To ignore codes, use the special comment which starts with `v8 ignore `:
|
|
314
|
-
-
|
|
326
|
+
- Ignoring all until stop
|
|
315
327
|
```js
|
|
316
328
|
/* v8 ignore start */
|
|
317
329
|
function uncovered() {
|
|
318
330
|
}
|
|
319
331
|
/* v8 ignore stop */
|
|
320
332
|
```
|
|
321
|
-
-
|
|
333
|
+
- Ignoring the next line or next N lines
|
|
322
334
|
```js
|
|
323
335
|
/* v8 ignore next */
|
|
324
336
|
const os = platform === 'wind32' ? 'Windows' : 'Other';
|
|
@@ -333,8 +345,8 @@ if (platform === 'linux') {
|
|
|
333
345
|
|
|
334
346
|
## Chromium Coverage API
|
|
335
347
|
- [V8 coverage report](https://v8.dev/blog/javascript-code-coverage) - Native support for JavaScript code coverage to V8. (Chromium only)
|
|
336
|
-
- [Puppeteer Coverage Class](https://pptr.dev/api/puppeteer.coverage)
|
|
337
348
|
- [Playwright Coverage Class](https://playwright.dev/docs/api/class-coverage)
|
|
349
|
+
- [Puppeteer Coverage class](https://pptr.dev/api/puppeteer.coverage)
|
|
338
350
|
- [DevTools Protocol for Coverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#method-startPreciseCoverage)
|
|
339
351
|
|
|
340
352
|
## V8 Coverage Data Format
|
|
@@ -380,6 +392,71 @@ export type V8CoverageData = ScriptCoverage[];
|
|
|
380
392
|
```
|
|
381
393
|
see devtools-protocol [ScriptCoverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-ScriptCoverage) and [v8-coverage](https://github.com/bcoe/v8-coverage)
|
|
382
394
|
|
|
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
|
+
## How to convert V8 to Istanbul
|
|
416
|
+
### Using [v8-to-istanbul](https://github.com/istanbuljs/v8-to-istanbul)
|
|
417
|
+
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:
|
|
418
|
+
- 1, The source mapping does not work well if the position is between the two consecutive mappings. for example:
|
|
419
|
+
```js
|
|
420
|
+
const a = tf ? 'true' : 'false';
|
|
421
|
+
^ ^ ^
|
|
422
|
+
m1 p m2
|
|
423
|
+
```
|
|
424
|
+
> `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.
|
|
425
|
+
|
|
426
|
+
- 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.
|
|
427
|
+
And also there are some problems about counting the functions and branches:
|
|
428
|
+
```js
|
|
429
|
+
// Problem: When the function or parent-level function is uncovered, then its sub-level functions will never be counted.
|
|
430
|
+
functions.forEach(block => {
|
|
431
|
+
block.ranges.forEach((range, i) => {
|
|
432
|
+
if (block.isBlockCoverage) {
|
|
433
|
+
// v8-to-istanbul: new CovBranch()
|
|
434
|
+
// Problem: not every block is branch, and the first block could be function.
|
|
435
|
+
if (block.functionName && i === 0) {
|
|
436
|
+
// v8-to-istanbul: new CovFunction()
|
|
437
|
+
// Problem: no anonymous function
|
|
438
|
+
}
|
|
439
|
+
} else if (block.functionName) {
|
|
440
|
+
// v8-to-istanbul: new CovFunction()
|
|
441
|
+
// Problem: no anonymous function
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
```
|
|
446
|
+
see source code [v8-to-istanbul.js](https://github.com/istanbuljs/v8-to-istanbul/blob/master/lib/v8-to-istanbul.js)
|
|
447
|
+
|
|
448
|
+
### How Monocart Works
|
|
449
|
+
We implemented new converter instead of v8-to-istanbul:
|
|
450
|
+
- 1, Trying to fix the middle position if not found the exact mapping for the position.
|
|
451
|
+
- 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.
|
|
452
|
+
|
|
453
|
+
| AST | V8 |
|
|
454
|
+
| :---------------------| :------------- |
|
|
455
|
+
| AssignmentPattern | 🛇 Not Support |
|
|
456
|
+
| ConditionalExpression | ✔ Not Perfect |
|
|
457
|
+
| IfStatement | ✔ Not Perfect |
|
|
458
|
+
| LogicalExpression | ✔ Not Perfect |
|
|
459
|
+
| SwitchStatement | ✔ Not Perfect |
|
|
383
460
|
|
|
384
461
|
## Istanbul Introduction
|
|
385
462
|
- [Istanbul coverage report](https://istanbul.js.org/) - Instrumenting source codes and generating coverage reports
|
package/lib/cli.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const EC = require('eight-colors');
|
|
6
|
+
const { fileURLToPath } = require('url');
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
foregroundChild,
|
|
10
|
+
program
|
|
11
|
+
} = require('./packages/monocart-coverage-vendor.js');
|
|
12
|
+
|
|
13
|
+
const MCR = require('./index.js');
|
|
14
|
+
const Util = require('./utils/util.js');
|
|
15
|
+
const defaultOptions = require('./default/options.js');
|
|
16
|
+
const version = require('../package.json').version;
|
|
17
|
+
|
|
18
|
+
const cleanDir = (dir) => {
|
|
19
|
+
if (fs.existsSync(dir)) {
|
|
20
|
+
fs.rmSync(dir, {
|
|
21
|
+
recursive: true,
|
|
22
|
+
force: true
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const initOptions = (cliOptions) => {
|
|
28
|
+
// merge all cli options
|
|
29
|
+
const configPath = cliOptions.config;
|
|
30
|
+
if (configPath) {
|
|
31
|
+
if (fs.existsSync(configPath)) {
|
|
32
|
+
const config = require(path.resolve(configPath));
|
|
33
|
+
Object.assign(cliOptions, config);
|
|
34
|
+
} else {
|
|
35
|
+
EC.logRed(`Not found config: ${configPath}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// init reports to list with `,`
|
|
40
|
+
const reports = cliOptions.reports;
|
|
41
|
+
if (reports) {
|
|
42
|
+
cliOptions.reports = reports.split(',');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// report options
|
|
46
|
+
const options = {
|
|
47
|
+
... defaultOptions
|
|
48
|
+
};
|
|
49
|
+
Object.keys(cliOptions).forEach((k) => {
|
|
50
|
+
if (Util.hasOwn(options, k)) {
|
|
51
|
+
options[k] = cliOptions[k];
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return options;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const executeCommand = async (command, cliOptions) => {
|
|
59
|
+
|
|
60
|
+
const options = initOptions(cliOptions);
|
|
61
|
+
|
|
62
|
+
console.log('Execute:', EC.cyan(command));
|
|
63
|
+
// console.log(options);
|
|
64
|
+
|
|
65
|
+
const nodeV8CoverageDir = path.resolve(options.outputDir, '.v8-coverage');
|
|
66
|
+
process.env.NODE_V8_COVERAGE = nodeV8CoverageDir;
|
|
67
|
+
|
|
68
|
+
cleanDir(nodeV8CoverageDir);
|
|
69
|
+
|
|
70
|
+
// console.log('nodeV8CoverageDir', nodeV8CoverageDir);
|
|
71
|
+
|
|
72
|
+
const coverageReport = MCR(options);
|
|
73
|
+
coverageReport.cleanCache();
|
|
74
|
+
|
|
75
|
+
// on start hook
|
|
76
|
+
const onStart = cliOptions.onStart;
|
|
77
|
+
if (typeof onStart === 'function') {
|
|
78
|
+
await onStart(coverageReport);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
foregroundChild(command, {
|
|
82
|
+
shell: true
|
|
83
|
+
}, async () => {
|
|
84
|
+
|
|
85
|
+
if (!fs.existsSync(nodeV8CoverageDir)) {
|
|
86
|
+
EC.logRed(`Not found coverage data: ${Util.relativePath(nodeV8CoverageDir)}`);
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const files = fs.readdirSync(nodeV8CoverageDir);
|
|
91
|
+
for (const filename of files) {
|
|
92
|
+
const content = fs.readFileSync(path.resolve(nodeV8CoverageDir, filename)).toString('utf-8');
|
|
93
|
+
const json = JSON.parse(content);
|
|
94
|
+
let coverageList = json.result;
|
|
95
|
+
|
|
96
|
+
// filter node internal files
|
|
97
|
+
coverageList = coverageList.filter((entry) => entry.url && entry.url.startsWith('file:'));
|
|
98
|
+
|
|
99
|
+
if (!coverageList.length) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// attached source content
|
|
103
|
+
coverageList.forEach((entry) => {
|
|
104
|
+
const filePath = fileURLToPath(entry.url);
|
|
105
|
+
if (fs.existsSync(filePath)) {
|
|
106
|
+
entry.source = fs.readFileSync(filePath).toString('utf8');
|
|
107
|
+
} else {
|
|
108
|
+
EC.logRed('not found file', Util.relativePath(filePath));
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await coverageReport.add(coverageList);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
await coverageReport.generate();
|
|
116
|
+
|
|
117
|
+
// remove nodeV8CoverageDir
|
|
118
|
+
if (options.logging !== 'debug') {
|
|
119
|
+
cleanDir(nodeV8CoverageDir);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return process.exitCode;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
program
|
|
128
|
+
.name('mcr')
|
|
129
|
+
.description('CLI to generate coverage reports')
|
|
130
|
+
.version(version)
|
|
131
|
+
.argument('<command>', 'command to execute')
|
|
132
|
+
.option('-c, --config <path>', 'config path for options')
|
|
133
|
+
.option('-o, --outputDir <dir>', 'output dir for reports')
|
|
134
|
+
.option('-r, --reports <name[,name]>', 'coverage reports to use')
|
|
135
|
+
.option('-n, --name <name>', 'report name for title')
|
|
136
|
+
|
|
137
|
+
.option('--outputFile <path>', 'output file for v8 report')
|
|
138
|
+
.option('--inline', 'inline html for v8 report')
|
|
139
|
+
.option('--assetsPath <path>', 'assets path if not inline')
|
|
140
|
+
|
|
141
|
+
.option('--lcov', 'generate lcov.info file')
|
|
142
|
+
|
|
143
|
+
.action((command, cliOptions) => {
|
|
144
|
+
executeCommand(command, cliOptions);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
program.parse();
|
package/lib/converter/ast.js
CHANGED
|
@@ -170,7 +170,7 @@ const getJsAstInfo = (item, coverageList) => {
|
|
|
170
170
|
try {
|
|
171
171
|
ast = acorn.parse(source, options);
|
|
172
172
|
} catch (e) {
|
|
173
|
-
Util.
|
|
173
|
+
Util.logInfo(`failed to parse source file: ${item.sourcePath} ${e.message}`);
|
|
174
174
|
// https://github.com/acornjs/acorn/tree/master/acorn-loose
|
|
175
175
|
// It is recommended to always try a parse with the regular acorn parser first,
|
|
176
176
|
// and only fall back to this parser when that one finds syntax errors.
|
|
@@ -774,8 +774,9 @@ const collectOriginalList = (item, state, originalMap, options) => {
|
|
|
774
774
|
|
|
775
775
|
// add file item
|
|
776
776
|
const url = fileUrls[sourcePath] || sourcePath;
|
|
777
|
+
|
|
777
778
|
// add dist for id
|
|
778
|
-
const id = Util.calculateSha1(distFile +
|
|
779
|
+
const id = Util.calculateSha1(distFile + sourcePath + source);
|
|
779
780
|
|
|
780
781
|
const sourceItem = {
|
|
781
782
|
url,
|
|
@@ -975,22 +976,30 @@ const convertV8List = async (v8list, options) => {
|
|
|
975
976
|
// merge state
|
|
976
977
|
Object.assign(fileSources, state.fileSources);
|
|
977
978
|
Object.assign(coverageData, state.coverageData);
|
|
978
|
-
|
|
979
|
+
|
|
980
|
+
if (Util.isList(state.sourceList)) {
|
|
979
981
|
sourceList = sourceList.concat(state.sourceList);
|
|
980
982
|
}
|
|
981
983
|
|
|
982
984
|
}
|
|
983
985
|
|
|
984
|
-
// dedupe
|
|
985
|
-
dedupeV8List(v8list);
|
|
986
|
-
|
|
987
986
|
// add all sources
|
|
988
987
|
if (sourceList.length) {
|
|
989
988
|
sourceList.forEach((item) => {
|
|
989
|
+
|
|
990
|
+
// exists same id, mark previous item as dedupe
|
|
991
|
+
const prevItem = v8list.find((it) => it.id === item.id);
|
|
992
|
+
if (prevItem) {
|
|
993
|
+
prevItem.dedupe = true;
|
|
994
|
+
}
|
|
995
|
+
|
|
990
996
|
v8list.push(item);
|
|
991
997
|
});
|
|
992
998
|
}
|
|
993
999
|
|
|
1000
|
+
// dedupe
|
|
1001
|
+
dedupeV8List(v8list);
|
|
1002
|
+
|
|
994
1003
|
return {
|
|
995
1004
|
fileSources,
|
|
996
1005
|
coverageData
|