monocart-coverage-reports 2.7.0 → 2.7.2
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 +7 -6
- package/lib/cli.js +73 -87
- package/lib/converter/collect-source-maps.js +35 -18
- package/lib/converter/converter.js +2 -2
- package/lib/generate.js +130 -1
- package/lib/index.d.ts +7 -1
- package/lib/index.js +11 -1
- package/lib/packages/monocart-coverage-v8.js +1 -1
- package/lib/register/hooks.js +40 -0
- package/lib/register/hooks.mjs +3 -0
- package/lib/register/register.js +4 -0
- package/lib/register/register.mjs +3 -0
- package/lib/utils/source-path.js +37 -17
- package/lib/v8/v8.js +1 -1
- package/package.json +91 -83
package/README.md
CHANGED
|
@@ -6,15 +6,14 @@
|
|
|
6
6
|

|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
> A code coverage tool to generate native [V8](https://v8.dev/blog/javascript-code-coverage) reports or [Istanbul](https://istanbul.js.org/) reports. As early as 2017, V8 started offering native support for coverage data. However, we have essentially only been able to see coverage reports from Istanbul. Nowadays, we can directly generate V8 coverage reports with `monocart-coverage-reports`.
|
|
9
|
+
> A code coverage tool to generate native [V8](https://v8.dev/blog/javascript-code-coverage) reports or [Istanbul](https://istanbul.js.org/) reports.
|
|
11
10
|
|
|
12
11
|
* [Usage](#usage)
|
|
13
12
|
* [Default Options](#default-options)
|
|
14
13
|
* [Available Reports](#available-reports)
|
|
15
14
|
* [Using `entryFilter` and `sourceFilter` to filter the results for V8 report](#using-entryfilter-and-sourcefilter-to-filter-the-results-for-v8-report)
|
|
16
15
|
* [onEnd Hook](#onend-hook)
|
|
17
|
-
* [
|
|
16
|
+
* [Command Line](#command-line)
|
|
18
17
|
* [Compare Reports](#compare-reports)
|
|
19
18
|
* [Compare Workflows](#compare-workflows)
|
|
20
19
|
* [Collecting Istanbul Coverage Data](#collecting-istanbul-coverage-data)
|
|
@@ -275,7 +274,7 @@ const coverageOptions = {
|
|
|
275
274
|
}
|
|
276
275
|
```
|
|
277
276
|
|
|
278
|
-
##
|
|
277
|
+
## Command Line
|
|
279
278
|
> 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`.
|
|
280
279
|
|
|
281
280
|
- Installing globally
|
|
@@ -324,8 +323,9 @@ Options:
|
|
|
324
323
|
- `mcr.config.json` - json format
|
|
325
324
|
- `.mcrrc.js`
|
|
326
325
|
- `.mcrrc` - json format
|
|
327
|
-
- `mcr.config.ts`
|
|
326
|
+
- `mcr.config.ts` (requires preloading the typescript module)
|
|
328
327
|
|
|
328
|
+
- Working with `tsx`, see [mcr-tsx](https://github.com/cenfun/mcr-tsx)
|
|
329
329
|
- Working with `ts-node`, see [mcr-ts-node](https://github.com/cenfun/mcr-ts-node)
|
|
330
330
|
|
|
331
331
|
## Compare Reports
|
|
@@ -454,7 +454,7 @@ Possible solutions:
|
|
|
454
454
|
> node [./test/test-node-koa.js](./test/test-node-koa.js)
|
|
455
455
|
|
|
456
456
|
- [Child Process](https://nodejs.org/docs/latest/api/child_process.html) + NODE_V8_COVERAGE
|
|
457
|
-
- see [
|
|
457
|
+
- see [Command Line](#command-line)
|
|
458
458
|
|
|
459
459
|
## Collecting V8 Coverage Data with `CDPClient` API
|
|
460
460
|
- Work with node debugger `--inspect=9229`
|
|
@@ -814,6 +814,7 @@ const coverageOptions = {
|
|
|
814
814
|
- [cypress-monocart-coverage](https://github.com/cenfun/cypress-monocart-coverage) - A Cypress plugin for coverage reports
|
|
815
815
|
|
|
816
816
|
### [CodeceptJS](https://github.com/codeceptjs/CodeceptJS)
|
|
817
|
+
- CodeceptJS has integrated MCR from version [3.5.15](https://github.com/codeceptjs/CodeceptJS/releases/tag/3.5.15)
|
|
817
818
|
- [codeceptjs-monocart-coverage](https://github.com/cenfun/codeceptjs-monocart-coverage) - A CodeceptJS plugin for coverage reports
|
|
818
819
|
|
|
819
820
|
### [WebdriverIO](https://github.com/webdriverio/webdriverio)
|
package/lib/cli.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const EC = require('eight-colors');
|
|
6
|
-
const { fileURLToPath } = require('url');
|
|
7
6
|
|
|
8
7
|
const {
|
|
9
8
|
foregroundChild,
|
|
@@ -12,120 +11,104 @@ const {
|
|
|
12
11
|
|
|
13
12
|
const MCR = require('./index.js');
|
|
14
13
|
const Util = require('./utils/util.js');
|
|
15
|
-
const { resolveSourceMap } = require('./converter/collect-source-maps.js');
|
|
16
14
|
|
|
17
15
|
const version = require('../package.json').version;
|
|
18
16
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (fs.existsSync(filePath)) {
|
|
24
|
-
source = fs.readFileSync(filePath).toString('utf8');
|
|
17
|
+
const getRegisterPath = (filename) => {
|
|
18
|
+
const rel = Util.relativePath(path.resolve(__dirname, 'register', filename));
|
|
19
|
+
if (rel.startsWith('.')) {
|
|
20
|
+
return rel;
|
|
25
21
|
}
|
|
22
|
+
return `./${rel}`;
|
|
23
|
+
};
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
const isFeatureSupported = (nv) => {
|
|
26
|
+
// module register added in Node.js: v20.6.0
|
|
27
|
+
const [major, minor] = nv.split('.').map((s) => parseInt(s));
|
|
28
|
+
if (major < 20) {
|
|
29
|
+
return false;
|
|
31
30
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const lineLengths = sourcemapData && sourcemapData.lineLengths;
|
|
35
|
-
|
|
36
|
-
// for fake source file (can not parse to AST)
|
|
37
|
-
if (lineLengths) {
|
|
38
|
-
let fakeSource = '';
|
|
39
|
-
sourcemapData.lineLengths.forEach((length) => {
|
|
40
|
-
fakeSource += `${''.padEnd(length, '*')}\n`;
|
|
41
|
-
});
|
|
42
|
-
entry.fake = true;
|
|
43
|
-
entry.source = fakeSource;
|
|
31
|
+
if (major > 20) {
|
|
32
|
+
return true;
|
|
44
33
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// This is a problem for typescript
|
|
48
|
-
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const resolveEntrySourceMap = (entry, sourceMapCache = {}) => {
|
|
52
|
-
// sourcemap data
|
|
53
|
-
const sourcemapData = sourceMapCache[entry.url];
|
|
54
|
-
if (sourcemapData) {
|
|
55
|
-
if (sourcemapData.data) {
|
|
56
|
-
entry.sourceMap = resolveSourceMap(sourcemapData.data, entry.url);
|
|
57
|
-
}
|
|
34
|
+
if (minor < 6) {
|
|
35
|
+
return false;
|
|
58
36
|
}
|
|
37
|
+
return true;
|
|
59
38
|
};
|
|
60
39
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
if (!
|
|
40
|
+
const initNodeOptions = async (cliOptions) => {
|
|
41
|
+
const nv = process.versions.node;
|
|
42
|
+
if (!isFeatureSupported(nv)) {
|
|
43
|
+
Util.logInfo(EC.yellow(`The current Node.js version "${nv}" does NOT support "module.register", it requires "20.6.0" or higher.`));
|
|
64
44
|
return;
|
|
65
45
|
}
|
|
66
46
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (!Util.isList(coverageList)) {
|
|
72
|
-
return;
|
|
47
|
+
const nodeOptions = [];
|
|
48
|
+
if (process.env.NODE_OPTIONS) {
|
|
49
|
+
nodeOptions.push(process.env.NODE_OPTIONS);
|
|
73
50
|
}
|
|
74
51
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
52
|
+
if (cliOptions.import) {
|
|
53
|
+
nodeOptions.push('--import');
|
|
54
|
+
nodeOptions.push(cliOptions.import);
|
|
55
|
+
await import(cliOptions.import);
|
|
56
|
+
} else if (cliOptions.require) {
|
|
57
|
+
nodeOptions.push('--require');
|
|
58
|
+
nodeOptions.push(cliOptions.require);
|
|
59
|
+
await import(cliOptions.require);
|
|
78
60
|
}
|
|
79
61
|
|
|
80
|
-
|
|
81
|
-
};
|
|
62
|
+
// console.log(nodeOptions);
|
|
82
63
|
|
|
83
|
-
|
|
64
|
+
// export source after
|
|
65
|
+
const hasImport = nodeOptions.find((it) => typeof it === 'string' && it.includes('--import'));
|
|
66
|
+
if (hasImport) {
|
|
67
|
+
const importPath = getRegisterPath('register.mjs');
|
|
68
|
+
const hasImportPath = nodeOptions.find((it) => typeof it === 'string' && it.includes(importPath));
|
|
69
|
+
if (!hasImportPath) {
|
|
70
|
+
nodeOptions.push('--import');
|
|
71
|
+
nodeOptions.push(importPath);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
const requirePath = getRegisterPath('register.js');
|
|
75
|
+
const hasRequirePath = nodeOptions.find((it) => typeof it === 'string' && it.includes(requirePath));
|
|
76
|
+
if (!hasRequirePath) {
|
|
77
|
+
nodeOptions.push('--require');
|
|
78
|
+
nodeOptions.push(requirePath);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
84
81
|
|
|
85
|
-
|
|
82
|
+
// console.log(nodeOptions);
|
|
86
83
|
|
|
87
|
-
|
|
88
|
-
if (!files.length) {
|
|
89
|
-
return added;
|
|
90
|
-
}
|
|
84
|
+
process.env.NODE_OPTIONS = nodeOptions.join(' ');
|
|
91
85
|
|
|
92
|
-
|
|
93
|
-
const entryFilter = coverageReport.getEntryFilter();
|
|
94
|
-
for (const filename of files) {
|
|
95
|
-
const filePath = path.resolve(nodeV8CoverageDir, filename);
|
|
96
|
-
const content = fs.readFileSync(filePath).toString('utf-8');
|
|
97
|
-
if (content) {
|
|
98
|
-
const json = JSON.parse(content);
|
|
99
|
-
const coverageList = json.result;
|
|
100
|
-
const sourceMapCache = json['source-map-cache'];
|
|
101
|
-
const coverageData = collectCoverageData(coverageList, entryFilter, sourceMapCache);
|
|
102
|
-
if (coverageData) {
|
|
103
|
-
await coverageReport.add(coverageData);
|
|
104
|
-
added = true;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
86
|
+
};
|
|
108
87
|
|
|
109
|
-
|
|
88
|
+
const initNodeV8CoverageDir = (coverageOptions) => {
|
|
89
|
+
// dir for node v8 coverage
|
|
90
|
+
const nodeV8CoverageDir = Util.relativePath(path.resolve(coverageOptions.outputDir, '.v8-coverage'));
|
|
91
|
+
process.env.NODE_V8_COVERAGE = nodeV8CoverageDir;
|
|
92
|
+
// clean v8 cache before running
|
|
93
|
+
Util.rmSync(nodeV8CoverageDir);
|
|
94
|
+
// Util.logInfo(`V8 coverage dir: ${EC.cyan(nodeV8CoverageDir)}`);
|
|
95
|
+
|
|
96
|
+
return nodeV8CoverageDir;
|
|
110
97
|
};
|
|
111
98
|
|
|
112
99
|
const executeCommand = async (command, cliOptions) => {
|
|
113
100
|
|
|
114
101
|
Util.logInfo(`Execute: ${EC.cyan(command)}`);
|
|
115
102
|
|
|
103
|
+
await initNodeOptions(cliOptions);
|
|
104
|
+
|
|
116
105
|
// console.log(options);
|
|
117
106
|
const coverageReport = MCR(cliOptions);
|
|
118
107
|
await coverageReport.loadConfig(cliOptions.config);
|
|
119
108
|
coverageReport.cleanCache();
|
|
120
109
|
|
|
121
110
|
const coverageOptions = coverageReport.options;
|
|
122
|
-
|
|
123
|
-
// dir for node v8 coverage
|
|
124
|
-
const nodeV8CoverageDir = Util.relativePath(path.resolve(coverageOptions.outputDir, '.v8-coverage'));
|
|
125
|
-
process.env.NODE_V8_COVERAGE = nodeV8CoverageDir;
|
|
126
|
-
// clean v8 cache before running
|
|
127
|
-
Util.rmSync(nodeV8CoverageDir);
|
|
128
|
-
// Util.logInfo(`V8 coverage dir: ${EC.cyan(nodeV8CoverageDir)}`);
|
|
111
|
+
const nodeV8CoverageDir = initNodeV8CoverageDir(coverageOptions);
|
|
129
112
|
|
|
130
113
|
// =========================================
|
|
131
114
|
// onStart hook
|
|
@@ -141,11 +124,6 @@ const executeCommand = async (command, cliOptions) => {
|
|
|
141
124
|
|
|
142
125
|
// generate coverage even it is failed. code != 0
|
|
143
126
|
|
|
144
|
-
if (!fs.existsSync(nodeV8CoverageDir)) {
|
|
145
|
-
EC.logRed(`Not found V8 coverage dir: ${Util.relativePath(nodeV8CoverageDir)}`);
|
|
146
|
-
return 1;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
127
|
// =========================================
|
|
150
128
|
// onReady hook before adding coverage data.
|
|
151
129
|
// Sometimes, the child process has not yet finished writing the coverage data, and it needs to wait here.
|
|
@@ -155,7 +133,12 @@ const executeCommand = async (command, cliOptions) => {
|
|
|
155
133
|
}
|
|
156
134
|
// =========================================
|
|
157
135
|
|
|
158
|
-
|
|
136
|
+
if (!fs.existsSync(nodeV8CoverageDir)) {
|
|
137
|
+
EC.logRed(`Invalid V8 coverage dir: ${Util.relativePath(nodeV8CoverageDir)}`);
|
|
138
|
+
return 1;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
await coverageReport.addFromDir(nodeV8CoverageDir);
|
|
159
142
|
await coverageReport.generate();
|
|
160
143
|
|
|
161
144
|
// remove nodeV8CoverageDir
|
|
@@ -196,6 +179,9 @@ program
|
|
|
196
179
|
|
|
197
180
|
.option('--lcov', 'generate lcov.info file')
|
|
198
181
|
|
|
182
|
+
.option('--import <module>', 'preload module at startup')
|
|
183
|
+
.option('--require <module>', 'preload module at startup')
|
|
184
|
+
|
|
199
185
|
.action((_command, cliOptions) => {
|
|
200
186
|
const command = program.args.join(' ');
|
|
201
187
|
executeCommand(command, cliOptions);
|
|
@@ -54,22 +54,37 @@ const getSourceMapUrl = (content, url) => {
|
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
-
const resolveSourcesContent = (
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
const resolveSourcesContent = (data, url) => {
|
|
58
|
+
|
|
59
|
+
const { sources, sourcesContent } = data;
|
|
60
|
+
|
|
61
|
+
let failed = false;
|
|
62
|
+
sources.forEach((file, i) => {
|
|
63
|
+
if (typeof sourcesContent[i] === 'string') {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
60
67
|
const sourceUrl = Util.resolveUrl(file, url);
|
|
61
68
|
if (sourceUrl) {
|
|
62
|
-
|
|
69
|
+
let sourcePath = sourceUrl.toString();
|
|
70
|
+
// could be no `file:`
|
|
63
71
|
if (sourcePath.startsWith('file:')) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
sourcePath = fileURLToPath(sourcePath);
|
|
73
|
+
}
|
|
74
|
+
const content = Util.readFileSync(path.resolve(sourcePath));
|
|
75
|
+
if (typeof content === 'string') {
|
|
76
|
+
sourcesContent[i] = content;
|
|
77
|
+
return;
|
|
67
78
|
}
|
|
68
79
|
}
|
|
80
|
+
|
|
69
81
|
Util.logDebug(EC.red(`failed to load source content ${file}`));
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
82
|
+
failed = true;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if (!failed) {
|
|
87
|
+
return data;
|
|
73
88
|
}
|
|
74
89
|
};
|
|
75
90
|
|
|
@@ -85,17 +100,19 @@ const resolveSourceMap = (data, url) => {
|
|
|
85
100
|
return;
|
|
86
101
|
}
|
|
87
102
|
|
|
88
|
-
//
|
|
89
|
-
if (sourcesContent
|
|
90
|
-
|
|
103
|
+
// check sources content
|
|
104
|
+
if (sourcesContent) {
|
|
105
|
+
// all should be string, could be [null]
|
|
106
|
+
const contents = sourcesContent.filter((content) => typeof content === 'string');
|
|
107
|
+
if (contents.length === sources.length) {
|
|
108
|
+
return data;
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
data.sourcesContent = [];
|
|
91
112
|
}
|
|
92
113
|
|
|
93
114
|
// load sources content by sources
|
|
94
|
-
|
|
95
|
-
if (contents) {
|
|
96
|
-
data.sourcesContent = contents;
|
|
97
|
-
return data;
|
|
98
|
-
}
|
|
115
|
+
return resolveSourcesContent(data, url);
|
|
99
116
|
|
|
100
117
|
};
|
|
101
118
|
|
|
@@ -1069,12 +1069,12 @@ const generateCoverageForDist = (state) => {
|
|
|
1069
1069
|
|
|
1070
1070
|
const unpackSourceMap = (state, options) => {
|
|
1071
1071
|
|
|
1072
|
-
const { sourceMap } = state;
|
|
1072
|
+
const { sourceMap, sourcePath } = state;
|
|
1073
1073
|
|
|
1074
1074
|
// keep original urls
|
|
1075
1075
|
const fileUrls = {};
|
|
1076
1076
|
const sourcePathHandler = getSourcePathHandler(options);
|
|
1077
|
-
initSourceMapSourcePath(sourceMap, fileUrls, sourcePathHandler);
|
|
1077
|
+
initSourceMapSourcePath(sourceMap, fileUrls, sourcePath, sourcePathHandler);
|
|
1078
1078
|
state.fileUrls = fileUrls;
|
|
1079
1079
|
|
|
1080
1080
|
// ===============================================
|
package/lib/generate.js
CHANGED
|
@@ -2,7 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const CG = require('console-grid');
|
|
4
4
|
const EC = require('eight-colors');
|
|
5
|
-
const { pathToFileURL } = require('url');
|
|
5
|
+
const { pathToFileURL, fileURLToPath } = require('url');
|
|
6
6
|
|
|
7
7
|
const Util = require('./utils/util.js');
|
|
8
8
|
const {
|
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
const { mergeV8Coverage, saveV8Report } = require('./v8/v8.js');
|
|
13
13
|
|
|
14
14
|
const { convertV8List } = require('./converter/converter.js');
|
|
15
|
+
const { resolveSourceMap } = require('./converter/collect-source-maps.js');
|
|
15
16
|
|
|
16
17
|
const { minimatch } = require('./packages/monocart-coverage-vendor.js');
|
|
17
18
|
const { getGroupedRows } = require('./utils/snapshot.js');
|
|
@@ -720,7 +721,135 @@ const getInputData = async (inputList, all, cacheDir, addHandler) => {
|
|
|
720
721
|
};
|
|
721
722
|
};
|
|
722
723
|
|
|
724
|
+
// ========================================================================================================
|
|
725
|
+
|
|
726
|
+
const resolveEntrySource = (entry, sourceMapCache, sourceCache) => {
|
|
727
|
+
|
|
728
|
+
const url = entry.url;
|
|
729
|
+
|
|
730
|
+
// source from `source-id.json`
|
|
731
|
+
const sourceData = sourceCache[url];
|
|
732
|
+
if (sourceData) {
|
|
733
|
+
entry.source = sourceData.source;
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// source for typescript file from source map cache
|
|
738
|
+
// Note: no runtime code but lineLengths
|
|
739
|
+
const tsExtensionsPattern = /\.([cm]?ts|[tj]sx)($|\?)/;
|
|
740
|
+
if (tsExtensionsPattern.test(url)) {
|
|
741
|
+
const sourcemapData = sourceMapCache[url];
|
|
742
|
+
const lineLengths = sourcemapData && sourcemapData.lineLengths;
|
|
743
|
+
|
|
744
|
+
// for fake source file (can not parse to AST)
|
|
745
|
+
if (lineLengths) {
|
|
746
|
+
let fakeSource = '';
|
|
747
|
+
sourcemapData.lineLengths.forEach((length) => {
|
|
748
|
+
fakeSource += `${''.padEnd(length, '*')}\n`;
|
|
749
|
+
});
|
|
750
|
+
entry.fake = true;
|
|
751
|
+
entry.source = fakeSource;
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Note: it could be jsx format even extname is `.js`
|
|
758
|
+
const filePath = fileURLToPath(url);
|
|
759
|
+
if (fs.existsSync(filePath)) {
|
|
760
|
+
entry.source = fs.readFileSync(filePath).toString('utf8');
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
const resolveEntrySourceMap = (entry, sourceMapCache) => {
|
|
766
|
+
// sourcemap data
|
|
767
|
+
const sourcemapData = sourceMapCache[entry.url];
|
|
768
|
+
if (sourcemapData) {
|
|
769
|
+
if (sourcemapData.data) {
|
|
770
|
+
entry.sourceMap = resolveSourceMap(sourcemapData.data, entry.url);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
const readCoverageData = (filePath, entryFilter, sourceCache) => {
|
|
776
|
+
|
|
777
|
+
const content = fs.readFileSync(filePath).toString('utf-8');
|
|
778
|
+
if (!content) {
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const json = JSON.parse(content);
|
|
783
|
+
let coverageData = json.result;
|
|
784
|
+
if (!Util.isList(coverageData)) {
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// filter node internal files, should no anonymous for nodejs
|
|
789
|
+
coverageData = coverageData.filter((entry) => entry.url && entry.url.startsWith('file:'));
|
|
790
|
+
coverageData = coverageData.filter(entryFilter);
|
|
791
|
+
|
|
792
|
+
if (!Util.isList(coverageData)) {
|
|
793
|
+
// Util.logDebug('No coverage data after filter');
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const sourceMapCache = json['source-map-cache'] || {};
|
|
798
|
+
for (const entry of coverageData) {
|
|
799
|
+
resolveEntrySource(entry, sourceMapCache, sourceCache);
|
|
800
|
+
resolveEntrySourceMap(entry, sourceMapCache);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
return coverageData;
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
const readFromDir = async (dir, entryFilter, addHandler) => {
|
|
807
|
+
|
|
808
|
+
if (!dir || !fs.existsSync(dir)) {
|
|
809
|
+
Util.logError(`Invalid V8 coverage dir: ${dir}`);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const files = fs.readdirSync(dir);
|
|
814
|
+
if (!files.length) {
|
|
815
|
+
Util.logInfo(`No coverage files in the dir: ${dir}`);
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const coverageFiles = [];
|
|
820
|
+
const sourceCache = {};
|
|
821
|
+
files.forEach((filename) => {
|
|
822
|
+
const isCoverage = filename.startsWith('coverage-');
|
|
823
|
+
if (isCoverage) {
|
|
824
|
+
coverageFiles.push(filename);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
const isSource = filename.startsWith('source-');
|
|
828
|
+
if (isSource) {
|
|
829
|
+
const filePath = path.resolve(dir, filename);
|
|
830
|
+
const content = fs.readFileSync(filePath).toString('utf-8');
|
|
831
|
+
if (content) {
|
|
832
|
+
const json = JSON.parse(content);
|
|
833
|
+
if (json.url) {
|
|
834
|
+
sourceCache[json.url] = json;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
// filter before adding
|
|
841
|
+
for (const filename of coverageFiles) {
|
|
842
|
+
const filePath = path.resolve(dir, filename);
|
|
843
|
+
const coverageData = readCoverageData(filePath, entryFilter, sourceCache);
|
|
844
|
+
if (coverageData) {
|
|
845
|
+
await addHandler(coverageData);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
};
|
|
850
|
+
|
|
723
851
|
module.exports = {
|
|
724
852
|
getInputData,
|
|
853
|
+
readFromDir,
|
|
725
854
|
generateCoverageReports
|
|
726
855
|
};
|
package/lib/index.d.ts
CHANGED
|
@@ -255,7 +255,7 @@ declare namespace MCR {
|
|
|
255
255
|
*
|
|
256
256
|
* {object} Replace key with value.
|
|
257
257
|
* */
|
|
258
|
-
sourcePath?: ((filePath: string) => string) | {
|
|
258
|
+
sourcePath?: ((filePath: string, info: any) => string) | {
|
|
259
259
|
[key: string]: string;
|
|
260
260
|
};
|
|
261
261
|
|
|
@@ -347,6 +347,12 @@ declare namespace MCR {
|
|
|
347
347
|
* @param coverageData {array} V8 format, {object} Istanbul format */
|
|
348
348
|
add: (coverageData: any[] | any) => Promise<AddedResults>;
|
|
349
349
|
|
|
350
|
+
/**
|
|
351
|
+
* add V8 coverage from a dir
|
|
352
|
+
* @param dir node v8 coverage dir
|
|
353
|
+
*/
|
|
354
|
+
addFromDir: (dir: string) => Promise<void>;
|
|
355
|
+
|
|
350
356
|
/** generate report */
|
|
351
357
|
generate: () => Promise<CoverageResults>;
|
|
352
358
|
|
package/lib/index.js
CHANGED
|
@@ -7,7 +7,9 @@ const defaultOptions = require('./default/options.js');
|
|
|
7
7
|
const Util = require('./utils/util.js');
|
|
8
8
|
|
|
9
9
|
const { initV8ListAndSourcemap, getEntryFilter } = require('./v8/v8.js');
|
|
10
|
-
const {
|
|
10
|
+
const {
|
|
11
|
+
getInputData, readFromDir, generateCoverageReports
|
|
12
|
+
} = require('./generate.js');
|
|
11
13
|
const { getSnapshot, diffSnapshot } = require('./utils/snapshot.js');
|
|
12
14
|
const CDPClient = require('./client/cdp-client.js');
|
|
13
15
|
|
|
@@ -113,6 +115,14 @@ class CoverageReport {
|
|
|
113
115
|
return results;
|
|
114
116
|
}
|
|
115
117
|
|
|
118
|
+
// add coverage from dir
|
|
119
|
+
addFromDir(dir) {
|
|
120
|
+
const entryFilter = this.getEntryFilter();
|
|
121
|
+
return readFromDir(dir, entryFilter, (coverageData) => {
|
|
122
|
+
return this.add(coverageData);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
116
126
|
// generate report
|
|
117
127
|
async generate() {
|
|
118
128
|
|