coverage.store.model

Coverage data model.

This is the core of the coverage module — everything else feeds into or reads from these types. The model is deliberately independent of gcov, lcov, or coverage.py internals.

Coverage tiers are names — free-form strings like "system", "unit", "manual", or anything a user wires up on the command line. LineHits and BranchHits store per-tier counts in dicts keyed by tier name, so adding a new tier requires no changes to the model. A CoverageStore also carries a tier_order list that defines the precedence of tiers for presentation purposes (first = highest precedence).

otto.coverage.store.model.TIER_SYSTEM = 'system'

Conventional tier name used by the merged .gcda pipeline.

Any string is a valid tier name; this constant spares callers a string literal when they mean the canonical system-coverage tier.

otto.coverage.store.model.STORE_FORMAT_VERSION = 3

store.json schema version, bumped on breaking on-disk changes.

Version 2 is the first version to carry an explicit "format" key — introduced alongside a run-table field/key rename that shortened every per-record and per-line name (JSON keys and the record class/attribute names alike) by dropping a now-redundant qualifying word. Version 3 renames the run table’s git-anchor field from pin to base_commit, matching the same rename on the capture artifact it is sourced from. There is no migration shim: a file that does not declare this exact version fails loud in CoverageStore.load() with a message telling the caller to regenerate it, rather than silently mis-reading renamed/reshaped keys under old names.

class otto.coverage.store.model.LineHits(counts: dict[str, int] = <factory>)

Bases: object

Per-tier hit counts for a single line.

Counts are a dict keyed by tier name. Absent keys mean zero hits for that tier.

counts : dict[str, int]
add(tier: str, n: int) None

Add n hits to tier.

for_tier(tier: str) int

Hit count for tier (0 if the tier has no entry).

total() int

Sum of hits across all tiers.

is_hit(tier: str | None = None) bool

Return True if hit in tier (or in any tier when tier is None).

merge(other: LineHits) None

Additive merge — sum counts for every tier present in other.

to_dict() dict[str, int]

Return a plain dict copy of per-tier hit counts.

class otto.coverage.store.model.BranchHits(block: int, branch: int, hits: ~otto.coverage.store.model.LineHits = <factory>, reachable: dict[str, bool] = <factory>)

Bases: object

Per-tier hit counts and reachability for one branch.

Reachability is tri-state per tier:

  • key present, value True → observed as reachable at least once

  • key present, value False → observed only as unreachable (lcov -)

  • key absent → no data yet for that tier

Once a tier sees the branch as reachable it stays reachable for that tier — merges only flip FalseTrue, never the other way.

block : int
branch : int
hits : LineHits
reachable : dict[str, bool]
property branch_id : tuple[int, int]

(block, branch) tuple that uniquely identifies this branch within a line.

set_reachable(tier: str, reachable: bool) None

Record a reachability observation for tier.

If the tier was already marked reachable, keep it reachable. Otherwise adopt the new value.

is_reachable(tier: str | None = None) bool | None

Reachability for tier, or combined across all tiers when None.

Returns None if no data has been recorded for the requested tier (or for any tier when tier is None).

is_hit_for(tier: str | None = None) bool

Return True if this branch was taken at least once in tier.

merge(other: BranchHits) None

Merge other into this branch, accumulating hits and updating reachability.

to_dict() dict[str, Any]

Return a JSON-serialisable dict representation of this branch record.

class otto.coverage.store.model.RunRecord(id: int, tier: str, label: str, board: str = '', labs: list[str] = <factory>, captured_at: str = '', tester: dict[str, str] | None = None, ticket: str | None = None, note: str | None = None, base_commit: str = '', dirty_remap: bool = False, aging: bool = False)

Bases: object

One coverage run in the report — a capture or a synthetic per-tier load.

The run table (CoverageStore.runs) is derived fresh at report time from the capture inputs; id is the record’s index into that list. label is what the drilldown chip shows: the host display name when the capture carries one, else the board (host id), else the tier name (synthetic runs pass neither).

id : int
tier : str
label : str
board : str = ''
labs : list[str]
captured_at : str = ''
tester : dict[str, str] | None = None
ticket : str | None = None
note : str | None = None
base_commit : str = ''
dirty_remap : bool = False
aging : bool = False
to_dict() dict[str, Any]

Return a JSON-serialisable dict representation of this run.

class otto.coverage.store.model.LineRecord(line_number: int, hits: ~otto.coverage.store.model.LineHits = <factory>, branches: list[~otto.coverage.store.model.BranchHits] = <factory>, state: str | None = None, run_hits: dict[int, int] = <factory>, stale_runs: list[int] = <factory>)

Bases: object

Coverage data for a single source line.

line_number : int
hits : LineHits
branches : list[BranchHits]
state : str | None = None
run_hits : dict[int, int]
stale_runs : list[int]
merge(other: LineRecord) None

Merge other into this line record, accumulating hits and branch data.

class otto.coverage.store.model.FileRecord(path: ~pathlib.Path, lines: dict[int, ~otto.coverage.store.model.LineRecord] = <factory>, excluded_lines: set[int] = <factory>)

Bases: object

Coverage data for a single source file.

path : Path
lines : dict[int, LineRecord]
excluded_lines : set[int]
get_or_create_line(line_number: int) LineRecord

Return the LineRecord for line_number, creating it if absent.

merge(other: FileRecord) None

Merge other into this file record, combining per-line hits and branches.

line_coverage_pct(tier: str | None = None) float

Line coverage percentage, optionally filtered by tier.

branch_coverage_pct(tier: str | None = None, conservative: bool = True) float

Branch coverage percentage.

conservative=True (default): denominator is only branches where is_reachable(tier) is True. Matches genhtml behaviour.

conservative=False: denominator is all branches seen in any .info file.

sorted_lines() Iterator[LineRecord]

Yield LineRecord objects in ascending line-number order.

to_dict() dict[str, Any]

Return a JSON-serialisable dict representation of this file record.

class otto.coverage.store.model.CoverageStore(tier_order: list[str] | None = None)

Bases: object

Central store for all coverage data across all files, hosts, and tiers.

tier_order holds the user-defined precedence of tiers — first entry is highest precedence. Downstream renderers iterate this list to drive column ordering and the winner-take-all row coloring used by the annotated source view. runs captures the run table — each run record corresponds to one coverage run or synthetic per-tier aggregate loaded into the store.

register_tier(tier: str) None

Append tier to the tier order if not already present.

Loaders call this when they see a new tier so the store’s tier_order list stays in sync with the data without requiring every call site to pre-declare every tier.

add_run(*, tier: str, label: str | None = None, board: str = '', labs: list[str] | None = None, captured_at: str = '', tester: dict[str, str] | None = None, ticket: str | None = None, note: str | None = None, base_commit: str = '', dirty_remap: bool = False) int

Register one run and return its run id (index into runs).

label falls back to board, then to tier — the synthetic per-tier runs pass neither. Also registers tier so the run table can never reference an unknown tier.

get_or_create_file(path: Path) FileRecord

Return the FileRecord for path, creating it if absent.

Resolves path to its canonical form when the file exists on disk, so duplicate entries from different relative or symlinked references to the same file are collapsed to one record.

merge_file(record: FileRecord) None

Merge record into the store, accumulating data for an existing path or inserting it.

files() Iterator[FileRecord]

Yield all FileRecord objects in sorted path order.

file_count() int

Return the number of source files tracked in the store.

overall_pct(tier: str | None = None) float

Overall line coverage percentage across all files.

overall_branch_pct(tier: str | None = None, conservative: bool = True) float

Overall branch coverage percentage across all files.

save(path: Path) None

Serialise the store to JSON.

classmethod load(path: Path) CoverageStore

Deserialise a store from JSON.

Raises:

ValueError – The file’s "format" key does not match STORE_FORMAT_VERSION (including files with no "format" key at all — everything predating this store-format version). There is no migration shim; the message tells the caller to regenerate the store.