From e9a114983c30d9de5e87b4297fc77cc07351207a Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Wed, 28 Jan 2026 16:58:13 -0800 Subject: [PATCH 1/3] Add coverage workflows for baseline and pull request coverage comparison --- .github/workflows/coverage-baseline.yml | 164 +++++++++++++++ .github/workflows/coverage.yml | 264 ++++++++++++++++++++++++ 2 files changed, 428 insertions(+) create mode 100644 .github/workflows/coverage-baseline.yml create mode 100644 .github/workflows/coverage.yml diff --git a/.github/workflows/coverage-baseline.yml b/.github/workflows/coverage-baseline.yml new file mode 100644 index 00000000..2ecb9912 --- /dev/null +++ b/.github/workflows/coverage-baseline.yml @@ -0,0 +1,164 @@ +name: Coverage Baseline + +on: + push: + branches: + - main + +jobs: + coverage: + name: Coverage Baseline + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-musl + - os: windows-latest + target: x86_64-pc-windows-msvc + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set Python to PATH + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Add Conda to PATH (Windows) + if: startsWith(matrix.os, 'windows') + run: | + $path = $env:PATH + ";" + $env:CONDA + "\condabin" + echo "PATH=$path" >> $env:GITHUB_ENV + + - name: Add Conda to PATH (Linux) + if: startsWith(matrix.os, 'ubuntu') + run: echo "PATH=$PATH:$CONDA/condabin" >> $GITHUB_ENV + shell: bash + + - name: Check Conda version + run: conda info --all + + - name: Create Conda Environments + run: | + conda create -n test-env1 python=3.12 -y + conda create -n test-env-no-python -y + conda create -p ./prefix-envs/.conda1 python=3.12 -y + conda create -p ./prefix-envs/.conda-nopy -y + + - name: Install pipenv + run: pip install pipenv + + - name: Check pipenv version + run: pipenv --version + + - name: Create a Pipenv Environment + run: pipenv install + + - name: Install virtualenvwrapper (Linux) + if: startsWith(matrix.os, 'ubuntu') + run: | + pip install virtualenvwrapper + echo "WORKON_HOME=$HOME/.virtualenvs" >> $GITHUB_ENV + mkdir -p $HOME/.virtualenvs + source virtualenvwrapper.sh + mkvirtualenv venv_wrapper_env1 + shell: bash + + - name: Install virtualenvwrapper-win (Windows) + if: startsWith(matrix.os, 'windows') + run: | + pip install virtualenvwrapper-win + echo "WORKON_HOME=$HOME/.virtualenvs" >> $GITHUB_ENV + shell: bash + + - name: Install pyenv (Windows) + if: startsWith(matrix.os, 'windows') + run: | + choco install pyenv-win -y + echo "PATH=$PATH;$HOME/.pyenv/pyenv-win/bin;$HOME/.pyenv/pyenv-win/shims" >> $GITHUB_ENV + echo "PYENV_ROOT=$HOME/.pyenv" >> $GITHUB_ENV + shell: bash + + - name: Install pyenv and pyenv-virtualenv (Linux) + if: startsWith(matrix.os, 'ubuntu') + run: | + curl https://pyenv.run | bash + echo "PYENV_ROOT=$HOME/.pyenv" >> $GITHUB_ENV + echo "PATH=$HOME/.pyenv/bin:$PATH" >> $GITHUB_ENV + shell: bash + + - name: Check Pyenv version + run: pyenv --version + shell: bash + + - name: Install Pyenv Python(s) (Linux) + if: startsWith(matrix.os, 'ubuntu') + run: | + pyenv install --list + pyenv install 3.13:latest 3.12:latest 3.9:latest + shell: bash + + - name: Install Pyenv Python(s) (Windows) + if: startsWith(matrix.os, 'windows') + run: | + pyenv install --list + pyenv install 3.10.5 3.8.10 + shell: bash + + - name: Create pyenv-virtualenv envs (Linux) + if: startsWith(matrix.os, 'ubuntu') + run: | + eval "$(pyenv virtualenv-init -)" + pyenv virtualenv 3.12 pyenv-virtualenv-env1 + shell: bash + + - name: Create .venv + run: python -m venv .venv + shell: bash + + - name: Create .venv2 + run: python -m venv .venv2 + shell: bash + + - name: Install Pixi + uses: prefix-dev/setup-pixi@v0.8.1 + with: + run-install: false + + - name: Create Pixi environments + run: | + pixi init + pixi add python + pixi add --feature dev python + pixi project environment add --feature dev dev + pixi install --environment dev + shell: bash + + - name: Rust Tool Chain setup + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + targets: ${{ matrix.target }} + + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + + - name: Cargo Fetch + run: cargo fetch + shell: bash + + - name: Run Tests with Coverage + run: cargo llvm-cov --features ci --lcov --output-path lcov.info -- --nocapture --test-threads=1 + env: + RUST_BACKTRACE: 1 + RUST_LOG: trace + shell: bash + + - name: Upload Coverage Artifact + uses: actions/upload-artifact@v4 + with: + name: coverage-baseline-${{ matrix.os }} + path: lcov.info + retention-days: 90 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000..3322bfce --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,264 @@ +name: Coverage + +on: + pull_request: + branches: + - main + - release* + - release/* + - release-* + +permissions: + actions: read + contents: read + +jobs: + coverage: + name: Coverage + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-musl + - os: windows-latest + target: x86_64-pc-windows-msvc + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set Python to PATH + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Add Conda to PATH (Windows) + if: startsWith(matrix.os, 'windows') + run: | + $path = $env:PATH + ";" + $env:CONDA + "\condabin" + echo "PATH=$path" >> $env:GITHUB_ENV + + - name: Add Conda to PATH (Linux) + if: startsWith(matrix.os, 'ubuntu') + run: echo "PATH=$PATH:$CONDA/condabin" >> $GITHUB_ENV + shell: bash + + - name: Check Conda version + run: conda info --all + + - name: Create Conda Environments + run: | + conda create -n test-env1 python=3.12 -y + conda create -n test-env-no-python -y + conda create -p ./prefix-envs/.conda1 python=3.12 -y + conda create -p ./prefix-envs/.conda-nopy -y + + - name: Install pipenv + run: pip install pipenv + + - name: Check pipenv version + run: pipenv --version + + - name: Create a Pipenv Environment + run: pipenv install + + - name: Install virtualenvwrapper (Linux) + if: startsWith(matrix.os, 'ubuntu') + run: | + pip install virtualenvwrapper + echo "WORKON_HOME=$HOME/.virtualenvs" >> $GITHUB_ENV + mkdir -p $HOME/.virtualenvs + source virtualenvwrapper.sh + mkvirtualenv venv_wrapper_env1 + shell: bash + + - name: Install virtualenvwrapper-win (Windows) + if: startsWith(matrix.os, 'windows') + run: | + pip install virtualenvwrapper-win + echo "WORKON_HOME=$HOME/.virtualenvs" >> $GITHUB_ENV + shell: bash + + - name: Install pyenv (Windows) + if: startsWith(matrix.os, 'windows') + run: | + choco install pyenv-win -y + echo "PATH=$PATH;$HOME/.pyenv/pyenv-win/bin;$HOME/.pyenv/pyenv-win/shims" >> $GITHUB_ENV + echo "PYENV_ROOT=$HOME/.pyenv" >> $GITHUB_ENV + shell: bash + + - name: Install pyenv and pyenv-virtualenv (Linux) + if: startsWith(matrix.os, 'ubuntu') + run: | + curl https://pyenv.run | bash + echo "PYENV_ROOT=$HOME/.pyenv" >> $GITHUB_ENV + echo "PATH=$HOME/.pyenv/bin:$PATH" >> $GITHUB_ENV + shell: bash + + - name: Check Pyenv version + run: pyenv --version + shell: bash + + - name: Install Pyenv Python(s) (Linux) + if: startsWith(matrix.os, 'ubuntu') + run: | + pyenv install --list + pyenv install 3.13:latest 3.12:latest 3.9:latest + shell: bash + + - name: Install Pyenv Python(s) (Windows) + if: startsWith(matrix.os, 'windows') + run: | + pyenv install --list + pyenv install 3.10.5 3.8.10 + shell: bash + + - name: Create pyenv-virtualenv envs (Linux) + if: startsWith(matrix.os, 'ubuntu') + run: | + eval "$(pyenv virtualenv-init -)" + pyenv virtualenv 3.12 pyenv-virtualenv-env1 + shell: bash + + - name: Create .venv + run: python -m venv .venv + shell: bash + + - name: Create .venv2 + run: python -m venv .venv2 + shell: bash + + - name: Install Pixi + uses: prefix-dev/setup-pixi@v0.8.1 + with: + run-install: false + + - name: Create Pixi environments + run: | + pixi init + pixi add python + pixi add --feature dev python + pixi project environment add --feature dev dev + pixi install --environment dev + shell: bash + + - name: Rust Tool Chain setup + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + targets: ${{ matrix.target }} + + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + + - name: Cargo Fetch + run: cargo fetch + shell: bash + + - name: Run Tests with Coverage + run: cargo llvm-cov --features ci --lcov --output-path lcov.info -- --nocapture --test-threads=1 + env: + RUST_BACKTRACE: 1 + RUST_LOG: trace + shell: bash + + - name: Upload PR Coverage Artifact + uses: actions/upload-artifact@v4 + with: + name: coverage-pr-${{ matrix.os }} + path: lcov.info + + - name: Download Baseline Coverage + uses: dawidd6/action-download-artifact@v6 + id: download-baseline + continue-on-error: true + with: + workflow: coverage-baseline.yml + branch: main + name: coverage-baseline-${{ matrix.os }} + path: baseline-coverage + + - name: Install lcov (Linux) + if: startsWith(matrix.os, 'ubuntu') && steps.download-baseline.outcome == 'success' + run: sudo apt-get update && sudo apt-get install -y lcov + + - name: Install lcov (Windows) + if: startsWith(matrix.os, 'windows') && steps.download-baseline.outcome == 'success' + run: choco install lcov -y + shell: bash + + - name: Compare Coverage (Linux) + if: startsWith(matrix.os, 'ubuntu') && steps.download-baseline.outcome == 'success' + run: | + echo "## Coverage Comparison for ${{ matrix.os }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Extract line coverage from baseline + if [ -f baseline-coverage/lcov.info ]; then + BASELINE_LINES=$(lcov --summary baseline-coverage/lcov.info 2>&1 | grep "lines" | sed 's/.*: //' | sed 's/%.*//') + BASELINE_FUNCTIONS=$(lcov --summary baseline-coverage/lcov.info 2>&1 | grep "functions" | sed 's/.*: //' | sed 's/%.*//') + echo "**Baseline Coverage:**" >> $GITHUB_STEP_SUMMARY + echo "- Lines: ${BASELINE_LINES}%" >> $GITHUB_STEP_SUMMARY + echo "- Functions: ${BASELINE_FUNCTIONS}%" >> $GITHUB_STEP_SUMMARY + else + echo "No baseline coverage found" >> $GITHUB_STEP_SUMMARY + fi + + # Extract line coverage from PR + PR_LINES=$(lcov --summary lcov.info 2>&1 | grep "lines" | sed 's/.*: //' | sed 's/%.*//') + PR_FUNCTIONS=$(lcov --summary lcov.info 2>&1 | grep "functions" | sed 's/.*: //' | sed 's/%.*//') + echo "" >> $GITHUB_STEP_SUMMARY + echo "**PR Coverage:**" >> $GITHUB_STEP_SUMMARY + echo "- Lines: ${PR_LINES}%" >> $GITHUB_STEP_SUMMARY + echo "- Functions: ${PR_FUNCTIONS}%" >> $GITHUB_STEP_SUMMARY + + # Calculate diff if baseline exists + if [ -f baseline-coverage/lcov.info ] && [ -n "$BASELINE_LINES" ]; then + LINE_DIFF=$(echo "$PR_LINES - $BASELINE_LINES" | bc) + FUNC_DIFF=$(echo "$PR_FUNCTIONS - $BASELINE_FUNCTIONS" | bc) + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Coverage Change:**" >> $GITHUB_STEP_SUMMARY + echo "- Lines: ${LINE_DIFF}%" >> $GITHUB_STEP_SUMMARY + echo "- Functions: ${FUNC_DIFF}%" >> $GITHUB_STEP_SUMMARY + fi + shell: bash + + - name: Compare Coverage (Windows) + if: startsWith(matrix.os, 'windows') && steps.download-baseline.outcome == 'success' + run: | + echo "## Coverage Comparison for ${{ matrix.os }}" >> $env:GITHUB_STEP_SUMMARY + echo "" >> $env:GITHUB_STEP_SUMMARY + + # Extract coverage info using PowerShell + if (Test-Path "baseline-coverage/lcov.info") { + $baselineContent = Get-Content -Path "baseline-coverage/lcov.info" -Raw + $baselineLinesFound = ($baselineContent | Select-String -Pattern "LF:(\d+)" -AllMatches).Matches | ForEach-Object { [int]$_.Groups[1].Value } | Measure-Object -Sum | Select-Object -ExpandProperty Sum + $baselineLinesHit = ($baselineContent | Select-String -Pattern "LH:(\d+)" -AllMatches).Matches | ForEach-Object { [int]$_.Groups[1].Value } | Measure-Object -Sum | Select-Object -ExpandProperty Sum + if ($baselineLinesFound -gt 0) { + $baselinePct = [math]::Round(($baselineLinesHit / $baselineLinesFound) * 100, 2) + echo "**Baseline Coverage:** ${baselinePct}% lines" >> $env:GITHUB_STEP_SUMMARY + } + } + + $prContent = Get-Content -Path "lcov.info" -Raw + $prLinesFound = ($prContent | Select-String -Pattern "LF:(\d+)" -AllMatches).Matches | ForEach-Object { [int]$_.Groups[1].Value } | Measure-Object -Sum | Select-Object -ExpandProperty Sum + $prLinesHit = ($prContent | Select-String -Pattern "LH:(\d+)" -AllMatches).Matches | ForEach-Object { [int]$_.Groups[1].Value } | Measure-Object -Sum | Select-Object -ExpandProperty Sum + if ($prLinesFound -gt 0) { + $prPct = [math]::Round(($prLinesHit / $prLinesFound) * 100, 2) + echo "**PR Coverage:** ${prPct}% lines" >> $env:GITHUB_STEP_SUMMARY + } + + if ($baselinePct -and $prPct) { + $diff = [math]::Round($prPct - $baselinePct, 2) + echo "**Coverage Change:** ${diff}%" >> $env:GITHUB_STEP_SUMMARY + } + shell: pwsh + + - name: No Baseline Available + if: steps.download-baseline.outcome != 'success' + run: | + echo "## Coverage Report for ${{ matrix.os }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "No baseline coverage available for comparison. This is expected for new repositories or when coverage-baseline workflow hasn't run yet on main branch." >> $GITHUB_STEP_SUMMARY + shell: bash From f698b7b6f235baab0e70184bda7835a298c417cb Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Wed, 28 Jan 2026 17:04:53 -0800 Subject: [PATCH 2/3] Add permissions section for coverage baseline workflow --- .github/workflows/coverage-baseline.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/coverage-baseline.yml b/.github/workflows/coverage-baseline.yml index 2ecb9912..22978de4 100644 --- a/.github/workflows/coverage-baseline.yml +++ b/.github/workflows/coverage-baseline.yml @@ -5,6 +5,9 @@ on: branches: - main +permissions: + contents: read + jobs: coverage: name: Coverage Baseline From 6fba5f1d9048a0fa973d3c09e56b5fbda0db2bb8 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Wed, 28 Jan 2026 17:27:47 -0800 Subject: [PATCH 3/3] Enhance coverage reporting in CI workflow with detailed metrics and comments for PRs --- .github/workflows/coverage.yml | 164 ++++++++++++++++++++++----------- 1 file changed, 112 insertions(+), 52 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 3322bfce..88e7a311 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -11,6 +11,7 @@ on: permissions: actions: read contents: read + pull-requests: write jobs: coverage: @@ -180,85 +181,144 @@ jobs: path: baseline-coverage - name: Install lcov (Linux) - if: startsWith(matrix.os, 'ubuntu') && steps.download-baseline.outcome == 'success' + if: startsWith(matrix.os, 'ubuntu') run: sudo apt-get update && sudo apt-get install -y lcov - name: Install lcov (Windows) - if: startsWith(matrix.os, 'windows') && steps.download-baseline.outcome == 'success' + if: startsWith(matrix.os, 'windows') run: choco install lcov -y shell: bash - - name: Compare Coverage (Linux) - if: startsWith(matrix.os, 'ubuntu') && steps.download-baseline.outcome == 'success' + - name: Generate Coverage Report (Linux) + if: startsWith(matrix.os, 'ubuntu') + id: coverage-linux run: | - echo "## Coverage Comparison for ${{ matrix.os }}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY + # Extract PR coverage + PR_LINES=$(lcov --summary lcov.info 2>&1 | grep "lines" | sed 's/.*: //' | sed 's/%.*//' | tr -d ' ') + PR_FUNCTIONS=$(lcov --summary lcov.info 2>&1 | grep "functions" | sed 's/.*: //' | sed 's/%.*//' | tr -d ' ') - # Extract line coverage from baseline + # Extract baseline coverage (default to 0 if not available) if [ -f baseline-coverage/lcov.info ]; then - BASELINE_LINES=$(lcov --summary baseline-coverage/lcov.info 2>&1 | grep "lines" | sed 's/.*: //' | sed 's/%.*//') - BASELINE_FUNCTIONS=$(lcov --summary baseline-coverage/lcov.info 2>&1 | grep "functions" | sed 's/.*: //' | sed 's/%.*//') - echo "**Baseline Coverage:**" >> $GITHUB_STEP_SUMMARY - echo "- Lines: ${BASELINE_LINES}%" >> $GITHUB_STEP_SUMMARY - echo "- Functions: ${BASELINE_FUNCTIONS}%" >> $GITHUB_STEP_SUMMARY + BASELINE_LINES=$(lcov --summary baseline-coverage/lcov.info 2>&1 | grep "lines" | sed 's/.*: //' | sed 's/%.*//' | tr -d ' ') + BASELINE_FUNCTIONS=$(lcov --summary baseline-coverage/lcov.info 2>&1 | grep "functions" | sed 's/.*: //' | sed 's/%.*//' | tr -d ' ') else - echo "No baseline coverage found" >> $GITHUB_STEP_SUMMARY + BASELINE_LINES="0" + BASELINE_FUNCTIONS="0" fi - # Extract line coverage from PR - PR_LINES=$(lcov --summary lcov.info 2>&1 | grep "lines" | sed 's/.*: //' | sed 's/%.*//') - PR_FUNCTIONS=$(lcov --summary lcov.info 2>&1 | grep "functions" | sed 's/.*: //' | sed 's/%.*//') - echo "" >> $GITHUB_STEP_SUMMARY - echo "**PR Coverage:**" >> $GITHUB_STEP_SUMMARY - echo "- Lines: ${PR_LINES}%" >> $GITHUB_STEP_SUMMARY - echo "- Functions: ${PR_FUNCTIONS}%" >> $GITHUB_STEP_SUMMARY - - # Calculate diff if baseline exists - if [ -f baseline-coverage/lcov.info ] && [ -n "$BASELINE_LINES" ]; then - LINE_DIFF=$(echo "$PR_LINES - $BASELINE_LINES" | bc) - FUNC_DIFF=$(echo "$PR_FUNCTIONS - $BASELINE_FUNCTIONS" | bc) - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Coverage Change:**" >> $GITHUB_STEP_SUMMARY - echo "- Lines: ${LINE_DIFF}%" >> $GITHUB_STEP_SUMMARY - echo "- Functions: ${FUNC_DIFF}%" >> $GITHUB_STEP_SUMMARY + # Calculate diff + LINE_DIFF=$(echo "$PR_LINES - $BASELINE_LINES" | bc) + FUNC_DIFF=$(echo "$PR_FUNCTIONS - $BASELINE_FUNCTIONS" | bc) + + # Determine delta indicator + if (( $(echo "$LINE_DIFF > 0" | bc -l) )); then + DELTA_INDICATOR=":white_check_mark:" + elif (( $(echo "$LINE_DIFF < 0" | bc -l) )); then + DELTA_INDICATOR=":x:" + else + DELTA_INDICATOR=":heavy_minus_sign:" fi + + # Set outputs + echo "pr_lines=$PR_LINES" >> $GITHUB_OUTPUT + echo "baseline_lines=$BASELINE_LINES" >> $GITHUB_OUTPUT + echo "line_diff=$LINE_DIFF" >> $GITHUB_OUTPUT + echo "delta_indicator=$DELTA_INDICATOR" >> $GITHUB_OUTPUT + + # Write step summary + echo "## Test Coverage Report (${{ matrix.os }})" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY + echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Current Coverage | ${PR_LINES}% |" >> $GITHUB_STEP_SUMMARY + echo "| Base Branch Coverage | ${BASELINE_LINES}% |" >> $GITHUB_STEP_SUMMARY + echo "| Delta | ${LINE_DIFF}% ${DELTA_INDICATOR} |" >> $GITHUB_STEP_SUMMARY shell: bash - - name: Compare Coverage (Windows) - if: startsWith(matrix.os, 'windows') && steps.download-baseline.outcome == 'success' + - name: Generate Coverage Report (Windows) + if: startsWith(matrix.os, 'windows') + id: coverage-windows run: | - echo "## Coverage Comparison for ${{ matrix.os }}" >> $env:GITHUB_STEP_SUMMARY - echo "" >> $env:GITHUB_STEP_SUMMARY + # Extract PR coverage + $prContent = Get-Content -Path "lcov.info" -Raw + $prLinesFound = ($prContent | Select-String -Pattern "LF:(\d+)" -AllMatches).Matches | ForEach-Object { [int]$_.Groups[1].Value } | Measure-Object -Sum | Select-Object -ExpandProperty Sum + $prLinesHit = ($prContent | Select-String -Pattern "LH:(\d+)" -AllMatches).Matches | ForEach-Object { [int]$_.Groups[1].Value } | Measure-Object -Sum | Select-Object -ExpandProperty Sum + if ($prLinesFound -gt 0) { + $prPct = [math]::Round(($prLinesHit / $prLinesFound) * 100, 2) + } else { + $prPct = 0 + } - # Extract coverage info using PowerShell + # Extract baseline coverage (default to 0 if not available) if (Test-Path "baseline-coverage/lcov.info") { $baselineContent = Get-Content -Path "baseline-coverage/lcov.info" -Raw $baselineLinesFound = ($baselineContent | Select-String -Pattern "LF:(\d+)" -AllMatches).Matches | ForEach-Object { [int]$_.Groups[1].Value } | Measure-Object -Sum | Select-Object -ExpandProperty Sum $baselineLinesHit = ($baselineContent | Select-String -Pattern "LH:(\d+)" -AllMatches).Matches | ForEach-Object { [int]$_.Groups[1].Value } | Measure-Object -Sum | Select-Object -ExpandProperty Sum if ($baselineLinesFound -gt 0) { $baselinePct = [math]::Round(($baselineLinesHit / $baselineLinesFound) * 100, 2) - echo "**Baseline Coverage:** ${baselinePct}% lines" >> $env:GITHUB_STEP_SUMMARY + } else { + $baselinePct = 0 } + } else { + $baselinePct = 0 } - $prContent = Get-Content -Path "lcov.info" -Raw - $prLinesFound = ($prContent | Select-String -Pattern "LF:(\d+)" -AllMatches).Matches | ForEach-Object { [int]$_.Groups[1].Value } | Measure-Object -Sum | Select-Object -ExpandProperty Sum - $prLinesHit = ($prContent | Select-String -Pattern "LH:(\d+)" -AllMatches).Matches | ForEach-Object { [int]$_.Groups[1].Value } | Measure-Object -Sum | Select-Object -ExpandProperty Sum - if ($prLinesFound -gt 0) { - $prPct = [math]::Round(($prLinesHit / $prLinesFound) * 100, 2) - echo "**PR Coverage:** ${prPct}% lines" >> $env:GITHUB_STEP_SUMMARY - } + $diff = [math]::Round($prPct - $baselinePct, 2) - if ($baselinePct -and $prPct) { - $diff = [math]::Round($prPct - $baselinePct, 2) - echo "**Coverage Change:** ${diff}%" >> $env:GITHUB_STEP_SUMMARY + if ($diff -gt 0) { + $deltaIndicator = ":white_check_mark:" + } elseif ($diff -lt 0) { + $deltaIndicator = ":x:" + } else { + $deltaIndicator = ":heavy_minus_sign:" } + + # Set outputs + echo "pr_lines=$prPct" >> $env:GITHUB_OUTPUT + echo "baseline_lines=$baselinePct" >> $env:GITHUB_OUTPUT + echo "line_diff=$diff" >> $env:GITHUB_OUTPUT + echo "delta_indicator=$deltaIndicator" >> $env:GITHUB_OUTPUT + + # Write step summary + echo "## Test Coverage Report (${{ matrix.os }})" >> $env:GITHUB_STEP_SUMMARY + echo "" >> $env:GITHUB_STEP_SUMMARY + echo "| Metric | Value |" >> $env:GITHUB_STEP_SUMMARY + echo "|--------|-------|" >> $env:GITHUB_STEP_SUMMARY + echo "| Current Coverage | ${prPct}% |" >> $env:GITHUB_STEP_SUMMARY + echo "| Base Branch Coverage | ${baselinePct}% |" >> $env:GITHUB_STEP_SUMMARY + echo "| Delta | ${diff}% ${deltaIndicator} |" >> $env:GITHUB_STEP_SUMMARY shell: pwsh - - name: No Baseline Available - if: steps.download-baseline.outcome != 'success' - run: | - echo "## Coverage Report for ${{ matrix.os }}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "No baseline coverage available for comparison. This is expected for new repositories or when coverage-baseline workflow hasn't run yet on main branch." >> $GITHUB_STEP_SUMMARY - shell: bash + - name: Post Coverage Comment (Linux) + if: startsWith(matrix.os, 'ubuntu') + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: coverage-linux + message: | + ## Test Coverage Report (Linux) + + | Metric | Value | + |--------|-------| + | Current Coverage | ${{ steps.coverage-linux.outputs.pr_lines }}% | + | Base Branch Coverage | ${{ steps.coverage-linux.outputs.baseline_lines }}% | + | Delta | ${{ steps.coverage-linux.outputs.line_diff }}% ${{ steps.coverage-linux.outputs.delta_indicator }} | + + --- + ${{ steps.coverage-linux.outputs.line_diff > 0 && 'Coverage increased! Great work!' || (steps.coverage-linux.outputs.line_diff < 0 && 'Coverage decreased. Please add tests for new code.' || 'Coverage unchanged.') }} + + - name: Post Coverage Comment (Windows) + if: startsWith(matrix.os, 'windows') + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: coverage-windows + message: | + ## Test Coverage Report (Windows) + + | Metric | Value | + |--------|-------| + | Current Coverage | ${{ steps.coverage-windows.outputs.pr_lines }}% | + | Base Branch Coverage | ${{ steps.coverage-windows.outputs.baseline_lines }}% | + | Delta | ${{ steps.coverage-windows.outputs.line_diff }}% ${{ steps.coverage-windows.outputs.delta_indicator }} | + + --- + ${{ steps.coverage-windows.outputs.line_diff > 0 && 'Coverage increased! Great work!' || (steps.coverage-windows.outputs.line_diff < 0 && 'Coverage decreased. Please add tests for new code.' || 'Coverage unchanged.') }}