diff --git a/.github/workflows/coverage-comment.yml b/.github/workflows/coverage-comment.yml new file mode 100644 index 0000000000..605ba75c3b --- /dev/null +++ b/.github/workflows/coverage-comment.yml @@ -0,0 +1,39 @@ +name: Post Coverage Comment +on: + workflow_run: + workflows: ["Build and Deploy Snapshot"] + types: [completed] + +permissions: + pull-requests: write + +jobs: + comment: + runs-on: ubuntu-24.04 + if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' + steps: + - name: Download comment artifact + uses: dawidd6/action-download-artifact@v6 + with: + name: pr-comment + path: pr-comment/ + run_id: ${{ github.event.workflow_run.id }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Post coverage comment + run: | + PR_NUMBER=$(cat pr-comment/pr-number.txt) + COMMENT_BODY=$(cat pr-comment/comment-body.txt) + + # Find and delete existing comment + COMMENT_ID=$(gh pr view $PR_NUMBER --json comments \ + --jq '.comments[] | select(.body | contains("")) | .id' | head -1) + + if [ -n "$COMMENT_ID" ]; then + gh api -X DELETE "repos/${{ github.repository }}/issues/comments/$COMMENT_ID" || true + fi + + # Post new comment + gh pr comment $PR_NUMBER --body "$COMMENT_BODY" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 083b5975f3..39fc05b1b2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -71,6 +71,22 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} files: ./target/site/jacoco/jacoco.xml flags: unittests + - name: Upload coverage report as artifact + if: ${{ matrix.release_build && github.event_name != 'pull_request' }} + uses: actions/upload-artifact@v4 + with: + name: jacoco-report + path: target/site/jacoco/jacoco.csv + retention-days: 30 + - name: Download base branch coverage + if: ${{ matrix.release_build && github.event_name == 'pull_request' }} + uses: dawidd6/action-download-artifact@v6 + continue-on-error: true + with: + workflow: main.yml + branch: ${{ github.event.pull_request.base.ref }} + name: jacoco-report + path: base-coverage/ - name: Generate coverage summary if: ${{ matrix.release_build && github.event_name == 'pull_request' }} id: jacoco @@ -80,51 +96,95 @@ jobs: generate-coverage-badge: false generate-branches-badge: false generate-summary: true - - name: Add coverage comment to PR + - name: Generate coverage comment if: ${{ matrix.release_build && github.event_name == 'pull_request' }} - continue-on-error: true run: | + # Helper functions + parse_coverage() { + local csv_file=$1 col_missed=$2 col_covered=$3 + awk -F',' -v m="$col_missed" -v c="$col_covered" \ + 'NR>1 && $1=="Jackson-databind" { + missed=$m; covered=$c; + if (missed + covered > 0) printf "%.1f", (covered * 100.0) / (missed + covered) + }' "$csv_file" | head -1 + } + + format_delta() { + local delta=$1 + if awk -v d="$delta" 'BEGIN { exit (d >= 0) ? 0 : 1 }'; then + echo "+${delta}%|📈" + else + echo "${delta}%|📉" + fi + } + + get_color() { + awk -v v="$1" 'BEGIN { + if (v >= 80) print "brightgreen" + else if (v >= 60) print "green" + else if (v >= 40) print "yellow" + else print "red" + }' + } + # Convert decimal to percentage and round to 1 decimal place COVERAGE=$(awk -v cov="${{ steps.jacoco.outputs.coverage }}" 'BEGIN { printf "%.1f", cov * 100 }') BRANCHES=$(awk -v br="${{ steps.jacoco.outputs.branches }}" 'BEGIN { printf "%.1f", br * 100 }') - # Determine color for coverage badge using awk (more portable than bc) - COV_COLOR=$(awk -v cov="$COVERAGE" 'BEGIN { - if (cov >= 80) print "brightgreen" - else if (cov >= 60) print "green" - else if (cov >= 40) print "yellow" - else print "red" - }') - - BR_COLOR=$(awk -v br="$BRANCHES" 'BEGIN { - if (br >= 80) print "brightgreen" - else if (br >= 60) print "green" - else if (br >= 40) print "yellow" - else print "red" - }') + # Check if base coverage artifact was downloaded and calculate deltas + HAS_DELTA=false + if [ -f "base-coverage/jacoco.csv" ]; then + echo "Found base branch coverage from artifact" + BASE_COVERAGE=$(parse_coverage "base-coverage/jacoco.csv" 4 5) + BASE_BRANCHES=$(parse_coverage "base-coverage/jacoco.csv" 6 7) - COMMENT_BODY="## :test_tube: Code Coverage Report + if [ -n "$BASE_COVERAGE" ] && [ -n "$BASE_BRANCHES" ]; then + COV_DELTA=$(awk -v curr="$COVERAGE" -v base="$BASE_COVERAGE" 'BEGIN { printf "%.1f", curr - base }') + BR_DELTA=$(awk -v curr="$BRANCHES" -v base="$BASE_BRANCHES" 'BEGIN { printf "%.1f", curr - base }') + + IFS='|' read -r COV_DELTA_STR COV_DELTA_EMOJI <<< "$(format_delta "$COV_DELTA")" + IFS='|' read -r BR_DELTA_STR BR_DELTA_EMOJI <<< "$(format_delta "$BR_DELTA")" + + HAS_DELTA=true + fi + fi + + # Determine badge colors + COV_COLOR=$(get_color "$COVERAGE") + BR_COLOR=$(get_color "$BRANCHES") - | Metric | Coverage | + # Build coverage table with or without deltas + if [ "$HAS_DELTA" = "true" ]; then + COVERAGE_TABLE="| Metric | Coverage | Change | + |--------|----------|--------| + | **Instructions** | ![coverage](https://img.shields.io/badge/coverage-${COVERAGE}%25-${COV_COLOR}) **${COVERAGE}%** | ${COV_DELTA_EMOJI} **${COV_DELTA_STR}** | + | **Branches** | ![branches](https://img.shields.io/badge/branches-${BRANCHES}%25-${BR_COLOR}) **${BRANCHES}%** | ${BR_DELTA_EMOJI} **${BR_DELTA_STR}** |" + else + COVERAGE_TABLE="| Metric | Coverage | |--------|----------| | **Instructions** | ![coverage](https://img.shields.io/badge/coverage-${COVERAGE}%25-${COV_COLOR}) **${COVERAGE}%** | - | **Branches** | ![branches](https://img.shields.io/badge/branches-${BRANCHES}%25-${BR_COLOR}) **${BRANCHES}%** | + | **Branches** | ![branches](https://img.shields.io/badge/branches-${BRANCHES}%25-${BR_COLOR}) **${BRANCHES}%** |" + fi - > Coverage data generated from JaCoCo test results + COMMENT_BODY="## :test_tube: Code Coverage Report - " + $COVERAGE_TABLE - # Find and delete existing coverage comment - COMMENT_ID=$(gh pr view ${{ github.event.pull_request.number }} --json comments --jq '.comments[] | select(.body | contains("")) | .id' | head -1) + > Coverage data generated from JaCoCo test results - if [ -n "$COMMENT_ID" ]; then - gh api -X DELETE "repos/${{ github.repository }}/issues/comments/$COMMENT_ID" || true - fi + " - # Post new comment (may fail for PRs from forks due to permissions) - gh pr comment ${{ github.event.pull_request.number }} --body "$COMMENT_BODY" - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Save comment for the workflow_run + mkdir -p pr-comment + echo "${{ github.event.pull_request.number }}" > pr-comment/pr-number.txt + echo "$COMMENT_BODY" > pr-comment/comment-body.txt + - name: Upload PR comment + if: ${{ matrix.release_build && github.event_name == 'pull_request' }} + uses: actions/upload-artifact@v4 + with: + name: pr-comment + path: pr-comment/ + retention-days: 1 trigger-dep-build-v2: name: Trigger v2 dep builds