Coverage — the collection pipeline¶
The problem: embedded and cross-compiled products execute where no coverage
tooling runs. gcov counters (.gcda) accumulate on the target — in memory or
on an on-device filesystem — while the compile-time graph (.gcno) and
sources live in the build tree on the runner. Neither side alone can make a
report; this pipeline marries them.
The stages (packages otto.coverage.fetcher → merge → capture →
renderer → reporter):
Fetch — pull
.gcdadata from each covered host after the run. Fetchers are per-family: file transfer for Unix hosts, console extraction for embedded targets. Which hosts are covered is repo-declared — the[coverage].hostsregex insettings.toml— never inferred, so hop hosts and uninstrumented beds can’t sneak into a report.Merge — match counters to the build tree’s
.gcnograph and remap embedded/sysroot paths back to source paths, merging counters across hosts and runs (lcov semantics).Capture — freeze the merged result into a per-board
capture.json: parsed hits in committed-code coordinates, anchored to the repo’sHEAD(base_commit) and per-file blob SHAs.Render / report —
otto cov reportassembles every tier — e2e captures, a fresh unit-tier harvest, the committed manual store — into an HTML report plus summary tiers.
The merge stage’s core invariant is build/counter identity: .gcda files
are only meaningful against the exact .gcno graph the binary was compiled
with. That pairing happens once, at collection — the capture holds
parsed hits, so the report step never touches the build tree again and a
later rebuild cannot invalidate it (a capture’s own guard is its
base_commit, which must match HEAD at report time). When the raw pairing disagrees — a
stale or partially rebuilt product tree at collection time, or a
pre-capture run directory re-merged via the legacy fallback — the pipeline
stops with a diagnostic error that names the mismatch and the rebuild that
fixes it, rather than a gcov stack trace or a silently wrong report. That
fail-with-instructions posture is a house rule (Design principles).
How this section is organized¶
The pipeline above is one axis; the four pages below are the other.
Coverage types is the vocabulary every stage speaks — tiers, stat types, line
states, thresholds — and the store schema that carries them. How coverage data is merged
is what otto cov report actually does with the pieces: the fold order,
run accumulation, and the rules that keep one piece of evidence from being
counted twice. Manual coverage tracking is the one tier whose data is committed, and
therefore the one that has to be re-proved against a moving tree on every
report. The renderer is the last mile — an assembled store to a
self-contained report directory.
What is unique about cov¶
otto cov report runs after the fact, over directories otto test --cov
or otto cov get already wrote: it still loads the lab — per-host toolchain
resolution (gcov, lcov) comes from host configuration, with the .gcno
header’s gcov version stamp as the fallback (a clang stamp routes counters
through llvm-cov gcov) — but it creates no output directory of its
own and runs no gate: reporting on yesterday’s run must never be
blocked by today’s reservations (The command lifecycle). Its siblings do touch the
lab: otto cov get fetches counters — into the standard per-invocation
output directory, or --output — and otto cov clean zeroes them on the
remotes.
Where the code lives¶
otto.coverage.fetcher— pulls.gcdaoff covered hosts (file transfer on Unix, console extraction on embedded)otto.coverage.merge— pairs counters to the.gcnobuild graph and merges hosts and runsotto.coverage.capture— freezes a merge into a per-boardcapture.json, anchored tobase_commitotto.coverage.tiers— resolves the declarative[coverage.tiers]table into precedence-orderedTierConfigvaluesotto.coverage.store— versioned store models (RunRecord,LineRecord,Thresholds,STAT_TYPES) andCoverageStore’ssave/load, including theSTORE_FORMAT_VERSIONexact-match loaderotto.coverage.report_config— resolves[coverage.report]’s raw settings dict into renderThresholdsat report timeotto.coverage.renderer— turns an assembled store into a reportotto.coverage.renderer.spa_data— pure-Python emitter for the covapp data chunks (cov_data/index.js+ per-file chunks)otto.coverage.renderer.spa_renderer—SpaRenderer: copies the built covapp bundle, then callsspa_data.emit_chunks; the reporter’s only rendererweb/src/covapp/— the covapp SPA’s TypeScript source (built bymake webintootto._webassets.COVAPP, wheel-embedded)otto.coverage.reporter—otto cov report’s store assembly: tiers, the base_commit guard, and the--tier NAME=PATHescape hatchotto.coverage.validity— the report-time valid/stale/aging pass over manual capturesotto.coverage.anchor—AnchorResolver: the per-capture tree-diff / blob-fallback resolution the validity pass consumes