coverage.reporter¶
CoverageReporter: merge, load, and render coverage from collected .gcda files.
This module replaces the old Pipeline class. It does not fetch
.gcda files (that is handled by otto test --cov via
GcdaFetcher). Instead it takes
directories of already-collected .gcda files, merges them with
lcov, loads the results into a CoverageStore, and renders
an HTML report.
Coverage tiers are user-defined. The reporter accepts an ordered list
of (tier_name, info_path) pairs where the order is the precedence
order — first entry has highest precedence in the renderer’s
winner-take-all row coloring and column layout. A None info_path
marks the implicit system tier produced by merging the supplied
.gcda directories with lcov; only the tier named
TIER_SYSTEM is allowed to omit a path.
Typical usage from the otto cov CLI command:
reporter = CoverageReporter(
gcda_dirs=[run1 / "cov" / "host1", run1 / "cov" / "host2"],
source_root=Path("/home/me/myproduct"),
output_dir=Path("./cov_report"),
tiers=[("unit", Path("u.info")), ("system", None)],
)
store = await reporter.run()
- otto.coverage.reporter.TierSpec¶
An ordered coverage tier specification:
(tier_name, info_path_or_None).A
Nonepath is only valid for the implicitsystemtier, which is produced by merging the supplied.gcdadirectories with lcov.
- class otto.coverage.reporter.CollectionInputs(repo_root: ~pathlib.Path | None = None, tier_configs: list[TierConfig] = <factory>, capture_paths: list[~pathlib.Path] = <factory>, extra_markers: list[str] = <factory>)¶
Bases:
objectThe collection-model inputs to
CoverageReporter(Task 10).All fields are optional; an all-default instance (the constructor default) selects the legacy, purely
.gcda-driven behavior — every collection-model step becomes a no-op.repo_root: SUT git repo root. Enables e2e captures + the manual store; also the pin-guard reference and the exclusion source.tier_configs: Declared coverage tiers (precedence order). Seedstier_order/tier_colorsand drives unit-harvest.capture_paths:capture.jsonfiles (one per board) to fold in under their own tier, subject to the HEAD pin guard.extra_markers: Extra source exclusion markers (spec §8).
- tier_configs : list[TierConfig]¶
- otto.coverage.reporter.read_cov_source_root(cov_dirs: list[Path]) Path¶
Read the source root from coverage metadata written by
otto test --cov.Looks for
.otto_cov_meta.jsonin each cov_dir. Returns thesut_dirfrom the first metadata file found.- Parameters:¶
cov_dirs – Coverage directories (each containing host subdirs and a
.otto_cov_meta.jsonsidecar).- Returns:¶
The source root path.
- Raises:¶
FileNotFoundError – If no metadata file is found in any cov dir.
- otto.coverage.reporter.read_cov_source_roots(cov_dirs: list[Path]) dict[str, Path]¶
Read per-host source roots from coverage metadata.
Returns a mapping of host directory name → source root
Path. If no source_roots info is present in the metadata, or if no metadata file is found, returns an empty dict.
- otto.coverage.reporter.read_cov_toolchains(cov_dirs: list[Path]) dict[str, Toolchain]¶
Read per-host toolchain info from coverage metadata.
Returns a mapping of host directory name →
Toolchain. If no toolchain info is present in the metadata, returns an empty dict.
- otto.coverage.reporter.discover_gcda_dirs(cov_dirs: list[Path]) list[Path]¶
Collect all per-host .gcda directories from one or more cov/ directories.
Each cov_dir is expected to contain per-host subdirectories:
cov_dir/ host_id_1/ host_id_2/- Returns:¶
List of per-host directories containing
.gcdafiles.
-
class otto.coverage.reporter.CoverageReporter(gcda_dirs: list[Path], source_root: Path, output_dir: Path, project_name: str =
'Coverage Report', toolchains: dict[str, Toolchain] | None =None, tiers: list[tuple[str, Path | None]] | None =None, source_roots: dict[str, Path] | None =None, *, collection: CollectionInputs | None =None, prefix: Path | None =None)¶ Bases:
objectMerge, load, and render coverage from pre-collected .gcda files.
- Parameters:¶
gcda_dirs – Per-host directories containing
.gcdafiles.source_root – Local source tree root (used for path mapping and .gcno discovery). Acts as the fallback for hosts that have no entry in source_roots.
output_dir – Directory for the HTML report output.
project_name – Title shown in the HTML report.
toolchains – Per-host toolchains keyed by host directory name. When a host has no entry the pipeline falls back to gcno-based auto-discovery, then to system defaults.
tiers – Ordered list of
(tier_name, info_path | None)pairs. Order is precedence order — first entry wins in the renderer’s winner-take-all row coloring.Nonepaths are only valid for thesystemtier and indicate the implicit lcov-merged output ofgcda_dirs. Defaults to[("system", None)]when omitted.source_roots – Per-host source roots keyed by gcda-dir name (i.e. the host id). When provided, each host’s
.gcdafiles are captured against its own.gcnodirectory instead of the shared source_root fallback. Hosts with no entry fall back to source_root.collection – The collection-model inputs (e2e captures, unit harvest, manual store, exclusion markers). Omitted / an all-default
CollectionInputsselects the legacy, purely.gcda-driven behavior — every new step is a no-op.prefix – Strip this leading directory from file paths shown in the report (display only, like
genhtml --prefix). Files outside the prefix display unchanged; links and store keys always use the full path.
- async run() CoverageStore¶
Execute the coverage merge, load, and render pipeline.
- Returns:¶
A populated
CoverageStorewith all coverage data.
-
async otto.coverage.reporter.run_coverage_report(cov_dirs: list[Path], report_dir: Path, project_name: str =
'Coverage Report', tier_specs: list[tuple[str, Path | None]] | None =None, *, repo_root: Path | None =None, tier_configs: list[TierConfig] | None =None, extra_markers: list[str] | None =None, prefix: Path | None =None) CoverageStore | None¶ Render an HTML coverage report from one or more cov/ directories.
Shared entry point used by both
otto cov report(multiple cov dirs, one per run) andotto test --cov-report(single cov dir produced by the test run just completed).Two modes, one precedence rule. When neither repo_root nor tier_configs is given the function runs the legacy path unchanged: it reads the source root + per-host toolchains from
.otto_cov_meta.json, discovers per-host gcda subdirs, and merges them with lcov — returningNone(as before) when there is no metadata sidecar or no gcda subdirs.When repo_root or tier_configs is given the function runs the collection-model path, which additionally consumes, in order:
e2e captures — board dirs holding a
capture.jsonload under a strict HEAD pin guard (CoverageDataMismatchErroron mismatch); board dirs without one keep the legacy gcda-merge.unit harvest — each
kind == "unit"tier’sharvest_dirs.manual store — every committed capture under repo_root’s
.otto/coverage/manual/(with report-time validity states).
Crucially the legacy “no metadata → return
None” early-outs do not fire in this mode: an empty cov_dirs plus a non-empty manual store still yields a report. The legacy gcda-merge only targets the conventionalsystemtier via tier_specs (default[("system", None)]); explicit--tierspecs, being a git-less escape hatch, never reach this mode (the CLI routes them through the legacy path).- Returns:¶
The populated
CoverageStore, orNonewhen the legacy path found no coverage data.
Typed coverage-pipeline errors carrying user-actionable messages.
- exception otto.coverage.errors.CoverageToolVersionError(detail: str)¶
Bases:
RuntimeErrorThe gcov tool used for capture cannot read this build’s coverage format.
geninfo probes the gcov tool and refuses when the
.gcda/.gcnowere written by a different compiler family or version (“Incompatible GCC/GCOV version”). The classic trigger: the product was built withclang --coverage— clang stamps the GCC 4.8-era file format onlyllvm-cov gcovstill reads — but capture ran GNU gcov. A cross-GCC build captured with the system gcov fails the same way.
- exception otto.coverage.errors.CoverageDataMismatchError(detail: str)¶
Bases:
RuntimeErrorFetched
.gcdadata does not match the current build’s.gcnonotes.gcov embeds a build stamp in both files; a (partial) rebuild of the product between
otto test --covandotto cov reportchanges the stamps, and gcov refuses the pairing (stamp mismatch with notes file).