Skip to content

CI: prototype SanitizerCoverage as an alternative to LLVM source-based coverage - #679

Closed
echoix wants to merge 23 commits into
mainfrom
claude/sancov-coverage-experiment
Closed

echoix wants to merge 23 commits into
mainfrom
claude/sancov-coverage-experiment

Conversation

@echoix

@echoix echoix commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Summary

Exploratory/investigative work comparing SanitizerCoverage (-fsanitize-coverage=, on both GCC and Clang) against the existing clang source-based coverage mechanism (GRASS_LLVM_COVERAGE), for pytest.yml's Linux matrix and (more narrowly) cmake.yml. This is not a replacement for GRASS_LLVM_COVERAGE — it's a prototype plus a written report with a bottom-line recommendation, backed by real GitHub Actions runs.

  • Adds an opt-in GRASS_SANCOV_COVERAGE (Autotools) / WITH_SANCOV_COVERAGE (CMake) path that instruments GRASS with -fsanitize-coverage=trace-pc (GCC) or trace-pc-guard (Clang), and produces coverage dumps via a small custom runtime (utils/sancov_runtime.c, since neither compiler auto-links a runtime for this flag the way clang does for -fprofile-instr-generate) and a symbolizer (utils/sancov_symbolize.py, using addr2line).
  • Restructures pytest.yml's Linux matrix to add sancov-gcc and sancov-clang cells alongside the existing none and llvm-source cells, and adds the same opt-in path to cmake.yml.
  • Full write-up in doc/development/sancov_coverage_experiment.md, including build/test timing deltas from real CI runs and a concrete macro-attribution demonstration (compiling the real _() gettext macro in lib/gis/parser_standard_options.c three ways and comparing the generated coverage output).
  • workflow_dispatch was temporarily added to both workflows so this branch's cells could be run on demand without a PR; noted inline as removable before any eventual merge.

Headline finding

GCC's -fsanitize-coverage= does not implement trace-pc-guard (only trace-cmp/trace-pc, confirmed empirically — contrary to widely-repeated claims). With no per-edge guard word available, the runtime's GCC path must dedup via a mutex-guarded hash-set insertion on every edge execution, not just the first. Under cmake.yml's real gunittest suite (run with GRASS's normal, unrestricted OpenMP threading), this measured +232% test time (2h02m05s vs 36m49s baseline) in a real CI run. Clang's trace-pc-guard path has no such penalty, but its coverage output is strictly coarser than the existing source-based mechanism for macro-expansion attribution (demonstrated concretely in the report).

Bottom line

Not recommended as an addition to GRASS's coverage tooling on either compiler, for the reasons above. GRASS_LLVM_COVERAGE should remain GRASS's coverage mechanism. This PR's value is the validated prototype and the documented findings, in case a future fuzzing use case (where trace-pc-guard's ABI is directly consumable by libFuzzer/AFL++) makes it worth revisiting.

Test plan

  • Full local Autotools build (GRASS_SANCOV_COVERAGE=1 GRASS_SANCOV_CC=gcc) built and installed end to end; ran a real GRASS tool and confirmed a real, symbolizable coverage dump.
  • Full local CMake build (-DWITH_SANCOV_COVERAGE=ON) reached 3996/3997 build steps (the one failure is an unrelated, pre-existing sandbox gap: wxPython not installed).
  • Real pytest.yml workflow_dispatch run with all four matrix cells (none, llvm-source, sancov-gcc, sancov-clang) green, including symbolized SanitizerCoverage output for both sancov cells: https://github.com/echoix/grass/actions/runs/33689957509
  • Real cmake.yml workflow_dispatch run (none, gcc) providing the headline timing comparison: https://github.com/echoix/grass/actions/runs/33686891578
  • Not validated: g.extension-built addons under CMake's sancov path (documented as an open limitation in the report — the addon's out-of-tree build can't find the installed runtime library)

🤖 Generated with Claude Code

https://claude.ai/code/session_01EFijpm8JqSEJfJ9BhJFeen


Generated by Claude Code

claude added 20 commits August 31, 2026 22:25
Adds opt-in clang -fprofile-instr-generate -fcoverage-mapping
instrumentation to the macOS and Linux pytest builds and wires the
resulting coverage to Codecov alongside the existing Python coverage.

Determinism under pytest-xdist: LLVM_PROFILE_FILE is set to
grass-%p-%9m.profraw so every subprocess and every instrumented binary
writes its own file, eliminating the collision that made re-runs
non-deterministic for untouched files. Merge uses llvm-profdata -sparse
which is bit-identical for a given input set (verified locally).

Fixes a latent ordering bug where utils/coverage_mapper.py ran before
coverage combine and could not read a .coverage that did not yet exist
in parallel mode. The mapper now runs on the combined data.

Adds utils/coverage_disk_usage.sh to log raw coverage disk usage before
the combine step so a run that threatens the runner storage budget is
visible in the log, and utils/llvm_coverage_export.sh to merge .profraw
into .profdata and export lcov for Codecov, filtering system, conda,
and build-tree paths.

macOS Codecov uploads use distinct flags macos-pytest-python and
macos-llvm-c; Linux adds ubuntu-llvm-c to exercise the same path
during local iteration. macOS coverage is combined after the
gunittest run so profraw from both pytest and gunittest are included.

Large use of AI assistance (Claude Code) helped draft the workflow
diffs and the utility scripts.
The instrumented build runs each tool with --interface-description to
emit its XML, which drops default.profraw next to the binary. Those
files are build artifacts (no tests ran, coverage from the build tree
would contaminate the test-run merge), so they belong ignored, and the
CI coverage-prep step deletes any stray default.profraw before the
test steps start writing controlled per-PID files under llvm-cov-raw/.

Adds *.profraw, *.profdata, coverage-llvm.lcov, llvm-cov-raw/, and
coverage_html_report/ to .gitignore.
Adds -fprofile-update=atomic to the coverage CFLAGS on both macOS and
Linux pytest builds. Without it, LLVM's coverage counter increments are
non-atomic, and GRASS tools with #pragma omp parallel / for regions
(e.g. r.univar's process_raster) lose counter updates across threads.
Two identical runs against a locally-built, instrumented grass produced
244 differing lcov lines, all inside r.univar_main.c's outlined OpenMP
region. This is the collision reported previously as coverage of
untouched files changing between runs.

Also tightens utils/llvm_coverage_export.sh so its -object list only
picks up real ELF / Mach-O binaries. The previous perm -u+x sweep
would pull in executable Python scripts (e.g. installed grass
scripts), and llvm-cov aborts on the first one it cannot recognize.

A full local rebuild + rerun is queued to verify the diff closes; if
anything remains after -fprofile-update=atomic it will land as a
follow-up commit on this branch.
Clang's SourceBasedCodeCoverage doc explicitly states that llvm
optimizations (inlining, CFG simplification) have no impact on
coverage report quality: the mapping from source regions to profile
counters is immutable and is generated before the optimizer runs, and
the feature does not rely on debug info line tables. Force -O0 -g was
gcov guidance, not the right choice here, and it inflated the
instrumented build size and the runtime cost this PR promised to
keep small.

Also fix the Linux path: setting CFLAGS explicitly at configure time
suppresses autoconf's default "-g -O2", so include -O2 in the coverage
CFLAGS (macOS already sets -O2 explicitly before the coverage block).
The pytest workflow's Ubuntu build failed at configure with "Unable to
locate OpenMP includes" once GRASS_LLVM_COVERAGE=1 flipped the compiler
to clang. Ubuntu's libomp-dev keeps omp.h at
/usr/lib/llvm-18/lib/clang/<ver>/include/omp.h rather than /usr/include,
and configure's plain omp.h probe doesn't add clang's own resource-dir
to its include search. Fix by passing --with-openmp-includes derived
from `$CC -print-resource-dir` when the coverage build is active. Also
add libomp-dev to apt.txt so the runtime lib is linkable.

Make the Python coverage combine step tolerate "no data" so that when
an earlier step fails and pytest is skipped, the combine step reports
"nothing to export" instead of failing on top of the real error.
The pytest workflow's Ubuntu build now clears the OpenMP-include probe
but fails to link every OpenMP-using tool (r.mapcalc, r.univar,
r.slope.aspect, r.sun, ...) once GRASS_LLVM_COVERAGE=1 flipped the
compiler to clang. Root cause: libomp-dev on Ubuntu 24.04 installs the
unversioned libomp.so at /usr/lib/llvm-18/lib, which is not in the
linker's default search path. configure's link probe for
omp_get_num_threads in -lomp therefore fails and it falls back to
-lgomp - but clang's OpenMP-instrumented code emits __kmpc_* calls that
gomp doesn't provide. Pass --with-openmp-libs=<clang's llvm libdir> so
configure finds -lomp and every OMP tool links cleanly. The libdir is
derived from clang -print-resource-dir so the fix is not tied to any
specific LLVM version number.
Adding clang, llvm, and libomp-dev to the shared apt.txt slowed down
every workflow that reads that list (ubuntu.yml, gcc.yml, codeql,
cmake, docs...) for a toolchain none of them need. Move them into a
new "Install LLVM coverage toolchain" step in pytest.yml, gated on
env.GRASS_LLVM_COVERAGE == '1', so only the coverage matrix pays the
apt cost.
Instrumenting the build costs ~1 min extra compile time and adds a
merge/export step. Running it in every matrix cell duplicated all of
that for no additional information. Gate GRASS_LLVM_COVERAGE, the
llvm/libomp toolchain install, the merge/export step, and the
ubuntu-llvm-c Codecov upload on matrix.python-version == '3.13' so
the 3.11 cell now takes the fast gcc/no-instrumentation path. Python
coverage still runs on both cells and continues to upload under its
own per-version flag.
utils/llvm_coverage_export.sh globbed all .profraw files onto
llvm-profdata's argv. The per-process %p-%9m naming produces one file
per (binary, pid) pair, and a full test run generates hundreds of
thousands of them (454754 on the macOS job that failed), which
overflows the OS argument-list limit on both the Linux pytest and
macOS jobs. Pass the file list via llvm-profdata's -f option (a
newline-separated list file) instead of argv.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P53N6mt8mDNedgUDBcYqfW
Resolves a conflict in .github/workflows/pytest.yml between this
branch's new "Install LLVM coverage toolchain" step and main's
routine version-pin bump of rui314/setup-mold: keeps both.
The macOS job's "Uninstalling Homebrew" step removes gpg early on,
same as it always has. The pre-existing "Upload test results to
Codecov" step already sets skip_validation: true to work around
this, but the two new coverage-upload steps this PR added did not,
so codecov-action's own dependency check failed before it could
upload anything. Match the existing step's skip_validation: true.
…d coverage

Adds an opt-in GRASS_SANCOV_COVERAGE / WITH_SANCOV_COVERAGE path (Autotools
and CMake) that instruments GRASS with -fsanitize-coverage= on GCC
(trace-pc) or Clang (trace-pc-guard) and produces coverage dumps via a
small custom runtime (utils/sancov_runtime.c) and symbolizer
(utils/sancov_symbolize.py), alongside the existing clang source-based
mechanism, for comparison. See doc/development/sancov_coverage_experiment.md
for the investigation, measurements, and the resulting recommendation.

Most of this was worked out through iterative, empirical debugging of real
link and toolchain failures (documented inline): GCC's -fsanitize-coverage=
does not implement trace-pc-guard, GNU ld's left-to-right link order breaks
a naive LDFLAGS-based runtime link, CMake's install(EXPORT ...) rejects a
non-exported link dependency, and so on. Substantial parts of this change,
including the debugging process itself, were produced with AI assistance.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFijpm8JqSEJfJ9BhJFeen
Three matrix cells (llvm-source, sancov-gcc, sancov-clang) now share
python-version 3.13, so names keyed only on matrix.python-version collide;
actions/upload-artifact hard-fails on a duplicate name within one run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFijpm8JqSEJfJ9BhJFeen
…self/exe

dladdr() reports the main executable's path exactly as it was exec()'d;
for a program found via a PATH search (GRASS's db drivers, spawned by
bare name) that is not a path addr2line can resolve later from an
arbitrary working directory. Confirmed on a real db.connect coverage
dump. A bare name can only be the main executable (every shared library
path comes from the dynamic linker's own absolute bookkeeping), and
/proc/self/exe always resolves to its real absolute path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFijpm8JqSEJfJ9BhJFeen
Mixing a base os/python-version/coverage cross product with include
entries that only partially override it (just coverage, here) is a known
GitHub Actions matrix pitfall: an include entry that does not exactly
match an existing combination on every key it names is added as a new,
separate combination instead of overriding the intended one. Confirmed on
a real workflow_dispatch run of the previous version: the "pytest" job's
instance list came back completely empty, so the whole matrix silently
built and tested nothing. Switched to an include-only matrix where every
entry is a complete, independent combination.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFijpm8JqSEJfJ9BhJFeen
Three matrix cells (llvm-source, sancov-gcc, sancov-clang) share
python-version 3.13; a concurrency group keyed only on that collides
across them, and cancel-in-progress: true cancels whichever job shares a
group with one that just started. Confirmed on a real workflow_dispatch
run: two of the three 3.13 cells were cancelled within a second of
starting, leaving only one to actually run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFijpm8JqSEJfJ9BhJFeen
Covers the runtime/symbolizer design, the real build-system bugs found
and fixed along the way (GCC lacking trace-pc-guard, GNU ld link order,
CMake's install(EXPORT) requirement, a dladdr() bare-path bug), and the
macro-attribution demonstration with real generated output. Sections 2
and 4 (CI timing numbers and bottom-line recommendation) are placeholders
pending real GitHub Actions runs of this branch, in progress.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFijpm8JqSEJfJ9BhJFeen
…age cells independent

Confirmed on a real workflow_dispatch run: GRASS's own $(MATHLIB) --
normally "-lm" -- comes out empty on this toolchain, because glibc merged
libm into libc (since 2.34; Ubuntu 24.04 ships 2.39) so configure's
"does atan link without -lm" probe already succeeds. GNU ld tolerates
this by transitively finding cos/sin/floor in libc via an already-linked
GRASS shared library; this workflow's mold (via rui314/setup-mold) does
not, so ps.map's link failed with "undefined symbol: cos/sin/floor"
under GRASS_SANCOV_CC=clang specifically (its wrapper script is the only
thing free to add the missing library, so it now does).

Also stopped that one cell's failure from cancelling the other three
matrix cells mid-run (fail-fast: true, the default) -- they are
independent comparisons, not four attempts at the same build, so losing
their timing data to an unrelated failure defeats the point of running
them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFijpm8JqSEJfJ9BhJFeen
Confirmed on a real CI run: a test that builds a GRASS addon into a
temporary directory via g.extension has that directory cleaned up once
the test finishes, well before this post-test symbolization step runs,
so the addon binary recorded as a module is gone by the time addr2line
would need it. Skip it with a warning instead of letting one vanished
module abort the whole run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFijpm8JqSEJfJ9BhJFeen
Fills in the build-time/test-time comparison (Section 2) and bottom-line
recommendation (Section 4) with real GitHub Actions run data, and
documents the five real, CI-only bugs found and fixed getting there
(Section 1.5): a matrix include pitfall producing zero job instances, a
concurrency-group collision cancelling sibling cells, a missing -lm under
mold specific to the clang+sancov link, a dladdr() bare-path bug, and a
vanished-module bug in the symbolizer.

The headline result: GCC's trace-pc mechanism (the only edge-level
-fsanitize-coverage= sub-option GCC implements) adds +232% test time
under cmake.yml's unrestricted OpenMP threading (2h02m vs 36m49s
baseline), consistent with its mutex-guarded per-edge-execution dedup
needed because GCC provides no per-edge guard word the way clang's
trace-pc-guard does. Neither compiler's SanitizerCoverage path offers a
granularity advantage over the existing GRASS_LLVM_COVERAGE mechanism,
which correctly distinguishes macro expansions at block level that
SanitizerCoverage collapses (Section 3.1). Bottom line: not recommended
as an addition to GRASS's coverage tooling on either compiler.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFijpm8JqSEJfJ9BhJFeen
Comment thread CMakeLists.txt Outdated
# export set too, which it has no reason to be part of (confirmed
# empirically: CMake's install(EXPORT ...) step refuses to generate
# otherwise). An IMPORTED library is exempt from that requirement.
set(SANCOV_RUNTIME_LIB "${CMAKE_BINARY_DIR}/libsancovrt${CMAKE_SHARED_LIBRARY_SUFFIX}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[pre-commit] reported by reviewdog 🐶

Suggested change
set(SANCOV_RUNTIME_LIB "${CMAKE_BINARY_DIR}/libsancovrt${CMAKE_SHARED_LIBRARY_SUFFIX}")
set(SANCOV_RUNTIME_LIB
"${CMAKE_BINARY_DIR}/libsancovrt${CMAKE_SHARED_LIBRARY_SUFFIX}"
)

Comment thread CMakeLists.txt Outdated
Comment on lines +300 to +302
COMMAND ${CMAKE_C_COMPILER} -O2 -fPIC -shared -pthread
-o ${SANCOV_RUNTIME_LIB}
${CMAKE_SOURCE_DIR}/utils/sancov_runtime.c -l${CMAKE_DL_LIBS}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[pre-commit] reported by reviewdog 🐶

Suggested change
COMMAND ${CMAKE_C_COMPILER} -O2 -fPIC -shared -pthread
-o ${SANCOV_RUNTIME_LIB}
${CMAKE_SOURCE_DIR}/utils/sancov_runtime.c -l${CMAKE_DL_LIBS}
COMMAND
${CMAKE_C_COMPILER} -O2 -fPIC -shared -pthread -o
${SANCOV_RUNTIME_LIB} ${CMAKE_SOURCE_DIR}/utils/sancov_runtime.c
-l${CMAKE_DL_LIBS}

Comment thread CMakeLists.txt Outdated
add_custom_target(sancovrt_build DEPENDS ${SANCOV_RUNTIME_LIB})
add_library(sancovrt SHARED IMPORTED GLOBAL)
set_target_properties(
sancovrt PROPERTIES IMPORTED_LOCATION ${SANCOV_RUNTIME_LIB}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[pre-commit] reported by reviewdog 🐶

Suggested change
sancovrt PROPERTIES IMPORTED_LOCATION ${SANCOV_RUNTIME_LIB}
sancovrt
PROPERTIES IMPORTED_LOCATION ${SANCOV_RUNTIME_LIB}

Comment thread utils/sancov_runtime.c Outdated
* So a bare (no '/') name can only be the main executable, and
* /proc/self/exe always resolves to its real absolute path. */
if (!strchr(path, '/')) {
ssize_t n = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[pre-commit] reported by reviewdog 🐶

Suggested change
ssize_t n = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
ssize_t n =
readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);

ruff, clang-format (v22.1.5, pinned by pre-commit) and gersemi flagged
utils/sancov_symbolize.py, utils/sancov_runtime.c and CMakeLists.txt;
apply their formatting. actionlint's shellcheck pass flagged the
unquoted, intentionally-optional GRASS_SANCOV_CMAKE_ARG expansion in
cmake.yml (SC2086); silence it with a comment explaining why the
variable stays unquoted. markdownlint flagged missing fenced-code-block
languages and non-compact table separator rows in the experiment
report; fix both.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFijpm8JqSEJfJ9BhJFeen
cmake.yml's gcc sancov job failed a gunittest test that builds a real
addon via g.extension, with "cannot find -lsancovrt". Every exported
GRASS::* library target records sancovrt (deliberately an IMPORTED,
non-exported target -- install(EXPORT ...) refuses a real dependency
that isn't itself exported) as a bare, unresolved name in its
INTERFACE_LINK_LIBRARIES, and the addon's own separate CMake project,
built via the installed etc/cmake/build_addon.cmake package, has
nothing by that name to resolve it to.

Define the same sancovrt IMPORTED target in GRASSConfig.cmake.in,
loaded by every find_package(GRASS ...) including addon builds, so it
points at the runtime .so this repository's own build already installs
to GRASS_INSTALL_LIBDIR. Guarded by the build's own
WITH_SANCOV_COVERAGE setting, so a non-coverage install defines
nothing extra.

Verified locally end to end: a WITH_SANCOV_COVERAGE=ON CMake build,
installed, then `g.extension extension=r.gdd operation=add` -- the
exact command cmake.yml's failing test runs -- now compiles and
installs r.gdd successfully, links it against the installed
libsancovrt.so, and running it produces a real coverage dump. Updates
the experiment report's Section 1.5 accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFijpm8JqSEJfJ9BhJFeen
Comment thread utils/sancov_symbolize.py Outdated
continue
resolved = addr2line_batch(module, sorted(offsets), args.addr2line)
for loc in resolved.values():
if loc in ("??:0", "??:?"):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ruff] reported by reviewdog 🐶

Suggested change
if loc in ("??:0", "??:?"):
if loc in {"??:0", "??:?"}:

ruff's preview-mode PLR6201 rule (enabled by the CI lint job's
--preview flag, not caught by a plain local `ruff check`) flags a
tuple used only for `in` membership testing; a set is the right type
for that and avoids a linear scan.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFijpm8JqSEJfJ9BhJFeen

echoix commented Sep 3, 2026

Copy link
Copy Markdown
Owner Author

ubuntu (24.04, db display doc docker general gui imagery lib misc ps python raster raster3d scripts testsuite utils vector visualization) failed on commit bd1f9b3 with a floating-point tolerance mismatch in vector/v.surf.rst/testsuite/test_vsurfrst.py::test_more_threads (r.info -r max/min differing from the reference by ~0.016-0.024 against an expected 1e-8 tolerance).

This is not this PR's failure: ubuntu.yml isn't touched by any commit here, this job doesn't set GRASS_LLVM_COVERAGE or GRASS_SANCOV_COVERAGE, and nothing in this branch's diff touches v.surf.rst, RST interpolation, or OpenMP threading. The same job passed cleanly on this PR's earlier commit (1e06b9a). test_more_threads compares single- vs multi-threaded RST interpolation output at a tight epsilon, which is a plausible source of thread-count-dependent floating-point non-determinism (summation order) rather than a real regression — a known flake shape, not something to root-cause in this experimental branch.

I don't have a fix to port (no related fix PR is open that I'm aware of). I'll re-run the failed job once the rest of that workflow run finishes (a sibling matrix job is still in progress, which is currently blocking a re-run) to confirm it's a flake, and report back.


Generated by Claude Code

echoix commented Sep 3, 2026

Copy link
Copy Markdown
Owner Author

build-cmake (gcc) on commit bd1f9b3: the -lsancovrt link error is fixed (confirmed — g.extension's addon build, test_addons_download.py, is no longer in the failure list, and the symbolizer produced a real 1575-line summary). But the gunittest run (test_thorough.sh) now fails on 8 test files, all with Timeout >300.0s: test_benchmark.py, test_pygrass_modules_doctests.py, test_r_neighbors.py, test_r_resamp_filter.py, test_rtexture.py, test_rtexture_parallel.py, test_r_univar_parallel.py, test_r_viewshed.py.

This isn't a new bug — it's the same overhead the report's Section 2.2 already measured and quantified as the headline finding of this experiment: gcc's trace-pc has no per-edge guard, so every edge execution takes a locked hash-set probe, and under cmake.yml's gunittest run (which doesn't restrict OMP_NUM_THREADS, unlike pytest.yml's parallel step), that compounds into a measured +232% total test time (36m49s → 2h02m05s) on an earlier, otherwise-identical run. These 8 timeouts are that same slowdown surfacing as individual 300s cutoffs on the naturally-slower tests (raster tests with OpenMP-parallel regions: r.neighbors, r.resamp.filter, r.texture, r.univar, r.viewshed).

I'm not fixing this by raising the timeout: the 300s cutoff lives in .gunittest.cfg, a repo-wide config shared by every CI workflow (ubuntu.yml, macos.yml, osgeo4w, this cmake.yml job's own none and other matrix cells), not something scoped to WITH_SANCOV_COVERAGE. Loosening it globally to accommodate one experimental, opt-in, already-too-slow mechanism would mask real future timeout regressions everywhere else. Skipping or disabling the affected tests for this configuration isn't the right move either. And the report's own bottom line (Section 4) already recommends against pursuing gcc's trace-pc further for exactly this reason — "fixing" this red check would mean either contradicting that finding or reaching for the very workaround (loosening a shared safety timeout) that finding is arguing against.

Given this branch is explicitly exploratory (see the report's opening paragraph) and not meant to replace GRASS_LLVM_COVERAGE, I'm leaving build-cmake (gcc) red with this explanation rather than force it green.


Generated by Claude Code

@echoix echoix closed this Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants