Skip to content

Commit 0c19b88

Browse files
abnegateclaude
andcommitted
(test): merge coverage from parallel test jobs
Run unit and integration tests in parallel with pcov coverage, then merge clover reports in a coverage gate job. Enforces 80% minimum and max 1% drop vs baseline on PRs. Remove separate integration workflow since it's now a job in tests.yml. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ed94a20 commit 0c19b88

2 files changed

Lines changed: 97 additions & 61 deletions

File tree

.github/workflows/integration.yml

Lines changed: 0 additions & 28 deletions
This file was deleted.

.github/workflows/tests.yml

Lines changed: 97 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,30 +25,17 @@ jobs:
2525
run: composer install --prefer-dist --no-progress
2626

2727
- name: Run tests with coverage
28-
run: ./vendor/bin/phpunit --testsuite Unit --coverage-clover=coverage/clover.xml --coverage-text | tee coverage/summary.txt
28+
run: ./vendor/bin/phpunit --testsuite Unit --coverage-clover=coverage/unit.xml
2929

30-
- name: Check coverage threshold
31-
run: |
32-
LINE_PCT=$(grep -oP 'Lines:\s+\K[\d.]+' coverage/summary.txt | head -1)
33-
echo "Line coverage: ${LINE_PCT}%"
34-
if [ "$(echo "$LINE_PCT < 80" | bc -l)" -eq 1 ]; then
35-
echo "::error::Coverage ${LINE_PCT}% is below 80% minimum"
36-
exit 1
37-
fi
38-
39-
- name: Upload coverage artifact
40-
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
30+
- name: Upload coverage
4131
uses: actions/upload-artifact@v4
4232
with:
43-
name: coverage-baseline
44-
path: coverage/clover.xml
45-
retention-days: 90
33+
name: coverage-unit
34+
path: coverage/unit.xml
4635

47-
coverage-diff:
48-
name: Coverage Diff
36+
integration:
37+
name: Integration Tests
4938
runs-on: ubuntu-latest
50-
if: github.event_name == 'pull_request'
51-
needs: unit
5239

5340
steps:
5441
- name: Checkout
@@ -64,10 +51,76 @@ jobs:
6451
- name: Install dependencies
6552
run: composer install --prefer-dist --no-progress
6653

67-
- name: Generate PR coverage
68-
run: ./vendor/bin/phpunit --testsuite Unit --coverage-clover=coverage/clover.xml --coverage-text | tee coverage/summary.txt
54+
- name: Run tests with coverage
55+
run: ./vendor/bin/phpunit --testsuite Integration --coverage-clover=coverage/integration.xml
56+
57+
- name: Upload coverage
58+
uses: actions/upload-artifact@v4
59+
with:
60+
name: coverage-integration
61+
path: coverage/integration.xml
62+
63+
coverage:
64+
name: Coverage Check
65+
runs-on: ubuntu-latest
66+
needs: [unit, integration]
67+
68+
steps:
69+
- name: Download unit coverage
70+
uses: actions/download-artifact@v4
71+
with:
72+
name: coverage-unit
73+
path: coverage
74+
75+
- name: Download integration coverage
76+
uses: actions/download-artifact@v4
77+
with:
78+
name: coverage-integration
79+
path: coverage
80+
81+
- name: Merge and check coverage
82+
run: |
83+
merge_clover() {
84+
local total_stmts=0 covered_stmts=0
85+
for f in "$@"; do
86+
[ -f "$f" ] || continue
87+
s=$(grep -oP 'statements="\K\d+' "$f" | paste -sd+ | bc)
88+
c=$(grep -oP 'coveredstatements="\K\d+' "$f" | paste -sd+ | bc)
89+
total_stmts=$((total_stmts + s))
90+
covered_stmts=$((covered_stmts + c))
91+
done
92+
echo "$covered_stmts $total_stmts"
93+
}
94+
95+
read COVERED TOTAL <<< $(merge_clover coverage/unit.xml coverage/integration.xml)
96+
PCT=$(echo "scale=2; $COVERED * 100 / $TOTAL" | bc)
97+
echo "Coverage: ${PCT}% ($COVERED / $TOTAL statements)"
98+
99+
if [ "$(echo "$PCT < 80" | bc -l)" -eq 1 ]; then
100+
echo "::error::Coverage ${PCT}% is below 80% minimum"
101+
exit 1
102+
fi
69103
70-
- name: Download baseline coverage
104+
- name: Upload merged baseline
105+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
106+
run: |
107+
echo "$COVERED $TOTAL" > coverage/baseline.txt
108+
env:
109+
COVERED: ${{ env.COVERED }}
110+
TOTAL: ${{ env.TOTAL }}
111+
112+
- name: Save baseline artifact
113+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
114+
uses: actions/upload-artifact@v4
115+
with:
116+
name: coverage-baseline
117+
path: |
118+
coverage/unit.xml
119+
coverage/integration.xml
120+
retention-days: 90
121+
122+
- name: Download baseline (PR only)
123+
if: github.event_name == 'pull_request'
71124
uses: dawidd6/action-download-artifact@v6
72125
with:
73126
name: coverage-baseline
@@ -76,25 +129,36 @@ jobs:
76129
workflow: tests.yml
77130
if_no_artifact_found: warn
78131

79-
- name: Compare coverage
132+
- name: Check coverage diff (PR only)
133+
if: github.event_name == 'pull_request'
80134
run: |
81-
PR_PCT=$(grep -oP 'Lines:\s+\K[\d.]+' coverage/summary.txt | head -1)
82-
echo "PR coverage: ${PR_PCT}%"
83-
84-
if [ ! -f coverage/baseline/clover.xml ]; then
135+
if [ ! -f coverage/baseline/unit.xml ]; then
85136
echo "No baseline found, skipping diff check"
86137
exit 0
87138
fi
88139
89-
BASE_STATEMENTS=$(grep -oP 'statements="\K\d+' coverage/baseline/clover.xml | paste -sd+ | bc)
90-
BASE_COVERED=$(grep -oP 'coveredstatements="\K\d+' coverage/baseline/clover.xml | paste -sd+ | bc)
91-
BASE_PCT=$(echo "scale=2; $BASE_COVERED * 100 / $BASE_STATEMENTS" | bc)
140+
merge_clover() {
141+
local total_stmts=0 covered_stmts=0
142+
for f in "$@"; do
143+
[ -f "$f" ] || continue
144+
s=$(grep -oP 'statements="\K\d+' "$f" | paste -sd+ | bc)
145+
c=$(grep -oP 'coveredstatements="\K\d+' "$f" | paste -sd+ | bc)
146+
total_stmts=$((total_stmts + s))
147+
covered_stmts=$((covered_stmts + c))
148+
done
149+
echo "$covered_stmts $total_stmts"
150+
}
151+
152+
read BASE_COV BASE_TOTAL <<< $(merge_clover coverage/baseline/unit.xml coverage/baseline/integration.xml)
153+
BASE_PCT=$(echo "scale=2; $BASE_COV * 100 / $BASE_TOTAL" | bc)
154+
155+
read PR_COV PR_TOTAL <<< $(merge_clover coverage/unit.xml coverage/integration.xml)
156+
PR_PCT=$(echo "scale=2; $PR_COV * 100 / $PR_TOTAL" | bc)
92157
93-
echo "Baseline coverage: ${BASE_PCT}%"
94158
DIFF=$(echo "$PR_PCT - $BASE_PCT" | bc -l)
95-
echo "Delta: ${DIFF}%"
159+
echo "Baseline: ${BASE_PCT}% → PR: ${PR_PCT}% (${DIFF}%)"
96160
97161
if [ "$(echo "$DIFF < -1" | bc -l)" -eq 1 ]; then
98-
echo "::error::Coverage dropped ${DIFF}% (baseline ${BASE_PCT}% → PR ${PR_PCT}%). Maximum allowed drop is 1%."
162+
echo "::error::Coverage dropped ${DIFF}% (max allowed: -1%)"
99163
exit 1
100164
fi

0 commit comments

Comments
 (0)