Skip to content

fix: Update copyright headers to ProbotSharp Contributors #31

fix: Update copyright headers to ProbotSharp Contributors

fix: Update copyright headers to ProbotSharp Contributors #31

name: Coverage Report
on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- 'src/**'
- 'tests/**'
- '*.sln'
- '*.csproj'
- 'coverlet.runsettings'
- '.github/workflows/coverage-report.yml'
permissions:
contents: read
pull-requests: write
checks: write
jobs:
detect-changes:
name: Detect File Changes
runs-on: ubuntu-latest
outputs:
skip_coverage: ${{ steps.file_changes.outputs.skip_coverage }}
change_type: ${{ steps.file_changes.outputs.change_type }}
src_files: ${{ steps.file_changes.outputs.src_files }}
test_files: ${{ steps.file_changes.outputs.test_files }}
config_files: ${{ steps.file_changes.outputs.config_files }}
steps:
- name: Checkout PR
uses: actions/checkout@v4
with:
fetch-depth: 0 # Need full history for merge detection
- name: Detect File Changes
id: file_changes
run: |
# Fetch base branch for comparison
git fetch origin ${{ github.base_ref }}
# Get list of changed files
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
# Count different types of changes
SRC_FILES=$(echo "$CHANGED_FILES" | grep -E '^src/.*\.cs$' | grep -v -E '(Migrations/|\.Designer\.cs$|DbContextModelSnapshot\.cs$)' | wc -l || echo "0")
TEST_FILES=$(echo "$CHANGED_FILES" | grep -E '^tests/.*\.cs$' | wc -l || echo "0")
CONFIG_FILES=$(echo "$CHANGED_FILES" | grep -E '\.(json|csproj|sln|props|targets)$' | wc -l || echo "0")
WORKFLOW_FILES=$(echo "$CHANGED_FILES" | grep -E '\.github/workflows/.*\.yml$' | wc -l || echo "0")
DOC_FILES=$(echo "$CHANGED_FILES" | grep -E '\.(md|txt)$' | wc -l || echo "0")
# Check if this is a merge commit
IS_MERGE=$(git rev-list --merges HEAD^..HEAD | wc -l)
# Determine change type
if [ "$SRC_FILES" -eq 0 ] && [ "$TEST_FILES" -eq 0 ]; then
CHANGE_TYPE="config-only"
SKIP_COVERAGE="true"
echo "::notice::No source or test code changes detected. Coverage check will be skipped."
elif [ "$SRC_FILES" -eq 0 ] && [ "$TEST_FILES" -gt 0 ]; then
CHANGE_TYPE="test-only"
SKIP_COVERAGE="informational"
echo "::notice::Only test files changed. Coverage check will be informational."
elif [ "$IS_MERGE" -gt 0 ] && [ "$SRC_FILES" -lt 3 ]; then
CHANGE_TYPE="merge-commit"
SKIP_COVERAGE="informational"
echo "::notice::Merge commit with minimal code changes. Coverage check will be informational."
else
CHANGE_TYPE="source-code"
SKIP_COVERAGE="false"
echo "::notice::Source code changes detected ($SRC_FILES files). Full coverage check enabled."
fi
# Output variables
echo "src_files=${SRC_FILES}" >> $GITHUB_OUTPUT
echo "test_files=${TEST_FILES}" >> $GITHUB_OUTPUT
echo "config_files=${CONFIG_FILES}" >> $GITHUB_OUTPUT
echo "change_type=${CHANGE_TYPE}" >> $GITHUB_OUTPUT
echo "skip_coverage=${SKIP_COVERAGE}" >> $GITHUB_OUTPUT
# Log summary
echo "📊 Change Detection Summary:"
echo " Source files: ${SRC_FILES}"
echo " Test files: ${TEST_FILES}"
echo " Config files: ${CONFIG_FILES}"
echo " Workflow files: ${WORKFLOW_FILES}"
echo " Documentation: ${DOC_FILES}"
echo " Change type: ${CHANGE_TYPE}"
echo " Coverage enforcement: ${SKIP_COVERAGE}"
run-coverage:
name: Run Coverage Analysis
runs-on: ubuntu-latest
needs: detect-changes
if: needs.detect-changes.outputs.skip_coverage != 'true'
steps:
- name: Checkout PR
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Restore tools
run: dotnet tool restore
- name: Install ReportGenerator
run: dotnet tool install -g dotnet-reportgenerator-globaltool
- name: Install jq for JSON parsing
run: |
if ! command -v jq &> /dev/null; then
sudo apt-get update
sudo apt-get install -y jq
fi
jq --version
- name: Install diff-cover
run: pip install diff-cover
- name: Restore dependencies
run: dotnet restore ProbotSharp.sln
- name: Build in Debug for Coverage
run: dotnet build ProbotSharp.sln --configuration Debug --no-restore
# Run coverage for current PR (Debug mode for accurate instrumentation)
- name: Run tests with coverage (PR)
run: |
dotnet test ProbotSharp.sln \
--configuration Debug \
--no-build \
--collect:"XPlat Code Coverage" \
--settings coverlet.runsettings \
--results-directory ./coverage-pr
- name: Generate PR Coverage Report
run: |
reportgenerator \
"-reports:coverage-pr/**/coverage.cobertura.xml" \
"-targetdir:coverage-report-pr" \
"-reporttypes:Html;Cobertura;TextSummary;JsonSummary;Badges;MarkdownSummary" \
"-assemblyfilters:+ProbotSharp.*;-*.Tests;-*.Tests.*" \
"-classfilters:+*;-*Migrations*;-*DbContextModelSnapshot" \
"-title:ProbotSharp Coverage Report (PR)"
- name: Generate Merged Cobertura for Diff Analysis
run: |
reportgenerator \
"-reports:coverage-pr/**/coverage.cobertura.xml" \
"-targetdir:coverage-merged" \
"-reporttypes:Cobertura" \
"-assemblyfilters:+ProbotSharp.*;-*.Tests;-*.Tests.*" \
"-classfilters:+*;-*Migrations*;-*DbContextModelSnapshot"
# Checkout base branch for comparison
- name: Checkout base branch
run: |
git fetch origin ${{ github.base_ref }}
git checkout origin/${{ github.base_ref }}
- name: Restore base branch
run: dotnet restore ProbotSharp.sln
- name: Build base branch in Debug for Coverage
run: dotnet build ProbotSharp.sln --configuration Debug --no-restore
# Run coverage for base branch (Debug mode for accurate instrumentation)
- name: Run tests with coverage (Base)
run: |
dotnet test ProbotSharp.sln \
--configuration Debug \
--no-build \
--collect:"XPlat Code Coverage" \
--settings coverlet.runsettings \
--results-directory ./coverage-base
continue-on-error: true
- name: Generate Base Coverage Report
run: |
reportgenerator \
"-reports:coverage-base/**/coverage.cobertura.xml" \
"-targetdir:coverage-report-base" \
"-reporttypes:JsonSummary" \
"-assemblyfilters:+ProbotSharp.*;-*.Tests;-*.Tests.*" \
"-classfilters:+*;-*Migrations*;-*DbContextModelSnapshot"
continue-on-error: true
- name: Upload Coverage Artifacts
uses: actions/upload-artifact@v4
with:
name: coverage-reports-${{ github.event.pull_request.number }}
path: |
coverage-report-pr/
coverage-report-base/
coverage-merged/
coverage-pr/
retention-days: 30
- name: Upload Summary.json for debugging
if: always()
uses: actions/upload-artifact@v4
with:
name: summary-json-debug-${{ github.event.pull_request.number }}
path: |
coverage-report-pr/Summary.json
coverage-report-base/Summary.json
retention-days: 7
report-coverage:
name: Report Coverage Results
runs-on: ubuntu-latest
needs: [detect-changes, run-coverage]
if: always()
steps:
- name: Checkout PR
if: needs.detect-changes.outputs.skip_coverage != 'true'
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Python
if: needs.detect-changes.outputs.skip_coverage != 'true'
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install jq for JSON parsing
if: needs.detect-changes.outputs.skip_coverage != 'true'
run: |
if ! command -v jq &> /dev/null; then
sudo apt-get update
sudo apt-get install -y jq
fi
jq --version
- name: Install diff-cover
if: needs.detect-changes.outputs.skip_coverage != 'true'
run: pip install diff-cover
- name: Download Coverage Artifacts
if: needs.detect-changes.outputs.skip_coverage != 'true'
uses: actions/download-artifact@v4
with:
name: coverage-reports-${{ github.event.pull_request.number }}
- name: Analyze PR Diff Coverage
if: needs.detect-changes.outputs.skip_coverage != 'true'
id: diff_coverage
run: |
# Fetch base branch for diff-cover comparison
git fetch origin ${{ github.base_ref }}
# Run diff-cover against base branch to get coverage of changed lines only
diff-cover coverage-merged/Cobertura.xml \
--compare-branch=origin/${{ github.base_ref }} \
--json-report coverage-diff.json \
--html-report coverage-diff.html || true
# Extract diff coverage metrics (only changed lines)
if [ -f coverage-diff.json ]; then
DIFF_LINE=$(jq -r '.totals.line_coverage // 0' coverage-diff.json)
DIFF_BRANCH=$(jq -r '.totals.branch_coverage // 0' coverage-diff.json)
DIFF_LINES_CHANGED=$(jq -r '.totals.num_changed_lines // 0' coverage-diff.json)
DIFF_LINES_COVERED=$(jq -r '.totals.num_covered_lines // 0' coverage-diff.json)
else
echo "::warning::Failed to generate diff coverage, will skip diff check"
DIFF_LINE="N/A"
DIFF_BRANCH="N/A"
DIFF_LINES_CHANGED="0"
DIFF_LINES_COVERED="0"
fi
echo "diff_line=${DIFF_LINE}" >> $GITHUB_OUTPUT
echo "diff_branch=${DIFF_BRANCH}" >> $GITHUB_OUTPUT
echo "diff_lines_changed=${DIFF_LINES_CHANGED}" >> $GITHUB_OUTPUT
echo "diff_lines_covered=${DIFF_LINES_COVERED}" >> $GITHUB_OUTPUT
# Determine diff coverage status (this is what blocks for source-code changes)
MIN_DIFF_LINE=80
MIN_DIFF_BRANCH=75
if [ "$DIFF_LINE" = "N/A" ]; then
echo "diff_status=warning" >> $GITHUB_OUTPUT
echo "diff_status_msg=⚠️ Unable to calculate diff coverage" >> $GITHUB_OUTPUT
elif [ "$DIFF_LINES_CHANGED" = "0" ]; then
echo "diff_status=success" >> $GITHUB_OUTPUT
echo "diff_status_msg=ℹ️ No code changes detected" >> $GITHUB_OUTPUT
elif (( $(echo "$DIFF_LINE < $MIN_DIFF_LINE" | bc -l) )); then
echo "diff_status=failure" >> $GITHUB_OUTPUT
echo "diff_status_msg=❌ Changed lines coverage below ${MIN_DIFF_LINE}% threshold" >> $GITHUB_OUTPUT
elif (( $(echo "$DIFF_BRANCH < $MIN_DIFF_BRANCH" | bc -l) )); then
echo "diff_status=failure" >> $GITHUB_OUTPUT
echo "diff_status_msg=❌ Changed branches coverage below ${MIN_DIFF_BRANCH}% threshold" >> $GITHUB_OUTPUT
else
echo "diff_status=success" >> $GITHUB_OUTPUT
echo "diff_status_msg=✅ Changed code meets coverage requirements" >> $GITHUB_OUTPUT
fi
- name: Generate Coverage Comparison
id: coverage_comparison
run: |
CHANGE_TYPE="${{ needs.detect-changes.outputs.change_type }}"
SKIP_COVERAGE="${{ needs.detect-changes.outputs.skip_coverage }}"
SRC_FILES="${{ needs.detect-changes.outputs.src_files }}"
TEST_FILES="${{ needs.detect-changes.outputs.test_files }}"
CONFIG_FILES="${{ needs.detect-changes.outputs.config_files }}"
# Handle edge case: No source code changes
if [ "$SKIP_COVERAGE" = "true" ]; then
echo "pr_line=N/A" >> $GITHUB_OUTPUT
echo "pr_branch=N/A" >> $GITHUB_OUTPUT
echo "base_line=N/A" >> $GITHUB_OUTPUT
echo "base_branch=N/A" >> $GITHUB_OUTPUT
echo "line_delta=0%" >> $GITHUB_OUTPUT
echo "branch_delta=0%" >> $GITHUB_OUTPUT
echo "line_emoji=ℹ️" >> $GITHUB_OUTPUT
echo "branch_emoji=ℹ️" >> $GITHUB_OUTPUT
echo "project_status=skipped" >> $GITHUB_OUTPUT
echo "project_status_msg=✅ **No source code changes detected** - Coverage check skipped" >> $GITHUB_OUTPUT
echo "change_summary=**Change Summary:** ${CONFIG_FILES} config file(s) changed, no source code modifications" >> $GITHUB_OUTPUT
exit 0
fi
# Extract PR coverage using jq for reliable JSON parsing
if [ -f coverage-report-pr/Summary.json ]; then
echo "::debug::PR Summary.json structure:"
jq '.summary | {linecoverage, branchcoverage, methodcoverage}' coverage-report-pr/Summary.json || echo "::warning::Failed to parse PR JSON"
PR_LINE=$(jq -r '.summary.linecoverage // 0' coverage-report-pr/Summary.json 2>/dev/null)
PR_BRANCH=$(jq -r '.summary.branchcoverage // 0' coverage-report-pr/Summary.json 2>/dev/null)
PR_METHOD=$(jq -r '.summary.methodcoverage // 0' coverage-report-pr/Summary.json 2>/dev/null)
if [ -z "$PR_LINE" ] || [ "$PR_LINE" = "null" ]; then
echo "::error::Failed to extract PR line coverage from Summary.json"
PR_LINE="0"
fi
echo "::notice::PR Coverage - Line: ${PR_LINE}%, Branch: ${PR_BRANCH}%, Method: ${PR_METHOD}%"
else
echo "::error::PR coverage report not found at coverage-report-pr/Summary.json"
PR_LINE="0"
PR_BRANCH="0"
PR_METHOD="0"
fi
# Extract base coverage using jq
if [ -f coverage-report-base/Summary.json ]; then
echo "::debug::Base Summary.json structure:"
jq '.summary | {linecoverage, branchcoverage, methodcoverage}' coverage-report-base/Summary.json || echo "::warning::Failed to parse base JSON"
BASE_LINE=$(jq -r '.summary.linecoverage // 0' coverage-report-base/Summary.json 2>/dev/null)
BASE_BRANCH=$(jq -r '.summary.branchcoverage // 0' coverage-report-base/Summary.json 2>/dev/null)
BASE_METHOD=$(jq -r '.summary.methodcoverage // 0' coverage-report-base/Summary.json 2>/dev/null)
if [ -z "$BASE_LINE" ] || [ "$BASE_LINE" = "null" ]; then
echo "::error::Failed to extract base line coverage from Summary.json"
BASE_LINE="0"
fi
echo "::notice::Base Coverage - Line: ${BASE_LINE}%, Branch: ${BASE_BRANCH}%, Method: ${BASE_METHOD}%"
BASE_AVAILABLE="true"
else
echo "::warning::Base branch coverage report not found - using absolute thresholds only"
BASE_LINE="${PR_LINE}"
BASE_BRANCH="${PR_BRANCH}"
BASE_METHOD="${PR_METHOD}"
BASE_AVAILABLE="false"
fi
# Calculate deltas
LINE_DELTA=$(echo "$PR_LINE - $BASE_LINE" | bc)
BRANCH_DELTA=$(echo "$PR_BRANCH - $BASE_BRANCH" | bc)
METHOD_DELTA=$(echo "$PR_METHOD - $BASE_METHOD" | bc)
# Format deltas with sign
if (( $(echo "$LINE_DELTA > 0" | bc -l) )); then
LINE_DELTA_STR="+${LINE_DELTA}%"
LINE_EMOJI="📈"
elif (( $(echo "$LINE_DELTA < 0" | bc -l) )); then
LINE_DELTA_STR="${LINE_DELTA}%"
LINE_EMOJI="📉"
else
LINE_DELTA_STR="0%"
LINE_EMOJI="➡️"
fi
if (( $(echo "$BRANCH_DELTA > 0" | bc -l) )); then
BRANCH_DELTA_STR="+${BRANCH_DELTA}%"
BRANCH_EMOJI="📈"
elif (( $(echo "$BRANCH_DELTA < 0" | bc -l) )); then
BRANCH_DELTA_STR="${BRANCH_DELTA}%"
BRANCH_EMOJI="📉"
else
BRANCH_DELTA_STR="0%"
BRANCH_EMOJI="➡️"
fi
# Save values for next steps
echo "pr_line=${PR_LINE}" >> $GITHUB_OUTPUT
echo "pr_branch=${PR_BRANCH}" >> $GITHUB_OUTPUT
echo "base_line=${BASE_LINE}" >> $GITHUB_OUTPUT
echo "base_branch=${BASE_BRANCH}" >> $GITHUB_OUTPUT
echo "line_delta=${LINE_DELTA_STR}" >> $GITHUB_OUTPUT
echo "branch_delta=${BRANCH_DELTA_STR}" >> $GITHUB_OUTPUT
echo "line_emoji=${LINE_EMOJI}" >> $GITHUB_OUTPUT
echo "branch_emoji=${BRANCH_EMOJI}" >> $GITHUB_OUTPUT
# Define minimum thresholds for project-wide coverage
MIN_LINE_COVERAGE=75
MIN_BRANCH_COVERAGE=70
# Generate change summary
if [ "$CHANGE_TYPE" = "test-only" ]; then
CHANGE_SUMMARY="**Change Summary:** ${TEST_FILES} test file(s) modified, no production code changes"
elif [ "$CHANGE_TYPE" = "merge-commit" ]; then
CHANGE_SUMMARY="**Change Summary:** Merge commit with ${SRC_FILES} source file(s) and ${TEST_FILES} test file(s) modified"
else
CHANGE_SUMMARY="**Change Summary:** ${SRC_FILES} source file(s) and ${TEST_FILES} test file(s) modified"
fi
echo "change_summary=${CHANGE_SUMMARY}" >> $GITHUB_OUTPUT
# Informational project-wide status - does NOT block
if (( $(echo "$PR_LINE < $MIN_LINE_COVERAGE" | bc -l) )) || (( $(echo "$PR_BRANCH < $MIN_BRANCH_COVERAGE" | bc -l) )); then
echo "project_status=below_target" >> $GITHUB_OUTPUT
echo "project_status_msg=⚠️ Project-wide coverage below targets (${MIN_LINE_COVERAGE}% line, ${MIN_BRANCH_COVERAGE}% branch)" >> $GITHUB_OUTPUT
echo "project_emoji=⚠️" >> $GITHUB_OUTPUT
elif [ "$BASE_AVAILABLE" = "true" ] && (( $(echo "$LINE_DELTA < -5" | bc -l) )) || (( $(echo "$BRANCH_DELTA < -5" | bc -l) )); then
echo "project_status=significant_decrease" >> $GITHUB_OUTPUT
echo "project_status_msg=📉 Significant coverage decrease detected" >> $GITHUB_OUTPUT
echo "project_emoji=📉" >> $GITHUB_OUTPUT
elif [ "$BASE_AVAILABLE" = "true" ] && (( $(echo "$LINE_DELTA < 0" | bc -l) )) || (( $(echo "$BRANCH_DELTA < 0" | bc -l) )); then
echo "project_status=slight_decrease" >> $GITHUB_OUTPUT
echo "project_status_msg=➖ Coverage decreased slightly" >> $GITHUB_OUTPUT
echo "project_emoji=➖" >> $GITHUB_OUTPUT
elif [ "$BASE_AVAILABLE" = "true" ] && (( $(echo "$LINE_DELTA > 0" | bc -l) )) && (( $(echo "$BRANCH_DELTA > 0" | bc -l) )); then
echo "project_status=improved" >> $GITHUB_OUTPUT
echo "project_status_msg=📈 Coverage improved!" >> $GITHUB_OUTPUT
echo "project_emoji=📈" >> $GITHUB_OUTPUT
else
echo "project_status=stable" >> $GITHUB_OUTPUT
echo "project_status_msg=➡️ Coverage stable" >> $GITHUB_OUTPUT
echo "project_emoji=➡️" >> $GITHUB_OUTPUT
fi
- name: Upload Diff Coverage HTML Report
if: needs.detect-changes.outputs.skip_coverage != 'true' && always()
uses: actions/upload-artifact@v4
with:
name: coverage-diff-html-pr-${{ github.event.pull_request.number }}
path: coverage-diff.html
retention-days: 30
- name: Comment PR with Coverage (Config-Only)
if: needs.detect-changes.outputs.skip_coverage == 'true'
uses: thollander/actions-comment-pull-request@v2
with:
message: |
## 📊 Coverage Report
${{ steps.coverage_comparison.outputs.project_status_msg }}
${{ steps.coverage_comparison.outputs.change_summary }}
<details>
<summary>Edge Case Handling</summary>
- **Config-only changes**: Coverage check skipped automatically
- **Test-only changes**: Coverage check runs in informational mode (non-blocking)
- **Merge commits**: Relaxed enforcement for conflict resolution
- **Missing base coverage**: Falls back to absolute threshold checking
</details>
---
*Coverage report generated by [ProbotSharp CI](.github/workflows/coverage-report.yml)*
- name: Comment PR with Coverage (Test-Only or Merge)
if: needs.detect-changes.outputs.skip_coverage == 'informational'
uses: thollander/actions-comment-pull-request@v2
with:
message: |
## 📊 Coverage Report
### ℹ️ Informational Mode
${{ needs.detect-changes.outputs.change_type == 'test-only' && '**Test-only changes** - Coverage check is informational' || '**Merge commit** - Relaxed enforcement' }}
${{ steps.coverage_comparison.outputs.change_summary }}
### Project-Wide Coverage (informational only)
${{ steps.coverage_comparison.outputs.project_emoji }} ${{ steps.coverage_comparison.outputs.project_status_msg }}
| Metric | Base Branch | This PR | Delta | Target |
|--------|-------------|---------|-------|--------|
| **Line Coverage** | ${{ steps.coverage_comparison.outputs.base_line }}% | ${{ steps.coverage_comparison.outputs.pr_line }}% | ${{ steps.coverage_comparison.outputs.line_emoji }} ${{ steps.coverage_comparison.outputs.line_delta }} | 75% |
| **Branch Coverage** | ${{ steps.coverage_comparison.outputs.base_branch }}% | ${{ steps.coverage_comparison.outputs.pr_branch }}% | ${{ steps.coverage_comparison.outputs.branch_emoji }} ${{ steps.coverage_comparison.outputs.branch_delta }} | 70% |
<details>
<summary>Edge Case Handling</summary>
- **Config-only changes**: Coverage check skipped automatically
- **Test-only changes**: Coverage check runs in informational mode (non-blocking)
- **Merge commits**: Relaxed enforcement for conflict resolution
- **Missing base coverage**: Falls back to absolute threshold checking
</details>
---
*Coverage report generated by [ProbotSharp CI](.github/workflows/coverage-report.yml)*
- name: Comment PR with Coverage (Source Code)
if: needs.detect-changes.outputs.skip_coverage == 'false'
uses: thollander/actions-comment-pull-request@v2
with:
message: |
## 📊 Coverage Report
### ${{ steps.diff_coverage.outputs.diff_status_msg }}
**PR Diff Coverage** (Changed lines only - **blocks merge**):
| Metric | Changed Lines Coverage | Threshold | Status |
|--------|----------------------|-----------|--------|
| **Line Coverage** | **${{ steps.diff_coverage.outputs.diff_line }}%** | 80% | ${{ steps.diff_coverage.outputs.diff_line >= 80 && '✅ Pass' || steps.diff_coverage.outputs.diff_line == 'N/A' && 'ℹ️ N/A' || '❌ Fail' }} |
| **Branch Coverage** | **${{ steps.diff_coverage.outputs.diff_branch }}%** | 75% | ${{ steps.diff_coverage.outputs.diff_branch >= 75 && '✅ Pass' || steps.diff_coverage.outputs.diff_branch == 'N/A' && 'ℹ️ N/A' || '❌ Fail' }} |
<sub>📝 Changed: ${{ steps.diff_coverage.outputs.diff_lines_covered }}/${{ steps.diff_coverage.outputs.diff_lines_changed }} lines covered</sub>
---
### Project-Wide Coverage (informational only)
${{ steps.coverage_comparison.outputs.project_emoji }} ${{ steps.coverage_comparison.outputs.project_status_msg }}
${{ steps.coverage_comparison.outputs.change_summary }}
| Metric | Base Branch | This PR | Delta | Target |
|--------|-------------|---------|-------|--------|
| **Line Coverage** | ${{ steps.coverage_comparison.outputs.base_line }}% | ${{ steps.coverage_comparison.outputs.pr_line }}% | ${{ steps.coverage_comparison.outputs.line_emoji }} ${{ steps.coverage_comparison.outputs.line_delta }} | 75% |
| **Branch Coverage** | ${{ steps.coverage_comparison.outputs.base_branch }}% | ${{ steps.coverage_comparison.outputs.pr_branch }}% | ${{ steps.coverage_comparison.outputs.branch_emoji }} ${{ steps.coverage_comparison.outputs.branch_delta }} | 70% |
<details>
<summary>📋 Coverage Targets by Layer</summary>
- **Domain Layer**: 90% line, 85% branch
- **Application Layer**: 80% line, 75% branch
- **Infrastructure Layer**: 70% line, 65% branch
- **Overall Target**: 75% line, 70% branch
ℹ️ These are aspirational targets. Only **PR Diff Coverage** blocks merges.
</details>
<details>
<summary>Edge Case Handling</summary>
- **Config-only changes**: Coverage check skipped automatically
- **Test-only changes**: Coverage check runs in informational mode (non-blocking)
- **Merge commits**: Relaxed enforcement for conflict resolution
- **Missing base coverage**: Falls back to absolute threshold checking
</details>
---
*Coverage analysis by [diff-cover](https://github.com/Bachmann1234/diff_cover) | [Full report](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})*
- name: Set Coverage Status Check
if: always()
run: |
SKIP_COVERAGE="${{ needs.detect-changes.outputs.skip_coverage }}"
CHANGE_TYPE="${{ needs.detect-changes.outputs.change_type }}"
DIFF_STATUS="${{ steps.diff_coverage.outputs.diff_status }}"
DIFF_LINE="${{ steps.diff_coverage.outputs.diff_line }}"
DIFF_BRANCH="${{ steps.diff_coverage.outputs.diff_branch }}"
PROJECT_STATUS="${{ steps.coverage_comparison.outputs.project_status }}"
PR_LINE="${{ steps.coverage_comparison.outputs.pr_line }}"
PR_BRANCH="${{ steps.coverage_comparison.outputs.pr_branch }}"
echo "=== Coverage Gate Results ==="
# Handle edge cases first
if [ "$SKIP_COVERAGE" = "true" ]; then
echo "Coverage check skipped - no source code changes detected"
echo "::notice::Coverage check skipped - no source code changes detected"
exit 0
fi
if [ "$SKIP_COVERAGE" = "informational" ]; then
if [ "$CHANGE_TYPE" = "test-only" ]; then
echo "Coverage check informational only - test-only changes"
echo "::notice::Coverage check informational only - test-only changes. Line: ${PR_LINE}%, Branch: ${PR_BRANCH}%"
elif [ "$CHANGE_TYPE" = "merge-commit" ]; then
echo "Coverage check informational - merge commit with minimal changes"
echo "::notice::Coverage check informational - merge commit with minimal changes. Line: ${PR_LINE}%, Branch: ${PR_BRANCH}%"
fi
exit 0
fi
# Normal enforcement for source code changes - use diff coverage as gate
echo "PR Diff Coverage (BLOCKING): ${DIFF_STATUS}"
echo " - Line: ${DIFF_LINE}% (min: 80%)"
echo " - Branch: ${DIFF_BRANCH}% (min: 75%)"
echo ""
echo "Project-Wide Status (INFORMATIONAL): ${PROJECT_STATUS}"
echo " - Line: ${PR_LINE}%"
echo " - Branch: ${PR_BRANCH}%"
echo ""
# Only block based on diff coverage
if [ "$DIFF_STATUS" = "failure" ]; then
echo "::error::PR diff coverage check failed. Changed lines coverage: Line ${DIFF_LINE}%, Branch ${DIFF_BRANCH}%"
echo "::error::Your changed code must have at least 80% line coverage and 75% branch coverage."
exit 1
elif [ "$DIFF_STATUS" = "warning" ]; then
echo "::warning::Unable to calculate diff coverage - manual review recommended"
else
echo "::notice::PR diff coverage check passed! Line: ${DIFF_LINE}%, Branch: ${DIFF_BRANCH}%"
fi
# Show project-wide status as informational notice
case "$PROJECT_STATUS" in
below_target)
echo "::notice::Project-wide coverage is below targets but this does not block the PR"
;;
significant_decrease)
echo "::warning::Project-wide coverage decreased significantly - consider adding more tests"
;;
improved)
echo "::notice::Great job! Project-wide coverage improved 🎉"
;;
esac