rhi

Coordinated trade for connected markets
git clone https://radroots.dev/git/rhi.git
Log | Files | Refs | README | LICENSE

commit 9afdd98d28278cc08660214ede012d6ef821bb4e
parent 07e497d4545c03b35681752f9011e7c34121f8a9
Author: triesap <tyson@radroots.org>
Date:   Tue, 31 Mar 2026 17:14:27 +0000

build: remove local coverage contract surface

Diffstat:
DMakefile | 20--------------------
Dcontract/coverage/POLICY.md | 32--------------------------------
Dcontract/coverage/include.txt | 5-----
Dcontract/coverage/thresholds.toml | 6------
Mflake.nix | 70+++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Dscripts/ci/verify_coverage.py | 181-------------------------------------------------------------------------------
6 files changed, 57 insertions(+), 257 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,20 +0,0 @@ -COVERAGE_OUTPUT_DIR := target/coverage -COVERAGE_SUMMARY := $(COVERAGE_OUTPUT_DIR)/summary.json -COVERAGE_LCOV := $(COVERAGE_OUTPUT_DIR)/lcov.info -COVERAGE_THRESHOLDS := contract/coverage/thresholds.toml -COVERAGE_INCLUDE := contract/coverage/include.txt - -.PHONY: coverage-report coverage-gate - -coverage-report: - mkdir -p $(COVERAGE_OUTPUT_DIR) - cargo +nightly llvm-cov clean --workspace - cargo +nightly llvm-cov --workspace --all-features --branch --no-report - cargo +nightly llvm-cov report --json --summary-only --output-path $(COVERAGE_SUMMARY) - cargo +nightly llvm-cov report --lcov --output-path $(COVERAGE_LCOV) - cargo +nightly llvm-cov report --summary-only - @echo "coverage summary: $(COVERAGE_SUMMARY)" - @echo "coverage lcov: $(COVERAGE_LCOV)" - -coverage-gate: coverage-report - python3 scripts/ci/verify_coverage.py --thresholds $(COVERAGE_THRESHOLDS) --summary $(COVERAGE_SUMMARY) --lcov $(COVERAGE_LCOV) --include $(COVERAGE_INCLUDE) diff --git a/contract/coverage/POLICY.md b/contract/coverage/POLICY.md @@ -1,32 +0,0 @@ -# rhi coverage policy - -this policy defines the required rust coverage gate for this repository. - -## gate contract - -- executable lines coverage: 100.0 -- function coverage: 100.0 -- branch coverage: 100.0 -- region coverage: 100.0 -- branch records must be present in coverage data - -all thresholds are merge-blocking and release-blocking. - -## toolchain contract - -- use nightly rust for coverage runs -- use `cargo llvm-cov` with `--branch` -- export summary json and lcov artifacts per run -- evaluate gates from `contract/coverage/thresholds.toml` - -## enforcement contract - -- evaluate coverage for the repository crate, not only aggregated workspace totals -- fail hard when any required metric is below threshold, including regions -- fail hard when required branch records are missing -- fail hard when required covered files in `contract/coverage/include.txt` are absent from summary output - -## local and ci contract - -- local development may run report-only commands -- ci must run strict gate commands with default thresholds diff --git a/contract/coverage/include.txt b/contract/coverage/include.txt @@ -1,5 +0,0 @@ -# required source files that must appear in coverage summary output -src/adapters/nostr/event.rs -src/features/trade_listing/state.rs -src/lib.rs -src/main.rs diff --git a/contract/coverage/thresholds.toml b/contract/coverage/thresholds.toml @@ -1,6 +0,0 @@ -[coverage] -line_percent = 100.0 -function_percent = 100.0 -branch_percent = 100.0 -region_percent = 100.0 -require_branch_records = true diff --git a/flake.nix b/flake.nix @@ -44,14 +44,22 @@ ]; libraryPath = pkgs.lib.makeLibraryPath basePackages; includePath = pkgs.lib.makeSearchPathOutput "dev" "include" basePackages; + llvmToolsBin = "${pkgs.llvmPackages.llvm}/bin"; darwinLdFlags = pkgs.lib.optionalString pkgs.stdenv.isDarwin "-L${pkgs.darwin.libiconv}/lib"; darwinRustFlags = pkgs.lib.optionalString pkgs.stdenv.isDarwin "-L native=${pkgs.darwin.libiconv}/lib"; + coveragePackages = basePackages ++ [ + pkgs.llvmPackages.llvm + ]; mkApp = - name: text: + name: + { + runtimeInputs ? basePackages, + text, + }: let script = pkgs.writeShellApplication { inherit name; - runtimeInputs = basePackages; + inherit runtimeInputs; text = '' set -euo pipefail repo_root="$(git rev-parse --show-toplevel)" @@ -75,10 +83,12 @@ f { inherit basePackages + coveragePackages darwinLdFlags darwinRustFlags includePath libraryPath + llvmToolsBin mkApp pkgs rustToolchain @@ -88,19 +98,53 @@ in { apps = forAllSystems ( - { mkApp, ... }: + { + coveragePackages, + llvmToolsBin, + mkApp, + ... + }: rec { default = check; - check = mkApp "check" '' - cargo metadata --format-version 1 --no-deps - cargo check - ''; - fmt = mkApp "fmt" '' - cargo fmt --all --check - ''; - test = mkApp "test" '' - cargo test - ''; + check = mkApp "check" { + text = '' + cargo metadata --format-version 1 --no-deps + cargo check + ''; + }; + coverage-report = mkApp "coverage-report" { + runtimeInputs = coveragePackages; + text = '' + export PATH="$HOME/.cargo/bin:$PATH" + cargo +nightly llvm-cov --version >/dev/null 2>&1 || { + echo "cargo +nightly llvm-cov must be available to run coverage-report" >&2 + exit 1 + } + export LLVM_COV="${llvmToolsBin}/llvm-cov" + export LLVM_PROFDATA="${llvmToolsBin}/llvm-profdata" + coverage_target_dir="$(mktemp -d "''${TMPDIR:-/tmp}/rhi-llvm-cov.XXXXXX")" + trap 'rm -rf "$coverage_target_dir"' EXIT + export CARGO_TARGET_DIR="$coverage_target_dir" + mkdir -p target/coverage + cargo +nightly llvm-cov clean --workspace + cargo +nightly llvm-cov --workspace --all-features --branch --no-report + cargo +nightly llvm-cov report --json --summary-only --output-path target/coverage/summary.json + cargo +nightly llvm-cov report --lcov --output-path target/coverage/lcov.info + cargo +nightly llvm-cov report --summary-only + echo "coverage summary: target/coverage/summary.json" + echo "coverage lcov: target/coverage/lcov.info" + ''; + }; + fmt = mkApp "fmt" { + text = '' + cargo fmt --all --check + ''; + }; + test = mkApp "test" { + text = '' + cargo test + ''; + }; } ); diff --git a/scripts/ci/verify_coverage.py b/scripts/ci/verify_coverage.py @@ -1,181 +0,0 @@ -#!/usr/bin/env python3 - -from __future__ import annotations - -import argparse -import json -import pathlib -import sys -import tomllib - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(description="verify coverage thresholds") - parser.add_argument("--thresholds", required=True, type=pathlib.Path) - parser.add_argument("--summary", required=True, type=pathlib.Path) - parser.add_argument("--lcov", required=True, type=pathlib.Path) - parser.add_argument("--include", required=False, type=pathlib.Path) - return parser.parse_args() - - -def read_thresholds(path: pathlib.Path) -> dict[str, float | bool]: - payload = tomllib.loads(path.read_text(encoding="utf-8")) - coverage = payload.get("coverage") - if not isinstance(coverage, dict): - raise ValueError("missing [coverage] table") - return { - "line_percent": float(coverage["line_percent"]), - "function_percent": float(coverage["function_percent"]), - "branch_percent": float(coverage["branch_percent"]), - "region_percent": float(coverage["region_percent"]), - "require_branch_records": bool(coverage["require_branch_records"]), - } - - -def read_totals(path: pathlib.Path) -> dict[str, object]: - payload = json.loads(path.read_text(encoding="utf-8")) - data = payload.get("data") - if not isinstance(data, list) or not data: - raise ValueError("coverage summary missing data entries") - totals = data[0].get("totals") - if not isinstance(totals, dict): - raise ValueError("coverage summary missing totals") - return totals - - -def read_covered_files(path: pathlib.Path) -> set[str]: - payload = json.loads(path.read_text(encoding="utf-8")) - data = payload.get("data") - if not isinstance(data, list) or not data: - raise ValueError("coverage summary missing data entries") - files = data[0].get("files") - if not isinstance(files, list): - raise ValueError("coverage summary missing files") - - repo_root = pathlib.Path.cwd().resolve() - covered: set[str] = set() - for entry in files: - if not isinstance(entry, dict): - continue - raw_name = entry.get("filename") - if not isinstance(raw_name, str): - continue - filename = pathlib.Path(raw_name) - if filename.is_absolute(): - try: - rel = filename.resolve().relative_to(repo_root) - covered.add(rel.as_posix()) - continue - except ValueError: - pass - covered.add(filename.as_posix()) - return covered - - -def read_required_files(path: pathlib.Path) -> set[str]: - required: set[str] = set() - for raw_line in path.read_text(encoding="utf-8").splitlines(): - line = raw_line.strip() - if not line or line.startswith("#"): - continue - required.add(pathlib.Path(line).as_posix()) - return required - - -def metric_percent(totals: dict[str, object], metric: str) -> float: - metric_obj = totals.get(metric) - if not isinstance(metric_obj, dict): - raise ValueError(f"missing totals metric: {metric}") - percent = metric_obj.get("percent") - if not isinstance(percent, (int, float)): - raise ValueError(f"missing percent for metric: {metric}") - return float(percent) - - -def metric_count(totals: dict[str, object], metric: str) -> int: - metric_obj = totals.get(metric) - if not isinstance(metric_obj, dict): - raise ValueError(f"missing totals metric: {metric}") - count = metric_obj.get("count") - if not isinstance(count, int): - raise ValueError(f"missing count for metric: {metric}") - return count - - -def lcov_branch_record_count(path: pathlib.Path) -> int: - total = 0 - for raw_line in path.read_text(encoding="utf-8").splitlines(): - if not raw_line.startswith("BRF:"): - continue - value = raw_line[4:].strip() - if not value: - continue - total += int(value) - return total - - -def main() -> int: - args = parse_args() - thresholds = read_thresholds(args.thresholds) - totals = read_totals(args.summary) - covered_files = read_covered_files(args.summary) - - checks = [ - ("lines", metric_percent(totals, "lines"), thresholds["line_percent"]), - ( - "functions", - metric_percent(totals, "functions"), - thresholds["function_percent"], - ), - ( - "branches", - metric_percent(totals, "branches"), - thresholds["branch_percent"], - ), - ( - "regions", - metric_percent(totals, "regions"), - thresholds["region_percent"], - ), - ] - - errors: list[str] = [] - for name, actual, required in checks: - if actual < float(required): - errors.append(f"{name} coverage {actual:.4f}% is below {required:.4f}%") - - if thresholds["require_branch_records"]: - branch_total = metric_count(totals, "branches") - if branch_total <= 0: - errors.append("summary has no branch records") - lcov_branches = lcov_branch_record_count(args.lcov) - if lcov_branches <= 0: - errors.append("lcov report has no branch records") - - if args.include is not None: - required_files = read_required_files(args.include) - missing = sorted(required_files - covered_files) - if missing: - errors.append( - "summary is missing required covered files: " - + ", ".join(missing) - ) - - print( - "coverage totals: " - f"lines={metric_percent(totals, 'lines'):.4f}% " - f"functions={metric_percent(totals, 'functions'):.4f}% " - f"branches={metric_percent(totals, 'branches'):.4f}% " - f"regions={metric_percent(totals, 'regions'):.4f}%" - ) - print(f"coverage files counted: {len(covered_files)}") - if errors: - for error in errors: - print(f"coverage gate error: {error}", file=sys.stderr) - return 1 - print("coverage gate passed") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main())