commit f628cba3e4a9b543a39565a38c97af9f81986041
parent 93f8ff5630273b21597b09945ce657b7e7e04bd2
Author: triesap <tyson@radroots.org>
Date: Wed, 4 Mar 2026 19:35:16 +0000
xtask: expand coverage command error-path tests
- add read and parse failure tests for summary required crates and lcov inputs
- add run command spawn-failure and report write-failure coverage tests
- refactor crate list output through a writer helper for deterministic failure testing
- keep xtask check and test lanes green while raising executable coverage
Diffstat:
1 file changed, 157 insertions(+), 8 deletions(-)
diff --git a/crates/xtask/src/coverage.rs b/crates/xtask/src/coverage.rs
@@ -809,21 +809,24 @@ fn list_required_crates() -> Result<(), String> {
.join("required-crates.toml");
let crates = read_required_crates(&required_path)?;
let mut stdout = std::io::stdout().lock();
- for crate_name in crates {
- if let Err(err) = writeln!(stdout, "{crate_name}") {
- return Err(format!("failed to write required crates output: {err}"));
- }
- }
- Ok(())
+ write_crate_names_output(&mut stdout, crates, "required crates")
}
fn list_workspace_crates() -> Result<(), String> {
let root = workspace_root()?;
let crates = read_workspace_crates(&root)?;
let mut stdout = std::io::stdout().lock();
+ write_crate_names_output(&mut stdout, crates, "workspace crates")
+}
+
+fn write_crate_names_output<W: Write>(
+ writer: &mut W,
+ crates: Vec<String>,
+ label: &str,
+) -> Result<(), String> {
for crate_name in crates {
- if let Err(err) = writeln!(stdout, "{crate_name}") {
- return Err(format!("failed to write workspace crates output: {err}"));
+ if let Err(err) = writeln!(writer, "{crate_name}") {
+ return Err(format!("failed to write {label} output: {err}"));
}
}
Ok(())
@@ -845,6 +848,7 @@ pub fn run(args: &[String]) -> Result<(), String> {
mod tests {
use super::*;
use std::fs;
+ use std::io::{self, Write};
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
@@ -871,6 +875,18 @@ mod tests {
fs::write(path, content).expect("write file");
}
+ struct FailingWriter;
+
+ impl Write for FailingWriter {
+ fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
+ Err(io::Error::other("forced write failure"))
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+ }
+
#[test]
fn reads_summary_totals_from_llvm_cov_json() {
let path = temp_file_path("summary");
@@ -899,6 +915,19 @@ mod tests {
}
#[test]
+ fn read_summary_reports_read_and_parse_errors() {
+ let missing = temp_file_path("summary_missing");
+ let read_err = read_summary(&missing).expect_err("missing summary should fail");
+ assert!(read_err.contains("failed to read summary"));
+
+ let invalid = temp_file_path("summary_invalid");
+ write_file(&invalid, "{not-json");
+ let parse_err = read_summary(&invalid).expect_err("invalid summary should fail");
+ assert!(parse_err.contains("failed to parse summary"));
+ fs::remove_file(invalid).expect("remove invalid summary");
+ }
+
+ #[test]
fn reads_lcov_da_and_branch_metrics() {
let path = temp_file_path("lcov");
fs::write(
@@ -982,6 +1011,19 @@ mod tests {
}
#[test]
+ fn read_required_crates_reports_read_and_parse_errors() {
+ let missing = temp_file_path("required_missing");
+ let read_err = read_required_crates(&missing).expect_err("missing required file");
+ assert!(read_err.contains("failed to read required crates"));
+
+ let invalid = temp_file_path("required_invalid");
+ write_file(&invalid, "not = [toml");
+ let parse_err = read_required_crates(&invalid).expect_err("invalid required file");
+ assert!(parse_err.contains("failed to parse required crates"));
+ fs::remove_file(invalid).expect("remove invalid required file");
+ }
+
+ #[test]
fn reads_workspace_crates_and_contains_xtask() {
let root = workspace_root().expect("workspace root");
let crates = read_workspace_crates(&root).expect("workspace crates");
@@ -1194,6 +1236,21 @@ test_threads = 0
}
#[test]
+ fn parse_toml_reports_read_and_parse_errors() {
+ let missing = temp_file_path("parse_toml_missing");
+ let read_err =
+ parse_toml::<CoverageRequiredContract>(&missing).expect_err("missing file should fail");
+ assert!(read_err.contains("failed to read"));
+
+ let invalid = temp_file_path("parse_toml_invalid");
+ write_file(&invalid, "[required]\ncrates = [\n");
+ let parse_err =
+ parse_toml::<CoverageRequiredContract>(&invalid).expect_err("invalid toml should fail");
+ assert!(parse_err.contains("failed to parse"));
+ fs::remove_file(invalid).expect("remove invalid toml");
+ }
+
+ #[test]
fn read_lcov_rejects_invalid_records() {
let cases = vec![
("invalid_da_shape", "DA:1\n", "invalid DA record"),
@@ -1227,6 +1284,13 @@ test_threads = 0
}
#[test]
+ fn read_lcov_reports_read_error() {
+ let missing = temp_file_path("lcov_missing");
+ let err = read_lcov(&missing).expect_err("missing lcov should fail");
+ assert!(err.contains("failed to read lcov"));
+ }
+
+ #[test]
fn read_lcov_uses_lf_lh_when_da_is_missing_and_branches_absent() {
let path = temp_file_path("lcov_lf_lh");
fs::write(&path, "LF:4\nLH:3\n").expect("write lcov");
@@ -1293,6 +1357,10 @@ test_threads = 0
fail.arg("-c").arg("exit 9");
let err = run_command(fail, "shell fail").expect_err("run failing command");
assert!(err.contains("shell fail failed with status"));
+
+ let missing = Command::new("/definitely/not/a/real/command");
+ let err = run_command(missing, "shell missing").expect_err("missing command");
+ assert!(err.contains("failed to run shell missing"));
}
#[test]
@@ -1462,6 +1530,64 @@ test_threads = 0
}
#[test]
+ fn report_gate_handles_nan_threshold_input() {
+ let root = temp_dir_path("report_gate_nan");
+ let summary_path = root.join("summary.json");
+ let lcov_path = root.join("coverage.info");
+ let out_path = root.join("gate-report.json");
+ write_file(
+ &summary_path,
+ r#"{"data":[{"totals":{"functions":{"percent":100.0},"lines":{"percent":100.0},"regions":{"percent":100.0}}}]}"#,
+ );
+ write_file(&lcov_path, "DA:1,1\nBRDA:1,0,0,1\n");
+
+ let args = vec![
+ "--scope".to_string(),
+ "crate-nan".to_string(),
+ "--summary".to_string(),
+ summary_path.display().to_string(),
+ "--lcov".to_string(),
+ lcov_path.display().to_string(),
+ "--out".to_string(),
+ out_path.display().to_string(),
+ "--fail-under-functions".to_string(),
+ "NaN".to_string(),
+ ];
+ let err = report_gate(&args).expect_err("nan threshold should fail coverage gate");
+ assert!(err.contains("coverage gate failed"));
+ fs::remove_dir_all(root).expect("remove report gate nan root");
+ }
+
+ #[test]
+ fn report_gate_reports_write_failure() {
+ let root = temp_dir_path("report_gate_write_fail");
+ let summary_path = root.join("summary.json");
+ let lcov_path = root.join("coverage.info");
+ let out_path = root.join("gate-report.json");
+ write_file(
+ &summary_path,
+ r#"{"data":[{"totals":{"functions":{"percent":100.0},"lines":{"percent":100.0},"regions":{"percent":100.0}}}]}"#,
+ );
+ write_file(&lcov_path, "DA:1,1\nBRDA:1,0,0,1\n");
+ fs::create_dir_all(&out_path).expect("create directory at output path");
+
+ let args = vec![
+ "--scope".to_string(),
+ "crate-write".to_string(),
+ "--summary".to_string(),
+ summary_path.display().to_string(),
+ "--lcov".to_string(),
+ lcov_path.display().to_string(),
+ "--out".to_string(),
+ out_path.display().to_string(),
+ "--require-branches".to_string(),
+ ];
+ let err = report_gate(&args).expect_err("writing report to directory should fail");
+ assert!(err.contains("failed to write"));
+ fs::remove_dir_all(root).expect("remove report gate write root");
+ }
+
+ #[test]
fn report_gate_logs_branch_unavailable_path() {
let root = temp_dir_path("report_gate_no_branches");
let summary_path = root.join("summary.json");
@@ -1501,6 +1627,29 @@ test_threads = 0
}
#[test]
+ fn write_crate_names_output_covers_success_and_error_paths() {
+ let mut output = Vec::new();
+ write_crate_names_output(
+ &mut output,
+ vec!["radroots-a".to_string(), "radroots-b".to_string()],
+ "required crates",
+ )
+ .expect("write crate names");
+ let rendered = String::from_utf8(output).expect("utf8");
+ assert!(rendered.contains("radroots-a"));
+ assert!(rendered.contains("radroots-b"));
+
+ let mut failing = FailingWriter;
+ let err = write_crate_names_output(
+ &mut failing,
+ vec!["radroots-a".to_string()],
+ "workspace crates",
+ )
+ .expect_err("writer failure");
+ assert!(err.contains("failed to write workspace crates output"));
+ }
+
+ #[test]
fn run_report_subcommand_dispatches_to_report_gate() {
let root = temp_dir_path("run_dispatch_report");
let summary_path = root.join("summary.json");