monocart-coverage-reports 2.1.0 → 2.2.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 +76 -9
- 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 +13 -8
- package/lib/v8/v8.js +12 -1
- package/package.json +12 -6
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* [Available Reports](#available-reports)
|
|
14
14
|
* [Multiprocessing Support](#multiprocessing-support)
|
|
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)
|
|
@@ -49,9 +50,13 @@ console.log(coverageResults.summary);
|
|
|
49
50
|
|
|
50
51
|
## Available Reports
|
|
51
52
|
- `v8` (V8 data only)
|
|
52
|
-
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
- Test in browser
|
|
54
|
+
- 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
|
+
- Build with [Rollup](https://cenfun.github.io/monocart-coverage-reports/v8-rollup) and [Esbuild](https://cenfun.github.io/monocart-coverage-reports/v8-esbuild)
|
|
56
|
+
- Collect with [puppeteer](https://cenfun.github.io/monocart-coverage-reports/puppeteer/)
|
|
57
|
+
- Test in Node.js
|
|
58
|
+
- 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
|
+
- Web server example: [koa](https://cenfun.github.io/monocart-coverage-reports/v8-node-koa/)
|
|
55
60
|
|
|
56
61
|

|
|
57
62
|
|
|
@@ -164,6 +169,43 @@ const coverageOptions = {
|
|
|
164
169
|
}
|
|
165
170
|
```
|
|
166
171
|
|
|
172
|
+
## `mcr` CLI
|
|
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
|
|
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
|
| :--------------| :------ | :------ | :---------------------- |
|
|
@@ -206,33 +248,39 @@ const coverageOptions = {
|
|
|
206
248
|
- Rollup: build with [options](https://rollupjs.org/configuration-options/) `sourcemap: true`
|
|
207
249
|
- Vite: build with [options](https://vitejs.dev/config/build-options.html) `sourcemap: true` and `minify: false`
|
|
208
250
|
- Browser (Chromium Only)
|
|
209
|
-
> 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)
|
|
251
|
+
> Collecting coverage data with [Chromium Coverage API](#chromium-coverage-api), see [playwright example](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-v8.js) or [puppeteer example](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-puppeteer.js)
|
|
210
252
|
- Node.js
|
|
211
253
|
> see [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
|
|
212
254
|
|
|
213
255
|
|
|
214
256
|
## Node.js V8 Coverage Report for Server Side
|
|
215
|
-
|
|
257
|
+
Possible solutions:
|
|
258
|
+
- Node.js env [NODE_V8_COVERAGE](https://nodejs.org/docs/latest/api/cli.html#node_v8_coveragedir)=`dir`
|
|
216
259
|
- 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
|
|
217
260
|
- Read the json file(s) from the `dir` and generate coverage report
|
|
218
261
|
- example:
|
|
219
262
|
> 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
263
|
|
|
221
|
-
-
|
|
264
|
+
- [V8](https://nodejs.org/docs/latest/api/v8.html#v8takecoverage) API + NODE_V8_COVERAGE
|
|
222
265
|
- Writing the coverage started by NODE_V8_COVERAGE to disk on demand with `v8.takeCoverage()` and stopping with `v8.stopCoverage()`.
|
|
223
266
|
- example:
|
|
224
267
|
> cross-env NODE_V8_COVERAGE=`.temp/v8-coverage-api` node [./test/test-node-api.js](./test/test-node-api.js)
|
|
225
268
|
|
|
226
|
-
-
|
|
269
|
+
- [Inspector](https://nodejs.org/docs/latest/api/inspector.html) API (or module [collect-v8-coverage](https://github.com/SimenB/collect-v8-coverage))
|
|
227
270
|
- Connecting to the V8 inspector and enable V8 coverage.
|
|
228
271
|
- Taking coverage data and adding to the report after your application runs.
|
|
229
272
|
- example: [./test/test-node-ins.js](./test/test-node-ins.js)
|
|
230
273
|
|
|
231
|
-
-
|
|
274
|
+
- [CDP](https://chromedevtools.github.io/devtools-protocol/) API
|
|
232
275
|
- Enabling [Node Debugging](https://nodejs.org/en/guides/debugging-getting-started/)
|
|
233
276
|
- Collecting coverage data with CDP API.
|
|
234
277
|
- example: [./test/test-node-cdp.js](./test/test-node-cdp.js)
|
|
235
278
|
|
|
279
|
+
- [Debugger](https://nodejs.org/en/guides/debugging-getting-started) + NODE_V8_COVERAGE + CDP + V8 API
|
|
280
|
+
> cross-env NODE_V8_COVERAGE=.temp/v8-coverage-koa node --inspect=9229 [./test/koa.js](./test/koa.js)
|
|
281
|
+
- example:
|
|
282
|
+
> node [./test/test-node-koa.js](./test/test-node-koa.js)
|
|
283
|
+
|
|
236
284
|
## Using `entryFilter` and `sourceFilter` to filter the results for V8 report
|
|
237
285
|
When V8 coverage data collected, it actually contains the data of all entry files, for example:
|
|
238
286
|
```
|
|
@@ -333,8 +381,8 @@ if (platform === 'linux') {
|
|
|
333
381
|
|
|
334
382
|
## Chromium Coverage API
|
|
335
383
|
- [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
384
|
- [Playwright Coverage Class](https://playwright.dev/docs/api/class-coverage)
|
|
385
|
+
- [Puppeteer Coverage class](https://pptr.dev/api/puppeteer.coverage)
|
|
338
386
|
- [DevTools Protocol for Coverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#method-startPreciseCoverage)
|
|
339
387
|
|
|
340
388
|
## V8 Coverage Data Format
|
|
@@ -380,6 +428,25 @@ export type V8CoverageData = ScriptCoverage[];
|
|
|
380
428
|
```
|
|
381
429
|
see devtools-protocol [ScriptCoverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-ScriptCoverage) and [v8-coverage](https://github.com/bcoe/v8-coverage)
|
|
382
430
|
|
|
431
|
+
### Collect raw v8 coverage data with Puppeteer
|
|
432
|
+
```js
|
|
433
|
+
await page.coverage.startJSCoverage({
|
|
434
|
+
// provide raw v8 coverage data
|
|
435
|
+
includeRawScriptCoverage: true
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
await page.goto(url);
|
|
439
|
+
|
|
440
|
+
const jsCoverage = await page.coverage.stopJSCoverage();
|
|
441
|
+
const rawV8CoverageData = jsCoverage.map((it) => {
|
|
442
|
+
// Convert to raw v8 coverage format
|
|
443
|
+
return {
|
|
444
|
+
source: it.text,
|
|
445
|
+
... it.rawScriptCoverage
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
see example: [./test/test-puppeteer.js](./test/test-puppeteer.js)
|
|
383
450
|
|
|
384
451
|
## Istanbul Introduction
|
|
385
452
|
- [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
|
package/lib/index.d.ts
CHANGED
|
@@ -104,19 +104,16 @@ declare namespace MCR {
|
|
|
104
104
|
total: number;
|
|
105
105
|
pct: number | "";
|
|
106
106
|
status: "low" | "medium" | "high" | "unknown";
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
export interface LinesSummary extends MetricsSummary {
|
|
110
|
-
/** V8 only */
|
|
107
|
+
/** V8 lines only */
|
|
111
108
|
blank?: number;
|
|
112
|
-
/** V8 only */
|
|
109
|
+
/** V8 lines only */
|
|
113
110
|
comment?: number;
|
|
114
111
|
}
|
|
115
112
|
|
|
116
113
|
export interface CoverageSummary {
|
|
117
114
|
functions: MetricsSummary;
|
|
118
115
|
branches: MetricsSummary;
|
|
119
|
-
lines:
|
|
116
|
+
lines: MetricsSummary;
|
|
120
117
|
/** V8 only */
|
|
121
118
|
bytes?: MetricsSummary;
|
|
122
119
|
/** Istanbul only */
|
|
@@ -232,6 +229,11 @@ declare namespace MCR {
|
|
|
232
229
|
cleanCache: () => boolean;
|
|
233
230
|
}
|
|
234
231
|
|
|
232
|
+
export interface McrCliOptions extends CoverageReportOptions {
|
|
233
|
+
/** {function} onStart hook */
|
|
234
|
+
onStart?: (coverageReport: CoverageReport) => Promise<void>;
|
|
235
|
+
}
|
|
236
|
+
|
|
235
237
|
}
|
|
236
238
|
|
|
237
239
|
/** create coverage report */
|