Commit f94ceca
Add cross-platform Coverage CI (#842)
* Add cross-platform Coverage CI
Adds a coverage workflow that mirrors the regular ctest invocation
across the CI matrix and surfaces a per-PR coverage delta. Coverage
is advisory only and never gates a PR.
Build wiring (CMakeLists.txt, cmake/run_coverage.cmake)
- New SNMALLOC_COVERAGE option (Clang/AppleClang only) adding
-fprofile-instr-generate -fcoverage-mapping to compile and link.
- Per-test LLVM_PROFILE_FILE so each test's profraws live in their
own subdir and can be invalidated independently by the cached
runner.
- Global SNMALLOC_TEST_BINARIES property collects every test target
so the new `coverage` custom target can pass them all to llvm-cov
without globbing build outputs.
- run_coverage.cmake drives ctest under coverage with sha256-keyed
per-test caching, then merges per-test profdata and exports a
unified coverage.json. Hashes are only cached when ctest reports
overall success, so a timed-out or crashed test does not get its
partial profraw frozen as the canonical answer for that binary.
Merge tool (.github/scripts/merge_coverage.py + tests)
- Per-line set-union merger of llvm-cov JSON exports across
platforms: merged.executable = ⋃ executable lines, merged.covered
= ⋃ covered lines, with covered ⊆ executable asserted at merge.
- Emits per-platform breakdown alongside the union for the comment
renderer.
- 16 pytest cases cover union semantics, invariant enforcement,
rendering, and determinism.
Reusable workflows (reusable-cmake-build.yml, reusable-vm-build.yml)
- New coverage-mode (off | tests | tests+selfhost) and
coverage-artifact-name inputs; artifact name validated against
^[A-Za-z0-9_-]+$.
- When coverage-mode != off: forces Debug, sets SNMALLOC_COVERAGE=ON,
invokes the `coverage` target instead of plain ctest.
- A single self-host step (always sets LLVM_PROFILE_FILE; harmless
when not a coverage build) plus a separate post-step that exports
selfhost.json only under coverage-mode == 'tests+selfhost'.
Coverage workflow (.github/workflows/coverage.yml)
- pull_request + nightly schedule + workflow_dispatch.
- Matrix: ubuntu-24.04, macos-14 (brew llvm@19, absolute tool
paths), linux-self-host-shim, linux-self-host-shim-checks (adds
SNMALLOC_MEMCPY_BOUNDS=ON + SNMALLOC_CHECK_LOADS=ON, runs
tests+selfhost), freebsd-14 (absolute /usr/local/llvm19/bin
paths), netbsd-10 (pkgsrc clang/clang++), and a Windows clang-cl
pre-gate build that exercises configure-time code paths without
producing coverage.
- Merge job: deterministic find | sort over downloaded artifacts,
produces a coverage-merged artifact consumed by the commenter.
Comment workflow (.github/workflows/coverage-comment.yml)
- workflow_run trigger keeps write permissions out of the build job
(read-all token in coverage.yml; pull-requests:write +
issues:write here, no contents).
- Validates artifact size/structure and the snmalloc-coverage-bot
marker before posting.
- PR path: dual-marker find-or-create with 3-attempt 409/403
backoff and pagination via github.paginate().
- Tracking-issue path (vars.COVERAGE_TRACKING_ISSUE) for nightly
runs and pushes to the default branch.
Marker-string coupling between the Python merger and the JS
commenter is documented at both call sites; maintainers must
update them in lockstep.
* coverage CI: fix VM-build expression parse error
GitHub Actions expressions only accept single-quoted strings, but
the ctest invocation contains single quotes (the -E regex). Move
the off-vs-coverage selection from a expression into a
shell inside the script body.
* coverage CI: fix four CI failures
- Validation regex: allow `.` in coverage-artifact-name so matrix
labels like `ubuntu-24.04` pass.
- FreeBSD: llvm19 port installs versioned binaries (clang19,
clang++19, llvm-cov19, llvm-profdata19) directly under
/usr/local/bin/, not /usr/local/llvm19/bin/. Update absolute
paths.
- macOS: brew clang on the Apple SDK warns -Wundef on the SDK's
`__STDC_VERSION__` check because brew clang doesn't treat the
SDK as system headers by default. Export SDKROOT in the
dependencies step so brew clang picks up the SDK as a sysroot
(system include path), suppressing -Wundef in those headers.
- Self-host coverage export: ls with two glob arguments returns
nonzero when one glob has no match (e.g. .dylib on Linux), and
pipefail propagated the failure. Use bash nullglob + array
instead.
Co-authored-by: Copilot <copilot@github.com>
* coverage CI: add NetBSD back with compiler-rt
pkgsrc's `llvm`/`clang` packages do not bundle libclang_rt.profile.a;
the profile runtime lives in the separate `compiler-rt` package.
Add it to pkg_add so -fprofile-instr-generate links cleanly. If
this leg goes red because pkgsrc cannot resolve `compiler-rt`, the
fallback is to fetch it from the NetBSD binary package mirror.
Co-authored-by: Copilot <copilot@github.com>
* coverage CI: drop NetBSD (pkgsrc compiler-rt-19 broken)
pkgsrc's compiler-rt-19 ships a profile runtime in which
__llvm_profile_raw_version is declared hidden but not defined.
This makes every -fprofile-instr-generate shared library
unlinkable — including libsnmallocshim.so during the regular
build, not just the self-host step:
R_X86_64_PC32 against undefined hidden symbol
`__llvm_profile_raw_version` can not be used when making a
shared object
There is no fix on our side. The leg can be reinstated once
pkgsrc ships a fixed compiler-rt, or by adding an in-VM
compiler-rt build from llvm-project source (rejected for now —
~10 min per CI run for marginal NetBSD-pal coverage).
* coverage CI: drop -format=json from selfhost llvm-cov export
llvm-cov export emits JSON by default; the -format flag only
accepts text|lcov, so -format=json failed CLI parsing.
Co-authored-by: Copilot <copilot@github.com>
* coverage CI: promote Windows clang-cl from pre-gate to full leg
Drop build-only and contribute Windows coverage to the merged
report. Exercises the Windows PAL surface, which no other leg
in the matrix touches.
windows-2022 runners ship LLVM (clang-cl, llvm-profdata, llvm-cov
under C:\Program Files\LLVM\bin, on PATH) and ninja preinstalled,
so no \`dependencies:\` step is needed.
* coverage CI: review-pass fixes
- Windows: pass absolute -DLLVM_COV / -DLLVM_PROFDATA paths
instead of relying on PATH ordering inside the runner image.
- Self-host step: tighten the if condition so it doesn't run a
pointless self-host build when self-host=true is requested
alongside an in-progress coverage build (the
coverage-mode=='tests+selfhost' branch already covers that).
Add a comment explaining the coupling with the Export step.
- test_all_platforms_empty: assert covered ⊆ executable invariant
for consistency with every other test in the suite.
Co-authored-by: Copilot <copilot@github.com>
* Coverage: trim Linux matrix to one self-host leg; fix multi-shim export
The coverage matrix had two redundant Linux legs:
- plain ubuntu-24.04 ctest, fully covered by macOS/FreeBSD/Windows ctest
- linux-self-host-shim with no extra mitigations
The remaining linux-self-host-shim-checks leg mirrors main.yml's
self-host job (SNMALLOC_MEMCPY_BOUNDS=ON + SNMALLOC_CHECK_LOADS=ON)
and is the only leg that exercises the bounds-checked memcpy and
load-check paths.
Also fix the self-host export: llvm-cov export was only given the
first shim variant as -object, silently dropping coverage mappings
for libsnmallocshim-checks.so and libsnmallocshim-checks-memcpy-only.so
even though the for-loop ran ninja under each. Pass every built
shim as a separate -object.
* Coverage: drop spaced absolute LLVM paths on Windows; rely on PATH
The Windows job passed -DLLVM_PROFDATA=C:/Program Files/LLVM/bin/llvm-profdata.exe
via a YAML folded scalar. The unquoted space survived YAML but split
at shell expansion of ${{ inputs.extra-cmake-flags }} in the
reusable workflow, so CMake received -DLLVM_PROFDATA=C:/Program plus
a phantom positional. Configure passed (find_program does not
override a preset cache value, even a bogus one), but the coverage
target later failed in execute_process with:
llvm-profdata merge failed (exit no such file or directory)
The windows-2022 runner image installs LLVM with C:\Program Files\LLVM\bin
already on PATH, so find_program(NAMES ... llvm-profdata) resolves
correctly without any override.
* Coverage: replace coverage-mode enum with coverage boolean
The 'coverage-mode' input had three values (off / tests / tests+selfhost)
that conflated two orthogonal concerns:
1. Is this a coverage build at all?
2. Should self-host run?
(2) is already expressed by the existing 'self-host' boolean input,
so 'tests+selfhost' was redundant — it just meant 'coverage && self-host'.
Replace with a 'coverage' boolean. Self-host now runs whenever the
caller asks for it, regardless of coverage. The export step's gate
becomes 'coverage && self-host'. The validate step drops its enum
case, keeping the artifact-name regex check.
No behavioural change for any existing call site:
coverage-mode: 'off' -> coverage: false (default)
coverage-mode: 'tests' -> coverage: true, self-host: false
coverage-mode: 'tests+selfhost' -> coverage: true, self-host: true
---------
Co-authored-by: Copilot <copilot@github.com>1 parent 25b0d60 commit f94ceca
8 files changed
Lines changed: 1540 additions & 15 deletions
File tree
- .github
- scripts
- workflows
- cmake
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
0 commit comments