Skip to main content

Coverage

DAGZ uses a home-grown, optimized coverage collector that instruments Python bytecode directly. No sys.settrace, no source modifications, no decorators. The overhead is near-zero compared to standard coverage tools.

Two Modes of Coverage

DAGZ collects coverage at two granularities. They serve different purposes and have different accuracy guarantees when test selection is active.

Semantic-unit coverage (DAGZ-native)

The granularity DAGZ uses for change-aware test selection. A semantic unit is the smallest trackable entity in the dependency graph: usually a function or method, but also module-level blocks, class definitions, registry entries, and cache lookups. Units are identified by content hash, not by name or location.

Always accurate, including under test selection. Every pytest --dagz run produces a full, up-to-date semantic coverage profile for the whole codebase, even when only a handful of tests were selected. DAGZ reaches this by folding the current run's observations into the previous baseline. Unchanged code keeps its prior coverage attribution, and only re-executed units refresh their entries. You never have to run the full suite to get a complete picture.

This is the profile exposed in the dashboard and used to decide which tests to select on subsequent runs.

Line coverage

Standard line-level hits, in the shape expected by coverage.py and Cobertura-compatible tooling (SonarQube, Codecov, GitLab coverage reports, etc.).

Accurate only for full runs (no test selection). Line hits are recorded directly from bytecode probes at execution time; they are not reconstructed from a baseline. If you ran a subset of tests, the line-coverage report reflects only what that subset executed. To produce a report for CI dashboards, run the suite without --dagz-skip-redundant (or disable selection some other way) and export afterwards.

Exporting coverage

Use zb export-cov to write a standard coverage file for the last job (or a specific job with -j):

zb export-cov --format pycoverage # writes .coverage (SQLite, coverage.py-compatible)
zb export-cov --format xml # writes coverage.xml (Cobertura)
zb export-cov --format xml -j j1201.1 -o out.xml

.coverage can be inspected with the standard tooling:

coverage report
coverage html

coverage.xml can be uploaded directly to Codecov, SonarQube, or consumed by GitLab's coverage_report artifact.

To annotate specific source files with hit/miss markers on the terminal without exporting, use:

zb cov path/to/module.py

What Gets Tracked

At the semantic-unit level, DAGZ tracks:

  • Functions and methods: direct calls, indirect calls through the call chain
  • Module-level code: global variables, class definitions, import side effects
  • Fixtures: pytest fixtures are tracked the same way as test functions
  • Dynamic imports: imports inside functions are captured at runtime
  • Registries: plugin registries, signal handlers, and similar patterns with per-entry precision
  • Caches: memoized functions called from multiple tests affect both, even when the second test didn't trigger full execution.

Distributed Coverage

Tests that spawn child processes (via subprocess, multiprocessing, etc.) normally lose coverage in the child. DAGZ automatically injects tracking into child processes and merges their coverage back into the parent test.

This is essential for end-to-end tests that launch servers, CLI tools, or worker pools.

Content Hashing

DAGZ identifies code by its content hash, not its name or line number:

  • Reformatting code → no tests selected (DAGZ normalizes whitespace)
  • Adding a comment → no tests selected (comments are stripped before hashing)
  • Changing actual logic → affected tests are selected

Terminal Capture

DAGZ captures stdout/stderr using a pseudo-terminal, so you don't lose stdout/stderr sync, nor fancy colors (usually lost with pytest pipe capturing).