Monocart Coverage Reports
Code coverage tool to generate native V8 reports or Istanbul reports.
Usage
const CoverageReport = require('monocart-coverage-reports');
const options = {
outputDir: './coverage-reports',
reports: "v8"
}
const coverageReport = new CoverageReport(options);
await coverageReport.add(coverageData1);
await coverageReport.add(coverageData2);
const coverageResults = await coverageReport.generate();
console.log(coverageResults.summary);
Default Options
Available Reports
Following are istanbul reports
clover
cobertura
html
html-spa
json
json-summary
lcov
lcovonly
none
teamcity
text
text-lcov
text-summary
const CoverageReport = require('monocart-coverage-reports');
const options = {
outputDir: './coverage-reports',
reports: [
['console-summary'],
['v8'],
['html', {
subdir: 'istanbul'
}],
['json', {
file: 'my-json-file.json'
}],
'lcovonly'
]
}
const coverageReport = new CoverageReport(options);
Integration
Multiprocessing Support
The data will be added to [outputDir]/.cache
, and the cache will be removed after reports generated.
const CoverageReport = require('monocart-coverage-reports');
const options = require('path-to/same-options.js');
const coverageReport = new CoverageReport(options);
await coverageReport.add(coverageData1);
const CoverageReport = require('monocart-coverage-reports');
const options = require('path-to/same-options.js');
const coverageReport = new CoverageReport(options);
await coverageReport.add(coverageData2);
const CoverageReport = require('monocart-coverage-reports');
const options = require('path-to/same-options.js');
const coverageReport = new CoverageReport(options);
const coverageResults = await coverageReport.generate();
console.log(coverageResults.summary);
Compare Reports
| Istanbul | V8 | V8 to Istanbul |
---|
Coverage data | Istanbul (Object) | V8 (Array) | V8 (Array) |
Output | Istanbul reports | V8 reports | Istanbul reports |
- Bytes | ❌ | ✅ | ❌ |
- Statements | ✅ | ❌ | ☑️❔ |
- Branches | ✅ | ☑️❔ | ☑️❔ |
- Functions | ✅ | ✅ | ✅ |
- Lines | ✅ | ✅ | ✅ |
- Execution counts | ✅ | ✅ | ✅ |
CSS coverage | ❌ | ✅ | ✅ |
Minified code | ❌ | ✅ | ❌ |
❔ - Partial or conditional support
Compare Workflows
-
Istanbul Workflows
-
V8 Workflows
Collecting Istanbul Coverage Data
- Instrumenting source code
Before collecting Istanbul coverage data, It requires your source code is instrumented with Istanbul
- Browser
Collecting coverage data from window.__coverage__
, example: test-istanbul.js
- Node.js
Collecting coverage data from global.__coverage__
Collecting V8 Coverage Data
Node.js V8 Coverage Report for Server Side
Using entryFilter
and sourceFilter
to filter the results for V8 report
When V8 coverage data collected, it actually contains the data of all entry files, for example:
1, dist/main.js
2, dist/vendor.js
3, dist/something-else.js
We can use entryFilter
to filter the entry files. For example, we should remove vendor.js
and something-else.js
if they are not in our coverage scope.
1, dist/main.js
When inline or linked sourcemap exists to the entry file, the source files will be extracted from the sourcemap for the entry file, and the entry file will be removed if logging
is not debug
.
1, src/index.js
2, src/components/app.js
3, node_modules/dependency/dist/dependency.js
We can use sourceFilter
to filter the source files. For example, we should remove dependency.js
if it is not in our coverage scope.
1, src/index.js
2, src/components/app.js
Example:
const coverageOptions = {
entryFilter: (entry) => entry.url.indexOf("main.js") !== -1,
sourceFilter: (sourcePath) => sourcePath.search(/src\//) !== -1
};
How to convert V8 to Istanbul
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, Vitest, but it has two major problems:
- 1, The source mapping does not work well if the position is between the two consecutive mappings. for example:
const a = tf ? 'true' : 'false';
^ ^ ^
m1 p m2
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.
- 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:
functions.forEach(block => {
block.ranges.forEach((range, i) => {
if (block.isBlockCoverage) {
if (block.functionName && i === 0) {
}
} else if (block.functionName) {
}
}
});
see source code v8-to-istanbul.js
How Monocart Works
We have removed v8-to-istanbul
because of the two major problems and implemented new converter:
- 1, Trying to fix the middle position if not found the exact mapping.
- 2, Finding all functions and branches by parsing the source code AST, however the V8 cannot provide effective branch coverage information, so the branches is still not perfect.
AST | V8 |
---|
AssignmentPattern | 🛇 Not Support |
ConditionalExpression | ✔ Not Perfect |
IfStatement | ✔ Not Perfect |
LogicalExpression | ✔ Not Perfect |
SwitchStatement | ✔ Not Perfect |
Chromium Coverage API
Istanbul Introduction
Thanks