Post PR Comment #1755
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
| # Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # This workflow posts coverage comments on PRs after the main workflow completes. | |
| # Uses workflow_run with guarded checkout to avoid executing untrusted code. | |
| # See: https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/ | |
| name: Post PR Comment | |
| on: | |
| workflow_run: | |
| workflows: ["On Push Qualification"] | |
| types: | |
| - completed | |
| permissions: | |
| contents: read | |
| jobs: | |
| # COUPLING: This workflow depends on "On Push Qualification" producing these artifacts: | |
| # - coverage-pr (coverage.out from PR build) | |
| # - coverage-comment-data (JSON: coverage, threshold, pass, color, pr_number) | |
| # - coverage-baseline (coverage.out from last successful main build) | |
| # Renaming the workflow or these artifacts will silently break PR comments. | |
| post-coverage-comment: | |
| name: Post Coverage Comment | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| # Run for PRs regardless of conclusion - coverage might exist even if other steps failed | |
| if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.head_repository.fork == false | |
| permissions: | |
| actions: read | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Checkout for go.mod version | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| sparse-checkout: go.mod | |
| sparse-checkout-cone-mode: false | |
| persist-credentials: false | |
| - name: Setup Go | |
| uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 | |
| with: | |
| go-version-file: go.mod | |
| cache: false | |
| - name: Install go-coverage-report | |
| run: go install github.com/fgrosse/go-coverage-report/cmd/go-coverage-report@v1.2.0 | |
| - name: Download PR Coverage | |
| id: download-pr | |
| uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 | |
| continue-on-error: true | |
| with: | |
| name: coverage-pr | |
| path: /tmp/pr-coverage | |
| github-token: ${{ github.token }} | |
| run-id: ${{ github.event.workflow_run.id }} | |
| - name: Download Coverage Metadata | |
| id: download-meta | |
| uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 | |
| continue-on-error: true | |
| with: | |
| name: coverage-comment-data | |
| path: /tmp/coverage-comment-data | |
| github-token: ${{ github.token }} | |
| run-id: ${{ github.event.workflow_run.id }} | |
| - name: Download Baseline Coverage | |
| id: download-baseline | |
| if: steps.download-pr.outcome == 'success' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -e | |
| mkdir -p /tmp/baseline-coverage | |
| # Find last successful main run | |
| LAST_RUN=$(gh run list \ | |
| --repo ${{ github.repository }} \ | |
| --workflow "On Push Qualification" \ | |
| --branch main \ | |
| --status success \ | |
| --event push \ | |
| --json databaseId \ | |
| --limit 1 \ | |
| -q '.[0].databaseId' 2>/dev/null || echo "") | |
| if [[ -n "$LAST_RUN" ]]; then | |
| echo "Found baseline run: $LAST_RUN" | |
| if gh run download "$LAST_RUN" \ | |
| --repo ${{ github.repository }} \ | |
| --name coverage-baseline \ | |
| --dir /tmp/baseline-coverage 2>/dev/null; then | |
| echo "baseline_found=true" >> $GITHUB_OUTPUT | |
| echo "✅ Baseline coverage downloaded" | |
| else | |
| echo "baseline_found=false" >> $GITHUB_OUTPUT | |
| echo "⚠️ Failed to download baseline (first run?)" | |
| # Create empty baseline | |
| echo "mode: set" > /tmp/baseline-coverage/coverage.out | |
| fi | |
| else | |
| echo "baseline_found=false" >> $GITHUB_OUTPUT | |
| echo "⚠️ No successful baseline run found" | |
| echo "mode: set" > /tmp/baseline-coverage/coverage.out | |
| fi | |
| - name: Get Changed Files | |
| id: changed-files | |
| if: steps.download-pr.outcome == 'success' | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p .github/outputs | |
| PR_URL="${{ github.event.workflow_run.pull_requests[0].url }}" | |
| if [[ -z "$PR_URL" || "$PR_URL" == "null" ]]; then | |
| echo "No PR URL found in workflow_run payload. Skipping changed files." >&2 | |
| echo "any_changed=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| FILES_JSON=$(curl -sS \ | |
| -H "Authorization: Bearer $GH_TOKEN" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| "$PR_URL/files?per_page=300") | |
| echo "$FILES_JSON" | jq -c ' | |
| [.[] | | |
| select(.filename | endswith(".go")) | | |
| select(.filename | test("^vendor/") | not) | | |
| select(.filename | test("_test\\.go$") | not) | | |
| .filename | |
| ]' > .github/outputs/all_modified_files.json | |
| if [[ "$(cat .github/outputs/all_modified_files.json)" == "[]" ]]; then | |
| echo "any_changed=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "any_changed=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Generate Coverage Delta Report | |
| id: delta | |
| if: steps.download-pr.outcome == 'success' && steps.changed-files.outputs.any_changed == 'true' && steps.download-baseline.outputs.baseline_found == 'true' | |
| run: | | |
| set -e | |
| # Generate delta report | |
| if go-coverage-report \ | |
| -root=github.com/NVIDIA/aicr \ | |
| /tmp/baseline-coverage/coverage.out \ | |
| /tmp/pr-coverage/coverage.out \ | |
| .github/outputs/all_modified_files.json > delta-report.md 2> delta-error.log; then | |
| echo "delta_generated=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "delta_generated=false" >> $GITHUB_OUTPUT | |
| echo "⚠️ Delta generation failed:" | |
| cat delta-error.log || true | |
| fi | |
| # Check if report shows no change (to reduce noise) | |
| if grep -q "will \*\*not change\*\* overall coverage" delta-report.md 2>/dev/null; then | |
| echo "no_change=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "no_change=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Post PR Comment | |
| if: steps.download-pr.outcome == 'success' && steps.download-meta.outcome == 'success' | |
| uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const data = JSON.parse(fs.readFileSync('/tmp/coverage-comment-data/coverage-data.json', 'utf8')); | |
| const coverage = data.coverage; | |
| const threshold = data.threshold; | |
| const pass = data.pass === 'true'; | |
| const color = data.color; | |
| const prNumber = parseInt(data.pr_number, 10); | |
| const status = pass ? '✅' : '❌'; | |
| const statusText = pass ? 'Pass' : 'Fail'; | |
| // Check if we have a delta report | |
| let deltaSection = ''; | |
| const deltaGenerated = '${{ steps.delta.outputs.delta_generated }}' === 'true'; | |
| const noChange = '${{ steps.delta.outputs.no_change }}' === 'true'; | |
| const anyChanged = '${{ steps.changed-files.outputs.any_changed }}' === 'true'; | |
| const baselineFound = '${{ steps.download-baseline.outputs.baseline_found }}' === 'true'; | |
| if (deltaGenerated && !noChange) { | |
| try { | |
| const deltaReport = fs.readFileSync('delta-report.md', 'utf8'); | |
| deltaSection = `\n\n${deltaReport}`; | |
| } catch (e) { | |
| console.log('No delta report found'); | |
| } | |
| } else if (!anyChanged) { | |
| deltaSection = '\n\n*No Go source files changed in this PR.*'; | |
| } else if (noChange) { | |
| deltaSection = '\n\n*Coverage unchanged by this PR.*'; | |
| } else if (!baselineFound) { | |
| deltaSection = '\n\n*No baseline coverage available yet. Delta reporting will be available after the next main branch build.*'; | |
| } | |
| const body = `## Coverage Report ${status} | |
| | Metric | Value | | |
| |--------|-------| | |
| | Coverage | **${coverage}%** | | |
| | Threshold | ${threshold}% | | |
| | Status | ${statusText} | | |
| <details> | |
| <summary>Coverage Badge</summary> | |
| \`\`\` | |
|  | |
| \`\`\` | |
| </details>${deltaSection}`; | |
| // Find existing comment | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| }); | |
| const botComment = comments.find(comment => | |
| comment.user.type === 'Bot' && | |
| comment.body.includes('## Coverage Report') | |
| ); | |
| if (botComment) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body: body | |
| }); | |
| console.log(`Updated existing comment ${botComment.id}`); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body: body | |
| }); | |
| console.log(`Created new comment on PR #${prNumber}`); | |
| } | |
| - name: Post unavailable notice | |
| if: steps.download-pr.outcome != 'success' || steps.download-meta.outcome != 'success' | |
| uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | |
| with: | |
| script: | | |
| // Get PR number from workflow_run payload | |
| const prs = context.payload.workflow_run.pull_requests; | |
| if (!prs || prs.length === 0) return; | |
| const prNumber = prs[0].number; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| }); | |
| const botComment = comments.find(comment => | |
| comment.user.type === 'Bot' && | |
| comment.body.includes('## Coverage Report') | |
| ); | |
| // Only post if there's no existing coverage comment | |
| if (!botComment) { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body: '## Coverage Report\n\nCoverage data unavailable for this run. This can happen if the qualification workflow was cancelled or failed before generating coverage artifacts.', | |
| }); | |
| } |