@@ -57,46 +57,152 @@ jobs:
5757 echo "VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json" >> "$GITHUB_ENV"
5858 vulkaninfo --summary || true
5959
60- - name : Generate coverage JSON (summary only)
60+ - name : Generate full coverage JSON
6161 run : |
6262 cargo llvm-cov --workspace \
6363 --features lambda-rs/with-vulkan,lambda-rs/audio-output-device \
64- --json --summary-only \
64+ --json \
6565 --output-path coverage.json
6666
67- - name : Extract total line coverage percentage
67+ - name : Get changed files in PR
68+ if : github.event_name == 'pull_request'
69+ id : changed
70+ run : |
71+ git fetch origin ${{ github.base_ref }} --depth=1
72+ changed_files=$(git diff --name-only origin/${{ github.base_ref }}...HEAD -- '*.rs' | tr '\n' ' ')
73+ echo "files=$changed_files" >> "$GITHUB_OUTPUT"
74+
75+ - name : Generate coverage report data
6876 id : cov
6977 run : |
78+ # Extract total coverage
7079 pct=$(jq -r '(.data[0].totals.lines.percent // 0)' coverage.json)
7180 covered=$(jq -r '(.data[0].totals.lines.covered // 0)' coverage.json)
7281 total=$(jq -r '(.data[0].totals.lines.count // 0)' coverage.json)
7382 echo "pct=$pct" >> "$GITHUB_OUTPUT"
7483 echo "covered=$covered" >> "$GITHUB_OUTPUT"
7584 echo "total=$total" >> "$GITHUB_OUTPUT"
7685
77- - name : Comment on PR
86+ # Extract per-file coverage as JSON for changed files
87+ jq -r '.data[0].files[] | "\(.filename)|\(.summary.lines.percent // 0)|\(.summary.lines.covered // 0)|\(.summary.lines.count // 0)"' coverage.json > file_coverage.txt
88+
89+ - name : Build PR coverage comment
7890 if : github.event_name == 'pull_request'
91+ id : comment
92+ env :
93+ CHANGED_FILES : ${{ steps.changed.outputs.files }}
94+ run : |
95+ # Build the comment body
96+ {
97+ echo "### ✅ Coverage Report"
98+ echo ""
99+ echo "#### Overall Coverage"
100+ echo ""
101+ echo "| Metric | Value |"
102+ echo "|--------|-------|"
103+ echo "| **Total Line Coverage** | ${{ steps.cov.outputs.pct }}% |"
104+ echo "| **Lines Covered** | ${{ steps.cov.outputs.covered }} / ${{ steps.cov.outputs.total }} |"
105+ echo ""
106+
107+ # Calculate coverage for changed files
108+ if [ -n "$CHANGED_FILES" ]; then
109+ echo "#### Changed Files in This PR"
110+ echo ""
111+ echo "| File | Coverage | Lines |"
112+ echo "|------|----------|-------|"
113+
114+ pr_covered=0
115+ pr_total=0
116+
117+ for file in $CHANGED_FILES; do
118+ # Find this file in coverage data (match by filename ending)
119+ match=$(grep -E "/${file}\|" file_coverage.txt || grep -E "^${file}\|" file_coverage.txt || true)
120+ if [ -n "$match" ]; then
121+ file_pct=$(echo "$match" | cut -d'|' -f2)
122+ file_covered=$(echo "$match" | cut -d'|' -f3)
123+ file_total=$(echo "$match" | cut -d'|' -f4)
124+ # Format percentage to 2 decimal places
125+ file_pct_fmt=$(printf "%.2f" "$file_pct")
126+ echo "| \`${file}\` | ${file_pct_fmt}% | ${file_covered}/${file_total} |"
127+ pr_covered=$((pr_covered + file_covered))
128+ pr_total=$((pr_total + file_total))
129+ else
130+ echo "| \`${file}\` | N/A | (no coverage data) |"
131+ fi
132+ done
133+
134+ echo ""
135+ if [ "$pr_total" -gt 0 ]; then
136+ pr_pct=$(echo "scale=2; $pr_covered * 100 / $pr_total" | bc)
137+ echo "**PR Files Coverage:** ${pr_pct}% (${pr_covered}/${pr_total} lines)"
138+ fi
139+ else
140+ echo "*No Rust files changed in this PR.*"
141+ fi
142+
143+ echo ""
144+ echo "---"
145+ echo "*Generated by [cargo-llvm-cov](https://github.com/taiki-e/cargo-llvm-cov)*"
146+ } > comment_body.md
147+
148+ # Store as output (handle multiline)
149+ {
150+ echo "body<<EOF"
151+ cat comment_body.md
152+ echo "EOF"
153+ } >> "$GITHUB_OUTPUT"
154+
155+ - name : Find existing coverage comment
156+ if : github.event_name == 'pull_request'
157+ id : find_comment
79158 uses : actions/github-script@v7
80159 with :
81160 script : |
82- const pct = `${{ steps.cov.outputs.pct }}`;
83- const covered = `${{ steps.cov.outputs.covered }}`;
84- const total = `${{ steps.cov.outputs.total }}`;
85-
86- const body = [
87- '### ✅ Coverage Report',
88- '',
89- '| Metric | Value |',
90- '|--------|-------|',
91- `| **Total Line Coverage** | ${pct}% |`,
92- `| **Lines Covered** | ${covered} / ${total} |`,
93- '',
94- '*Generated by [cargo-llvm-cov](https://github.com/taiki-e/cargo-llvm-cov)*'
95- ].join('\n');
161+ const { owner, repo } = context.repo;
162+ const issue_number = context.issue.number;
96163
164+ const comments = await github.rest.issues.listComments({
165+ owner,
166+ repo,
167+ issue_number,
168+ });
169+
170+ const botComment = comments.data.find(comment =>
171+ comment.user.type === 'Bot' &&
172+ comment.body.includes('### ✅ Coverage Report')
173+ );
174+
175+ return botComment ? botComment.id : null;
176+ result-encoding : string
177+
178+ - name : Create or update PR comment
179+ if : github.event_name == 'pull_request'
180+ uses : actions/github-script@v7
181+ with :
182+ script : |
183+ const fs = require('fs');
184+ const body = fs.readFileSync('comment_body.md', 'utf8');
97185 const { owner, repo } = context.repo;
98186 const issue_number = context.issue.number;
99- await github.rest.issues.createComment({ owner, repo, issue_number, body });
187+ const existingCommentId = ${{ steps.find_comment.outputs.result }};
188+
189+ if (existingCommentId) {
190+ await github.rest.issues.updateComment({
191+ owner,
192+ repo,
193+ comment_id: existingCommentId,
194+ body,
195+ });
196+ console.log(`Updated existing comment ${existingCommentId}`);
197+ } else {
198+ await github.rest.issues.createComment({
199+ owner,
200+ repo,
201+ issue_number,
202+ body,
203+ });
204+ console.log('Created new coverage comment');
205+ }
100206
101207 - name : Upload coverage JSON as artifact
102208 uses : actions/upload-artifact@v4
0 commit comments