From 19662f1d347e79b9abc08d567ce560bf1717e810 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Sat, 31 Jan 2026 13:22:57 -0800 Subject: [PATCH 1/7] [add] pipeline for generating code coverage on pull requests. --- .github/workflows/coverage.yml | 106 +++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 .github/workflows/coverage.yml diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000..2a8ae3a4 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,106 @@ +name: Code Coverage + +on: + pull_request: + branches: [main] + + push: + branches: [main] + + workflow_dispatch: + +permissions: + contents: read + pull-requests: write + +defaults: + run: + shell: bash + +jobs: + coverage: + name: Generate code coverage with cargo-llvm-cov + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Cache cargo builds + uses: Swatinem/rust-cache@v2 + + - name: Install Rust toolchain with llvm-tools-preview + uses: dtolnay/rust-toolchain@stable + with: + components: llvm-tools-preview + + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + + - name: Install Linux deps for winit/wgpu + run: | + sudo apt-get update + sudo apt-get install -y \ + pkg-config libx11-dev libxcb1-dev libxcb-render0-dev \ + libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev \ + libwayland-dev libudev-dev \ + libvulkan-dev libvulkan1 mesa-vulkan-drivers vulkan-tools + + - name: Install Linux deps for audio + run: | + sudo apt-get install -y libasound2-dev + + - name: Configure Vulkan (Ubuntu) + run: | + echo "WGPU_BACKEND=vulkan" >> "$GITHUB_ENV" + # Prefer Mesa's software Vulkan (lavapipe) for headless availability + echo "VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json" >> "$GITHUB_ENV" + vulkaninfo --summary || true + + - name: Generate coverage JSON (summary only) + run: | + cargo llvm-cov --workspace \ + --features lambda-rs/with-vulkan,lambda-rs/audio-output-device \ + --json --summary-only \ + --output-path coverage.json + + - name: Extract total line coverage percentage + id: cov + run: | + pct=$(jq -r '(.data[0].totals.lines.percent // 0)' coverage.json) + covered=$(jq -r '(.data[0].totals.lines.covered // 0)' coverage.json) + total=$(jq -r '(.data[0].totals.lines.count // 0)' coverage.json) + echo "pct=$pct" >> "$GITHUB_OUTPUT" + echo "covered=$covered" >> "$GITHUB_OUTPUT" + echo "total=$total" >> "$GITHUB_OUTPUT" + + - name: Comment on PR + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const pct = `${{ steps.cov.outputs.pct }}`; + const covered = `${{ steps.cov.outputs.covered }}`; + const total = `${{ steps.cov.outputs.total }}`; + + const body = [ + '### ✅ Coverage Report', + '', + '| Metric | Value |', + '|--------|-------|', + `| **Total Line Coverage** | ${pct}% |`, + `| **Lines Covered** | ${covered} / ${total} |`, + '', + '*Generated by [cargo-llvm-cov](https://github.com/taiki-e/cargo-llvm-cov)*' + ].join('\n'); + + const { owner, repo } = context.repo; + const issue_number = context.issue.number; + await github.rest.issues.createComment({ owner, repo, issue_number, body }); + + - name: Upload coverage JSON as artifact + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: coverage.json + retention-days: 30 From 6882e45c159a3d112c5311e527f6ece09ce4837b Mon Sep 17 00:00:00 2001 From: vmarcella Date: Sat, 31 Jan 2026 13:33:26 -0800 Subject: [PATCH 2/7] [update] workflow to update original code coverage comment and have it list out code coverage by modified file. --- .github/workflows/coverage.yml | 144 ++++++++++++++++++++++++++++----- 1 file changed, 125 insertions(+), 19 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 2a8ae3a4..d058546a 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -57,16 +57,25 @@ jobs: echo "VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json" >> "$GITHUB_ENV" vulkaninfo --summary || true - - name: Generate coverage JSON (summary only) + - name: Generate full coverage JSON run: | cargo llvm-cov --workspace \ --features lambda-rs/with-vulkan,lambda-rs/audio-output-device \ - --json --summary-only \ + --json \ --output-path coverage.json - - name: Extract total line coverage percentage + - name: Get changed files in PR + if: github.event_name == 'pull_request' + id: changed + run: | + git fetch origin ${{ github.base_ref }} --depth=1 + changed_files=$(git diff --name-only origin/${{ github.base_ref }}...HEAD -- '*.rs' | tr '\n' ' ') + echo "files=$changed_files" >> "$GITHUB_OUTPUT" + + - name: Generate coverage report data id: cov run: | + # Extract total coverage pct=$(jq -r '(.data[0].totals.lines.percent // 0)' coverage.json) covered=$(jq -r '(.data[0].totals.lines.covered // 0)' coverage.json) total=$(jq -r '(.data[0].totals.lines.count // 0)' coverage.json) @@ -74,29 +83,126 @@ jobs: echo "covered=$covered" >> "$GITHUB_OUTPUT" echo "total=$total" >> "$GITHUB_OUTPUT" - - name: Comment on PR + # Extract per-file coverage as JSON for changed files + jq -r '.data[0].files[] | "\(.filename)|\(.summary.lines.percent // 0)|\(.summary.lines.covered // 0)|\(.summary.lines.count // 0)"' coverage.json > file_coverage.txt + + - name: Build PR coverage comment if: github.event_name == 'pull_request' + id: comment + env: + CHANGED_FILES: ${{ steps.changed.outputs.files }} + run: | + # Build the comment body + { + echo "### ✅ Coverage Report" + echo "" + echo "#### Overall Coverage" + echo "" + echo "| Metric | Value |" + echo "|--------|-------|" + echo "| **Total Line Coverage** | ${{ steps.cov.outputs.pct }}% |" + echo "| **Lines Covered** | ${{ steps.cov.outputs.covered }} / ${{ steps.cov.outputs.total }} |" + echo "" + + # Calculate coverage for changed files + if [ -n "$CHANGED_FILES" ]; then + echo "#### Changed Files in This PR" + echo "" + echo "| File | Coverage | Lines |" + echo "|------|----------|-------|" + + pr_covered=0 + pr_total=0 + + for file in $CHANGED_FILES; do + # Find this file in coverage data (match by filename ending) + match=$(grep -E "/${file}\|" file_coverage.txt || grep -E "^${file}\|" file_coverage.txt || true) + if [ -n "$match" ]; then + file_pct=$(echo "$match" | cut -d'|' -f2) + file_covered=$(echo "$match" | cut -d'|' -f3) + file_total=$(echo "$match" | cut -d'|' -f4) + # Format percentage to 2 decimal places + file_pct_fmt=$(printf "%.2f" "$file_pct") + echo "| \`${file}\` | ${file_pct_fmt}% | ${file_covered}/${file_total} |" + pr_covered=$((pr_covered + file_covered)) + pr_total=$((pr_total + file_total)) + else + echo "| \`${file}\` | N/A | (no coverage data) |" + fi + done + + echo "" + if [ "$pr_total" -gt 0 ]; then + pr_pct=$(echo "scale=2; $pr_covered * 100 / $pr_total" | bc) + echo "**PR Files Coverage:** ${pr_pct}% (${pr_covered}/${pr_total} lines)" + fi + else + echo "*No Rust files changed in this PR.*" + fi + + echo "" + echo "---" + echo "*Generated by [cargo-llvm-cov](https://github.com/taiki-e/cargo-llvm-cov)*" + } > comment_body.md + + # Store as output (handle multiline) + { + echo "body<> "$GITHUB_OUTPUT" + + - name: Find existing coverage comment + if: github.event_name == 'pull_request' + id: find_comment uses: actions/github-script@v7 with: script: | - const pct = `${{ steps.cov.outputs.pct }}`; - const covered = `${{ steps.cov.outputs.covered }}`; - const total = `${{ steps.cov.outputs.total }}`; - - const body = [ - '### ✅ Coverage Report', - '', - '| Metric | Value |', - '|--------|-------|', - `| **Total Line Coverage** | ${pct}% |`, - `| **Lines Covered** | ${covered} / ${total} |`, - '', - '*Generated by [cargo-llvm-cov](https://github.com/taiki-e/cargo-llvm-cov)*' - ].join('\n'); + const { owner, repo } = context.repo; + const issue_number = context.issue.number; + const comments = await github.rest.issues.listComments({ + owner, + repo, + issue_number, + }); + + const botComment = comments.data.find(comment => + comment.user.type === 'Bot' && + comment.body.includes('### ✅ Coverage Report') + ); + + return botComment ? botComment.id : null; + result-encoding: string + + - name: Create or update PR comment + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const body = fs.readFileSync('comment_body.md', 'utf8'); const { owner, repo } = context.repo; const issue_number = context.issue.number; - await github.rest.issues.createComment({ owner, repo, issue_number, body }); + const existingCommentId = ${{ steps.find_comment.outputs.result }}; + + if (existingCommentId) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existingCommentId, + body, + }); + console.log(`Updated existing comment ${existingCommentId}`); + } else { + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body, + }); + console.log('Created new coverage comment'); + } - name: Upload coverage JSON as artifact uses: actions/upload-artifact@v4 From 0f78fea811a6df51f401b2abc6f79edbfd9ff00e Mon Sep 17 00:00:00 2001 From: vmarcella Date: Sat, 31 Jan 2026 13:37:10 -0800 Subject: [PATCH 3/7] [fix] issue with fetch-depth being too shallow and use the pull request base/head. --- .github/workflows/coverage.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d058546a..9376dac0 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -25,6 +25,9 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 + with: + # Fetch enough history for diff against base branch + fetch-depth: 0 - name: Cache cargo builds uses: Swatinem/rust-cache@v2 @@ -68,8 +71,10 @@ jobs: if: github.event_name == 'pull_request' id: changed run: | - git fetch origin ${{ github.base_ref }} --depth=1 - changed_files=$(git diff --name-only origin/${{ github.base_ref }}...HEAD -- '*.rs' | tr '\n' ' ') + # Use GitHub's provided base/head SHAs for accurate diff + base_sha="${{ github.event.pull_request.base.sha }}" + head_sha="${{ github.event.pull_request.head.sha }}" + changed_files=$(git diff --name-only "$base_sha" "$head_sha" -- '*.rs' | tr '\n' ' ') echo "files=$changed_files" >> "$GITHUB_OUTPUT" - name: Generate coverage report data From c4ca893606ae6dcb7bb15800361a0cd068c29f39 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Sat, 31 Jan 2026 13:41:08 -0800 Subject: [PATCH 4/7] [update] formatting to only show 2 digits. --- .github/workflows/coverage.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 9376dac0..944cd0ef 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -80,8 +80,9 @@ jobs: - name: Generate coverage report data id: cov run: | - # Extract total coverage - pct=$(jq -r '(.data[0].totals.lines.percent // 0)' coverage.json) + # Extract total coverage and round to 2 decimal places + pct_raw=$(jq -r '(.data[0].totals.lines.percent // 0)' coverage.json) + pct=$(printf "%.2f" "$pct_raw") covered=$(jq -r '(.data[0].totals.lines.covered // 0)' coverage.json) total=$(jq -r '(.data[0].totals.lines.count // 0)' coverage.json) echo "pct=$pct" >> "$GITHUB_OUTPUT" From bc36ad1423aa7ce25d3e7544a2a836c621b7fd24 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Sat, 31 Jan 2026 13:46:05 -0800 Subject: [PATCH 5/7] [update] code coverage to be published to github pages and link to each file changed in the pull request. --- .github/workflows/coverage.yml | 62 ++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 944cd0ef..b32b8d1a 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -12,6 +12,8 @@ on: permissions: contents: read pull-requests: write + pages: write + id-token: write defaults: run: @@ -67,6 +69,14 @@ jobs: --json \ --output-path coverage.json + - name: Generate HTML coverage report + run: | + cargo llvm-cov --workspace \ + --features lambda-rs/with-vulkan,lambda-rs/audio-output-device \ + --html \ + --output-dir coverage-html \ + --no-run + - name: Get changed files in PR if: github.event_name == 'pull_request' id: changed @@ -97,11 +107,18 @@ jobs: id: comment env: CHANGED_FILES: ${{ steps.changed.outputs.files }} + RUN_ID: ${{ github.run_id }} + REPO: ${{ github.repository }} run: | + # Base URL for GitHub Pages coverage (from main branch) + PAGES_BASE="https://lambda-sh.github.io/lambda/coverage" + # Build the comment body { echo "### ✅ Coverage Report" echo "" + echo "📊 [View Full HTML Report](https://github.com/${REPO}/actions/runs/${RUN_ID}) (download artifact)" + echo "" echo "#### Overall Coverage" echo "" echo "| Metric | Value |" @@ -129,7 +146,9 @@ jobs: file_total=$(echo "$match" | cut -d'|' -f4) # Format percentage to 2 decimal places file_pct_fmt=$(printf "%.2f" "$file_pct") - echo "| \`${file}\` | ${file_pct_fmt}% | ${file_covered}/${file_total} |" + # Create HTML filename (replace / with path structure, add .html) + html_file=$(echo "$file" | sed 's|/|/|g').html + echo "| [\`${file}\`](${PAGES_BASE}/${html_file}) | ${file_pct_fmt}% | ${file_covered}/${file_total} |" pr_covered=$((pr_covered + file_covered)) pr_total=$((pr_total + file_total)) else @@ -148,7 +167,7 @@ jobs: echo "" echo "---" - echo "*Generated by [cargo-llvm-cov](https://github.com/taiki-e/cargo-llvm-cov)*" + echo "*Generated by [cargo-llvm-cov](https://github.com/taiki-e/cargo-llvm-cov) · [Latest main coverage](${PAGES_BASE})*" } > comment_body.md # Store as output (handle multiline) @@ -210,9 +229,46 @@ jobs: console.log('Created new coverage comment'); } + - name: Upload coverage HTML as artifact + uses: actions/upload-artifact@v4 + with: + name: coverage-html-report + path: coverage-html/ + retention-days: 30 + - name: Upload coverage JSON as artifact uses: actions/upload-artifact@v4 with: - name: coverage-report + name: coverage-json path: coverage.json retention-days: 30 + + # Deploy HTML report to GitHub Pages on pushes to main + deploy-coverage: + name: Deploy coverage to GitHub Pages + needs: coverage + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Download coverage HTML artifact + uses: actions/download-artifact@v4 + with: + name: coverage-html-report + path: coverage-html + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload to GitHub Pages + uses: actions/upload-pages-artifact@v3 + with: + path: coverage-html + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 From f96a03d7a0c5597a8714403369e214da379c23f6 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Sat, 31 Jan 2026 13:51:12 -0800 Subject: [PATCH 6/7] [update] pipeline to update package repo before setup. --- .github/workflows/coverage.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index b32b8d1a..40b307b2 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -53,6 +53,7 @@ jobs: - name: Install Linux deps for audio run: | + sudo apt-get update sudo apt-get install -y libasound2-dev - name: Configure Vulkan (Ubuntu) From 1aaa56a242939572b6ec08eda82364c16a85e59a Mon Sep 17 00:00:00 2001 From: vmarcella Date: Sat, 31 Jan 2026 14:01:36 -0800 Subject: [PATCH 7/7] [update] pipeline to track last update. --- .github/workflows/coverage.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 40b307b2..5a96b778 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -110,9 +110,14 @@ jobs: CHANGED_FILES: ${{ steps.changed.outputs.files }} RUN_ID: ${{ github.run_id }} REPO: ${{ github.repository }} + COMMIT_SHA: ${{ github.event.pull_request.head.sha }} run: | # Base URL for GitHub Pages coverage (from main branch) PAGES_BASE="https://lambda-sh.github.io/lambda/coverage" + # Get current timestamp in UTC + TIMESTAMP=$(date -u +"%Y-%m-%d %H:%M:%S UTC") + # Short commit SHA for display + SHORT_SHA="${COMMIT_SHA:0:7}" # Build the comment body { @@ -169,6 +174,8 @@ jobs: echo "" echo "---" echo "*Generated by [cargo-llvm-cov](https://github.com/taiki-e/cargo-llvm-cov) · [Latest main coverage](${PAGES_BASE})*" + echo "" + echo "Last updated: ${TIMESTAMP} · Commit: [\`${SHORT_SHA}\`](https://github.com/${REPO}/commit/${COMMIT_SHA})" } > comment_body.md # Store as output (handle multiline)