Skip to content

Commit 141d786

Browse files
committed
ci(coverage): gate changed-line coverage
- Use diff-cover changed-line coverage as the changed-code gate; keep the overall coverage delta gate at -0.1% and JaCoCo reports as summary-only snapshots. - Handle non-Java PRs: when diff-cover reports no changed Java lines (workflow-only, docs-only, proto-only), emit NA and treat it as SKIPPED in the enforcement step so the gate does not wrongly fail. - Harden the diff-cover step: guard against empty SRC_ROOTS before invoking diff-cover, verify diff-cover.json was written, and surface exit codes in failure messages. - Clarify metric semantics in the step summary: Changed-line is LINE coverage (diff-cover) while Overall / Delta are INSTRUCTION coverage (jacoco-report).
1 parent f0a8f0f commit 141d786

1 file changed

Lines changed: 93 additions & 25 deletions

File tree

.github/workflows/pr-build.yml

Lines changed: 93 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,68 @@ jobs:
279279
echo "base_xmls=$BASE_XMLS" >> "$GITHUB_OUTPUT"
280280
echo "pr_xmls=$PR_XMLS" >> "$GITHUB_OUTPUT"
281281
282+
- name: Set up Python
283+
uses: actions/setup-python@v5
284+
with:
285+
python-version: '3.11'
286+
287+
- name: Changed-line coverage (diff-cover)
288+
id: diff-cover
289+
env:
290+
BASE_REF: ${{ github.event.pull_request.base.ref }}
291+
run: |
292+
set -euo pipefail
293+
pip install --quiet 'diff-cover==9.2.0'
294+
295+
# Ensure the base branch ref is available locally for diff-cover.
296+
git fetch --no-tags origin "+refs/heads/${BASE_REF}:refs/remotes/origin/${BASE_REF}"
297+
298+
PR_XMLS=$(find coverage/pr -name "jacocoTestReport.xml" | sort)
299+
SRC_ROOTS=$(find . -type d -path '*/src/main/java' \
300+
-not -path './coverage/*' -not -path './.git/*' | sort)
301+
if [ -z "$SRC_ROOTS" ]; then
302+
echo "No src/main/java directories found; cannot run diff-cover." >&2
303+
exit 1
304+
fi
305+
306+
set +e
307+
diff-cover $PR_XMLS \
308+
--compare-branch="origin/${BASE_REF}" \
309+
--src-roots $SRC_ROOTS \
310+
--fail-under=0 \
311+
--json-report=diff-cover.json \
312+
--markdown-report=diff-cover.md
313+
DIFF_RC=$?
314+
set -e
315+
316+
if [ ! -f diff-cover.json ]; then
317+
echo "diff-cover did not produce JSON report (exit=${DIFF_RC})." >&2
318+
exit 1
319+
fi
320+
321+
TOTAL_NUM_LINES=$(jq -r '.total_num_lines // 0' diff-cover.json)
322+
if [ "${TOTAL_NUM_LINES}" = "0" ]; then
323+
echo "No changed Java source lines; skipping changed-line gate."
324+
echo "changed_line_coverage=NA" >> "$GITHUB_OUTPUT"
325+
else
326+
CHANGED_LINE_COVERAGE=$(jq -r '.total_percent_covered // empty' diff-cover.json)
327+
if [ -z "$CHANGED_LINE_COVERAGE" ]; then
328+
echo "Unable to parse changed-line coverage from diff-cover.json."
329+
exit 1
330+
fi
331+
echo "changed_line_coverage=${CHANGED_LINE_COVERAGE}" >> "$GITHUB_OUTPUT"
332+
fi
333+
334+
{
335+
echo "### Changed-line Coverage (diff-cover)"
336+
echo ""
337+
if [ -f diff-cover.md ] && [ -s diff-cover.md ]; then
338+
cat diff-cover.md
339+
else
340+
echo "_diff-cover produced no report._"
341+
fi
342+
} >> "$GITHUB_STEP_SUMMARY"
343+
282344
- name: Aggregate base coverage
283345
id: jacoco-base
284346
uses: madrapps/jacoco-report@v1.7.2
@@ -288,6 +350,7 @@ jobs:
288350
min-coverage-overall: 0
289351
min-coverage-changed-files: 0
290352
skip-if-no-changes: true
353+
comment-type: summary
291354
title: '## Base Coverage Snapshot'
292355
update-comment: false
293356

@@ -300,14 +363,15 @@ jobs:
300363
min-coverage-overall: 0
301364
min-coverage-changed-files: 0
302365
skip-if-no-changes: true
366+
comment-type: summary
303367
title: '## PR Code Coverage Report'
304368
update-comment: false
305369

306370
- name: Enforce coverage gates
307371
env:
308372
BASE_OVERALL_RAW: ${{ steps.jacoco-base.outputs.coverage-overall }}
309373
PR_OVERALL_RAW: ${{ steps.jacoco-pr.outputs.coverage-overall }}
310-
PR_CHANGED_RAW: ${{ steps.jacoco-pr.outputs.coverage-changed-files }}
374+
CHANGED_LINE_RAW: ${{ steps.diff-cover.outputs.changed_line_coverage }}
311375
run: |
312376
set -euo pipefail
313377
@@ -329,7 +393,7 @@ jobs:
329393
# 1) Parse metrics from jacoco-report outputs
330394
BASE_OVERALL="$(sanitize "$BASE_OVERALL_RAW")"
331395
PR_OVERALL="$(sanitize "$PR_OVERALL_RAW")"
332-
PR_CHANGED="$(sanitize "$PR_CHANGED_RAW")"
396+
CHANGED_LINE="$(sanitize "$CHANGED_LINE_RAW")"
333397
334398
if ! is_number "$BASE_OVERALL" || ! is_number "$PR_OVERALL"; then
335399
echo "Failed to parse coverage values: base='${BASE_OVERALL}', pr='${PR_OVERALL}'."
@@ -340,18 +404,18 @@ jobs:
340404
DELTA=$(awk -v pr="$PR_OVERALL" -v base="$BASE_OVERALL" 'BEGIN { printf "%.4f", pr - base }')
341405
DELTA_OK=$(compare_float "${DELTA} >= ${MAX_DROP}")
342406
343-
CHANGED_STATUS="SKIPPED (no changed coverage value)"
344-
CHANGED_OK=1
345-
if [ -n "$PR_CHANGED" ] && [ "$PR_CHANGED" != "NaN" ]; then
346-
if ! is_number "$PR_CHANGED"; then
347-
echo "Failed to parse changed-files coverage: changed='${PR_CHANGED}'."
348-
exit 1
349-
fi
350-
CHANGED_OK=$(compare_float "${PR_CHANGED} > ${MIN_CHANGED}")
351-
if [ "$CHANGED_OK" -eq 1 ]; then
352-
CHANGED_STATUS="PASS (> ${MIN_CHANGED}%)"
407+
if [ "$CHANGED_LINE" = "NA" ]; then
408+
CHANGED_LINE_OK=1
409+
CHANGED_LINE_STATUS="SKIPPED (no changed Java source lines)"
410+
elif [ -z "$CHANGED_LINE" ] || [ "$CHANGED_LINE" = "NaN" ] || ! is_number "$CHANGED_LINE"; then
411+
echo "Failed to parse changed-line coverage: changed-line='${CHANGED_LINE}'."
412+
exit 1
413+
else
414+
CHANGED_LINE_OK=$(compare_float "${CHANGED_LINE} > ${MIN_CHANGED}")
415+
if [ "$CHANGED_LINE_OK" -eq 1 ]; then
416+
CHANGED_LINE_STATUS="PASS (> ${MIN_CHANGED}%)"
353417
else
354-
CHANGED_STATUS="FAIL (<= ${MIN_CHANGED}%)"
418+
CHANGED_LINE_STATUS="FAIL (<= ${MIN_CHANGED}%)"
355419
fi
356420
fi
357421
@@ -361,13 +425,20 @@ jobs:
361425
OVERALL_STATUS="FAIL (< ${MAX_DROP}%)"
362426
fi
363427
428+
if [ "$CHANGED_LINE" = "NA" ]; then
429+
CHANGED_LINE_DISPLAY="NA"
430+
else
431+
CHANGED_LINE_DISPLAY="${CHANGED_LINE}%"
432+
fi
433+
364434
METRICS_TEXT=$(cat <<EOF
365-
Changed Files Coverage: ${PR_CHANGED}%
435+
Changed-line Coverage: ${CHANGED_LINE_DISPLAY}
366436
PR Overall Coverage: ${PR_OVERALL}%
367437
Base Overall Coverage: ${BASE_OVERALL}%
368438
Delta (PR - Base): ${DELTA}%
369-
Changed Files Gate: ${CHANGED_STATUS}
439+
Changed-line Gate: ${CHANGED_LINE_STATUS}
370440
Overall Delta Gate: ${OVERALL_STATUS}
441+
Note: Changed-line uses LINE coverage (diff-cover); Overall/Delta use INSTRUCTION coverage (jacoco-report). The two counters are not directly comparable.
371442
EOF
372443
)
373444
@@ -376,12 +447,14 @@ jobs:
376447
{
377448
echo "### Coverage Gate Metrics"
378449
echo ""
379-
echo "- Changed Files Coverage: ${PR_CHANGED}%"
450+
echo "- Changed-line Coverage: ${CHANGED_LINE_DISPLAY}"
380451
echo "- PR Overall Coverage: ${PR_OVERALL}%"
381452
echo "- Base Overall Coverage: ${BASE_OVERALL}%"
382453
echo "- Delta (PR - Base): ${DELTA}%"
383-
echo "- Changed Files Gate: ${CHANGED_STATUS}"
454+
echo "- Changed-line Gate: ${CHANGED_LINE_STATUS}"
384455
echo "- Overall Delta Gate: ${OVERALL_STATUS}"
456+
echo ""
457+
echo "_Note: Changed-line uses LINE coverage (diff-cover); Overall/Delta use INSTRUCTION coverage (jacoco-report). The two counters are not directly comparable._"
385458
} >> "$GITHUB_STEP_SUMMARY"
386459
387460
# 4) Decide CI pass/fail
@@ -391,14 +464,9 @@ jobs:
391464
exit 1
392465
fi
393466
394-
if [ -z "$PR_CHANGED" ] || [ "$PR_CHANGED" = "NaN" ]; then
395-
echo "No changed-files coverage value detected, skip changed-files gate."
396-
exit 0
397-
fi
398-
399-
if [ "$CHANGED_OK" -ne 1 ]; then
400-
echo "Coverage gate failed: changed files coverage must be > 60%."
401-
echo "changed=${PR_CHANGED}%"
467+
if [ "$CHANGED_LINE_OK" -ne 1 ]; then
468+
echo "Coverage gate failed: changed-line coverage must be > 60%."
469+
echo "changed-line=${CHANGED_LINE}%"
402470
exit 1
403471
fi
404472

0 commit comments

Comments
 (0)