Skip to content

Add code coverage comment on the PR #1

Add code coverage comment on the PR

Add code coverage comment on the PR #1

name: Code Coverage Comparison
on:
workflow_dispatch: # Allows manual triggering from Actions tab
pull_request:
branches:
- main
paths:
- 'src/**'
- 'test/**'
- 'build/**/*.yml'
- '.github/workflows/**'
- 'CodeCoverage.runsettings'
permissions:
contents: read
pull-requests: write
actions: read
jobs:
coverage-comparison:
name: Compare Code Coverage
runs-on: ubuntu-latest
steps:
- name: Checkout PR branch
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Install coverage tools
run: |
dotnet tool install --global dotnet-coverage
dotnet tool install --global dotnet-reportgenerator-globaltool
- name: Build and test PR branch with coverage
run: |
dotnet build --configuration Release
dotnet test \
--configuration Release \
--no-build \
--collect "XPlat Code Coverage" \
--settings CodeCoverage.runsettings \
--results-directory ./TestResults/PR \
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura
- name: Merge PR coverage reports
run: |
dotnet-coverage merge ./TestResults/PR/**/coverage.cobertura.xml \
--output ./TestResults/pr-coverage.xml \
--output-format cobertura
- name: Checkout main branch
run: |
git fetch origin main
git checkout origin/main
- name: Build and test main branch with coverage
run: |
dotnet build --configuration Release
dotnet test \
--configuration Release \
--no-build \
--collect "XPlat Code Coverage" \
--settings CodeCoverage.runsettings \
--results-directory ./TestResults/Main \
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura
continue-on-error: true
- name: Merge main coverage reports
run: |
dotnet-coverage merge ./TestResults/Main/**/coverage.cobertura.xml \
--output ./TestResults/main-coverage.xml \
--output-format cobertura
continue-on-error: true
- name: Compare coverage and generate report
id: coverage-compare
shell: pwsh
run: |
$prCoverageFile = "./TestResults/pr-coverage.xml"
$mainCoverageFile = "./TestResults/main-coverage.xml"
function Get-ProjectCoverage {
param([string]$CoverageFile)
if (-not (Test-Path $CoverageFile)) {
return @{}
}
[xml]$xml = Get-Content $CoverageFile
$results = @{}
foreach ($package in $xml.coverage.packages.package) {
$name = $package.name
$lineRate = [double]$package.'line-rate' * 100
$branchRate = [double]$package.'branch-rate' * 100
$lines = [int]$package.SelectNodes('.//line').Count
$coveredLines = [int]$package.SelectNodes('.//line[@hits > 0]').Count
$results[$name] = @{
LineRate = [math]::Round($lineRate, 2)
BranchRate = [math]::Round($branchRate, 2)
TotalLines = $lines
CoveredLines = $coveredLines
}
}
# Overall coverage
$overallLineRate = [double]$xml.coverage.'line-rate' * 100
$overallBranchRate = [double]$xml.coverage.'branch-rate' * 100
$results['__OVERALL__'] = @{
LineRate = [math]::Round($overallLineRate, 2)
BranchRate = [math]::Round($overallBranchRate, 2)
}
return $results
}
$prCoverage = Get-ProjectCoverage -CoverageFile $prCoverageFile
$mainCoverage = Get-ProjectCoverage -CoverageFile $mainCoverageFile
$markdown = "## πŸ“Š Code Coverage Comparison`n`n"
# Overall comparison
$prOverall = $prCoverage['__OVERALL__']
$mainOverall = $mainCoverage['__OVERALL__']
if ($mainOverall -and $prOverall) {
$overallDiff = $prOverall.LineRate - $mainOverall.LineRate
$diffIcon = if ($overallDiff -ge 0) { "🟒" } elseif ($overallDiff -gt -1) { "🟑" } else { "πŸ”΄" }
$diffSign = if ($overallDiff -ge 0) { "+" } else { "" }
$markdown += "### Overall Coverage`n"
$markdown += "| Metric | Main | PR | Diff |`n"
$markdown += "|--------|------|-----|------|`n"
$markdown += "| Line Coverage | $($mainOverall.LineRate)% | $($prOverall.LineRate)% | $diffIcon $diffSign$([math]::Round($overallDiff, 2))% |`n"
$markdown += "| Branch Coverage | $($mainOverall.BranchRate)% | $($prOverall.BranchRate)% | $diffSign$([math]::Round($prOverall.BranchRate - $mainOverall.BranchRate, 2))% |`n`n"
}
# Projects with degradation
$degraded = @()
$improved = @()
$allKeys = ($mainCoverage.Keys + $prCoverage.Keys) | Where-Object { $_ -ne '__OVERALL__' } | Sort-Object -Unique
foreach ($key in $allKeys) {
$main = $mainCoverage[$key]
$pr = $prCoverage[$key]
if ($null -eq $main -or $null -eq $pr) { continue }
$diff = $pr.LineRate - $main.LineRate
if ($diff -lt -1) {
$degraded += @{
Name = $key
MainRate = $main.LineRate
PRRate = $pr.LineRate
Diff = $diff
}
}
elseif ($diff -gt 1) {
$improved += @{
Name = $key
MainRate = $main.LineRate
PRRate = $pr.LineRate
Diff = $diff
}
}
}
# Coverage degradations
if ($degraded.Count -gt 0) {
$markdown += "### ⚠️ Coverage Degradation Detected`n`n"
$markdown += "The following projects have significant coverage drops (>1%):`n`n"
$markdown += "| Project | Main | PR | Change |`n"
$markdown += "|---------|------|-----|--------|`n"
foreach ($item in $degraded | Sort-Object Diff) {
$shortName = $item.Name -replace 'Microsoft\.Health\.Fhir\.', ''
$markdown += "| $shortName | $($item.MainRate)% | $($item.PRRate)% | πŸ”΄ $($item.Diff)% |`n"
}
$markdown += "`n"
}
# Coverage improvements
if ($improved.Count -gt 0) {
$markdown += "### βœ… Coverage Improvements`n`n"
$markdown += "| Project | Main | PR | Change |`n"
$markdown += "|---------|------|-----|--------|`n"
foreach ($item in $improved | Sort-Object Diff -Descending) {
$shortName = $item.Name -replace 'Microsoft\.Health\.Fhir\.', ''
$markdown += "| $shortName | $($item.MainRate)% | $($item.PRRate)% | 🟒 +$($item.Diff)% |`n"
}
$markdown += "`n"
}
if ($degraded.Count -eq 0 -and $improved.Count -eq 0) {
$markdown += "βœ… No significant coverage changes detected.`n"
}
$markdown += "`n---`n*Coverage comparison generated automatically.*"
# Save to file for the comment step
$markdown | Out-File -FilePath ./coverage-report.md -Encoding utf8
# Set output for degradation check
$hasDegradation = $degraded.Count -gt 0
echo "has_degradation=$hasDegradation" >> $env:GITHUB_OUTPUT
echo "degradation_count=$($degraded.Count)" >> $env:GITHUB_OUTPUT
- name: Find existing coverage comment
uses: peter-evans/find-comment@v3
id: find-comment
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: 'πŸ“Š Code Coverage Comparison'
- name: Post or update coverage comment
uses: peter-evans/create-or-update-comment@v4
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body-path: ./coverage-report.md
edit-mode: replace
- name: Fail if coverage degraded significantly
if: steps.coverage-compare.outputs.has_degradation == 'true'
run: |
echo "::warning::Code coverage has degraded in ${{ steps.coverage-compare.outputs.degradation_count }} project(s)!"
# Uncomment the next line to fail the build on coverage degradation
# exit 1