lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit 78f74b512eaf491763582d8b694f80e6e8b2500c
parent 6014c14bba0548a018bd3a2205064a3bd2c9ac7c
Author: triesap <tyson@radroots.org>
Date:   Sat, 21 Feb 2026 17:10:05 +0000

xtask: add per-crate coverage run and report commands


- add sdk coverage run-crate command using nightly cargo llvm-cov branch mode
- add sdk coverage report command to emit deterministic gate summary json
- wire command arg parsing for thresholds branch requirements and output paths
- run cargo check -q -p xtask cargo test -q -p xtask and xtask coverage smoke run

Diffstat:
Mcrates/xtask/src/coverage.rs | 311+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/xtask/src/main.rs | 5++++-
2 files changed, 315 insertions(+), 1 deletion(-)

diff --git a/crates/xtask/src/coverage.rs b/crates/xtask/src/coverage.rs @@ -2,8 +2,11 @@ use std::fs; use std::path::Path; +use std::path::PathBuf; +use std::process::Command; use serde::Deserialize; +use serde::Serialize; #[derive(Debug, Clone)] pub struct CoverageSummary { @@ -44,6 +47,52 @@ pub struct CoverageGateResult { pub fail_reasons: Vec<String>, } +#[derive(Debug, Serialize)] +struct CoverageGateReport { + scope: String, + thresholds: CoverageGateReportThresholds, + measured: CoverageGateReportMeasured, + counts: CoverageGateReportCounts, + result: CoverageGateReportResult, +} + +#[derive(Debug, Serialize)] +struct CoverageGateReportThresholds { + executable_lines: f64, + functions: f64, + branches: f64, + branches_required: bool, +} + +#[derive(Debug, Serialize)] +struct CoverageGateReportMeasured { + executable_lines_percent: f64, + executable_lines_source: String, + functions_percent: f64, + branches_percent: Option<f64>, + branches_available: bool, + summary_lines_percent: f64, + summary_regions_percent: f64, +} + +#[derive(Debug, Serialize)] +struct CoverageGateReportCounts { + executable_lines: CoverageCount, + branches: CoverageCount, +} + +#[derive(Debug, Serialize)] +struct CoverageCount { + covered: u64, + total: u64, +} + +#[derive(Debug, Serialize)] +struct CoverageGateReportResult { + pass: bool, + fail_reasons: Vec<String>, +} + #[derive(Debug, Deserialize)] struct LlvmCovSummaryRoot { data: Vec<LlvmCovSummaryData>, @@ -218,9 +267,271 @@ pub fn evaluate_gate( CoverageGateResult { pass, fail_reasons } } +fn executable_source_label(source: ExecutableSource) -> &'static str { + match source { + ExecutableSource::Da => "da", + ExecutableSource::LfLh => "lf_lh", + } +} + +fn parse_string_arg(args: &[String], name: &str) -> Result<String, String> { + let flag = format!("--{name}"); + let mut index = 0usize; + while index < args.len() { + if args[index] == flag { + let Some(value) = args.get(index + 1) else { + return Err(format!("missing value for --{name}")); + }; + return Ok(value.clone()); + } + index += 1; + } + Err(format!("missing --{name}")) +} + +fn parse_optional_string_arg(args: &[String], name: &str) -> Option<String> { + let flag = format!("--{name}"); + let mut index = 0usize; + while index < args.len() { + if args[index] == flag { + return args.get(index + 1).cloned(); + } + index += 1; + } + None +} + +fn parse_f64_arg(args: &[String], name: &str, default: f64) -> Result<f64, String> { + if let Some(raw) = parse_optional_string_arg(args, name) { + return raw + .parse::<f64>() + .map_err(|err| format!("invalid --{name} value `{raw}`: {err}")); + } + Ok(default) +} + +fn parse_u32_arg(args: &[String], name: &str, default: u32) -> Result<u32, String> { + if let Some(raw) = parse_optional_string_arg(args, name) { + return raw + .parse::<u32>() + .map_err(|err| format!("invalid --{name} value `{raw}`: {err}")); + } + Ok(default) +} + +fn parse_bool_flag(args: &[String], name: &str) -> bool { + let flag = format!("--{name}"); + args.iter().any(|arg| arg == &flag) +} + +fn workspace_root() -> Result<PathBuf, String> { + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + let Some(crates_dir) = manifest_dir.parent() else { + return Err("failed to resolve crates dir".to_string()); + }; + let Some(root) = crates_dir.parent() else { + return Err("failed to resolve workspace root".to_string()); + }; + Ok(root.to_path_buf()) +} + +fn run_command(mut command: Command, name: &str) -> Result<(), String> { + let status = command + .status() + .map_err(|err| format!("failed to run {name}: {err}"))?; + if !status.success() { + return Err(format!("{name} failed with status {status}")); + } + Ok(()) +} + +fn run_crate(args: &[String]) -> Result<(), String> { + let crate_name = parse_string_arg(args, "crate")?; + let workspace_root = workspace_root()?; + let out_dir = if let Some(raw) = parse_optional_string_arg(args, "out") { + PathBuf::from(raw) + } else { + workspace_root + .join("target") + .join("coverage") + .join(crate_name.replace('-', "_")) + }; + let test_threads = parse_u32_arg(args, "test-threads", 1)?; + + fs::create_dir_all(&out_dir) + .map_err(|err| format!("failed to create {}: {err}", out_dir.display()))?; + + run_command( + { + let mut cmd = Command::new("rustup"); + cmd.arg("run") + .arg("nightly") + .arg("cargo") + .arg("llvm-cov") + .arg("clean") + .arg("--workspace") + .current_dir(&workspace_root); + cmd + }, + "cargo llvm-cov clean --workspace", + )?; + + run_command( + { + let mut cmd = Command::new("rustup"); + cmd.arg("run") + .arg("nightly") + .arg("cargo") + .arg("llvm-cov") + .arg("-p") + .arg(&crate_name) + .arg("--no-report") + .arg("--branch") + .arg("--") + .arg(format!("--test-threads={test_threads}")) + .current_dir(&workspace_root); + cmd + }, + "cargo llvm-cov --no-report", + )?; + + let summary_path = out_dir.join("coverage-summary.json"); + run_command( + { + let mut cmd = Command::new("rustup"); + cmd.arg("run") + .arg("nightly") + .arg("cargo") + .arg("llvm-cov") + .arg("report") + .arg("-p") + .arg(&crate_name) + .arg("--json") + .arg("--summary-only") + .arg("--branch") + .arg("--output-path") + .arg(&summary_path) + .current_dir(&workspace_root); + cmd + }, + "cargo llvm-cov report --json --summary-only", + )?; + + let lcov_path = out_dir.join("coverage-lcov.info"); + run_command( + { + let mut cmd = Command::new("rustup"); + cmd.arg("run") + .arg("nightly") + .arg("cargo") + .arg("llvm-cov") + .arg("report") + .arg("-p") + .arg(&crate_name) + .arg("--lcov") + .arg("--branch") + .arg("--output-path") + .arg(&lcov_path) + .current_dir(&workspace_root); + cmd + }, + "cargo llvm-cov report --lcov", + )?; + + eprintln!("coverage summary: {}", summary_path.display()); + eprintln!("coverage lcov: {}", lcov_path.display()); + Ok(()) +} + +fn report_gate(args: &[String]) -> Result<(), String> { + let scope = parse_string_arg(args, "scope")?; + let summary_path = PathBuf::from(parse_string_arg(args, "summary")?); + let lcov_path = PathBuf::from(parse_string_arg(args, "lcov")?); + let out_path = PathBuf::from(parse_string_arg(args, "out")?); + let thresholds = CoverageThresholds { + fail_under_exec_lines: parse_f64_arg(args, "fail-under-exec-lines", 100.0)?, + fail_under_functions: parse_f64_arg(args, "fail-under-functions", 100.0)?, + fail_under_branches: parse_f64_arg(args, "fail-under-branches", 100.0)?, + require_branches: parse_bool_flag(args, "require-branches"), + }; + + let summary = read_summary(&summary_path)?; + let lcov = read_lcov(&lcov_path)?; + let gate = evaluate_gate(&summary, &lcov, thresholds); + + let report = CoverageGateReport { + scope: scope.clone(), + thresholds: CoverageGateReportThresholds { + executable_lines: thresholds.fail_under_exec_lines, + functions: thresholds.fail_under_functions, + branches: thresholds.fail_under_branches, + branches_required: thresholds.require_branches, + }, + measured: CoverageGateReportMeasured { + executable_lines_percent: lcov.executable_percent, + executable_lines_source: executable_source_label(lcov.executable_source).to_string(), + functions_percent: summary.functions_percent, + branches_percent: lcov.branch_percent, + branches_available: lcov.branches_available, + summary_lines_percent: summary.summary_lines_percent, + summary_regions_percent: summary.summary_regions_percent, + }, + counts: CoverageGateReportCounts { + executable_lines: CoverageCount { + covered: lcov.executable_covered, + total: lcov.executable_total, + }, + branches: CoverageCount { + covered: lcov.branch_covered, + total: lcov.branch_total, + }, + }, + result: CoverageGateReportResult { + pass: gate.pass, + fail_reasons: gate.fail_reasons.clone(), + }, + }; + + let json = serde_json::to_string_pretty(&report) + .map_err(|err| format!("failed to encode coverage report json: {err}"))?; + fs::write(&out_path, format!("{json}\n")) + .map_err(|err| format!("failed to write {}: {err}", out_path.display()))?; + + if lcov.branches_available { + eprintln!( + "{} coverage: executable_lines={:.6} functions={:.6} branches={:.6}", + scope, + lcov.executable_percent, + summary.functions_percent, + lcov.branch_percent.unwrap_or(0.0) + ); + } else { + eprintln!( + "{} coverage: executable_lines={:.6} functions={:.6} branches=unavailable", + scope, lcov.executable_percent, summary.functions_percent + ); + } + + eprintln!( + "{} summary (informational): lines={:.6} regions={:.6}", + scope, summary.summary_lines_percent, summary.summary_regions_percent + ); + + if !gate.pass { + for reason in &gate.fail_reasons { + eprintln!("{scope} gate fail: {reason}"); + } + return Err("coverage gate failed".to_string()); + } + + Ok(()) +} + pub fn run(args: &[String]) -> Result<(), String> { match args.first().map(String::as_str) { Some("help") => Ok(()), + Some("run-crate") => run_crate(&args[1..]), + Some("report") => report_gate(&args[1..]), Some(_) => Err("unknown sdk coverage subcommand".to_string()), None => Err("missing sdk coverage subcommand".to_string()), } diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs @@ -16,7 +16,10 @@ fn usage() { eprintln!(" cargo xtask sdk export-ts-wasm [--out <dir>]"); eprintln!(" cargo xtask sdk export-manifest [--out <dir>]"); eprintln!(" cargo xtask sdk validate"); - eprintln!(" cargo xtask sdk coverage <subcommand>"); + eprintln!(" cargo xtask sdk coverage run-crate --crate <crate> [--out <dir>]"); + eprintln!( + " cargo xtask sdk coverage report --scope <scope> --summary <file> --lcov <file> --out <file>" + ); } fn workspace_root() -> Result<PathBuf, String> {