monocart-coverage-reports 2.0.4 → 2.0.6
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 +152 -89
- package/lib/converter/ast.js +1 -0
- package/lib/converter/collect-source-maps.js +23 -58
- package/lib/converter/converter.js +5 -4
- package/lib/generate.js +114 -2
- package/lib/index.d.ts +1 -0
- package/lib/index.js +6 -4
- package/lib/istanbul/istanbul-summary.js +13 -0
- package/lib/utils/decode-mappings-worker.js +9 -0
- package/lib/utils/decode-mappings.js +24 -38
- package/lib/v8/v8.js +4 -4
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -4,19 +4,24 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/monocart-coverage-reports)
|
|
5
5
|

|
|
6
6
|
|
|
7
|
-
> Code coverage tool to generate [V8](https://v8.dev/blog/javascript-code-coverage) or [Istanbul](https://istanbul.js.org/)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
> Code coverage tool to generate native [V8](https://v8.dev/blog/javascript-code-coverage) reports or [Istanbul](https://istanbul.js.org/) reports.
|
|
8
|
+
|
|
9
|
+
* [Usage](#usage)
|
|
10
|
+
* [Default Options](#default-options)
|
|
11
|
+
* [Available Reports](#available-reports)
|
|
12
|
+
* [Multiprocessing Support](#multiprocessing-support)
|
|
13
|
+
* [Compare Reports](#compare-reports)
|
|
14
|
+
* [Compare Workflows](#compare-workflows)
|
|
15
|
+
* [Collecting Istanbul Coverage Data](#collecting-istanbul-coverage-data)
|
|
16
|
+
* [Collecting V8 Coverage Data](#collecting-v8-coverage-data)
|
|
17
|
+
* [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
|
|
18
|
+
* [Using `entryFilter` and `sourceFilter` to filter the results for V8 report](#using-entryfilter-and-sourcefilter-to-filter-the-results-for-v8-report)
|
|
19
|
+
* [How to convert V8 to Istanbul](#how-to-convert-v8-to-istanbul)
|
|
20
|
+
- [Using `v8-to-istanbul`](#using-v8-to-istanbul)
|
|
21
|
+
- [How Monocart Works](#how-monocart-works)
|
|
22
|
+
* [Chromium Coverage API](#chromium-coverage-api)
|
|
23
|
+
* [Istanbul Introduction](#istanbul-introduction)
|
|
24
|
+
* [Thanks](#thanks)
|
|
20
25
|
|
|
21
26
|
## Usage
|
|
22
27
|
```js
|
|
@@ -37,30 +42,13 @@ console.log(coverageResults.summary);
|
|
|
37
42
|
## Default Options
|
|
38
43
|
- [lib/default/options.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/lib/default/options.js)
|
|
39
44
|
|
|
40
|
-
##
|
|
41
|
-
### V8 reports
|
|
42
|
-
- v8 (html)
|
|
43
|
-
- v8-json
|
|
44
|
-
### [Istanbul reports](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib)
|
|
45
|
-
- clover
|
|
46
|
-
- cobertura
|
|
47
|
-
- html
|
|
48
|
-
- html-spa
|
|
49
|
-
- json
|
|
50
|
-
- json-summary
|
|
51
|
-
- lcov
|
|
52
|
-
- lcovonly
|
|
53
|
-
- none
|
|
54
|
-
- teamcity
|
|
55
|
-
- text
|
|
56
|
-
- text-lcov
|
|
57
|
-
- text-summary
|
|
58
|
-
|
|
45
|
+
## Available Reports
|
|
59
46
|
```js
|
|
60
47
|
const CoverageReport = require('monocart-coverage-reports');
|
|
61
48
|
const options = {
|
|
62
49
|
outputDir: './coverage-reports',
|
|
63
50
|
reports: [
|
|
51
|
+
['console-summary'],
|
|
64
52
|
['v8'],
|
|
65
53
|
['html', {
|
|
66
54
|
subdir: 'istanbul'
|
|
@@ -73,6 +61,43 @@ const options = {
|
|
|
73
61
|
}
|
|
74
62
|
const coverageReport = new CoverageReport(options);
|
|
75
63
|
```
|
|
64
|
+
- `v8` (V8 data only)
|
|
65
|
+
- [V8 html](https://cenfun.github.io/monocart-coverage-reports/v8) - Example for browser, build with webpack, and also [Rollup](https://cenfun.github.io/monocart-coverage-reports/v8-rollup) and [Esbuild](https://cenfun.github.io/monocart-coverage-reports/v8-esbuild)
|
|
66
|
+
- [V8 Node](https://cenfun.github.io/monocart-coverage-reports/v8-node-env) - Example for Node.js using 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)
|
|
67
|
+
- [V8 Minify](https://cenfun.github.io/monocart-coverage-reports/v8-minify)
|
|
68
|
+
|
|
69
|
+

|
|
70
|
+
|
|
71
|
+
- `v8-json` (V8 data only)
|
|
72
|
+
- [V8 coverage-report.json](https://cenfun.github.io/monocart-coverage-reports/v8-and-istanbul/coverage-report.json)
|
|
73
|
+
- `console-summary` shows coverage summary in console
|
|
74
|
+
|
|
75
|
+

|
|
76
|
+
|
|
77
|
+
> Following are [istanbul reports](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib)
|
|
78
|
+
- `clover`
|
|
79
|
+
- `cobertura`
|
|
80
|
+
- `html`
|
|
81
|
+
- [Istanbul html](https://cenfun.github.io/monocart-coverage-reports/istanbul/)
|
|
82
|
+
- [V8 to Istanbul](https://cenfun.github.io/monocart-coverage-reports/v8-and-istanbul/istanbul)
|
|
83
|
+
- `html-spa`
|
|
84
|
+
- [Istanbul html-spa](https://cenfun.github.io/monocart-coverage-reports/istanbul/html-spa/)
|
|
85
|
+
- `json`
|
|
86
|
+
- `json-summary`
|
|
87
|
+
- `lcov`
|
|
88
|
+
- `lcovonly`
|
|
89
|
+
- [V8 lcov.info](https://cenfun.github.io/monocart-coverage-reports/v8/lcov.info)
|
|
90
|
+
- [Istanbul lcov.info](https://cenfun.github.io/monocart-coverage-reports/istanbul/lcov.info)
|
|
91
|
+
- `none`
|
|
92
|
+
- `teamcity`
|
|
93
|
+
- `text`
|
|
94
|
+
- `text-lcov`
|
|
95
|
+
- `text-summary`
|
|
96
|
+
|
|
97
|
+
## Integration
|
|
98
|
+
- [monocart-reporter](https://cenfun.github.io/monocart-reporter/) - Test reporter for [Playwright](https://github.com/microsoft/playwright)
|
|
99
|
+
- [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage) - Integration with [Vitest](https://github.com/vitest-dev/vitest) coverage
|
|
100
|
+
|
|
76
101
|
|
|
77
102
|
## Multiprocessing Support
|
|
78
103
|
The data will be added to `[outputDir]/.cache`, and the cache will be removed after reports generated.
|
|
@@ -105,7 +130,6 @@ console.log(coverageResults.summary);
|
|
|
105
130
|
## Compare Reports
|
|
106
131
|
| | Istanbul | V8 | V8 to Istanbul |
|
|
107
132
|
| :--------------| :------ | :------ | :---------------------- |
|
|
108
|
-
| Instrumenting code | Required | No | No |
|
|
109
133
|
| Coverage data | Istanbul (Object) | V8 (Array) | V8 (Array) |
|
|
110
134
|
| Output | [Istanbul reports](#istanbul-reports) | [V8 reports](#v8-reports) | [Istanbul reports](#istanbul-reports) |
|
|
111
135
|
| - Bytes | ❌ | ✅ | ❌ |
|
|
@@ -121,70 +145,58 @@ console.log(coverageResults.summary);
|
|
|
121
145
|
|
|
122
146
|
## Compare Workflows
|
|
123
147
|
- Istanbul Workflows
|
|
124
|
-
- 1,
|
|
125
|
-
- 2,
|
|
126
|
-
- 3, Generating coverage report.
|
|
148
|
+
- 1, [Collecting Istanbul coverage data](#collecting-istanbul-coverage-data)
|
|
149
|
+
- 2, Adding coverage data and generating coverage report
|
|
127
150
|
|
|
128
151
|
- V8 Workflows
|
|
129
|
-
- 1,
|
|
130
|
-
-
|
|
131
|
-
- 3, Generating coverage report.
|
|
152
|
+
- 1, [Collecting V8 coverage data](#collecting-v8-coverage-data)
|
|
153
|
+
- 3, Adding coverage data and generating coverage report
|
|
132
154
|
|
|
133
|
-
##
|
|
134
|
-
-
|
|
135
|
-
|
|
136
|
-
- [
|
|
137
|
-
- [
|
|
155
|
+
## Collecting Istanbul Coverage Data
|
|
156
|
+
- Instrumenting source code
|
|
157
|
+
> Before collecting Istanbul coverage data, It requires your source code is instrumented with Istanbul
|
|
158
|
+
- 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)
|
|
159
|
+
- Rollup: [rollup-plugin-istanbul](https://github.com/artberri/rollup-plugin-istanbul)
|
|
160
|
+
- Vite: [vite-plugin-istanbul](https://github.com/ifaxity/vite-plugin-istanbul)
|
|
161
|
+
- Browser
|
|
162
|
+
> Collecting coverage data from `window.__coverage__`, example: [test-istanbul.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/test-istanbul.js)
|
|
163
|
+
- Node.js
|
|
164
|
+
> Collecting coverage data from `global.__coverage__`
|
|
165
|
+
|
|
166
|
+
## Collecting V8 Coverage Data
|
|
167
|
+
- For source code: enable `sourcemap` and do not compress/minify:
|
|
168
|
+
- Webpack: with `source-map` [devtool](https://webpack.js.org/configuration/devtool/) and `development` [mode](https://webpack.js.org/configuration/mode/), example [webpack.config-v8.js](https://github.com/cenfun/monocart-coverage-reports/blob/main/test/webpack.config-v8.js)
|
|
169
|
+
- Rollup: [options](https://rollupjs.org/configuration-options/) `sourcemap: true`
|
|
170
|
+
- Browser (Chromium Only)
|
|
171
|
+
> 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)
|
|
172
|
+
- Node.js
|
|
173
|
+
> see [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
|
|
138
174
|
|
|
139
175
|
|
|
140
176
|
## Node.js V8 Coverage Report for Server Side
|
|
141
|
-
There are two ways to collect native V8 coverage data and generate coverage report:
|
|
142
177
|
- Using Node.js env [NODE_V8_COVERAGE](https://nodejs.org/docs/latest/api/cli.html#node_v8_coveragedir)=`dir`
|
|
143
|
-
-
|
|
144
|
-
-
|
|
178
|
+
- 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
|
|
179
|
+
- Read the json file(s) from the `dir` and generate coverage report
|
|
180
|
+
- example:
|
|
145
181
|
> cross-env NODE_V8_COVERAGE=`.temp/v8-coverage` node [./test/test-node-env.js](./test/test-node-env.js) && node [./test/generate-node-report.js](./test/generate-node-report.js)
|
|
146
|
-
|
|
147
|
-
- Using [
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
```
|
|
162
|
-
- 2, Taking coverage data and adding to the report after your application runs.
|
|
163
|
-
```js
|
|
164
|
-
const takeV8Coverage = (session) => {
|
|
165
|
-
return new Promise((resolve) => {
|
|
166
|
-
session.post('Profiler.takePreciseCoverage', (error, coverage) => {
|
|
167
|
-
if (error) {
|
|
168
|
-
console.log(error);
|
|
169
|
-
resolve();
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
resolve(coverage.result);
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
};
|
|
176
|
-
const coverageList = await takeV8Coverage(session);
|
|
177
|
-
// filter node internal files
|
|
178
|
-
coverageList = coverageList.filter((entry) => entry.url && !entry.url.startsWith('node:'));
|
|
179
|
-
await new CoverageReport(coverageOptions).add(coverageList);
|
|
180
|
-
```
|
|
181
|
-
- 3, Generating coverage report. see example: [./test/test-node-ins.js](./test/test-node-ins.js)
|
|
182
|
-
```js
|
|
183
|
-
await new CoverageReport(coverageOptions).generate();
|
|
184
|
-
```
|
|
182
|
+
|
|
183
|
+
- Using [V8](https://nodejs.org/docs/latest/api/v8.html#v8takecoverage) API
|
|
184
|
+
- Writing the coverage started by NODE_V8_COVERAGE to disk on demand with `v8.takeCoverage()` and stopping with `v8.stopCoverage()`.
|
|
185
|
+
- example:
|
|
186
|
+
> cross-env NODE_V8_COVERAGE=`.temp/v8-coverage` node [./test/test-node-api.js](./test/test-node-api.js)
|
|
187
|
+
|
|
188
|
+
- Using [Inspector](https://nodejs.org/docs/latest/api/inspector.html) API (or module [collect-v8-coverage](https://github.com/SimenB/collect-v8-coverage))
|
|
189
|
+
- Connecting to the V8 inspector and enable V8 coverage.
|
|
190
|
+
- Taking coverage data and adding to the report after your application runs.
|
|
191
|
+
- example: [./test/test-node-ins.js](./test/test-node-ins.js)
|
|
192
|
+
|
|
193
|
+
- Using [CDP](https://chromedevtools.github.io/devtools-protocol/) API
|
|
194
|
+
- Enabling [Node Debugging](https://nodejs.org/en/guides/debugging-getting-started/)
|
|
195
|
+
- Collecting coverage data with CDP API.
|
|
196
|
+
- example: [./test/test-node-cdp.js](./test/test-node-cdp.js)
|
|
185
197
|
|
|
186
198
|
## Using `entryFilter` and `sourceFilter` to filter the results for V8 report
|
|
187
|
-
When
|
|
199
|
+
When V8 coverage data collected, it actually contains the data of all entry files, for example:
|
|
188
200
|
```
|
|
189
201
|
1, dist/main.js
|
|
190
202
|
2, dist/vendor.js
|
|
@@ -213,12 +225,63 @@ const coverageOptions = {
|
|
|
213
225
|
};
|
|
214
226
|
```
|
|
215
227
|
|
|
216
|
-
## Istanbul
|
|
228
|
+
## How to convert V8 to Istanbul
|
|
229
|
+
### Using [v8-to-istanbul](https://github.com/istanbuljs/v8-to-istanbul)
|
|
230
|
+
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:
|
|
231
|
+
- 1, The source mapping does not work well if the position is between the two consecutive mappings. for example:
|
|
232
|
+
```js
|
|
233
|
+
const a = tf ? 'true' : 'false';
|
|
234
|
+
^ ^ ^
|
|
235
|
+
m1 p m2
|
|
236
|
+
```
|
|
237
|
+
> `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.
|
|
238
|
+
|
|
239
|
+
- 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.
|
|
240
|
+
And also there are some problems about counting the functions and branches:
|
|
241
|
+
```js
|
|
242
|
+
functions.forEach(block => {
|
|
243
|
+
block.ranges.forEach((range, i) => {
|
|
244
|
+
if (block.isBlockCoverage) {
|
|
245
|
+
// v8-to-istanbul: count range as branch here.
|
|
246
|
+
// Problem: not every block is branch, and the first block is actually function.
|
|
247
|
+
if (block.functionName && i === 0) {
|
|
248
|
+
// v8-to-istanbul: count range as function here.
|
|
249
|
+
// Problem: there is no function name if the function is anonymous or expression.
|
|
250
|
+
}
|
|
251
|
+
} else if (block.functionName) {
|
|
252
|
+
// v8-to-istanbul: count range as function here.
|
|
253
|
+
// Problem: same as above function.
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
// Problem: When the function or parent-level function is uncovered, then its sub-level functions will never be counted.
|
|
258
|
+
```
|
|
259
|
+
see source code [v8-to-istanbul.js](https://github.com/istanbuljs/v8-to-istanbul/blob/master/lib/v8-to-istanbul.js)
|
|
260
|
+
|
|
261
|
+
### How Monocart Works
|
|
262
|
+
We have removed `v8-to-istanbul` because of the two major problems and implemented new converter:
|
|
263
|
+
- 1, Trying to fix the middle position if not found the exact mapping.
|
|
264
|
+
- 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.
|
|
265
|
+
|
|
266
|
+
| AST | V8 |
|
|
267
|
+
| :--------------| :------ |
|
|
268
|
+
| AssignmentPattern | 🛇 Not Support |
|
|
269
|
+
| ConditionalExpression | ✔ Not Perfect |
|
|
270
|
+
| IfStatement | ✔ Not Perfect |
|
|
271
|
+
| LogicalExpression | ✔ Not Perfect |
|
|
272
|
+
| SwitchStatement | ✔ Not Perfect |
|
|
273
|
+
|
|
274
|
+
## Chromium Coverage API
|
|
275
|
+
- [V8 coverage report](https://v8.dev/blog/javascript-code-coverage) - Native support for JavaScript code coverage to V8. (Chromium only)
|
|
276
|
+
- [Puppeteer Coverage Class](https://pptr.dev/api/puppeteer.coverage)
|
|
277
|
+
- [Playwright Coverage Class](https://playwright.dev/docs/api/class-coverage)
|
|
278
|
+
- [DevTools Protocol for Coverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#method-startPreciseCoverage)
|
|
279
|
+
|
|
280
|
+
## Istanbul Introduction
|
|
217
281
|
- [Istanbul coverage report](https://istanbul.js.org/) - Instrumenting source codes and generating coverage reports
|
|
218
282
|
- [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul)
|
|
219
283
|
- [istanbul-reports](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib)
|
|
220
284
|
- [Code Coverage Introduction](https://docs.cypress.io/guides/tooling/code-coverage)
|
|
221
285
|
|
|
222
|
-
|
|
223
286
|
## Thanks
|
|
224
287
|
- Special thanks to [@edumserrano](https://github.com/edumserrano)
|
package/lib/converter/ast.js
CHANGED
|
@@ -162,6 +162,7 @@ const getJsAstInfo = (item, coverageList) => {
|
|
|
162
162
|
allowReturnOutsideFunction: true,
|
|
163
163
|
allowImportExportEverywhere: true,
|
|
164
164
|
allowSuperOutsideMethod: true,
|
|
165
|
+
allowAwaitOutsideFunction: true,
|
|
165
166
|
// first line: #!/usr/bin/env node
|
|
166
167
|
allowHashBang: true
|
|
167
168
|
};
|
|
@@ -79,56 +79,27 @@ const resolveSourceMap = (data) => {
|
|
|
79
79
|
}
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
const concurrency = new Concurrency();
|
|
85
|
-
for (const item of v8list) {
|
|
86
|
-
|
|
87
|
-
const { type, source } = item;
|
|
88
|
-
|
|
89
|
-
// only for js
|
|
90
|
-
if (type === 'js') {
|
|
91
|
-
const converter = convertSourceMap.fromSource(source);
|
|
92
|
-
if (converter) {
|
|
93
|
-
item.sourceMap = resolveSourceMap(converter.sourcemap);
|
|
94
|
-
count += 1;
|
|
95
|
-
// source map comments is inline source map, remove it because it is big
|
|
96
|
-
item.source = convertSourceMap.removeComments(source);
|
|
97
|
-
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
const sourceMapUrl = getSourceMapUrl(source, item.url);
|
|
101
|
-
if (sourceMapUrl) {
|
|
102
|
-
// console.log('========================', sourceMapUrl);
|
|
103
|
-
item.sourceMapUrl = sourceMapUrl;
|
|
104
|
-
concurrency.addItem(item);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
await concurrency.start(async (item) => {
|
|
109
|
-
const data = await loadSourceMap(item.sourceMapUrl);
|
|
110
|
-
if (data) {
|
|
111
|
-
item.sourceMap = resolveSourceMap(data);
|
|
112
|
-
count += 1;
|
|
113
|
-
// source map comments is a link, no need remove
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
return count;
|
|
82
|
+
const saveSourceFile = async (filePath, data) => {
|
|
83
|
+
await Util.writeFile(filePath, JSON.stringify(data));
|
|
118
84
|
};
|
|
119
85
|
|
|
120
|
-
const
|
|
86
|
+
const collectSourceMaps = async (v8list, options) => {
|
|
87
|
+
|
|
121
88
|
let count = 0;
|
|
122
89
|
const concurrency = new Concurrency();
|
|
123
90
|
for (const item of v8list) {
|
|
124
91
|
|
|
125
92
|
const {
|
|
126
|
-
type, url, source,
|
|
93
|
+
type, url, id, source, sourceMap
|
|
127
94
|
} = item;
|
|
128
95
|
|
|
129
|
-
//
|
|
96
|
+
// source and sourceMap will be saved as separated file (could be cached)
|
|
97
|
+
// just keep functions coverage ( could be multiple times, will be merged )
|
|
98
|
+
// so remove source and sourceMap
|
|
130
99
|
delete item.source;
|
|
100
|
+
delete item.sourceMap;
|
|
131
101
|
|
|
102
|
+
// source and sourceMap already saved
|
|
132
103
|
const sourcePath = Util.resolveCacheSourcePath(options.cacheDir, id);
|
|
133
104
|
if (fs.existsSync(sourcePath)) {
|
|
134
105
|
continue;
|
|
@@ -137,18 +108,23 @@ const collectFileSourceMaps = async (v8list, options) => {
|
|
|
137
108
|
const sourceData = {
|
|
138
109
|
url,
|
|
139
110
|
id,
|
|
140
|
-
|
|
111
|
+
// source,
|
|
112
|
+
source: convertSourceMap.removeComments(source),
|
|
113
|
+
// could be existed
|
|
114
|
+
sourceMap
|
|
141
115
|
};
|
|
142
116
|
|
|
143
|
-
// only for js
|
|
144
|
-
if (type === 'js') {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
117
|
+
// check sourceMap only for js
|
|
118
|
+
if (type === 'js' && !sourceData.sourceMap) {
|
|
119
|
+
// from inline sync
|
|
120
|
+
const smc = convertSourceMap.fromSource(source);
|
|
121
|
+
if (smc) {
|
|
122
|
+
sourceData.sourceMap = resolveSourceMap(smc.sourcemap);
|
|
148
123
|
count += 1;
|
|
149
124
|
await saveSourceFile(sourcePath, sourceData);
|
|
150
125
|
continue;
|
|
151
126
|
}
|
|
127
|
+
// from url async
|
|
152
128
|
const sourceMapUrl = getSourceMapUrl(source, item.url);
|
|
153
129
|
if (sourceMapUrl) {
|
|
154
130
|
concurrency.addItem({
|
|
@@ -160,10 +136,12 @@ const collectFileSourceMaps = async (v8list, options) => {
|
|
|
160
136
|
}
|
|
161
137
|
}
|
|
162
138
|
|
|
139
|
+
// no need check sourceMap
|
|
163
140
|
await saveSourceFile(sourcePath, sourceData);
|
|
164
141
|
|
|
165
142
|
}
|
|
166
143
|
|
|
144
|
+
// from url concurrency
|
|
167
145
|
await concurrency.start(async (item) => {
|
|
168
146
|
const sourceData = item.sourceData;
|
|
169
147
|
const data = await loadSourceMap(item.sourceMapUrl);
|
|
@@ -175,19 +153,6 @@ const collectFileSourceMaps = async (v8list, options) => {
|
|
|
175
153
|
});
|
|
176
154
|
|
|
177
155
|
return count;
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
const saveSourceFile = async (filePath, data) => {
|
|
181
|
-
await Util.writeFile(filePath, JSON.stringify(data));
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
const collectSourceMaps = (v8list, options, inlineSourceMap) => {
|
|
185
|
-
|
|
186
|
-
if (inlineSourceMap) {
|
|
187
|
-
return collectInlineSourceMaps(v8list);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return collectFileSourceMaps(v8list, options);
|
|
191
156
|
|
|
192
157
|
};
|
|
193
158
|
|
|
@@ -475,7 +475,7 @@ const generateCoverageForDist = (item, state) => {
|
|
|
475
475
|
|
|
476
476
|
// ========================================================================================================
|
|
477
477
|
|
|
478
|
-
const decodeSourceMappings = async (
|
|
478
|
+
const decodeSourceMappings = async (state, originalDecodedMap) => {
|
|
479
479
|
|
|
480
480
|
const generatedLocator = state.locator;
|
|
481
481
|
|
|
@@ -566,7 +566,7 @@ const getOriginalDecodedMappings = (originalDecodedMap, sourceIndex, locator) =>
|
|
|
566
566
|
return decodedMappings;
|
|
567
567
|
};
|
|
568
568
|
|
|
569
|
-
const initOriginalList = (
|
|
569
|
+
const initOriginalList = (state, originalDecodedMap, options) => {
|
|
570
570
|
|
|
571
571
|
// source filter
|
|
572
572
|
let sourceFilter = options.sourceFilter;
|
|
@@ -580,6 +580,7 @@ const initOriginalList = (item, state, originalDecodedMap, options) => {
|
|
|
580
580
|
const originalMap = new Map();
|
|
581
581
|
|
|
582
582
|
const { sources, sourcesContent } = state.sourceMap;
|
|
583
|
+
|
|
583
584
|
sources.forEach((sourcePath, sourceIndex) => {
|
|
584
585
|
|
|
585
586
|
// filter
|
|
@@ -860,12 +861,12 @@ const unpackSourceMap = async (item, state, options) => {
|
|
|
860
861
|
const time_start_decode = Date.now();
|
|
861
862
|
const originalDecodedMap = new Map();
|
|
862
863
|
// for find-original-range
|
|
863
|
-
state.decodedMappings = await decodeSourceMappings(
|
|
864
|
+
state.decodedMappings = await decodeSourceMappings(state, originalDecodedMap);
|
|
864
865
|
// only debug level
|
|
865
866
|
Util.logTime(`decode source mappings: ${sourcePath}`, time_start_decode);
|
|
866
867
|
|
|
867
868
|
// filter original list and init list
|
|
868
|
-
const originalMap = initOriginalList(
|
|
869
|
+
const originalMap = initOriginalList(state, originalDecodedMap, options);
|
|
869
870
|
|
|
870
871
|
originalDecodedMap.clear();
|
|
871
872
|
|
package/lib/generate.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
|
|
3
|
+
const CG = require('console-grid');
|
|
4
|
+
const EC = require('eight-colors');
|
|
4
5
|
const Util = require('./utils/util.js');
|
|
5
6
|
const {
|
|
6
7
|
initIstanbulData, mergeIstanbulCoverage, saveIstanbulReports
|
|
@@ -72,6 +73,9 @@ const getReportGroup = (reports, lcov, dataType) => {
|
|
|
72
73
|
'v8',
|
|
73
74
|
'v8-json',
|
|
74
75
|
|
|
76
|
+
// both
|
|
77
|
+
'console-summary',
|
|
78
|
+
|
|
75
79
|
// istanbul
|
|
76
80
|
'clover',
|
|
77
81
|
'cobertura',
|
|
@@ -95,6 +99,7 @@ const getReportGroup = (reports, lcov, dataType) => {
|
|
|
95
99
|
return;
|
|
96
100
|
}
|
|
97
101
|
|
|
102
|
+
// v8
|
|
98
103
|
if (k.startsWith('v8')) {
|
|
99
104
|
// v8
|
|
100
105
|
if (!reportGroup.v8) {
|
|
@@ -104,6 +109,12 @@ const getReportGroup = (reports, lcov, dataType) => {
|
|
|
104
109
|
return;
|
|
105
110
|
}
|
|
106
111
|
|
|
112
|
+
// both
|
|
113
|
+
if (k === 'console-summary') {
|
|
114
|
+
reportGroup[k] = reportMap[k];
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
107
118
|
// istanbul
|
|
108
119
|
if (!reportGroup.istanbul) {
|
|
109
120
|
reportGroup.istanbul = {};
|
|
@@ -114,6 +125,8 @@ const getReportGroup = (reports, lcov, dataType) => {
|
|
|
114
125
|
return reportGroup;
|
|
115
126
|
};
|
|
116
127
|
|
|
128
|
+
// ========================================================================================================
|
|
129
|
+
|
|
117
130
|
|
|
118
131
|
const generateCoverageReports = async (dataList, options) => {
|
|
119
132
|
|
|
@@ -144,6 +157,8 @@ const generateCoverageReports = async (dataList, options) => {
|
|
|
144
157
|
|
|
145
158
|
};
|
|
146
159
|
|
|
160
|
+
// ========================================================================================================
|
|
161
|
+
|
|
147
162
|
|
|
148
163
|
const getCoverageDataList = async (cacheDir) => {
|
|
149
164
|
const files = fs.readdirSync(cacheDir).filter((f) => f.startsWith('coverage-'));
|
|
@@ -164,8 +179,105 @@ const getCoverageDataList = async (cacheDir) => {
|
|
|
164
179
|
}
|
|
165
180
|
};
|
|
166
181
|
|
|
182
|
+
// ========================================================================================================
|
|
183
|
+
|
|
184
|
+
const capitalizeFirstLetter = (string) => {
|
|
185
|
+
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const pFormatter = (v, row, column) => {
|
|
189
|
+
if (typeof v === 'number') {
|
|
190
|
+
const p = Util.PSF(v, 100, 2);
|
|
191
|
+
if (row.status === 'low') {
|
|
192
|
+
return EC.red(p);
|
|
193
|
+
}
|
|
194
|
+
if (row.status === 'medium') {
|
|
195
|
+
return EC.yellow(p);
|
|
196
|
+
}
|
|
197
|
+
if (row.status === 'high') {
|
|
198
|
+
return EC.green(p);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return v;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const nFormatter = (v) => {
|
|
205
|
+
if (typeof v === 'number') {
|
|
206
|
+
return Util.NF(v);
|
|
207
|
+
}
|
|
208
|
+
return v;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const showConsoleSummary = (coverageResults, options) => {
|
|
212
|
+
|
|
213
|
+
const reportGroup = options.reportGroup;
|
|
214
|
+
const consoleOptions = reportGroup['console-summary'];
|
|
215
|
+
if (!consoleOptions) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const {
|
|
220
|
+
summary, name, type
|
|
221
|
+
} = coverageResults;
|
|
222
|
+
if (name) {
|
|
223
|
+
EC.logCyan(name);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const indicators = {
|
|
227
|
+
v8: ['bytes', 'functions', 'branches', 'lines'],
|
|
228
|
+
istanbul: ['statements', 'functions', 'branches', 'lines']
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const list = indicators[type] || indicators.v8;
|
|
232
|
+
const rows = list.map((k) => {
|
|
233
|
+
return {
|
|
234
|
+
... summary[k],
|
|
235
|
+
name: capitalizeFirstLetter(k)
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const columns = [{
|
|
240
|
+
id: 'name',
|
|
241
|
+
name: 'Name'
|
|
242
|
+
}, {
|
|
243
|
+
id: 'pct',
|
|
244
|
+
name: 'Coverage %',
|
|
245
|
+
align: 'right',
|
|
246
|
+
formatter: pFormatter
|
|
247
|
+
}, {
|
|
248
|
+
id: 'covered',
|
|
249
|
+
name: 'Covered',
|
|
250
|
+
align: 'right',
|
|
251
|
+
formatter: nFormatter
|
|
252
|
+
}, {
|
|
253
|
+
id: 'uncovered',
|
|
254
|
+
name: 'Uncovered',
|
|
255
|
+
align: 'right',
|
|
256
|
+
formatter: nFormatter
|
|
257
|
+
}, {
|
|
258
|
+
id: 'total',
|
|
259
|
+
name: 'Total',
|
|
260
|
+
align: 'right',
|
|
261
|
+
formatter: nFormatter
|
|
262
|
+
}];
|
|
263
|
+
|
|
264
|
+
if (type === 'istanbul') {
|
|
265
|
+
columns.splice(4, 0, {
|
|
266
|
+
id: 'skipped',
|
|
267
|
+
name: 'Skipped',
|
|
268
|
+
align: 'right',
|
|
269
|
+
formatter: nFormatter
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
CG({
|
|
274
|
+
columns,
|
|
275
|
+
rows
|
|
276
|
+
});
|
|
277
|
+
};
|
|
167
278
|
|
|
168
279
|
module.exports = {
|
|
169
280
|
getCoverageDataList,
|
|
170
|
-
generateCoverageReports
|
|
281
|
+
generateCoverageReports,
|
|
282
|
+
showConsoleSummary
|
|
171
283
|
};
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -4,7 +4,9 @@ const defaultOptions = require('./default/options.js');
|
|
|
4
4
|
const Util = require('./utils/util.js');
|
|
5
5
|
|
|
6
6
|
const { initV8ListAndSourcemap } = require('./v8/v8.js');
|
|
7
|
-
const {
|
|
7
|
+
const {
|
|
8
|
+
getCoverageDataList, generateCoverageReports, showConsoleSummary
|
|
9
|
+
} = require('./generate.js');
|
|
8
10
|
|
|
9
11
|
class CoverageReport {
|
|
10
12
|
|
|
@@ -63,9 +65,7 @@ class CoverageReport {
|
|
|
63
65
|
|
|
64
66
|
if (Array.isArray(data)) {
|
|
65
67
|
results.type = 'v8';
|
|
66
|
-
|
|
67
|
-
const inlineSourceMap = false;
|
|
68
|
-
results.data = await initV8ListAndSourcemap(data, this.options, inlineSourceMap);
|
|
68
|
+
results.data = await initV8ListAndSourcemap(data, this.options);
|
|
69
69
|
} else {
|
|
70
70
|
results.type = 'istanbul';
|
|
71
71
|
results.data = data;
|
|
@@ -109,6 +109,8 @@ class CoverageReport {
|
|
|
109
109
|
Util.rmSync(cacheDir);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
showConsoleSummary(coverageResults, this.options);
|
|
113
|
+
|
|
112
114
|
const onEnd = this.options.onEnd;
|
|
113
115
|
if (typeof onEnd === 'function') {
|
|
114
116
|
await onEnd(coverageResults);
|
|
@@ -37,6 +37,19 @@ class IstanbulSummary extends ReportBase {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
getReport() {
|
|
40
|
+
|
|
41
|
+
// remove branchesTrue
|
|
42
|
+
delete this.summary.branchesTrue;
|
|
43
|
+
|
|
44
|
+
// add uncovered
|
|
45
|
+
Object.keys(this.summary).forEach((id) => {
|
|
46
|
+
const item = this.summary[id];
|
|
47
|
+
// NOTE: skipped
|
|
48
|
+
item.uncovered = item.total - item.covered - item.skipped;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// console.log(this.summary);
|
|
52
|
+
|
|
40
53
|
return {
|
|
41
54
|
summary: this.summary,
|
|
42
55
|
files: this.files
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const { parentPort } = require('worker_threads');
|
|
2
|
+
const { decode } = require('../packages/monocart-coverage-vendor.js');
|
|
3
|
+
|
|
4
|
+
parentPort.on('message', (message) => {
|
|
5
|
+
const result = decode(message);
|
|
6
|
+
parentPort.postMessage(result);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
parentPort.postMessage('workerReady');
|
|
@@ -1,49 +1,35 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
} = require('worker_threads');
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { Worker } = require('worker_threads');
|
|
4
3
|
const Util = require('./util.js');
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
// do NOT use isMainThread because it could be executed in worker too. (worker in worker)
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
const decodeMappings = (mappings = '') => {
|
|
8
|
+
if (typeof text !== 'string') {
|
|
9
|
+
mappings = String(mappings);
|
|
10
|
+
}
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
return new Promise((resolve) => {
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
worker.on('message', (message) => {
|
|
18
|
-
if (message === 'workerReady') {
|
|
19
|
-
worker.postMessage(mappings);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
resolve(message);
|
|
23
|
-
worker.terminate();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
worker.on('error', (e) => {
|
|
27
|
-
Util.logError(e.message);
|
|
28
|
-
resolve([]);
|
|
29
|
-
worker.terminate();
|
|
30
|
-
});
|
|
14
|
+
const workerPath = path.resolve(__dirname, 'decode-mappings-worker.js');
|
|
15
|
+
const worker = new Worker(workerPath);
|
|
31
16
|
|
|
17
|
+
worker.on('message', (message) => {
|
|
18
|
+
if (message === 'workerReady') {
|
|
19
|
+
worker.postMessage(mappings);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
resolve(message);
|
|
23
|
+
worker.terminate();
|
|
32
24
|
});
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
module.exports = decodeMappings;
|
|
36
|
-
|
|
37
|
-
} else {
|
|
38
25
|
|
|
39
|
-
|
|
26
|
+
worker.on('error', (e) => {
|
|
27
|
+
Util.logError(e.message);
|
|
28
|
+
resolve([]);
|
|
29
|
+
worker.terminate();
|
|
30
|
+
});
|
|
40
31
|
|
|
41
|
-
parentPort.on('message', (message) => {
|
|
42
|
-
const result = decode(message);
|
|
43
|
-
parentPort.postMessage(result);
|
|
44
32
|
});
|
|
33
|
+
};
|
|
45
34
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
35
|
+
module.exports = decodeMappings;
|
package/lib/v8/v8.js
CHANGED
|
@@ -7,7 +7,7 @@ const { mergeScriptCovs } = require('../packages/monocart-coverage-vendor.js');
|
|
|
7
7
|
const collectSourceMaps = require('../converter/collect-source-maps.js');
|
|
8
8
|
const version = require('../../package.json').version;
|
|
9
9
|
|
|
10
|
-
const initV8ListAndSourcemap = async (v8list, options
|
|
10
|
+
const initV8ListAndSourcemap = async (v8list, options) => {
|
|
11
11
|
|
|
12
12
|
// filter list first
|
|
13
13
|
const entryFilter = options.entryFilter;
|
|
@@ -47,8 +47,8 @@ const initV8ListAndSourcemap = async (v8list, options, inlineSourceMap) => {
|
|
|
47
47
|
data.type = 'js';
|
|
48
48
|
data.functions = item.functions;
|
|
49
49
|
data.source = item.source;
|
|
50
|
-
//
|
|
51
|
-
|
|
50
|
+
// could be existed
|
|
51
|
+
data.sourceMap = item.sourceMap;
|
|
52
52
|
} else if (item.ranges) {
|
|
53
53
|
data.type = 'css';
|
|
54
54
|
data.ranges = item.ranges;
|
|
@@ -72,7 +72,7 @@ const initV8ListAndSourcemap = async (v8list, options, inlineSourceMap) => {
|
|
|
72
72
|
|
|
73
73
|
// collect sourcemap first
|
|
74
74
|
const time_start = Date.now();
|
|
75
|
-
const count = await collectSourceMaps(v8list, options
|
|
75
|
+
const count = await collectSourceMaps(v8list, options);
|
|
76
76
|
if (count) {
|
|
77
77
|
// debug level time
|
|
78
78
|
Util.logTime(`loaded ${count} sourcemaps`, time_start);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "monocart-coverage-reports",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "Monocart coverage reports",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -17,13 +17,15 @@
|
|
|
17
17
|
"build-docs": "node ./scripts/build-docs.js",
|
|
18
18
|
"build-test": "node ./scripts/build-test.js",
|
|
19
19
|
"build": "sf lint && sf b -p && npm run build-test",
|
|
20
|
-
"test-node": "npm run test-node-env && npm run test-node-ins && npm run test-node-api",
|
|
20
|
+
"test-node": "npm run test-node-env && npm run test-node-ins && npm run test-node-api && npm run test-node-cdp",
|
|
21
21
|
"test-node-env": "cross-env NODE_V8_COVERAGE=.temp/v8-coverage node ./test/test-node-env.js && node ./test/generate-node-report.js",
|
|
22
22
|
"test-node-ins": "node ./test/test-node-ins.js",
|
|
23
23
|
"test-node-api": "cross-env NODE_V8_COVERAGE=.temp/v8-coverage node ./test/test-node-api.js",
|
|
24
|
+
"test-node-cdp": "node --inspect=9229 ./test/test-node-cdp.js",
|
|
24
25
|
"test-browser": "node ./test/test.js",
|
|
25
26
|
"test": "npm run test-browser && npm run test-node && npm run build-docs",
|
|
26
27
|
"dev": "sf d v8",
|
|
28
|
+
"open": "node ./scripts/open.js",
|
|
27
29
|
"patch": "npm run build && sf publish patch -r"
|
|
28
30
|
},
|
|
29
31
|
"workspaces": [
|
|
@@ -46,11 +48,10 @@
|
|
|
46
48
|
"istanbul-reports": "~3.1.6",
|
|
47
49
|
"lz-utils": "~2.0.1",
|
|
48
50
|
"monocart-code-viewer": "^1.0.13",
|
|
49
|
-
"monocart-formatter": "~2.2.
|
|
51
|
+
"monocart-formatter": "~2.2.1",
|
|
50
52
|
"turbogrid": "~3.0.12"
|
|
51
53
|
},
|
|
52
54
|
"devDependencies": {
|
|
53
|
-
"cross-env": "^7.0.3",
|
|
54
55
|
"eslint": "^8.56.0",
|
|
55
56
|
"eslint-config-plus": "^1.0.6",
|
|
56
57
|
"eslint-plugin-html": "^7.1.0",
|