Skip to content

Commit 68892ba

Browse files
committed
Add test coverage report
1 parent fc6682f commit 68892ba

File tree

4 files changed

+175
-1
lines changed

4 files changed

+175
-1
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: 'Parse Coverage and Post Comment'
2+
description: 'Parses a coverage report and posts a comment on a PR'
3+
inputs:
4+
lcov-file:
5+
description: 'Path to the lcov.info file'
6+
required: true
7+
title:
8+
description: 'Title of the comment'
9+
default: 'Code Coverage Report'
10+
11+
runs:
12+
using: 'composite'
13+
steps:
14+
- name: Parse Coverage
15+
shell: bash
16+
if: github.event_name == 'pull_request'
17+
id: parse
18+
run: |
19+
./.github/actions/coverage-report/scripts/parse-coverage.js ${{ inputs.lcov-file }} > coverage-summary.txt
20+
echo "coverage-summary<<EOF" >> $GITHUB_OUTPUT
21+
cat coverage-summary.txt >> $GITHUB_OUTPUT
22+
echo "EOF" >> $GITHUB_OUTPUT
23+
24+
- name: Find Coverage Comment
25+
if: github.event_name == 'pull_request'
26+
uses: peter-evans/find-comment@v3
27+
id: fc
28+
with:
29+
issue-number: ${{ github.event.pull_request.number }}
30+
comment-author: 'github-actions[bot]'
31+
body-includes: '### 📊 ${{ inputs.title }}'
32+
33+
- name: Post Coverage Comment
34+
uses: peter-evans/create-or-update-comment@v4
35+
with:
36+
comment-id: ${{ steps.fc.outputs.comment-id }}
37+
edit-mode: replace
38+
issue-number: ${{ github.event.pull_request.number }}
39+
body: |
40+
### 📊 ${{ inputs.title }}
41+
${{ steps.parse.outputs.coverage-summary }}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#!/usr/bin/env node
2+
const lcovParse = require("lcov-parse");
3+
const fs = require("fs");
4+
5+
const lcovPath = process.argv[2];
6+
const needsImprovementBelow = parseFloat(
7+
process.argv.length >= 4 ? process.argv[3] : "90"
8+
);
9+
const poorBelow = parseFloat(process.argv[4] >= 5 ? process.argv[4] : "50");
10+
11+
if (!lcovPath || isNaN(needsImprovementBelow) || isNaN(poorBelow)) {
12+
console.error(
13+
"Please provide the path to the lcov.info file and the 'needs-improvement-below' and 'poor-below' percentages as command-line arguments."
14+
);
15+
process.exit(1);
16+
}
17+
18+
if (!fs.existsSync(lcovPath)) {
19+
console.error(
20+
`The file ${lcovPath} does not exist. Please provide the path to the lcov.info file.`
21+
);
22+
process.exit(1);
23+
}
24+
25+
const outputFormat = "markdown";
26+
27+
if (outputFormat === "markdown") {
28+
console.log(
29+
"| File | Lines | Lines Hit / Found | Uncovered Lines | Branches |"
30+
);
31+
console.log("| --- | --- | --- | --- | --- |");
32+
}
33+
34+
function shortenPath(path, maxLength) {
35+
if (path.length <= maxLength) {
36+
return path;
37+
}
38+
39+
const start = path.substring(0, maxLength / 2 - 2); // -2 for the '..' in the middle
40+
const end = path.substring(path.length - maxLength / 2, path.length);
41+
42+
return `${start}..${end}`;
43+
}
44+
45+
function getEmoji(lineCoverage, needsImprovementBelow, poorBelow) {
46+
if (lineCoverage >= needsImprovementBelow) {
47+
return "✅"; // white-check emoji
48+
} else if (
49+
lineCoverage < needsImprovementBelow &&
50+
lineCoverage >= poorBelow
51+
) {
52+
return "🟡"; // yellow-ball emoji
53+
} else {
54+
return "❌"; // red-x emoji
55+
}
56+
}
57+
58+
lcovParse(lcovPath, function (err, data) {
59+
if (err) {
60+
console.error(err);
61+
} else {
62+
let totalLinesHit = 0;
63+
let totalLinesFound = 0;
64+
let totalBranchesHit = 0;
65+
let totalBranchesFound = 0;
66+
67+
data.forEach((file) => {
68+
totalLinesHit += file.lines.hit;
69+
totalLinesFound += file.lines.found;
70+
totalBranchesHit += file.branches.hit;
71+
totalBranchesFound += file.branches.found;
72+
const relativePath = shortenPath(
73+
file.file.replace(process.cwd(), ""),
74+
50
75+
);
76+
const lineCoverage = ((file.lines.hit / file.lines.found) * 100).toFixed(
77+
1
78+
);
79+
const branchCoverage = (
80+
(file.branches.hit / file.branches.found) *
81+
100
82+
).toFixed(1);
83+
let emoji = getEmoji(lineCoverage, needsImprovementBelow, poorBelow);
84+
if (outputFormat === "markdown") {
85+
console.log(
86+
`| ${relativePath} | ${emoji}&nbsp;${lineCoverage}% | ${
87+
file.lines.hit
88+
}&nbsp;/&nbsp;${file.lines.found} | ${
89+
file.lines.found - file.lines.hit
90+
} | ${file.branches.found === 0 ? "-" : `${branchCoverage}%`} |`
91+
);
92+
} else {
93+
console.log(
94+
`${emoji} File: ${relativePath}, Line Coverage: ${lineCoverage}%, Branch Coverage: ${branchCoverage}%`
95+
);
96+
}
97+
});
98+
99+
const overallLineCoverage = (
100+
(totalLinesHit / totalLinesFound) *
101+
100
102+
).toFixed(1);
103+
const overallBranchCoverage = (
104+
(totalBranchesHit / totalBranchesFound) *
105+
100
106+
).toFixed(1);
107+
const totalUncoveredLines = totalLinesFound - totalLinesHit;
108+
const overallEmoji = getEmoji(
109+
overallLineCoverage,
110+
needsImprovementBelow,
111+
poorBelow
112+
);
113+
114+
if (outputFormat === "markdown") {
115+
console.log(
116+
`| Overall | ${overallEmoji}&nbsp;${overallLineCoverage}% | ${totalLinesHit}&nbsp;/&nbsp;${totalLinesFound} | ${totalUncoveredLines} | ${
117+
totalBranchesFound === 0 ? "-" : `${overallBranchCoverage}%`
118+
} |`
119+
);
120+
} else {
121+
console.log(
122+
`Overall Line Coverage: ${overallLineCoverage}%, Overall Branch Coverage: ${overallBranchCoverage}%`
123+
);
124+
}
125+
}
126+
});

.github/workflows/ci.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,10 @@ jobs:
4848

4949
- name: Test
5050
run: npm run test
51+
52+
- name: Upload code coverage
53+
if: github.event_name == 'pull_request' && needs.check-access.outputs.has-token-access == 'true'
54+
uses: ./.github/actions/coverage-report
55+
with:
56+
lcov-file: coverage/lcov.info
57+
title: Node.js Code Coverage Report

jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ module.exports = {
4242
// "lcov",
4343
// "clover"
4444
// ],
45-
coverageReporters: ['html', 'text'],
45+
coverageReporters: ['lcov', 'html', 'text'],
4646

4747
// An object that configures minimum threshold enforcement for coverage results
4848
// coverageThreshold: undefined,

0 commit comments

Comments
 (0)