Skip to content

Commit 800e2d0

Browse files
committed
feat(plugin-coverage): implement lcov parsing
1 parent 513c518 commit 800e2d0

15 files changed

+901
-1
lines changed

package-lock.json

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"chalk": "^5.3.0",
5454
"cli-table3": "^0.6.3",
5555
"multi-progress-bars": "^5.0.3",
56+
"parse-lcov": "^1.0.4",
5657
"simple-git": "^3.20.0",
5758
"yargs": "^17.7.2",
5859
"zod": "^3.22.4"

packages/plugin-coverage/README.md

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# @code-pushup/coverage-plugin
2+
3+
**Code PushUp plugin for tracking code coverage.**
4+
5+
This plugin allows you to measure and track code coverage on your project.
6+
7+
Measured coverage types are mapped to Code PushUp audits in the following way
8+
9+
- both the score and value is in range 0-1 and represents the code coverage for all passed results (_covered / total_)
10+
- missing coverage is mapped to issues in the audit details (uncalled functions, uncovered branches or lines)
11+
12+
## Getting started
13+
14+
1. If you haven't already, install [@code-pushup/cli](../cli/README.md) and create a configuration file.
15+
16+
2. Prepare either existing code coverage result files or a command for a coverage tool of your choice that will generate the results, such as [Istanbul](https://github.com/istanbuljs).
17+
18+
3. Add this plugin to the `plugins` array in your Code PushUp CLI config file (e.g. `code-pushup.config.js`).
19+
20+
Pass coverage types you wish to track (line, branch or function), paths to the code coverage results in LCOV format and optionally define your code coverage tool to be run first.
21+
22+
Please note that when you define the tool command, you still need to define the paths to the coverage results.
23+
24+
The configuration will look similarly to the following:
25+
26+
```js
27+
import coveragePlugin from '@code-pushup/coverage-plugin';
28+
29+
export default {
30+
// ...
31+
plugins: [
32+
// ...
33+
await coveragePlugin({
34+
coverageType: ['branch', 'function', 'line'],
35+
reports: ['coverage/cli/lcov.info'],
36+
coverageToolCommand: {
37+
command: 'npx nx run-many',
38+
args: ['-t', 'test', '--coverage'],
39+
},
40+
}),
41+
],
42+
};
43+
```
44+
45+
4. (Optional) Reference audits which you wish to include in custom categories (use `npx code-pushup print-config` to list audits and groups).
46+
47+
Assign weights based on what influence each coverage type should have on the overall category score (assign weight 0 to only include as extra info, without influencing category score).
48+
49+
Note that categories can combine multiple plugins.
50+
51+
```js
52+
export default {
53+
// ...
54+
categories: [
55+
{
56+
slug: 'code-coverage',
57+
title: 'Code coverage',
58+
refs: [
59+
{
60+
type: 'audit',
61+
plugin: 'coverage',
62+
slug: 'function-coverage',
63+
weight: 2,
64+
},
65+
{
66+
type: 'audit',
67+
plugin: 'coverage',
68+
slug: 'branch-coverage',
69+
weight: 1,
70+
},
71+
{
72+
type: 'audit',
73+
plugin: 'coverage',
74+
slug: 'line-coverage',
75+
weight: 1,
76+
},
77+
// ...
78+
],
79+
},
80+
// ...
81+
],
82+
};
83+
```
84+
85+
5. Run the CLI with `npx code-pushup collect` and view or upload report (refer to [CLI docs](../cli/README.md)).
86+
87+
## About code coverage
88+
89+
Code coverage is a metric that indicates what percentage of source code is executed by unit tests. It can give insights into test effectiveness and uncover parts of source code that would otherwise go untested.
90+
91+
However, please note that code coverage is not the same as test coverage. Test coverage measures the amount of acceptance criteria covered by tests and is hard to formally verify. This means that code coverage cannot guarantee that the designed software caters to the business requirements.
92+
93+
### LCOV format
94+
95+
The LCOV format was originally used by [GCOV](https://gcc.gnu.org/onlinedocs/gcc/gcov/introduction-to-gcov.html) tool for coverage results in C/C++ projects.
96+
It recognises the following entities:
97+
98+
- TN [test name]
99+
- SF [source file]
100+
- FN [line number] [function name]
101+
- FNF [number of functions found]
102+
- FNH [number of functions hit]
103+
- FNDA [number of hits] [function name]
104+
- BRDA [line number] [block number] [branch name] [number of hits]
105+
- BRF [number of branches found]
106+
- BRH [number of branches taken]
107+
- DA [line number] [number of hits]
108+
- LF [lines found]
109+
- LH [lines hit]
110+
111+
[Here](https://github.com/linux-test-project/lcov/issues/113#issuecomment-762335134) is the source of the information above.
112+
113+
## Plugin architecture
114+
115+
### Plugin configuration specification
116+
117+
The plugin accepts the following parameters:
118+
119+
- `coverageType`: An array of types of coverage that you wish to track. Supported values: function, branch, line.
120+
- `reports`: Array of paths to files with code coverage results. Supported formats: LCOV.
121+
- (optional) `coverageToolCommand`: If you wish to run your coverage tool to generate the results first, you may define it here.
122+
- (optional) `perfectScoreThreshold`: If your coverage goal is not 100%, you may define it here in range 0-1. Any score above the defined threshold will be given the perfect score. The value will stay unaffected.
123+
124+
### Audit output
125+
126+
An audit is an aggregation of all results for one coverage type passed to the plugin.
127+
128+
For functions and branches, an issue points to a single instance of a branch or function not covered in any test and counts as an error. In line coverage, one issue groups any amount of consecutive lines together to reduce the total amount of issues and counts as a warning.
129+
130+
For instance, the following can be an audit output for line coverage.
131+
132+
```json
133+
{
134+
"slug": "line-coverage",
135+
"displayValue": "95 %",
136+
"score": 0.95,
137+
"value": 0.95,
138+
"details": {
139+
"issues": [
140+
{
141+
"message": "Lines 7-9 are not covered in any test case.",
142+
"severity": "warning",
143+
"source": {
144+
"file": "src/lib/utils.ts",
145+
"position": {
146+
"startLine": 7,
147+
"endLine": 9
148+
}
149+
}
150+
}
151+
]
152+
}
153+
}
154+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
TN:
2+
SF:src\lib\utils.ts
3+
FN:2,formatReportScore
4+
FN:6,calcDuration
5+
FNF:2
6+
FNH:2
7+
FNDA:1,formatReportScore
8+
FNDA:6,calcDuration
9+
DA:1,1
10+
DA:2,1
11+
DA:3,1
12+
DA:4,1
13+
DA:5,1
14+
DA:6,1
15+
DA:7,0
16+
DA:8,0
17+
DA:9,0
18+
DA:10,1
19+
LF:10
20+
LH:7
21+
BRDA:1,0,0,6
22+
BRDA:1,1,0,5
23+
BRDA:2,4,0,1
24+
BRDA:4,5,0,17
25+
BRDA:5,6,0,4
26+
BRDA:6,7,0,13
27+
BRDA:6,10,0,0
28+
BRDA:7,11,0,3
29+
BRDA:10,12,0,12
30+
BRDA:10,13,0,0
31+
BRF:10
32+
BRH:8
33+
end_of_record
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`lcovResultsToAuditOutputs > should correctly convert lcov results to AuditOutputs 1`] = `
4+
[
5+
{
6+
"details": {
7+
"issues": [
8+
{
9+
"message": "Branch 0 is not taken in any test case.",
10+
"severity": "error",
11+
"source": {
12+
"file": "src/lib/utils.ts",
13+
"position": {
14+
"startLine": 6,
15+
},
16+
},
17+
},
18+
{
19+
"message": "Branch 0 is not taken in any test case.",
20+
"severity": "error",
21+
"source": {
22+
"file": "src/lib/utils.ts",
23+
"position": {
24+
"startLine": 10,
25+
},
26+
},
27+
},
28+
],
29+
},
30+
"displayValue": "80 %",
31+
"score": 0.8,
32+
"slug": "branch-coverage",
33+
"value": 80,
34+
},
35+
{
36+
"details": undefined,
37+
"displayValue": "100 %",
38+
"score": 1,
39+
"slug": "function-coverage",
40+
"value": 100,
41+
},
42+
{
43+
"details": {
44+
"issues": [
45+
{
46+
"message": "Lines 7-9 are not covered in any test case.",
47+
"severity": "warning",
48+
"source": {
49+
"file": "src/lib/utils.ts",
50+
"position": {
51+
"endLine": 9,
52+
"startLine": 7,
53+
},
54+
},
55+
},
56+
],
57+
},
58+
"displayValue": "70 %",
59+
"score": 0.7,
60+
"slug": "line-coverage",
61+
"value": 70,
62+
},
63+
]
64+
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import parseLcovExport from 'parse-lcov';
2+
3+
type ParseLcovFn = typeof parseLcovExport;
4+
5+
// the parse-lcov export is inconsistent (sometimes it's .default, sometimes it's .default.default)
6+
const godKnows = parseLcovExport as unknown as
7+
| ParseLcovFn
8+
| { default: ParseLcovFn };
9+
10+
export const parseLcov: ParseLcovFn =
11+
'default' in godKnows ? godKnows.default : godKnows;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { join } from 'node:path';
2+
import { describe, it } from 'vitest';
3+
import { lcovResultsToAuditOutputs } from './runner';
4+
5+
describe('lcovResultsToAuditOutputs', () => {
6+
it('should correctly convert lcov results to AuditOutputs', async () => {
7+
/**
8+
* The stats passed in the fixture are as follows
9+
* Functions: 2 found, 2 covered (100% coverage)
10+
* Branches: 10 found, 8 covered (80% coverage) - last value of BRDA
11+
* Lines: 10 found, 7 covered (70% coverage) - merged into one issue with line range
12+
*/
13+
const results = await lcovResultsToAuditOutputs(
14+
[join('packages', 'plugin-coverage', 'mocks', 'single-record-lcov.info')],
15+
['branch', 'function', 'line'],
16+
);
17+
expect(results).toMatchSnapshot();
18+
});
19+
});

0 commit comments

Comments
 (0)