Extend bond handling rules for Molecular Inorganics #71
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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'); | |
| } |