monocart-coverage-reports 2.5.3 → 2.5.5
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 +10 -30
- package/lib/converter/ast.js +17 -13
- package/lib/converter/collect-source-maps.js +56 -15
- package/lib/converter/converter.js +8 -1
- package/lib/generate.js +1 -1
- package/lib/packages/monocart-coverage-v8.js +1 -1
- package/lib/platform/share.js +1 -1
- package/lib/utils/util.js +9 -1
- package/lib/v8/v8.js +6 -4
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -65,10 +65,10 @@ console.log(coverageResults.summary);
|
|
|
65
65
|
## Available Reports
|
|
66
66
|
> V8 build-in reports (V8 data only):
|
|
67
67
|
- `v8`
|
|
68
|
-
- Browser: Build with webpack [V8](https://cenfun.github.io/monocart-coverage-reports/v8) and [
|
|
69
|
-
- Node.js: Collect with [env](https://cenfun.github.io/monocart-coverage-reports/
|
|
68
|
+
- Browser: Build with webpack [V8](https://cenfun.github.io/monocart-coverage-reports/v8) and [Minify](https://cenfun.github.io/monocart-coverage-reports/minify); Build with [Rollup](https://cenfun.github.io/monocart-coverage-reports/rollup) and [Esbuild](https://cenfun.github.io/monocart-coverage-reports/esbuild); Collect with [puppeteer](https://cenfun.github.io/monocart-coverage-reports/puppeteer/); [anonymous](https://cenfun.github.io/monocart-coverage-reports/anonymous/) and [css](https://cenfun.github.io/monocart-coverage-reports/css/)
|
|
69
|
+
- Node.js: Collect with [env](https://cenfun.github.io/monocart-coverage-reports/node-env), and also V8 [API](https://cenfun.github.io/monocart-coverage-reports/node-api), [Inspector](https://cenfun.github.io/monocart-coverage-reports/node-ins) and [CDP](https://cenfun.github.io/monocart-coverage-reports/node-cdp); Web server example: [koa](https://cenfun.github.io/monocart-coverage-reports/node-koa/)
|
|
70
70
|
|
|
71
|
-

|
|
72
72
|
|
|
73
73
|
- `v8-json`
|
|
74
74
|
- [V8 coverage-report.json](https://cenfun.github.io/monocart-coverage-reports/v8-and-istanbul/coverage-report.json)
|
|
@@ -76,7 +76,7 @@ console.log(coverageResults.summary);
|
|
|
76
76
|
- coverage data for [Codecov](https://docs.codecov.com/docs/codecov-custom-coverage-format), see [example](https://app.codecov.io/github/cenfun/monocart-coverage-reports)
|
|
77
77
|
- `console-details` Show file coverage and uncovered lines in the console. Like `text`, but for V8. For Github actions, we can enforce color with env: `FORCE_COLOR: true`.
|
|
78
78
|
|
|
79
|
-

|
|
80
80
|
|
|
81
81
|
> Istanbul build-in reports (both V8 and istanbul data):
|
|
82
82
|
- `clover`
|
|
@@ -101,7 +101,7 @@ console.log(coverageResults.summary);
|
|
|
101
101
|
> Other reports:
|
|
102
102
|
- `console-summary` shows coverage summary in the console
|
|
103
103
|
|
|
104
|
-

|
|
105
105
|
|
|
106
106
|
- `raw` only keep all original data, which can be used for other reports input with `inputDir`
|
|
107
107
|
- see [Merge Coverage Reports](#merge-coverage-reports)
|
|
@@ -597,32 +597,12 @@ const a = tf ? 'true' : 'false';
|
|
|
597
597
|
```
|
|
598
598
|
> `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.
|
|
599
599
|
|
|
600
|
-
- 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.
|
|
601
|
-
And also there are some problems about counting the functions and branches:
|
|
602
|
-
```js
|
|
603
|
-
// Problem: When the function or parent-level function is uncovered, then its sub-level functions will never be counted.
|
|
604
|
-
functions.forEach(block => {
|
|
605
|
-
block.ranges.forEach((range, i) => {
|
|
606
|
-
if (block.isBlockCoverage) {
|
|
607
|
-
// v8-to-istanbul: new CovBranch()
|
|
608
|
-
// Problem: not every block is branch, and the first block could be function.
|
|
609
|
-
if (block.functionName && i === 0) {
|
|
610
|
-
// v8-to-istanbul: new CovFunction()
|
|
611
|
-
// Problem: no anonymous function
|
|
612
|
-
}
|
|
613
|
-
} else if (block.functionName) {
|
|
614
|
-
// v8-to-istanbul: new CovFunction()
|
|
615
|
-
// Problem: no anonymous function
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
});
|
|
619
|
-
```
|
|
620
|
-
see source code [v8-to-istanbul.js](https://github.com/istanbuljs/v8-to-istanbul/blob/master/lib/v8-to-istanbul.js)
|
|
600
|
+
- 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. And also there are some problems about counting the functions and branches.
|
|
621
601
|
|
|
622
602
|
### How Monocart Works
|
|
623
|
-
We implemented new converter
|
|
603
|
+
We implemented new converter:
|
|
624
604
|
- 1, Trying to fix the middle position if not found the exact mapping for the position.
|
|
625
|
-
- 2, Finding all functions, statements and branches by parsing the source code [AST](https://github.com/acornjs/acorn),
|
|
605
|
+
- 2, Finding all functions, statements and branches by parsing the source code [AST](https://github.com/acornjs/acorn). However, there's a small issue, which is the V8 cannot provide effective branch coverage information for `AssignmentPattern`.
|
|
626
606
|
|
|
627
607
|
| AST | V8 |
|
|
628
608
|
| :---------------------| :------------- |
|
|
@@ -645,11 +625,11 @@ const coverageOptions = {
|
|
|
645
625
|
};
|
|
646
626
|
```
|
|
647
627
|
When `logging` is `debug`, the raw report data will be preserved in `[outputDir]/.cache` or `[outputDir]/raw` if `raw` report is used. And the dist file will be preserved in the V8 list, and by opening the browser's devtool, it makes data verification visualization effortless.
|
|
648
|
-

|
|
649
629
|
|
|
650
630
|
- Check sourcemap with [Source Map Visualization](https://evanw.github.io/source-map-visualization/)
|
|
651
631
|
|
|
652
|
-

|
|
653
633
|
|
|
654
634
|
## Integration
|
|
655
635
|
### [Playwright](https://github.com/microsoft/playwright)
|
package/lib/converter/ast.js
CHANGED
|
@@ -148,7 +148,9 @@ const getFakeAstInfo = (coverageList) => {
|
|
|
148
148
|
|
|
149
149
|
const getJsAstInfo = (item, coverageList) => {
|
|
150
150
|
|
|
151
|
-
const {
|
|
151
|
+
const {
|
|
152
|
+
source, fake, empty
|
|
153
|
+
} = item;
|
|
152
154
|
|
|
153
155
|
if (fake) {
|
|
154
156
|
return getFakeAstInfo(coverageList);
|
|
@@ -169,7 +171,11 @@ const getJsAstInfo = (item, coverageList) => {
|
|
|
169
171
|
try {
|
|
170
172
|
ast = acorn.parse(source, options);
|
|
171
173
|
} catch (e) {
|
|
172
|
-
|
|
174
|
+
|
|
175
|
+
// empty = untested file, could be .ts, .vue and so on, that can not be parsed normally
|
|
176
|
+
if (!empty) {
|
|
177
|
+
Util.logInfo(`failed to parse source file: ${item.sourcePath} ${e.message}`);
|
|
178
|
+
}
|
|
173
179
|
// https://github.com/acornjs/acorn/tree/master/acorn-loose
|
|
174
180
|
// It is recommended to always try a parse with the regular acorn parser first,
|
|
175
181
|
// and only fall back to this parser when that one finds syntax errors.
|
|
@@ -208,18 +214,20 @@ const addRule = (item, coverageList, coveredRanges, count = 0) => {
|
|
|
208
214
|
|
|
209
215
|
};
|
|
210
216
|
|
|
211
|
-
const addAtRule = (item, coverageList, coveredRanges) => {
|
|
217
|
+
const addAtRule = (item, coverageList, coveredRanges, empty) => {
|
|
212
218
|
const { name, nodes } = item;
|
|
213
219
|
|
|
220
|
+
const defaultCount = empty ? 0 : 1;
|
|
221
|
+
|
|
214
222
|
if (['charset', 'import', 'namespace'].includes(name)) {
|
|
215
|
-
addRule(item, coverageList, coveredRanges,
|
|
223
|
+
addRule(item, coverageList, coveredRanges, defaultCount);
|
|
216
224
|
return;
|
|
217
225
|
}
|
|
218
226
|
|
|
219
227
|
if (['media', 'supports', 'container', 'layer'].includes(name)) {
|
|
220
228
|
|
|
221
229
|
const childCoverageList = [];
|
|
222
|
-
const count = addCssRules(nodes, childCoverageList, coveredRanges);
|
|
230
|
+
const count = addCssRules(nodes, childCoverageList, coveredRanges, empty);
|
|
223
231
|
if (count) {
|
|
224
232
|
|
|
225
233
|
coverageList.push({
|
|
@@ -249,7 +257,7 @@ const addAtRule = (item, coverageList, coveredRanges) => {
|
|
|
249
257
|
|
|
250
258
|
};
|
|
251
259
|
|
|
252
|
-
const addCssRules = (list, coverageList, coveredRanges) => {
|
|
260
|
+
const addCssRules = (list, coverageList, coveredRanges, empty) => {
|
|
253
261
|
|
|
254
262
|
let count = 0;
|
|
255
263
|
|
|
@@ -287,7 +295,7 @@ const addCssRules = (list, coverageList, coveredRanges) => {
|
|
|
287
295
|
// });
|
|
288
296
|
|
|
289
297
|
if (type === 'atrule') {
|
|
290
|
-
addAtRule(item, coverageList, coveredRanges);
|
|
298
|
+
addAtRule(item, coverageList, coveredRanges, empty);
|
|
291
299
|
}
|
|
292
300
|
|
|
293
301
|
});
|
|
@@ -298,7 +306,7 @@ const addCssRules = (list, coverageList, coveredRanges) => {
|
|
|
298
306
|
const getCssAstInfo = (item, coverageList) => {
|
|
299
307
|
|
|
300
308
|
const {
|
|
301
|
-
source,
|
|
309
|
+
source, ranges, empty
|
|
302
310
|
} = item;
|
|
303
311
|
|
|
304
312
|
// sort covered ranges
|
|
@@ -312,13 +320,9 @@ const getCssAstInfo = (item, coverageList) => {
|
|
|
312
320
|
};
|
|
313
321
|
});
|
|
314
322
|
|
|
315
|
-
const time_start_parse = new Date();
|
|
316
323
|
const ast = parseCss(source);
|
|
317
|
-
Util.logTime(`parse css: ${sourcePath}`, time_start_parse);
|
|
318
324
|
|
|
319
|
-
|
|
320
|
-
addCssRules(ast.nodes, coverageList, coveredRanges);
|
|
321
|
-
// }
|
|
325
|
+
addCssRules(ast.nodes, coverageList, coveredRanges, empty);
|
|
322
326
|
|
|
323
327
|
return {
|
|
324
328
|
functions: [],
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
2
3
|
const EC = require('eight-colors');
|
|
3
4
|
const { fileURLToPath, pathToFileURL } = require('url');
|
|
4
5
|
const Concurrency = require('../platform/concurrency.js');
|
|
@@ -66,16 +67,49 @@ const getSourceMapUrl = (content, url) => {
|
|
|
66
67
|
}
|
|
67
68
|
};
|
|
68
69
|
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
const resolveSourcesContent = async (sources, url) => {
|
|
71
|
+
const list = [];
|
|
72
|
+
for (const file of sources) {
|
|
73
|
+
const sourceUrl = Util.resolveUrl(file, url);
|
|
74
|
+
if (sourceUrl) {
|
|
75
|
+
const sourcePath = sourceUrl.toString();
|
|
76
|
+
if (sourcePath.startsWith('file:')) {
|
|
77
|
+
const content = await Util.readFile(path.resolve(fileURLToPath(sourcePath)));
|
|
78
|
+
list.push(content);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
76
81
|
}
|
|
82
|
+
Util.logDebug(EC.red(`failed to load source content ${file}`));
|
|
83
|
+
}
|
|
84
|
+
if (list.length === sources.length) {
|
|
85
|
+
return list;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const resolveSourceMap = async (data, url) => {
|
|
90
|
+
if (!data) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const {
|
|
94
|
+
sources, sourcesContent, mappings
|
|
95
|
+
} = data;
|
|
96
|
+
|
|
97
|
+
if (!sources || !mappings) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// exists sources content
|
|
102
|
+
if (sourcesContent && sourcesContent.length === sources.length) {
|
|
77
103
|
return data;
|
|
78
104
|
}
|
|
105
|
+
|
|
106
|
+
// load sources content by sources
|
|
107
|
+
const contents = await resolveSourcesContent(sources, url);
|
|
108
|
+
if (contents) {
|
|
109
|
+
data.sourcesContent = contents;
|
|
110
|
+
return data;
|
|
111
|
+
}
|
|
112
|
+
|
|
79
113
|
};
|
|
80
114
|
|
|
81
115
|
const saveSourceFile = async (filePath, data) => {
|
|
@@ -92,7 +126,7 @@ const saveSourceFile = async (filePath, data) => {
|
|
|
92
126
|
|
|
93
127
|
const collectSourceMaps = async (v8list, options) => {
|
|
94
128
|
|
|
95
|
-
|
|
129
|
+
const smList = [];
|
|
96
130
|
const concurrency = new Concurrency();
|
|
97
131
|
for (const item of v8list) {
|
|
98
132
|
|
|
@@ -130,8 +164,11 @@ const collectSourceMaps = async (v8list, options) => {
|
|
|
130
164
|
// from inline sync
|
|
131
165
|
const smc = convertSourceMap.fromSource(source);
|
|
132
166
|
if (smc) {
|
|
133
|
-
sourceData.sourceMap = resolveSourceMap(smc.sourcemap);
|
|
134
|
-
|
|
167
|
+
sourceData.sourceMap = await resolveSourceMap(smc.sourcemap, url);
|
|
168
|
+
smList.push({
|
|
169
|
+
url,
|
|
170
|
+
inline: true
|
|
171
|
+
});
|
|
135
172
|
await saveSourceFile(sourcePath, sourceData);
|
|
136
173
|
continue;
|
|
137
174
|
}
|
|
@@ -154,16 +191,20 @@ const collectSourceMaps = async (v8list, options) => {
|
|
|
154
191
|
|
|
155
192
|
// from url concurrency
|
|
156
193
|
await concurrency.start(async (item) => {
|
|
157
|
-
const sourceData = item
|
|
158
|
-
const
|
|
194
|
+
const { sourceMapUrl, sourceData } = item;
|
|
195
|
+
const { url } = sourceData;
|
|
196
|
+
const data = await loadSourceMap(sourceMapUrl);
|
|
159
197
|
if (data) {
|
|
160
|
-
sourceData.sourceMap = resolveSourceMap(data);
|
|
161
|
-
|
|
198
|
+
sourceData.sourceMap = await resolveSourceMap(data, url);
|
|
199
|
+
smList.push({
|
|
200
|
+
url,
|
|
201
|
+
sourceMapUrl
|
|
202
|
+
});
|
|
162
203
|
}
|
|
163
204
|
await saveSourceFile(item.sourcePath, sourceData);
|
|
164
205
|
});
|
|
165
206
|
|
|
166
|
-
return
|
|
207
|
+
return smList;
|
|
167
208
|
|
|
168
209
|
};
|
|
169
210
|
|
|
@@ -1300,6 +1300,7 @@ const convertV8List = (v8list, options) => {
|
|
|
1300
1300
|
}
|
|
1301
1301
|
|
|
1302
1302
|
// ============================
|
|
1303
|
+
const time_start_ast = Date.now();
|
|
1303
1304
|
// move functions and ranges to coverageList
|
|
1304
1305
|
let coverageList = [];
|
|
1305
1306
|
let astInfo;
|
|
@@ -1318,6 +1319,7 @@ const convertV8List = (v8list, options) => {
|
|
|
1318
1319
|
delete item.ranges;
|
|
1319
1320
|
}
|
|
1320
1321
|
}
|
|
1322
|
+
Util.logTime(`- parsed ast: ${sourcePath}`, time_start_ast);
|
|
1321
1323
|
|
|
1322
1324
|
// console.log(sourcePath, astInfo.statements.length);
|
|
1323
1325
|
// ============================
|
|
@@ -1345,14 +1347,19 @@ const convertV8List = (v8list, options) => {
|
|
|
1345
1347
|
v8Data: item
|
|
1346
1348
|
};
|
|
1347
1349
|
|
|
1350
|
+
const time_start_unpack = Date.now();
|
|
1348
1351
|
unpackDistFile(item, state, options);
|
|
1352
|
+
Util.logTime(`- unpacked dist: ${sourcePath}`, time_start_unpack);
|
|
1349
1353
|
|
|
1350
1354
|
stateList.push(state);
|
|
1351
1355
|
|
|
1352
1356
|
}
|
|
1353
1357
|
|
|
1354
|
-
|
|
1358
|
+
const time_start_convert = Date.now();
|
|
1359
|
+
const dataList = generateV8DataList(stateList, options);
|
|
1360
|
+
Util.logTime('- generated v8 data list', time_start_convert);
|
|
1355
1361
|
|
|
1362
|
+
return dataList;
|
|
1356
1363
|
};
|
|
1357
1364
|
|
|
1358
1365
|
module.exports = {
|
package/lib/generate.js
CHANGED
|
@@ -247,7 +247,7 @@ const getCoverageResults = async (dataList, sourceCache, options) => {
|
|
|
247
247
|
const {
|
|
248
248
|
v8DataList, coverageData, fileSources
|
|
249
249
|
} = results;
|
|
250
|
-
Util.logTime(`converted v8
|
|
250
|
+
Util.logTime(`converted v8 list: ${v8DataList.length}`, time_start);
|
|
251
251
|
|
|
252
252
|
return generateV8ListReports(v8DataList, coverageData, fileSources, options);
|
|
253
253
|
}
|