coverage.capture¶
The per-board capture.json artifact (spec §3).
A capture stores line/branch data in committed-code coordinates, pinned to the commit whose numbering they mean, with per-file blob SHAs as the rebase-tolerant validity anchor.
- class otto.coverage.capture.model.CaptureFileCov(*, blob: str | None = None, lines: dict[int, int] = <factory>, branches: dict[int, list[tuple[int, int, int | None]]] = <factory>)¶
Bases:
BaseModelCoverage for one source file, keyed in pin coordinates.
- class otto.coverage.capture.model.Capture(*, schema: int = 1, tier: str, pin: str, dirty_remap: bool = False, captured_at: str = '', tester: dict[str, str] | None = None, ticket: str | None = None, note: str | None = None, labs: list[str] = <factory>, board: str = '', files: dict[str, ~otto.coverage.capture.model.CaptureFileCov] = <factory>)¶
Bases:
BaseModelOne board’s retrieval result — the universal capture artifact.
- files : dict[str, CaptureFileCov]¶
- otto.coverage.capture.model.parse_info(info_path: Path) dict[str, tuple[dict[int, int], dict[int, list[tuple[int, int, int | None]]]]]¶
Minimal SF/DA/BRDA parser: source path → (line hits, branch triples).
Branch triples are
(block, branch, taken)wheretakenisNonefor lcov’s-(branch never reached) and anintcount otherwise — preserving the never-reached/reached-but-not-taken distinction through to the capture file.
-
otto.coverage.capture.model.build_capture(*, info_path: Path, tier: str, repo_root: Path, board: str, labs: list[str], tester: dict[str, str] | None =
None, ticket: str | None =None, note: str | None =None, now: datetime | None =None) Capture¶ Build a pinned
Capturefrom an lcov.infofile.
Map line numbers across a -U0 unified diff.
One engine, two uses (spec §6): retrieval-time dirty-tree correction
(NEW = modified working tree → OLD = HEAD) and report-time manual
validity (OLD = pinned capture → NEW = current source). Lines inside a
hunk on either side have no counterpart and map to None.
- class otto.coverage.capture.remap.Hunk(old_start: int, old_count: int, new_start: int, new_count: int)¶
Bases:
objectOne
@@ -a,b +c,d @@header (counts default to 1 when omitted).
- otto.coverage.capture.remap.parse_u0_hunks(diff_text: str) list[Hunk]¶
Parse hunk headers out of a single-file
-U0diff.
- class otto.coverage.capture.remap.LineRemapper(hunks: list[Hunk])¶
Bases:
objectBidirectional line mapping across the hunks of one file’s diff.
For each hunk, “has line moved past this hunk yet” is decided by a threshold computed from the other side’s fields translated through the cumulative offset – not from this hunk’s own same-side
start + count. That distinction only matters for a zero-count side (a pure insertion hasold_count == 0; a pure deletion hasnew_count == 0): git’s “position is the line before” convention means a zero-count side’s ownstartis not a reliable same-side boundary marker (some hunks encode it asold_start + offset, the line right at the gap; others as one less, the last untouched line before the gap – both occur in practice). Translating the always- unambiguous opposite side through the running offset sidesteps the ambiguity entirely:old_start + old_count(resp.new_start + new_count) is well-defined regardless of which convention the other side’s zero-count marker happens to follow.
Thin subprocess wrappers around the git plumbing coverage needs.
Everything is synchronous and side-effect-free on the repo (read-only
commands only). Callers pass the sut repo root; a non-repo raises
GitUnavailableError with a clean message.
Bases:
RuntimeErrorRaised when git cannot answer (not a repo / git missing).
- otto.coverage.capture.gitio.is_dirty(repo_root: Path) bool¶
Return True if there are uncommitted changes.
-
otto.coverage.capture.gitio.blob_sha(repo_root: Path, relpath: Path, rev: str =
'HEAD') str | None¶ Return the SHA of a blob at a path/revision, or None if not found.
The
./prefix makes git resolve the path againstrepo_root(our cwd) — a bareREV:<path>resolves against the repo toplevel, which is wrong wheneverrepo_rootis a subdirectory of a larger repo (e.g. a sut checked out inside another project).
- otto.coverage.capture.gitio.hash_object(repo_root: Path, path: Path) str¶
Return the SHA1 hash of a file object.
- otto.coverage.capture.gitio.blob_exists(repo_root: Path, sha: str) bool¶
Return True if a blob exists in the repository.
- otto.coverage.capture.gitio.cat_blob(repo_root: Path, sha: str) bytes¶
Return the contents of a blob.
- otto.coverage.capture.gitio.diff_worktree_file_u0(repo_root: Path, relpath: Path) str¶
Return unified diff (U0) of HEAD vs worktree file.
- otto.coverage.capture.gitio.diff_no_index_u0(path_a: Path, path_b: Path) str¶
Return unified diff (U0) between two files outside a repo.
The committed in-repo manual-capture store (spec §3).
- otto.coverage.capture.store_dir.manual_store_dir(repo_root: Path) Path¶
Directory storing committed manual capture records.
- otto.coverage.capture.store_dir.write_manual_capture(capture: Capture, repo_root: Path) Path¶
Write a capture to the manual store and return its path.
- otto.coverage.capture.store_dir.load_manual_captures(repo_root: Path) list[Capture]¶
Load all manual captures from repo_root, sorted by filename.
Per-board capture.json production from fetched .gcda counters.
Turns the raw .gcda counters collected by otto test --cov into a
pinned Capture per board. For each
board directory under cov_dir this:
Resolves the board’s toolchain and gcno (build) source root from the
.otto_cov_meta.jsonsidecar via theotto.coverage.reporterhelpers.Runs
capture()for that board alone, producing<board>/board.info.Auto-discovers path mappings and rewrites the embedded
SF:paths to their local,repo_root-relative form, producing<board>/board.resolved.info.Builds and saves
<board>/capture.jsonviabuild_capture().
The raw .gcda, board.info, and board.resolved.info all stay
on disk as debug artifacts (spec decision 18).
-
async otto.coverage.capture.produce.produce_captures(cov_dir: Path, *, tier: str, repo_root: Path, labs: list[str], tester: dict[str, str] | None =
None, ticket: str | None =None, note: str | None =None) list[Path]¶ Produce a pinned
capture.jsonfor each board dir under cov_dir.A board dir is any direct subdirectory of cov_dir containing at least one
.gcdafile (recursively). Boards with no.gcdafiles are skipped with a warning.- Parameters:¶
cov_dir – Coverage directory written by
otto test --cov, containing per-board subdirs and a.otto_cov_meta.jsonsidecar.tier – Coverage tier name to stamp onto each capture.
repo_root – SUT git repo root, used for pin/blob resolution and as the path-correlation target.
labs – Lab identifiers to stamp onto each capture.
tester – Optional tester identity to stamp onto each capture.
ticket – Optional ticket reference to stamp onto each capture.
note – Optional free-text note to stamp onto each capture.
- Returns:¶
Paths of the
capture.jsonfiles written, one per board, in board-name sort order.- Raises:¶
otto.coverage.capture.gitio.GitUnavailableError – If repo_root is not a git repository.