1414
1515concurrency :
1616 group : ci-${{ github.workflow }}-${{ github.ref }}
17- cancel-in-progress : true
17+ cancel-in-progress : ${{ github.event_name == 'pull_request' }}
1818
1919permissions :
2020 contents : read
2121 security-events : write
2222 actions : read
2323 checks : write # For test results
24- id-token : write # Required for Codecov OIDC uploads
2524
2625env :
2726 NO_INTERACTIVE : ' 1'
4140 uses : actions/checkout@v5
4241 with :
4342 fetch-depth : 1 # Shallow checkout for faster validation
43+ submodules : recursive
4444
4545 - name : Cache template validation
4646 uses : actions/cache@v4
@@ -121,13 +121,15 @@ jobs:
121121 name : Unit Tests (Python ${{ matrix.python-version }})
122122 runs-on : ubuntu-latest
123123 strategy :
124+ fail-fast : false
124125 matrix :
125126 python-version : ['3.11', '3.12']
126127 steps :
127128 - name : Checkout repository
128129 uses : actions/checkout@v5
129130 with :
130131 fetch-depth : 1
132+ submodules : recursive
131133
132134 - name : Python setup & deps
133135 uses : ./.github/actions/python-setup
@@ -156,6 +158,9 @@ jobs:
156158 MARKERS="$MARKERS and not slow"
157159 fi
158160
161+ # Set coverage file per Python version
162+ export COVERAGE_FILE=".coverage.${{ matrix.python-version }}"
163+
159164 pytest tests/ \
160165 --cov=src \
161166 --cov=generate \
@@ -170,41 +175,99 @@ jobs:
170175 --strict-config \
171176 ${{ matrix.python-version == '3.11' && '--verbose' || '--quiet' }}
172177
173- - name : Upload coverage to Codecov
174- if : matrix.python-version == '3.11' && !cancelled()
175- uses : codecov/codecov-action@v5
176- with :
177- files : ./coverage.xml
178- flags : unittests
179- name : codecov-umbrella
180- fail_ci_if_error : false
181- verbose : true
178+ - name : Package coverage artifacts
179+ if : always()
180+ run : |
181+ mkdir -p coverage_artifacts
182+ cp -f .coverage.${{ matrix.python-version }} coverage_artifacts/ || true
183+ cp -f coverage.xml coverage_artifacts/ || true
184+ cp -f junit-unit.xml coverage_artifacts/ || true
185+ if [ -d htmlcov ]; then tar -czf coverage_artifacts/htmlcov.tar.gz htmlcov; fi
182186
183- - name : Upload test results
187+ - name : Upload coverage artifact
184188 uses : actions/upload-artifact@v5
185189 if : always()
186190 with :
187- name : test-results-py${{ matrix.python-version }}
188- path : |
189- junit-unit.xml
190- coverage.xml
191- htmlcov/
191+ name : coverage-py${{ matrix.python-version }}
192+ path : coverage_artifacts/
192193 retention-days : 7
193194
194- dependency-review :
195- name : Dependency Review
196- if : github.event_name == 'pull_request'
195+ coverage-upload :
196+ name : Coverage Upload (Codecov)
197197 runs-on : ubuntu-latest
198+ needs : [unit-tests]
199+ if : always()
200+ permissions :
201+ contents : read
202+ id-token : write
203+ actions : read
198204 steps :
199- - name : Checkout repository
205+ - name : Checkout repository (for coverage tools)
200206 uses : actions/checkout@v5
207+ with :
208+ fetch-depth : 1
201209
202- - name : Dependency Review
203- uses : actions/dependency-review-action @v4
210+ - name : Download coverage artifacts
211+ uses : actions/download-artifact @v4
204212 with :
205- fail-on-severity : moderate
206- allow-licenses : MIT, BSD-2-Clause, BSD-3-Clause, Apache-2.0, ISC, GPL-3.0
207- comment-summary-in-pr : on-failure
213+ path : coverage_dl
214+
215+ - name : Setup Python for combining
216+ uses : actions/setup-python@v6
217+ with :
218+ python-version : ' 3.11'
219+
220+ - name : Combine coverage
221+ run : |
222+ python -m pip install --upgrade coverage
223+ mkdir -p merged
224+ # Collect raw coverage data files
225+ find coverage_dl -name ".coverage.*" -print -exec cp {} merged/ \; || true
226+ if compgen -G "merged/.coverage.*" > /dev/null; then
227+ export COVERAGE_FILE="merged/.coverage"
228+ python -m coverage combine merged || true
229+ python -m coverage xml -o merged/coverage.xml || true
230+ python -m coverage html -d merged/htmlcov || true
231+ else
232+ echo "No raw coverage data; fallback to first XML"
233+ XML=$(find coverage_dl -name "coverage.xml" | head -n1)
234+ [ -n "$XML" ] && cp "$XML" merged/coverage.xml
235+ fi
236+ ls -la merged || true
237+
238+ - name : Check if coverage XML exists
239+ id : check_coverage
240+ run : |
241+ if [ -f "merged/coverage.xml" ]; then
242+ echo "exists=true" >> $GITHUB_OUTPUT
243+ else
244+ echo "exists=false" >> $GITHUB_OUTPUT
245+ fi
246+
247+ - name : Upload to Codecov (OIDC, skip forks)
248+ if : >
249+ !cancelled() &&
250+ steps.check_coverage.outputs.exists == 'true' &&
251+ (
252+ github.event_name != 'pull_request' ||
253+ github.event.pull_request.head.repo.fork == false
254+ )
255+ uses : codecov/codecov-action@v5.5.1
256+ with :
257+ files : merged/coverage.xml
258+ name : codecov-umbrella
259+ flags : unittests
260+ use_oidc : true
261+ verbose : true
262+ fail_ci_if_error : false
263+
264+ - name : Upload merged coverage artifact
265+ uses : actions/upload-artifact@v5
266+ if : always()
267+ with :
268+ name : coverage-merged
269+ path : merged/
270+ retention-days : 7
208271
209272 packaging :
210273 name : Build & Package
@@ -222,6 +285,7 @@ jobs:
222285 uses : actions/checkout@v5
223286 with :
224287 fetch-depth : 0 # Need full history for version calculation
288+ submodules : recursive
225289
226290 - name : Python setup (build tools)
227291 uses : ./.github/actions/python-setup
@@ -260,7 +324,7 @@ jobs:
260324 ci-summary :
261325 name : CI Summary
262326 runs-on : ubuntu-latest
263- needs : [validation, security-analysis, unit-tests, dependency-review , packaging]
327+ needs : [validation, security-analysis, unit-tests, coverage-upload , packaging]
264328 if : always()
265329 steps :
266330 - name : Check workflow status
@@ -289,13 +353,13 @@ jobs:
289353 echo "❌ Unit tests failed" >> $GITHUB_STEP_SUMMARY
290354 fi
291355
292- # Dependency review status (only for PRs)
293- if [[ "${{ github.event_name }}" == "pull_request " ]]; then
294- if [[ "${{ needs.dependency-review.result }}" == "success" ]]; then
295- echo "✅ Dependency review passed" >> $GITHUB_STEP_SUMMARY
296- else
297- echo "❌ Dependency review failed" >> $GITHUB_STEP_SUMMARY
298- fi
356+ # Coverage upload status
357+ if [[ "${{ needs.coverage-upload.result }}" == "success " ]]; then
358+ echo "✅ Coverage uploaded" >> $GITHUB_STEP_SUMMARY
359+ elif [[ "${{ needs.coverage-upload.result }}" == "skipped" ]]; then
360+ echo "⏸️ Coverage upload skipped" >> $GITHUB_STEP_SUMMARY
361+ else
362+ echo "⚠️ Coverage upload failed (non-blocking)" >> $GITHUB_STEP_SUMMARY
299363 fi
300364
301365 # Packaging status
0 commit comments