Skip to content

Extend bond handling rules for Molecular Inorganics #71

Extend bond handling rules for Molecular Inorganics

Extend bond handling rules for Molecular Inorganics #71

name: Test for unit test coverage regression
on:
pull_request:
branches: [ dev ]
workflow_dispatch:
permissions:
contents: read
pull-requests: write
concurrency:
group: pr-coverage-${{ github.run_id }}
cancel-in-progress: true
jobs:
coverage:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install build dependencies
run: |
sudo ./INCHI-1-TEST/install_build_dependencies.sh
- name: Generate coverage report
uses: ./.github/actions/generate_coverage_report
- name: Test for unit test regression
run: |
set -euo pipefail
cd CMake_build/coverage_build
BASE_SUMMARY_URL="https://iupac-inchi.github.io/InChI/coverage/summary.txt"
BASE_SUMMARY_FILE="$(mktemp)"
PR_SUMMARY_FILE="coverage_reports/summary.txt"
COVERAGE_REPORT_MD="coverage_reports/coverage_report.md"
mkdir -p coverage_reports
trap 'rm -f "$BASE_SUMMARY_FILE"' EXIT
extract_pct() {
local metric="$1"
local file="$2"
awk -v metric="$metric" '
$0 ~ "^[[:space:]]*" metric "\\.*:" {
if (match($0, /[0-9]+([.][0-9]+)?%/)) {
value = substr($0, RSTART, RLENGTH - 1)
print value
}
exit
}
' "$file"
}
parse_metrics() {
local prefix="$1"
local file="$2"
local parse_error="$3"
local lines branches
lines="$(extract_pct lines "$file")"
branches="$(extract_pct branches "$file")"
if [[ -n "$lines" && -n "$branches" ]]; then
printf -v "${prefix}_LINES" '%s' "$lines"
printf -v "${prefix}_BRANCHES" '%s' "$branches"
printf -v "${prefix}_AVAILABLE" 'true'
else
PROBLEMS+=("$parse_error")
fi
}
pct_diff() { awk -v pr="$1" -v base="$2" 'BEGIN {printf "%+.2f", pr-base}'; }
is_lt() { awk -v pr="$1" -v base="$2" 'BEGIN {exit !(pr < base)}'; }
PROBLEMS=()
PR_AVAILABLE=false
BASE_AVAILABLE=false
PR_LINES=""
PR_BRANCHES=""
BASE_LINES=""
BASE_BRANCHES=""
if lcov --summary coverage.lcov --branch-coverage --ignore-errors inconsistent > "$PR_SUMMARY_FILE"; then
parse_metrics PR "$PR_SUMMARY_FILE" "Unable to parse PR coverage metrics from $PR_SUMMARY_FILE"
else
PROBLEMS+=("Unable to generate PR coverage summary from coverage.lcov")
fi
if curl -fsSL --max-time 10 "$BASE_SUMMARY_URL" -o "$BASE_SUMMARY_FILE"; then
parse_metrics BASE "$BASE_SUMMARY_FILE" "Base summary fetched, but required metrics were incomplete"
else
PROBLEMS+=("Base summary fetch failed from $BASE_SUMMARY_URL")
fi
PR_LINES_DISPLAY="${PR_LINES:-N/A}"
PR_BRANCHES_DISPLAY="${PR_BRANCHES:-N/A}"
BASE_LINES_DISPLAY="${BASE_LINES:-N/A}"
BASE_BRANCHES_DISPLAY="${BASE_BRANCHES:-N/A}"
DIFF_LINES="N/A"
DIFF_BRANCHES="N/A"
if [[ "$PR_AVAILABLE" == true && "$BASE_AVAILABLE" == true ]]; then
DIFF_LINES="$(pct_diff "$PR_LINES" "$BASE_LINES")"
DIFF_BRANCHES="$(pct_diff "$PR_BRANCHES" "$BASE_BRANCHES")"
fi
{
echo "### Coverage Regression Summary"
echo
echo "| Metric | PR (%) | Base (%) | Difference (pp) |"
echo "| --- | ---: | ---: | ---: |"
echo "| Lines | ${PR_LINES_DISPLAY} | ${BASE_LINES_DISPLAY} | ${DIFF_LINES} |"
echo "| Branches | ${PR_BRANCHES_DISPLAY} | ${BASE_BRANCHES_DISPLAY} | ${DIFF_BRANCHES} |"
if [[ ${#PROBLEMS[@]} -gt 0 ]]; then
echo
echo "### Problems"
echo
printf -- "- %s\n" "${PROBLEMS[@]}"
fi
echo
echo "Find details on the base coverage at https://iupac-inchi.github.io/InChI/coverage/index.html"
} > "$COVERAGE_REPORT_MD"
if [[ "$PR_AVAILABLE" == false || "$BASE_AVAILABLE" == false ]]; then
echo "Coverage regression check skipped due to missing PR or base metrics."
exit 0
fi
FAIL=false
if is_lt "$PR_LINES" "$BASE_LINES"; then
echo "Line coverage regression: ${PR_LINES}% < base ${BASE_LINES}%"
FAIL=true
fi
if is_lt "$PR_BRANCHES" "$BASE_BRANCHES"; then
echo "Branch coverage regression: ${PR_BRANCHES}% < base ${BASE_BRANCHES}%"
FAIL=true
fi
if [[ "$FAIL" == true ]]; then
exit 1
fi
echo "Coverage OK: lines ${PR_LINES}% >= base ${BASE_LINES}%, branches ${PR_BRANCHES}% >= base ${BASE_BRANCHES}%"
- name: Upload coverage reports
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: coverage-reports-${{ github.run_id }}
path: CMake_build/coverage_build/coverage_reports/
retention-days: 30
- name: Post sticky PR comment (same-repo only)
if: always() && github.event.pull_request.head.repo.full_name == github.repository
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const fs = require('fs');
const report = fs.readFileSync('CMake_build/coverage_build/coverage_reports/coverage_report.md', 'utf8');
// Build artifact link
const runId = context.runId;
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`;
const artifactLink = `Find details on this PR's coverage by downloading [coverage-reports-${runId}](${runUrl}) and opening **html/index.html**`;
const body = `## Unit Test Coverage Report\n\n${report}\n\n${artifactLink}`;
const identifier = '<!-- InChI Unit Test Coverage Report -->';
const bodyWithId = `${identifier}\n${body}`;
// Search for existing comment with the identifier
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
per_page: 100,
});
const existingComment = comments.find(comment =>
comment.body.includes(identifier)
);
if (existingComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: bodyWithId,
});
console.log('Updated existing comment:', existingComment.id);
} else {
// Create new comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: bodyWithId,
});
console.log('Created new comment');
}