Skip to content

Coverage Gate

The Coverage Gate fails a pull request when line coverage of the CPU build falls below a floor. Two required checks run it: Coverage Gate (hosted CPU build) and Coverage GPU (self-hosted GPU build). Both are defined in .github/workflows/tests-and-quality-gates.yml and listed in the Required Checks Aggregator (ADR-1297). The floors come from docs/principles.md §3; the ratchet rules are ADR-0922.

When you need this page

A PR shows Coverage Gate or Coverage GPU red, or you want to raise or lower a floor. To reproduce a failure, run the commands in Debugging a failing gate.

What the gate checks

scripts/ci/coverage-check.sh takes a gcovr JSON summary and two percentages:

scripts/ci/coverage-check.sh <gcovr-summary.json> <overall_min%> <critical_min%>
  1. Overall line coverage, the unweighted average over every file in the summary, must reach the overall minimum. Exit code 1 on failure.
  2. Every security-critical file must reach the critical minimum, unless it has an entry in the PER_FILE_MIN map of the script. Security-critical files match *core/src/dnn/*, *core/src/opt.cpp and *core/src/read_json_model.cpp. A critical file with no executed lines is listed but not enforced.

The script defaults are 70 % overall and 90 % critical. The workflow passes its own values:

Job Invocation in the workflow Overall Critical
Coverage Gate (coverage) coverage-check.sh core/build-coverage/coverage.json 37 85 37 % 85 %
Coverage GPU (coverage-gpu) coverage-check.sh core/build-coverage-gpu/coverage.json 70 85 70 % 85 %

The CPU job's 37 % overall floor tracks the measured value after the 2026-05-19 merge burst of new scaffold code, as the comment above the step records (ADR-0637). The per-file values in PER_FILE_MIN apply in both jobs.

The coverage job skips itself in-job when scripts/ci/plan-ci-impact.py (ADR-1140) reports no impacted source, so the check always reports and skipped is never its outcome.

Per-file overrides

Some files have a structural ceiling below the critical floor. They live in the PER_FILE_MIN map of coverage-check.sh, and each entry cites the ADR that justifies it.

File Floor Justification
core/src/dnn/ort_backend.c 83 % ADR-0114: the CUDA execution-provider success arm is unreachable on a CPU-only ONNX Runtime.
core/src/dnn/dnn_api.c 83 % ADR-0114: same ceiling as ort_backend.c.
core/src/dnn/tiny_extractor_template.h 75 % ADR-0881: template helpers are only instantiated by callers.

Overrides only ratchet upward.

The per-PR delta script

scripts/ci/coverage-delta-check.sh compares a base and a head gcovr summary and fails when overall coverage, or a touched file's coverage, drops by more than a tolerance (0.5 percentage points by default). Exit code 1 means the overall drop, 2 a per-file drop, 3 a usage error. ADR-0922 introduced it, but no step of tests-and-quality-gates.yml calls it today, so it is a local tool, not a required check. Run it by hand:

scripts/ci/coverage-delta-check.sh \
  --base-json /path/to/base-coverage.json \
  --head-json core/build-coverage/coverage.json \
  --changed-files "$(git diff --name-only origin/master...HEAD)"

The options are --base-json, --head-json, --changed-files, --max-overall-drop and --max-file-drop; read the header of the script for the exact input format.

Debugging a failing gate

The coverage-cpu and coverage-gpu artifacts of the failed run hold coverage.json, coverage.txt and coverage.xml. coverage.txt lists per-file numbers. To check a downloaded summary:

scripts/ci/coverage-check.sh coverage.json 37 85

To rebuild what the Coverage Gate job builds (needs gcovr):

cd core
meson setup build-coverage --buildtype=debug \
  -Db_coverage=true -Denable_cuda=false -Denable_sycl=false \
  -Denable_float=true -Denable_avx512=true -Denable_dnn=enabled \
  -Dc_args=-fprofile-update=atomic -Dcpp_args=-fprofile-update=atomic
ninja -C build-coverage
python3 ../scripts/ci/run_meson_test.py -- -C build-coverage --print-errorlogs --num-processes 1
gcovr --root .. \
    --filter 'src/.*' \
    --exclude '.*/test/.*' --exclude '.*/tests/.*' --exclude '.*/subprojects/.*' \
    --gcov-ignore-parse-errors=negative_hits.warn \
    --gcov-ignore-parse-errors=suspicious_hits.warn \
    --json-summary build-coverage/coverage.json \
    --txt build-coverage/coverage.txt \
    build-coverage
../scripts/ci/coverage-check.sh build-coverage/coverage.json 37 85

-fprofile-update=atomic and --num-processes 1 are both needed so test binaries do not corrupt each other's counters in the shared libvmaf.so (ADR-0110). The CI job also runs a Python suite before collecting coverage; a local run without it measures lower on the DNN files.

make coverage-check runs the same recipe locally: it builds the instrumented tree in build-coverage/, runs the meson suite serially, writes the gcovr summary build-coverage/coverage.json and passes it to coverage-check.sh with the local floors COVERAGE_MIN_OVERALL (37) and COVERAGE_MIN_CRITICAL (85) from the Makefile. make coverage-html renders the gcovr HTML report. coverage-check.sh exits 2 on an input that is not a gcovr JSON summary, an lcov .info file included. Like the CI job without its Python suite, a local run measures the DNN files lower.

Raising a floor

Raising needs no ADR. Change the numbers in the Enforce coverage thresholds step of the workflow (or the value in PER_FILE_MIN), confirm that the latest master run is above the new floor, and leave a comment with the date and the measurement.

Lowering a floor

Lowering any floor needs a new ADR that supersedes ADR-0922 (and ADR-0114 for a per-file override):

  1. Allocate it with scripts/adr/next-free.sh --claim <slug>. State why the lower floor is unavoidable and what mitigates the loss of coverage.
  2. Cite the ADR inline at the changed number, for example # ADR-NNNN — supersedes ADR-0922, lowers floor to X%. A bare number change is rejected in review.
  3. Use PER_FILE_MIN for a per-file exception. No other suppression mechanism exists.
ADR Topic
ADR-0110 Atomic gcov counters; foundation of the gate.
ADR-0111 Move from lcov to gcovr, so per-file numbers are honest.
ADR-0114 PER_FILE_MIN map and the structural-ceiling rationale.
ADR-0117 gcovr suspicious-hits warning filter.
ADR-0637 Floor tracks the measured value, ratcheted upward as tests land.
ADR-0881 tiny_extractor_template.h floor rationale.
ADR-0922 Ratchet rules and the delta script.