Skip to content

Commit 86500f5

Browse files
authored
Add code coverage for CI (#657)
1 parent 6b20d46 commit 86500f5

2 files changed

Lines changed: 176 additions & 3 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
post-comment:
12+
runs-on: ubuntu-24.04
13+
if: >
14+
github.event.workflow_run.event == 'pull_request' &&
15+
github.event.workflow_run.conclusion == 'success'
16+
steps:
17+
- name: Download PR comment artifact
18+
uses: dawidd6/action-download-artifact@v13
19+
with:
20+
workflow: main.yml
21+
run_id: ${{ github.event.workflow_run.id }}
22+
name: pr-coverage-comment
23+
path: pr-coverage-comment/
24+
- name: Post or update PR comment
25+
uses: actions/github-script@v7
26+
with:
27+
script: |
28+
const fs = require('fs');
29+
const prNumber = parseInt(
30+
fs.readFileSync('pr-coverage-comment/pr-number.txt', 'utf8').trim()
31+
);
32+
const commentBody = fs.readFileSync(
33+
'pr-coverage-comment/comment-body.txt', 'utf8'
34+
).trim();
35+
36+
const marker = '## :test_tube: Code Coverage Report';
37+
const { data: comments } = await github.rest.issues.listComments({
38+
owner: context.repo.owner,
39+
repo: context.repo.repo,
40+
issue_number: prNumber,
41+
});
42+
43+
const existing = comments.find(c => c.body.startsWith(marker));
44+
if (existing) {
45+
await github.rest.issues.updateComment({
46+
owner: context.repo.owner,
47+
repo: context.repo.repo,
48+
comment_id: existing.id,
49+
body: commentBody,
50+
});
51+
} else {
52+
await github.rest.issues.createComment({
53+
owner: context.repo.owner,
54+
repo: context.repo.repo,
55+
issue_number: prNumber,
56+
body: commentBody,
57+
});
58+
}

.github/workflows/main.yml

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ on:
1111
- "release-notes/*"
1212
permissions:
1313
contents: read
14+
pull-requests: write
1415

1516
jobs:
1617
build:
@@ -51,12 +52,126 @@ jobs:
5152
# MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
5253
run: ./mvnw -B -q -ff -DskipTests -ntp source:jar deploy
5354
- name: Generate code coverage
54-
if: ${{ matrix.release_build && github.event_name != 'pull_request' }}
55-
run: ./mvnw -B -q -ff -ntp test
55+
if: ${{ matrix.release_build }}
56+
run: ./mvnw -B -q -ff -ntp test jacoco:report
5657
- name: Publish code coverage
5758
if: ${{ matrix.release_build && github.event_name != 'pull_request' }}
5859
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
5960
with:
6061
token: ${{ secrets.CODECOV_TOKEN }}
61-
files: ./target/site/jacoco/jacoco.xml
62+
files: cbor/target/site/jacoco/jacoco.xml,smile/target/site/jacoco/jacoco.xml,avro/target/site/jacoco/jacoco.xml,protobuf/target/site/jacoco/jacoco.xml,ion/target/site/jacoco/jacoco.xml
6263
flags: unittests
64+
- name: Upload coverage report as artifact
65+
if: ${{ matrix.release_build && github.event_name != 'pull_request' && github.repository == 'FasterXML/jackson-dataformats-binary' }}
66+
uses: actions/upload-artifact@v6
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@v13
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@v2.12.1
84+
with:
85+
jacoco-csv-file: >
86+
cbor/target/site/jacoco/jacoco.csv
87+
smile/target/site/jacoco/jacoco.csv
88+
avro/target/site/jacoco/jacoco.csv
89+
protobuf/target/site/jacoco/jacoco.csv
90+
ion/target/site/jacoco/jacoco.csv
91+
generate-coverage-badge: false
92+
generate-branches-badge: false
93+
generate-summary: true
94+
- name: Generate coverage comment
95+
if: ${{ matrix.release_build && github.event_name == 'pull_request' }}
96+
run: |
97+
parse_coverage() {
98+
local col_missed=$1 col_covered=$2
99+
shift 2
100+
awk -F',' -v m="$col_missed" -v c="$col_covered" \
101+
'FNR>1 { total_missed += $m; total_covered += $c }
102+
END { if (total_missed + total_covered > 0)
103+
printf "%.2f", (total_covered * 100.0) / (total_missed + total_covered) }' "$@"
104+
}
105+
106+
badge_color() {
107+
awk -v pct="$1" 'BEGIN{
108+
if (pct >= 90) print "brightgreen"
109+
else if (pct >= 80) print "green"
110+
else if (pct >= 70) print "yellowgreen"
111+
else if (pct >= 60) print "yellow"
112+
else if (pct >= 50) print "orange"
113+
else print "red"
114+
}'
115+
}
116+
117+
compute_delta() {
118+
awk -v pr="$1" -v base="$2" 'BEGIN{
119+
delta = pr - base
120+
if (delta > 0) printf "📈 +%.2f%%", delta
121+
else if (delta < 0) printf "📉 %.2f%%", delta
122+
else printf "="
123+
}'
124+
}
125+
126+
PR_CSV_FILES="cbor/target/site/jacoco/jacoco.csv \
127+
smile/target/site/jacoco/jacoco.csv \
128+
avro/target/site/jacoco/jacoco.csv \
129+
protobuf/target/site/jacoco/jacoco.csv \
130+
ion/target/site/jacoco/jacoco.csv"
131+
132+
PR_COVERAGE=$(parse_coverage 4 5 $PR_CSV_FILES)
133+
PR_BRANCHES=$(parse_coverage 6 7 $PR_CSV_FILES)
134+
135+
COV_COLOR=$(badge_color "$PR_COVERAGE")
136+
BR_COLOR=$(badge_color "$PR_BRANCHES")
137+
138+
COV_BADGE="![${PR_COVERAGE}%](https://img.shields.io/badge/instructions-${PR_COVERAGE}%25-${COV_COLOR})"
139+
BR_BADGE="![${PR_BRANCHES}%](https://img.shields.io/badge/branches-${PR_BRANCHES}%25-${BR_COLOR})"
140+
141+
BASE_CSV_FILES=$(find base-coverage -name "jacoco.csv" 2>/dev/null | tr '\n' ' ')
142+
143+
if [ -n "$BASE_CSV_FILES" ]; then
144+
BASE_COVERAGE=$(parse_coverage 4 5 $BASE_CSV_FILES)
145+
BASE_BRANCHES=$(parse_coverage 6 7 $BASE_CSV_FILES)
146+
COVERAGE_DELTA=$(compute_delta "$PR_COVERAGE" "$BASE_COVERAGE")
147+
BRANCHES_DELTA=$(compute_delta "$PR_BRANCHES" "$BASE_BRANCHES")
148+
TABLE_HEADER="| Coverage Type | Coverage | Change |"
149+
TABLE_SEP="|---|---|---|"
150+
TABLE_COV="| :memo: Instructions | ${COV_BADGE} | ${COVERAGE_DELTA} |"
151+
TABLE_BR="| :twisted_rightwards_arrows: Branches | ${BR_BADGE} | ${BRANCHES_DELTA} |"
152+
else
153+
TABLE_HEADER="| Coverage Type | Coverage |"
154+
TABLE_SEP="|---|---|"
155+
TABLE_COV="| :memo: Instructions | ${COV_BADGE} |"
156+
TABLE_BR="| :twisted_rightwards_arrows: Branches | ${BR_BADGE} |"
157+
fi
158+
159+
{
160+
echo '## :test_tube: Code Coverage Report'
161+
echo ''
162+
echo "$TABLE_HEADER"
163+
echo "$TABLE_SEP"
164+
echo "$TABLE_COV"
165+
echo "$TABLE_BR"
166+
} > comment-body.txt
167+
168+
echo "${{ github.event.pull_request.number }}" > pr-number.txt
169+
- name: Upload PR comment artifact
170+
if: ${{ matrix.release_build && github.event_name == 'pull_request' }}
171+
uses: actions/upload-artifact@v6
172+
with:
173+
name: pr-coverage-comment
174+
path: |
175+
pr-number.txt
176+
comment-body.txt
177+
retention-days: 1

0 commit comments

Comments
 (0)