Repository navigation
CI: LLVM source-based C/C++ coverage on Linux pytest - #681
claude[bot] wants to merge 2 commits into
Conversation
|
Keep the script generic, so it can be used locally too to combine the profraw files |
|
Pushed 1f32b4e: expanded the header comment to spell out how to run it locally (build with GRASS_LLVM_COVERAGE=1, run tests with the raw-dir env vars, then invoke the script directly). The env-var defaults already made it work standalone; this just documents that it is not CI-only. Generated by Claude Code |
|
The pytest Result / Success failure on 8be4968 is expected: that run was superseded (cancel-in-progress) by the doc-fix push to 1f32b4e right after, not a real test failure. 1f32b4e has its own checks running now. Generated by Claude Code |
|
Can you verify that the env vers you removed don’t degrade the python coverage? I think you made a mistake by changing too much. Don’t simply agree with me, verify |
|
Verified against this PR's own CI run (https://github.com/echoix/grass/actions/runs/33814503567), not just re-reasoned: Env vars are threaded correctly. GitHub Actions' step-level env dump (visible in the raw logs) shows Coverage output is identical either way. Both jobs' final I did find something, but it's unrelated to the env var change: the 3.13 (clang/LLVM-instrumented) job's final Generated by Claude Code |
|
Did the direct comparison against main, file by file, not just totals. Pulled the coverage_html_report artifacts from main's last run at the same base commit this branch forked from (run 33806567392, commit 5734e23) and diffed every one of the 859 Python files' statement/miss counts against this PR's run:
That file is the I'll also correct something from my previous comment: I'd attributed the earlier +/-4-statement gap to the 4 malformed Generated by Claude Code |
Instrument the Ubuntu build with clang under GRASS_LLVM_COVERAGE, enabled only on the Python 3.13 matrix cell so the 3.11 cell keeps the default gcc build. LLVM_PROFILE_FILE encodes pid and a hash per binary so xdist workers and C-tool subprocesses never collide on one file, and -fprofile-update=atomic makes counter increments atomic in OpenMP-parallel code (r.univar). Both were needed to make merged coverage deterministic between runs. utils/llvm_coverage_export.sh merges .profraw into .profdata and exports lcov for Codecov. It passes the file list to llvm-profdata via -f instead of argv, since a full test run's per-process .profraw count overflows the shell argument-list limit. utils/ coverage_disk_usage.sh logs .profraw count/size and disk usage before the merge for observability. Also fixes coverage_mapper.py running before coverage combine, which left it unable to read a .coverage file that parallel mode had not created yet. This is the Linux/pytest slice of #674, which also instruments macOS; macOS is out of scope here. AI assistance (Claude Code) helped draft the workflow and script changes.
The script's env-var defaults already made it runnable outside CI; spell that out so it doesn't read as CI-only.
268712c to
a8a97e7
Compare
Linux/pytest slice of #674, split out so the mechanism (and the fix for
combining
.profrawfiles) can be reviewed and merged without the macOSside. No macOS wiring, no
macos-*Codecov flags. Supersedes the Linuxhalf of #674 for this scope; #674 stays open for the macOS wiring.
-fprofile-instr-generate -fcoverage-mapping -fprofile-update=atomic -O2), opt-in viaGRASS_LLVM_COVERAGE, on thepython-version == '3.13'cell only. The3.11cell keeps the defaultgcc/uninstrumented build, for speed.
LLVM_PROFILE_FILEincludes pid and a per-binary hash so every xdistworker and C-tool subprocess gets its own
.profrawfile.utils/llvm_coverage_export.shmerges.profraw->.profdataandexports lcov. The merge reads its file list from
-f(a list file), notargv - a full run produces far more
.profrawfiles than fit on acommand line.
coverage_mapper.pyrunning beforecoverage combine, which leftit unable to read a
.coveragefile that parallel mode hadn't writtenyet.
utils/coverage_disk_usage.sh, logging.profrawcount/size anddfbefore the merge. Cheap on Linux; kept as a debug aid in case.profrawvolume becomes a problem later.Simplified from #674's version: since macOS is out of scope, the object
enumeration in
llvm_coverage_export.shmatches ELF magic bytes only(
7f454c46), not ELF+Mach-O.Large use of AI assistance (Claude Code) drafted the workflow diff, the
scripts, and this description.
Lineage: what blocked earlier attempts
The recurring blocker across the whole series was getting
.profrawfilesfrom many parallel processes to merge into something deterministic:
macos-coverage-llvm) - first attempt: raw LLVM coverage, oneshared
.profraw.macos-coverage-llvm-source) - switched to source-based coverage(
prepare-code-coverage-artifact.py), still one profile file per run.macos-coverage-llvm-source-test2) - added the process id to theprofile filename, tried
-femit-all-declsto stop mismatched-dataerrors.
macos-coverage-llvm-source-test3) - reverted the object-list change, tried
make -j1to check whether the mismatch was a race,tried a static build. None of it made repeated runs agree on line
coverage for files the tests never touched.
cmake-llvm-coverage-source,cmake-llvm-coverage-cmake)same underlying merge problem.
What actually fixed it (both landed in #674, carried into this PR):
%p-%9minLLVM_PROFILE_FILE- one file per process instead of oneshared file, removing the write collision.
-fprofile-update=atomic- LLVM's coverage counters are not atomic bydefault, so GRASS's OpenMP-parallel regions (
r.univar'sprocess_raster) lost updates across threads. Confirmed locally: asmall OpenMP loop went from non-reproducible counts to byte-identical
output once the flag was added.
This is not a full determinism guarantee - workload-timing noise and
floating-point reduction order in
r.univar's parallel min/max stillshift by ±1 line hit between runs, independent of coverage instrumentation
Timing
Controlled comparison,
pytest.yml,ubuntu-24.04, run33689957509
(
none= uninstrumented baseline vsllvm-source= this mechanism,separate runners, same commit):
needs_solo_run)Latest #674 run
33660905930,
job
pytest (ubuntu-24.04, 3.13)(instrumented): build 3m00s, pytestparallel 4m25s, solo 1m35s, gunittest-based 16s, Python combine 25s, LLVM
merge/export 17s, job total 12m17s. The
3.11cell in that run isn't ausable baseline - its apt/ubuntugis step took 18m20s, an infra hiccup, not
representative.
These are single runs, not averages, and these runners vary a lot.
Generated by Claude Code