Skip to content

Commit 9066f1b

Browse files
authored
Add code coverage to CI (#804)
1 parent 424df53 commit 9066f1b

File tree

2 files changed

+154
-2
lines changed

2 files changed

+154
-2
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Post Coverage Comment
2+
on:
3+
workflow_run:
4+
workflows: ["Build and Deploy Snapshot"]
5+
types: [completed]
6+
7+
permissions:
8+
pull-requests: write
9+
10+
jobs:
11+
comment:
12+
runs-on: ubuntu-24.04
13+
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
14+
steps:
15+
- name: Download comment artifact
16+
uses: dawidd6/action-download-artifact@2536c51d3d126276eb39f74d6bc9c72ac6ef30d3 # v16
17+
with:
18+
name: pr-comment
19+
path: pr-comment/
20+
run_id: ${{ github.event.workflow_run.id }}
21+
github_token: ${{ secrets.GITHUB_TOKEN }}
22+
23+
- name: Post coverage comment
24+
run: |
25+
PR_NUMBER=$(cat pr-comment/pr-number.txt)
26+
COMMENT_BODY=$(cat pr-comment/comment-body.txt)
27+
COMMENT_ID=$(gh pr view $PR_NUMBER --repo ${{ github.repository }} --json comments \
28+
--jq '.comments[] | select(.body | contains("<!-- jacoco-coverage-comment -->")) | .id' | head -1)
29+
if [ -n "$COMMENT_ID" ]; then
30+
gh api -X DELETE "repos/${{ github.repository }}/issues/comments/$COMMENT_ID" || true
31+
fi
32+
gh pr comment $PR_NUMBER --repo ${{ github.repository }} --body "$COMMENT_BODY"
33+
env:
34+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/main.yml

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,130 @@ jobs:
5252
# MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
5353
run: ./mvnw -B -q -ff -DskipTests -ntp source:jar deploy
5454
- name: Generate code coverage
55-
if: ${{ matrix.release_build && github.event_name != 'pull_request' }}
56-
run: ./mvnw -B -q -ff -ntp test
55+
if: ${{ matrix.release_build }}
56+
run: ./mvnw -B -q -ff -ntp test jacoco:report
5757
- name: Publish code coverage
5858
if: ${{ matrix.release_build && github.event_name != 'pull_request' }}
5959
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
6060
with:
6161
token: ${{ secrets.CODECOV_TOKEN }}
6262
files: ./target/site/jacoco/jacoco.xml
6363
flags: unittests
64+
- name: Upload coverage report as artifact
65+
if: ${{ matrix.release_build && github.event_name != 'pull_request' }}
66+
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
67+
with:
68+
name: jacoco-report
69+
path: target/site/jacoco/jacoco.csv
70+
retention-days: 30
71+
- name: Download base branch coverage
72+
if: ${{ matrix.release_build && github.event_name == 'pull_request' }}
73+
uses: dawidd6/action-download-artifact@2536c51d3d126276eb39f74d6bc9c72ac6ef30d3 # v16
74+
continue-on-error: true
75+
with:
76+
workflow: main.yml
77+
branch: ${{ github.event.pull_request.base.ref }}
78+
name: jacoco-report
79+
path: base-coverage/
80+
- name: Generate coverage summary
81+
if: ${{ matrix.release_build && github.event_name == 'pull_request' }}
82+
id: jacoco
83+
uses: cicirello/jacoco-badge-generator@72266185b7ee48a6fd74eaf0238395cc8b14fef8 # v2.12.1
84+
with:
85+
jacoco-csv-file: target/site/jacoco/jacoco.csv
86+
generate-coverage-badge: false
87+
generate-branches-badge: false
88+
generate-summary: true
89+
- name: Generate coverage comment
90+
if: ${{ matrix.release_build && github.event_name == 'pull_request' }}
91+
run: |
92+
# Helper functions
93+
parse_coverage() {
94+
local csv_file=$1 col_missed=$2 col_covered=$3
95+
awk -F',' -v m="$col_missed" -v c="$col_covered" \
96+
'NR>1 && $1=="jackson-dataformat-xml" {
97+
total_missed += $m;
98+
total_covered += $c;
99+
}
100+
END {
101+
if (total_missed + total_covered > 0)
102+
printf "%.2f", (total_covered * 100.0) / (total_missed + total_covered)
103+
}' "$csv_file"
104+
}
105+
106+
format_delta() {
107+
local delta=$1
108+
if awk -v d="$delta" 'BEGIN { exit (d >= 0) ? 0 : 1 }'; then
109+
echo "+${delta}%|📈"
110+
else
111+
echo "${delta}%|📉"
112+
fi
113+
}
114+
115+
get_color() {
116+
awk -v v="$1" 'BEGIN {
117+
if (v >= 80) print "brightgreen"
118+
else if (v >= 60) print "green"
119+
else if (v >= 40) print "yellow"
120+
else print "red"
121+
}'
122+
}
123+
124+
# Convert decimal to percentage and round to 1 decimal place
125+
COVERAGE=$(awk -v cov="${{ steps.jacoco.outputs.coverage }}" 'BEGIN { printf "%.2f", cov * 100 }')
126+
BRANCHES=$(awk -v br="${{ steps.jacoco.outputs.branches }}" 'BEGIN { printf "%.2f", br * 100 }')
127+
128+
# Check if base coverage artifact was downloaded and calculate deltas
129+
HAS_DELTA=false
130+
if [ -f "base-coverage/jacoco.csv" ]; then
131+
echo "Found base branch coverage from artifact"
132+
BASE_COVERAGE=$(parse_coverage "base-coverage/jacoco.csv" 4 5)
133+
BASE_BRANCHES=$(parse_coverage "base-coverage/jacoco.csv" 6 7)
134+
135+
if [ -n "$BASE_COVERAGE" ] && [ -n "$BASE_BRANCHES" ]; then
136+
COV_DELTA=$(awk -v curr="$COVERAGE" -v base="$BASE_COVERAGE" 'BEGIN { printf "%.3f", curr - base }')
137+
BR_DELTA=$(awk -v curr="$BRANCHES" -v base="$BASE_BRANCHES" 'BEGIN { printf "%.3f", curr - base }')
138+
139+
IFS='|' read -r COV_DELTA_STR COV_DELTA_EMOJI <<< "$(format_delta "$COV_DELTA")"
140+
IFS='|' read -r BR_DELTA_STR BR_DELTA_EMOJI <<< "$(format_delta "$BR_DELTA")"
141+
142+
HAS_DELTA=true
143+
fi
144+
fi
145+
146+
# Determine badge colors
147+
COV_COLOR=$(get_color "$COVERAGE")
148+
BR_COLOR=$(get_color "$BRANCHES")
149+
150+
# Build coverage table with or without deltas
151+
if [ "$HAS_DELTA" = "true" ]; then
152+
COVERAGE_TABLE="| Metric | Coverage | Change |
153+
|--------|----------|--------|
154+
| **Instructions** | ![coverage](https://img.shields.io/badge/coverage-${COVERAGE}%25-${COV_COLOR}) **${COVERAGE}%** | ${COV_DELTA_EMOJI} **${COV_DELTA_STR}** |
155+
| **Branches** | ![branches](https://img.shields.io/badge/branches-${BRANCHES}%25-${BR_COLOR}) **${BRANCHES}%** | ${BR_DELTA_EMOJI} **${BR_DELTA_STR}** |"
156+
else
157+
COVERAGE_TABLE="| Metric | Coverage |
158+
|--------|----------|
159+
| **Instructions** | ![coverage](https://img.shields.io/badge/coverage-${COVERAGE}%25-${COV_COLOR}) **${COVERAGE}%** |
160+
| **Branches** | ![branches](https://img.shields.io/badge/branches-${BRANCHES}%25-${BR_COLOR}) **${BRANCHES}%** |"
161+
fi
162+
163+
COMMENT_BODY="## :test_tube: Code Coverage Report
164+
165+
$COVERAGE_TABLE
166+
167+
> Coverage data generated from JaCoCo test results
168+
169+
<!-- jacoco-coverage-comment -->"
170+
171+
# Save comment for the workflow_run
172+
mkdir -p pr-comment
173+
echo "${{ github.event.pull_request.number }}" > pr-comment/pr-number.txt
174+
echo "$COMMENT_BODY" > pr-comment/comment-body.txt
175+
- name: Upload PR comment
176+
if: ${{ matrix.release_build && github.event_name == 'pull_request' }}
177+
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
178+
with:
179+
name: pr-comment
180+
path: pr-comment/
181+
retention-days: 1

0 commit comments

Comments
 (0)