Skip to content

Commit 517b89a

Browse files
ci: add code coverage checking script (#58)
## Summary Add code coverage checking script. ## Detail Add a script that checks that the coverage of each files matches our minimum defined threshold. Currently, the script doesn't fail. Once you files meet our defined threshold, we can uncomment the line tht will make the script start failing. ### Checklist - [ ] Did you add new tests and confirm all tests pass? (`yarn test`) - [] Did you update relevant docs? (docs are found in the `docs` folder) - [x] Do your commits follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) standard? - [x] Does your PR title also follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) standard? - [ ] If you have a breaking change, is it [correctly reflected in your commit message](https://www.conventionalcommits.org/en/v1.0.0/#examples)? (e.g. `feat!: breaking change`) - [ ] Did you run lint (`yarn lint`) and fix any issues? - [] Did you run formatter (`yarn format:check`) and fix any issues (`yarn format:write`)? ## Testing See `Coverage -> run_coverage (pull_request) -> Run Test Coverage` job logs in latest PR build. Co-authored-by: Huawei <hgu@circle.com>
1 parent d8a96d2 commit 517b89a

File tree

5 files changed

+106
-3
lines changed

5 files changed

+106
-3
lines changed

.github/pull_request_template.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Mandatory section.
88

99
### Checklist
1010
- [ ] Did you add new tests and confirm all tests pass? (`yarn test`)
11+
- [ ] Did you ensure any new Solidity source code files meet minimum test coverage requirements? (`yarn coverage`)
1112
- [ ] Did you update relevant docs? (docs are found in the `docs` folder)
1213
- [ ] Do your commits follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) standard?
1314
- [ ] Does your PR title also follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) standard?

.github/workflows/coverage.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,5 @@ jobs:
2020
- name: Setup environment
2121
uses: ./.github/actions/setup
2222

23-
# TODO: fail the build if coverage is below the threshold
24-
# TODO: print the coverage report in the github output
2523
- name: Run Test Coverage
2624
run: yarn coverage

foundry.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ via_ir = true
1111
auto_detect_solc = false
1212
auto_detect_remappings = false
1313
deny_warnings = true
14+
no_match_coverage = "test/|script/"
1415

1516
[fuzz]
1617
runs = 1024

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"lint": "solhint -w 0 -c .solhint-src.json './src/**/*.sol' && solhint -w 0 -c .solhint-test.json './test/**/*.sol' && solhint -w 0 -c .solhint-script.json './script/**/*.sol'",
99
"test": "forge test -vv",
1010
"gasreport": "forge test --gas-report > gas/reports/gas-report.txt",
11-
"coverage": "forge coverage",
11+
"coverage": "forge coverage > temp-ci_yarn_check-coverage.out && node script/test/checkCoverage.js temp-ci_yarn_check-coverage.out && rm temp-ci_yarn_check-coverage.out",
1212
"format:check": "forge fmt --check",
1313
"format:write": "forge fmt"
1414
},

script/test/checkCoverage.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
const fs = require('fs');
2+
3+
const COVERAGE_TABLE_HEADER = "| File | % Lines | % Statements | % Branches | % Funcs |";
4+
const COVERAGE_TABLE_HEADER_SEPARATOR = "|--------------------------------------------------------------------------------|--------------------|--------------------|------------------|------------------|";
5+
const COVERAGE_TABLE_TOTAL_ROW_NAME = 'Total';
6+
const COVERAGE_TABLE_COLUMN_DELIM = '|';
7+
8+
// Matches expressions like "12.25%"
9+
const COVERAGE_TABLE_COVERAGE_PECENTAGE_REGEXP = /[\d\.]+%/;
10+
11+
const MIN_REQUIRED_LINE_COVERAGE_PERCENTAGE = 90;
12+
const MIN_REQUIRED_STATEMENT_COVERAGE_PERCENTAGE = 90;
13+
const MIN_REQUIRED_BRANCH_COVERAGE_PERCENTAGE = 90;
14+
const MIN_REQUIRED_FUNCTION_COVERAGE_PERCENTAGE = 90;
15+
16+
const NUM_COLUMNS = COVERAGE_TABLE_HEADER.split(COVERAGE_TABLE_COLUMN_DELIM).length - 2;
17+
18+
function parsePercentage(rawCoveragePercentText) {
19+
const numericDecimalText = COVERAGE_TABLE_COVERAGE_PECENTAGE_REGEXP.exec(rawCoveragePercentText)[0].slice(0, -1);
20+
return parseFloat(numericDecimalText);
21+
}
22+
23+
function parseCoverageTableRow(rawRowText) {
24+
let rowParts = rawRowText.split(COVERAGE_TABLE_COLUMN_DELIM);
25+
if (rowParts.length - 2 != NUM_COLUMNS) {
26+
return null
27+
}
28+
29+
rowParts = rowParts.slice(1, -1);
30+
return {
31+
fileName: rowParts[0].trim(),
32+
lineCoveragePercent: parsePercentage(rowParts[1]),
33+
statementCoveragePercent: parsePercentage(rowParts[2]),
34+
branchCoveragePercent: parsePercentage(rowParts[3]),
35+
functionCoveragePercent: parsePercentage(rowParts[4]),
36+
}
37+
}
38+
39+
function getFormattedCoverageTableRowsTest(coverageTableRows) {
40+
return COVERAGE_TABLE_HEADER + '\n'
41+
+ COVERAGE_TABLE_HEADER_SEPARATOR + '\n'
42+
+ coverageTableRows.join('\n') + '\n';
43+
}
44+
45+
(async function main() {
46+
const coverateReportFileName = process.argv[2];
47+
const coverageReportRawText = fs.readFileSync(coverateReportFileName, "utf8");
48+
49+
let coverageTableBodyRaw = "";
50+
try {
51+
coverageTableBodyRaw = coverageReportRawText.split(COVERAGE_TABLE_HEADER)[1];
52+
} catch (error) {
53+
console.error("Unexpected coverage report format");
54+
console.error(error);
55+
process.exit(1);
56+
}
57+
58+
const belowThresholdFiles = [];
59+
const aboveThresholdFiles = [];
60+
let totalCoverageRow = "";
61+
const coverageTableRows = coverageTableBodyRaw.split("\n").slice(3);
62+
63+
for (const coverageTableRowRaw of coverageTableRows) {
64+
const coverageRow = parseCoverageTableRow(coverageTableRowRaw);
65+
if (!coverageRow) {
66+
continue;
67+
}
68+
69+
// Check minimum required coverage percentages
70+
if (coverageRow.fileName == COVERAGE_TABLE_TOTAL_ROW_NAME) {
71+
totalCoverageRow = coverageTableRowRaw;
72+
} else if (coverageRow.lineCoveragePercent < MIN_REQUIRED_LINE_COVERAGE_PERCENTAGE ||
73+
coverageRow.statementCoveragePercent < MIN_REQUIRED_STATEMENT_COVERAGE_PERCENTAGE ||
74+
coverageRow.branchCoveragePercent < MIN_REQUIRED_BRANCH_COVERAGE_PERCENTAGE ||
75+
coverageRow.functionCoveragePercent < MIN_REQUIRED_FUNCTION_COVERAGE_PERCENTAGE) {
76+
77+
belowThresholdFiles.push(coverageTableRowRaw);
78+
} else {
79+
aboveThresholdFiles.push(coverageTableRowRaw);
80+
}
81+
}
82+
83+
// Print coverage breakdown details
84+
console.log("Total coverage: ");
85+
console.log(getFormattedCoverageTableRowsTest([totalCoverageRow]));
86+
87+
if (belowThresholdFiles.length > 0) {
88+
console.log("Found files below coverage threshold: ");
89+
console.log(getFormattedCoverageTableRowsTest(belowThresholdFiles));
90+
} else {
91+
console.log("All source code files meet minimum coverage requirements.");
92+
}
93+
if (aboveThresholdFiles.length > 0) {
94+
console.log("Files above coverage threshold: ");
95+
console.log(getFormattedCoverageTableRowsTest(aboveThresholdFiles));
96+
}
97+
98+
// Fail if any files found below the minimum coverage threshold
99+
if (belowThresholdFiles.length > 0) {
100+
// TODO: uncomment line once source code coverages have been bumped up
101+
// process.exit(2);
102+
}
103+
})();

0 commit comments

Comments
 (0)