Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci: add code coverage checking script #58

Merged
merged 2 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Mandatory section.

### Checklist
- [ ] Did you add new tests and confirm all tests pass? (`yarn test`)
- [ ] Did you ensure any new Solidity source code files meet minimum test coverage requirements? (`yarn coverage`)
- [ ] Did you update relevant docs? (docs are found in the `docs` folder)
- [ ] Do your commits follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) standard?
- [ ] Does your PR title also follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) standard?
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,5 @@ jobs:
- name: Setup environment
uses: ./.github/actions/setup

# TODO: fail the build if coverage is below the threshold
# TODO: print the coverage report in the github output
- name: Run Test Coverage
run: yarn coverage
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ via_ir = true
auto_detect_solc = false
auto_detect_remappings = false
deny_warnings = true
no_match_coverage = "test/|script/"

[fuzz]
runs = 1024
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"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'",
"test": "forge test -vv",
"gasreport": "forge test --gas-report > gas/reports/gas-report.txt",
"coverage": "forge coverage",
"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",
"format:check": "forge fmt --check",
"format:write": "forge fmt"
},
Expand Down
103 changes: 103 additions & 0 deletions script/test/checkCoverage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
const fs = require('fs');

const COVERAGE_TABLE_HEADER = "| File | % Lines | % Statements | % Branches | % Funcs |";
const COVERAGE_TABLE_HEADER_SEPARATOR = "|--------------------------------------------------------------------------------|--------------------|--------------------|------------------|------------------|";
const COVERAGE_TABLE_TOTAL_ROW_NAME = 'Total';
const COVERAGE_TABLE_COLUMN_DELIM = '|';

// Matches expressions like "12.25%"
const COVERAGE_TABLE_COVERAGE_PECENTAGE_REGEXP = /[\d\.]+%/;

const MIN_REQUIRED_LINE_COVERAGE_PERCENTAGE = 90;
const MIN_REQUIRED_STATEMENT_COVERAGE_PERCENTAGE = 90;
const MIN_REQUIRED_BRANCH_COVERAGE_PERCENTAGE = 90;
const MIN_REQUIRED_FUNCTION_COVERAGE_PERCENTAGE = 90;

const NUM_COLUMNS = COVERAGE_TABLE_HEADER.split(COVERAGE_TABLE_COLUMN_DELIM).length - 2;

function parsePercentage(rawCoveragePercentText) {
const numericDecimalText = COVERAGE_TABLE_COVERAGE_PECENTAGE_REGEXP.exec(rawCoveragePercentText)[0].slice(0, -1);
return parseFloat(numericDecimalText);
}

function parseCoverageTableRow(rawRowText) {
let rowParts = rawRowText.split(COVERAGE_TABLE_COLUMN_DELIM);
if (rowParts.length - 2 != NUM_COLUMNS) {
return null
}

rowParts = rowParts.slice(1, -1);
return {
fileName: rowParts[0].trim(),
lineCoveragePercent: parsePercentage(rowParts[1]),
statementCoveragePercent: parsePercentage(rowParts[2]),
branchCoveragePercent: parsePercentage(rowParts[3]),
functionCoveragePercent: parsePercentage(rowParts[4]),
}
}

function getFormattedCoverageTableRowsTest(coverageTableRows) {
return COVERAGE_TABLE_HEADER + '\n'
+ COVERAGE_TABLE_HEADER_SEPARATOR + '\n'
+ coverageTableRows.join('\n') + '\n';
}

(async function main() {
const coverateReportFileName = process.argv[2];
const coverageReportRawText = fs.readFileSync(coverateReportFileName, "utf8");

let coverageTableBodyRaw = "";
try {
coverageTableBodyRaw = coverageReportRawText.split(COVERAGE_TABLE_HEADER)[1];
} catch (error) {
console.error("Unexpected coverage report format");
console.error(error);
process.exit(1);
}

const belowThresholdFiles = [];
const aboveThresholdFiles = [];
let totalCoverageRow = "";
const coverageTableRows = coverageTableBodyRaw.split("\n").slice(3);

for (const coverageTableRowRaw of coverageTableRows) {
const coverageRow = parseCoverageTableRow(coverageTableRowRaw);
if (!coverageRow) {
continue;
}

// Check minimum required coverage percentages
if (coverageRow.fileName == COVERAGE_TABLE_TOTAL_ROW_NAME) {
totalCoverageRow = coverageTableRowRaw;
} else if (coverageRow.lineCoveragePercent < MIN_REQUIRED_LINE_COVERAGE_PERCENTAGE ||
coverageRow.statementCoveragePercent < MIN_REQUIRED_STATEMENT_COVERAGE_PERCENTAGE ||
coverageRow.branchCoveragePercent < MIN_REQUIRED_BRANCH_COVERAGE_PERCENTAGE ||
coverageRow.functionCoveragePercent < MIN_REQUIRED_FUNCTION_COVERAGE_PERCENTAGE) {

belowThresholdFiles.push(coverageTableRowRaw);
} else {
aboveThresholdFiles.push(coverageTableRowRaw);
}
}

// Print coverage breakdown details
console.log("Total coverage: ");
console.log(getFormattedCoverageTableRowsTest([totalCoverageRow]));

if (belowThresholdFiles.length > 0) {
console.log("Found files below coverage threshold: ");
console.log(getFormattedCoverageTableRowsTest(belowThresholdFiles));
} else {
console.log("All source code files meet minimum coverage requirements.");
}
if (aboveThresholdFiles.length > 0) {
console.log("Files above coverage threshold: ");
console.log(getFormattedCoverageTableRowsTest(aboveThresholdFiles));
}

// Fail if any files found below the minimum coverage threshold
if (belowThresholdFiles.length > 0) {
// TODO: uncomment line once source code coverages have been bumped up
// process.exit(2);
}
})();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing new line

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we please fix this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually you can fix this when the threshold is enforced

Loading