1313#
1414# prepare ───────────────┬──► test-gcc ──┐
1515# build-gcc ─────────────┘ ├──► publish (on failure)
16- # prepare ───────────────┬──► test-i686 ──┘
17- # build-i686 ────────────┘
16+ # prepare ───────────────┬──► test-i686 ──┤
17+ # build-i686 ────────────┘ └──► coverage
1818# bench (pull requests only; independent)
1919#
2020# A shared run timestamp keeps results under the same tests/runs/<timestamp>/
2323# `edg-docker-bench --posture=release` does and uploads them as bench-results
2424# (c/ and cpp/ runs, for comparison against the checked-in
2525# benchmarks/baselines/project/release-{c,cpp}).
26+ #
27+ # The debug builds are instrumented for gcov (EDG_CPFE_USE_GCOV) and ship
28+ # their .gcno files with the build products. Each test leg turns the .gcda
29+ # files its run produces into a gcovr JSON report for src/; the coverage job
30+ # merges the reports of all test legs and uploads one report to Codecov
31+ # (see .github/codecov.yml). The coverage steps never fail the run; only
32+ # edgy's results do.
2633
2734name : Build & Test CI
2835
@@ -40,6 +47,9 @@ concurrency:
4047 group : ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
4148 cancel-in-progress : true
4249
50+ env :
51+ GCOVR_VERSION : ' 8.7'
52+
4353jobs :
4454 prepare :
4555 name : Prepare run timestamp
7181 - name : Configure linux-gcc-debug
7282 env :
7383 EDG_BASE : ${{ github.workspace }}/bases/docker/dev-env/gcc
74- run : cmake --preset linux-gcc-debug
84+ run : cmake --preset linux-gcc-debug -DEDG_CPFE_USE_GCOV=ON
7585
7686 - name : Build linux-gcc-debug
7787 env :
8090
8191 # Runtime layout only: environment.sh / eccp need bin/, lib/, and the
8292 # per-target lib_linux_* / lib_win* dirs (eccp rewrites ECCP_LIBDIR to
83- # lib_<target>). lib_src is build-only and omitted.
93+ # lib_<target>). lib_src is build-only and omitted. The .gcno files
94+ # let the test legs produce coverage reports.
8495 - name : Upload build products
8596 uses : actions/upload-artifact@v7
8697 with :
91102 build/gcc/lib_linux_*
92103 build/gcc/lib_win*
93104 build/gcc/environment.sh
105+ build/gcc/src/**/*.gcno
94106 if-no-files-found : error
95107 retention-days : 1
96108 compression-level : 6
@@ -112,7 +124,7 @@ jobs:
112124 - name : Configure linux-i686-gcc-debug
113125 env :
114126 EDG_BASE : ${{ github.workspace }}/bases/docker/dev-env/gcc-i686
115- run : cmake --preset linux-i686-gcc-debug
127+ run : cmake --preset linux-i686-gcc-debug -DEDG_CPFE_USE_GCOV=ON
116128
117129 - name : Build linux-i686-gcc-debug
118130 env :
@@ -129,6 +141,7 @@ jobs:
129141 build/gcc-i686/lib_linux_*
130142 build/gcc-i686/lib_win*
131143 build/gcc-i686/environment.sh
144+ build/gcc-i686/src/**/*.gcno
132145 if-no-files-found : error
133146 retention-days : 1
134147 compression-level : 6
@@ -201,6 +214,51 @@ jobs:
201214 if-no-files-found : warn
202215 retention-days : 1
203216
217+ # Coverage: the instrumented cpfe / cpfe-cp wrote .gcda files next to
218+ # the downloaded .gcno files under build/gcc/src. Each leg saves a gcovr
219+ # JSON report; the coverage job merges them and uploads to Codecov.
220+ # Coverage problems must never fail the test leg, hence
221+ # continue-on-error.
222+ - name : Install gcovr
223+ if : ${{ !cancelled() }}
224+ continue-on-error : true
225+ run : |
226+ python3 -m venv "${RUNNER_TEMP}/gcovr"
227+ "${RUNNER_TEMP}/gcovr/bin/pip" install --quiet \
228+ "gcovr==${GCOVR_VERSION}"
229+
230+ # cpfe and cpfe-cp compile the same sources; separate function merging
231+ # keeps gcovr from rejecting functions seen at different lines. A few
232+ # hot loops exceed gcovr's "suspicious hits" threshold; warn about them
233+ # instead of failing.
234+ - name : Generate coverage report
235+ if : ${{ !cancelled() }}
236+ continue-on-error : true
237+ env :
238+ CONFIG : ${{ matrix.config }}
239+ run : |
240+ mkdir -p coverage
241+ "${RUNNER_TEMP}/gcovr/bin/gcovr" \
242+ --root . \
243+ --filter 'src/' \
244+ --merge-mode-functions=separate \
245+ --gcov-ignore-parse-errors=negative_hits.warn_once_per_file \
246+ --gcov-ignore-parse-errors=suspicious_hits.warn_once_per_file \
247+ -j "$(nproc)" \
248+ --json "coverage/${CONFIG}.json" \
249+ --print-summary \
250+ build/gcc
251+
252+ - name : Upload coverage report
253+ if : ${{ !cancelled() && hashFiles('coverage/*.json') != '' }}
254+ continue-on-error : true
255+ uses : actions/upload-artifact@v7
256+ with :
257+ name : coverage-${{ matrix.config }}
258+ path : coverage/${{ matrix.config }}.json
259+ if-no-files-found : warn
260+ retention-days : 1
261+
204262 test-i686 :
205263 name : Test ${{ matrix.config }}
206264 needs :
@@ -269,6 +327,51 @@ jobs:
269327 if-no-files-found : warn
270328 retention-days : 1
271329
330+ # Coverage: the instrumented cpfe / cpfe-cp wrote .gcda files next to
331+ # the downloaded .gcno files under build/gcc-i686/src. Each leg saves a gcovr
332+ # JSON report; the coverage job merges them and uploads to Codecov.
333+ # Coverage problems must never fail the test leg, hence
334+ # continue-on-error.
335+ - name : Install gcovr
336+ if : ${{ !cancelled() }}
337+ continue-on-error : true
338+ run : |
339+ python3 -m venv "${RUNNER_TEMP}/gcovr"
340+ "${RUNNER_TEMP}/gcovr/bin/pip" install --quiet \
341+ "gcovr==${GCOVR_VERSION}"
342+
343+ # cpfe and cpfe-cp compile the same sources; separate function merging
344+ # keeps gcovr from rejecting functions seen at different lines. A few
345+ # hot loops exceed gcovr's "suspicious hits" threshold; warn about them
346+ # instead of failing.
347+ - name : Generate coverage report
348+ if : ${{ !cancelled() }}
349+ continue-on-error : true
350+ env :
351+ CONFIG : ${{ matrix.config }}
352+ run : |
353+ mkdir -p coverage
354+ "${RUNNER_TEMP}/gcovr/bin/gcovr" \
355+ --root . \
356+ --filter 'src/' \
357+ --merge-mode-functions=separate \
358+ --gcov-ignore-parse-errors=negative_hits.warn_once_per_file \
359+ --gcov-ignore-parse-errors=suspicious_hits.warn_once_per_file \
360+ -j "$(nproc)" \
361+ --json "coverage/${CONFIG}.json" \
362+ --print-summary \
363+ build/gcc-i686
364+
365+ - name : Upload coverage report
366+ if : ${{ !cancelled() && hashFiles('coverage/*.json') != '' }}
367+ continue-on-error : true
368+ uses : actions/upload-artifact@v7
369+ with :
370+ name : coverage-${{ matrix.config }}
371+ path : coverage/${{ matrix.config }}.json
372+ if-no-files-found : warn
373+ retention-days : 1
374+
272375 publish-test-results :
273376 name : Publish aggregated test results
274377 needs :
@@ -318,6 +421,71 @@ jobs:
318421 if-no-files-found : warn
319422 retention-days : 14
320423
424+ coverage :
425+ name : Publish coverage to Codecov
426+ needs :
427+ - test-gcc
428+ - test-i686
429+ # Like publish-test-results, merge whatever the test legs produced,
430+ # whether or not their tests passed. The number of configs is not
431+ # fixed: every coverage-* artifact is merged into one report and
432+ # uploaded once.
433+ if : ${{ !cancelled() }}
434+ runs-on : ubuntu-latest
435+ timeout-minutes : 30
436+ steps :
437+ - name : Check out repository
438+ uses : actions/checkout@v7
439+
440+ - name : Download per-config coverage reports
441+ continue-on-error : true
442+ uses : actions/download-artifact@v7
443+ with :
444+ pattern : coverage-*
445+ path : coverage
446+ merge-multiple : true
447+
448+ - name : Install gcovr
449+ continue-on-error : true
450+ run : |
451+ python3 -m venv "${RUNNER_TEMP}/gcovr"
452+ "${RUNNER_TEMP}/gcovr/bin/pip" install --quiet \
453+ "gcovr==${GCOVR_VERSION}"
454+
455+ - name : Merge coverage reports
456+ if : ${{ hashFiles('coverage/*.json') != '' }}
457+ continue-on-error : true
458+ run : |
459+ ls -l coverage
460+ "${RUNNER_TEMP}/gcovr/bin/gcovr" \
461+ --root . \
462+ --merge-mode-functions=separate \
463+ --json-add-tracefile 'coverage/*.json' \
464+ --cobertura coverage.xml \
465+ --print-summary
466+
467+ # Pull requests from forks have no access to secrets; the Codecov
468+ # action then falls back to a tokenless upload, which Codecov accepts
469+ # for public repositories.
470+ #
471+ # The report is complete as generated by gcovr, so skip the CLI's own
472+ # gcov run (plugins: noop) and its "file fixes" pass, which opens every
473+ # file git tracks and crashes on the dangling symlinks used as test
474+ # inputs (e.g. tests/.../embed___has_embed/embed___has_embed.c).
475+ - name : Upload coverage to Codecov
476+ if : ${{ hashFiles('coverage.xml') != '' }}
477+ continue-on-error : true
478+ uses : codecov/codecov-action@v7
479+ with :
480+ token : ${{ secrets.CODECOV_TOKEN }}
481+ files : coverage.xml
482+ disable_search : true
483+ disable_file_fixes : true
484+ network_filter : src/
485+ plugins : noop
486+ fail_ci_if_error : true
487+ verbose : true
488+
321489 bench :
322490 name : Benchmark linux-gcc-release
323491 if : ${{ github.event_name == 'pull_request' }}
0 commit comments