nornir 0.4.45

Companion to cargo: dependency tracking, release gating, deploy, benchmarks, and documentation assembly. Project-agnostic.
//! 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")
}