crates/nornir-coverage/src/runner.rs
//! Run `cargo-llvm-cov` over a repo and parse the result into a
//! [`CoverageReport`](super::CoverageReport).
//!
//! This is the only impure seam of the coverage feature: it shells out to
//! `cargo llvm-cov --json` (LLVM source-based coverage) in the repo dir, then
//! hands the JSON to the pure [`super::parse_llvm_cov_json`]. The percentages,
//! the per-crate rollup, the boundary classification — all of that is tested
//! against a fixed JSON blob, so this module needs no test of its own beyond a
//! tool-presence probe.
use std::path::Path;
use std::process::Command;
use anyhow::{bail, Context, Result};
use super::{crate_from_path, parse_llvm_cov_json, CoverageReport};
/// Is `cargo-llvm-cov` available on PATH? The `coverage` CLI verb checks this
/// first so it can print a precise install hint instead of a raw spawn error.
pub fn tool_available() -> bool {
Command::new("cargo")
.args(["llvm-cov", "--version"])
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
/// The install hint shown when the tool is missing.
pub const INSTALL_HINT: &str = "cargo-llvm-cov not found. Install it with:\n \
rustup component add llvm-tools-preview\n \
cargo install cargo-llvm-cov";
/// Run `cargo llvm-cov --json` over `repo_root` (labelled `repo`) and parse the
/// LLVM export into a [`CoverageReport`]. `extra_args` are appended verbatim
/// (e.g. `--all-features`, `-p <crate>`). Honours `CARGO_TARGET_DIR` from the
/// environment so the build can be steered off a slow disk.
pub fn run(repo: &str, repo_root: &Path, extra_args: &[String]) -> Result<CoverageReport> {
if !tool_available() {
bail!("{INSTALL_HINT}");
}
let root = repo_root.to_string_lossy().to_string();
let mut cmd = Command::new("cargo");
cmd.arg("llvm-cov")
// JSON export to stdout; summarise OFF so we get the full per-function
// `functions` array (needed for the boundary view).
.arg("--json")
.current_dir(repo_root);
for a in extra_args {
cmd.arg(a);
}
let out = cmd
.output()
.with_context(|| format!("spawn `cargo llvm-cov --json` in {root}"))?;
if !out.status.success() {
bail!(
"cargo llvm-cov failed ({}):\n{}",
out.status,
String::from_utf8_lossy(&out.stderr)
);
}
let json = String::from_utf8(out.stdout).context("llvm-cov JSON is not UTF-8")?;
parse_llvm_cov_json(&json, repo, &root, crate_from_path(repo))
.with_context(|| "parse cargo llvm-cov --json export")
}