monocart-coverage-reports 2.2.2 → 2.3.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 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)
@@ -66,11 +69,10 @@ console.log(coverageResults.summary);
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)
68
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)
69
- - `console-summary` shows coverage summary in console
70
72
 
71
73
  ![](test/console-summary.png)
72
74
 
73
- > Following are [istanbul reports](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib)
75
+ > Istanbul [build-in reports](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib)
74
76
  - `clover`
75
77
  - `cobertura`
76
78
  - `html`
@@ -90,11 +92,41 @@ console.log(coverageResults.summary);
90
92
  - `text-lcov`
91
93
  - `text-summary`
92
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:
93
124
  ```js
94
125
  const MCR = require('monocart-coverage-reports');
95
126
  const options = {
96
127
  outputDir: './coverage-reports',
97
128
  reports: [
129
+ // build-in reports
98
130
  ['console-summary'],
99
131
  ['v8'],
100
132
  ['html', {
@@ -103,7 +135,18 @@ const options = {
103
135
  ['json', {
104
136
  file: 'my-json-file.json'
105
137
  }],
106
- '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
+
107
150
  ]
108
151
  }
109
152
  const coverageReport = MCR(options);
@@ -250,13 +293,48 @@ mcr "node ./test/test-node-env.js" -c test/cli-options.js
250
293
  - [rollup](https://rollupjs.org/configuration-options/): `sourcemap: true`
251
294
  - [vite](https://vitejs.dev/config/build-options.html): `sourcemap: true` and `minify: false`
252
295
  - [esbuild](https://esbuild.github.io/api/): `sourcemap: true` and `minify: false`
296
+ - [Manually Resolve the Sourcemap](#manually-resolve-the-sourcemap)
253
297
  - Browser (Chromium Only)
254
298
  > Collecting coverage data with [Chromium Coverage API](#chromium-coverage-api):
255
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)
256
- - [Puppeteer example](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-puppeteer.js)
300
+ - see [Collecting Raw V8 Coverage Data with Puppeteer](#collecting-raw-v8-coverage-data-with-puppeteer)
257
301
  - Node.js
258
- - see following [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
302
+ - see [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
259
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
+ ```
260
338
 
261
339
  ## Node.js V8 Coverage Report for Server Side
262
340
  Possible solutions:
@@ -319,9 +397,73 @@ const coverageResults = await coverageReport.generate();
319
397
  console.log(coverageResults.summary);
320
398
  ```
321
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 fs from "fs";
424
+ import { CoverageReport } from 'monocart-coverage-reports';
425
+ const coverageOptions = {
426
+ name: 'My Merged Coverage Report',
427
+ inputDir: [
428
+ './coverage-reports/unit/raw',
429
+ './coverage-reports/e2e/raw'
430
+ ],
431
+ outputDir: './coverage-reports/merged',
432
+ reports: [
433
+ ['v8'],
434
+ ['console-summary']
435
+ ],
436
+ onEnd: () => {
437
+ // remove the raw files if it useless
438
+ fs.rmSync('./coverage-reports/unit/raw', {
439
+ recursive: true,
440
+ force: true
441
+ })
442
+ }
443
+ };
444
+ await new CoverageReport(coverageOptions).generate();
445
+ ```
446
+ If the source file comes from the sourcemap, then its path is a virtual path. Using the `sourcePath` option to convert it.
447
+ ```js
448
+ const coverageOptions = {
449
+ sourcePath: (filePath) => {
450
+ // Remove the virtual prefix
451
+ const list = ['my-dist-file1/', 'my-dist-file2/'];
452
+ for (const str of list) {
453
+ if (filePath.startsWith(str)) {
454
+ return filePath.slice(str.length);
455
+ }
456
+ }
457
+ return filePath;
458
+ }
459
+ };
460
+ ```
461
+ see example: [./test/test-merge.js](./test/test-merge.js)
462
+
322
463
  ## Integration
323
464
  - [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
465
+ - [jest-monocart-coverage](https://github.com/cenfun/jest-monocart-coverage) - Integration with [Jest](https://github.com/jestjs/jest/) for coverage reports
466
+ - [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage) - Integration with [Vitest](https://github.com/vitest-dev/vitest) for coverage reports
325
467
 
326
468
  ## Ignoring Uncovered Codes
327
469
  To ignore codes, use the special comment which starts with `v8 ignore `:
@@ -394,26 +536,6 @@ export type V8CoverageData = ScriptCoverage[];
394
536
  ```
395
537
  see devtools-protocol [ScriptCoverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-ScriptCoverage) and [v8-coverage](https://github.com/bcoe/v8-coverage)
396
538
 
397
- ### Collect raw v8 coverage data with Puppeteer
398
- ```js
399
- await page.coverage.startJSCoverage({
400
- // provide raw v8 coverage data
401
- includeRawScriptCoverage: true
402
- });
403
-
404
- await page.goto(url);
405
-
406
- const jsCoverage = await page.coverage.stopJSCoverage();
407
- const rawV8CoverageData = jsCoverage.map((it) => {
408
- // Convert to raw v8 coverage format
409
- return {
410
- source: it.text,
411
- ... it.rawScriptCoverage
412
- };
413
- }
414
- ```
415
- see example: [./test/test-puppeteer.js](./test/test-puppeteer.js)
416
-
417
539
  ## How to convert V8 to Istanbul
418
540
  ### Using [v8-to-istanbul](https://github.com/istanbuljs/v8-to-istanbul)
419
541
  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 && typeof reports === 'string') {
42
- cliOptions.reports = reports.split(',');
43
- }
44
-
45
39
  // report options
46
40
  const options = {
47
41
  ... defaultOptions
@@ -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 { functionName, ranges } = block;
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
- // source,
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