rhi

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

commit da8e7f701cb3b19e48bec10c01a11211427cb55b
parent 419a2a52be47451fb7d92da20653797cab56e0b1
Author: triesap <tyson@radroots.org>
Date:   Tue,  3 Mar 2026 18:38:00 +0000

build: add deterministic coverage gate scripts

Diffstat:
Ascripts/ci/verify_coverage.py | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/just/coverage_gate.sh | 15+++++++++++++++
Ascripts/just/coverage_report.sh | 20++++++++++++++++++++
Ascripts/just/dev.sh | 6++++++
Ascripts/just/log_reset.sh | 5+++++
5 files changed, 169 insertions(+), 0 deletions(-)

diff --git a/scripts/ci/verify_coverage.py b/scripts/ci/verify_coverage.py @@ -0,0 +1,123 @@ +#!/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) + 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"]), + "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 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) + + 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"], + ), + ] + + 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") + + print( + "coverage totals: " + f"lines={metric_percent(totals, 'lines'):.4f}% " + f"functions={metric_percent(totals, 'functions'):.4f}% " + f"branches={metric_percent(totals, 'branches'):.4f}%" + ) + 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()) diff --git a/scripts/just/coverage_gate.sh b/scripts/just/coverage_gate.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "${script_dir}/../.." && pwd)" +summary_path="${repo_root}/target/coverage/summary.json" +lcov_path="${repo_root}/target/coverage/lcov.info" +thresholds_path="${repo_root}/contract/coverage/thresholds.toml" + +"${script_dir}/coverage_report.sh" + +python3 "${repo_root}/scripts/ci/verify_coverage.py" \ + --thresholds "${thresholds_path}" \ + --summary "${summary_path}" \ + --lcov "${lcov_path}" diff --git a/scripts/just/coverage_report.sh b/scripts/just/coverage_report.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "${script_dir}/../.." && pwd)" +output_dir="${repo_root}/target/coverage" +summary_path="${output_dir}/summary.json" +lcov_path="${output_dir}/lcov.info" + +mkdir -p "${output_dir}" +cd "${repo_root}" + +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 "${summary_path}" +cargo +nightly llvm-cov report --lcov --output-path "${lcov_path}" +cargo +nightly llvm-cov report --summary-only + +echo "coverage summary: ${summary_path}" +echo "coverage lcov: ${lcov_path}" diff --git a/scripts/just/dev.sh b/scripts/just/dev.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail + +log_dir="${1:?missing log dir}" + +RADROOTS_LOG_DIR="${log_dir}" cargo run -- --config config.dev.toml --identity identity.json --allow-generate-identity diff --git a/scripts/just/log_reset.sh b/scripts/just/log_reset.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +log_dir="${1:?missing log dir}" +rm -rf "${log_dir}"/*