Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

crates/nornir-testmatrix/src/gatedtests.rs

//! **Silenced-test guard** — catch `tests/*.rs` files that compile to ZERO tests.
//!
//! ## The trap
//!
//! A test file carrying a *crate-level inner attribute*
//!
//! ```ignore
//! #![cfg(all(feature = "viz", feature = "server", feature = "testmatrix"))]
//! ```
//!
//! compiles to an **empty test binary** whenever any required feature is absent
//! from the crate's `default` set. `cargo test` then reports
//! `running 0 tests ... ok` and the whole file is green-by-vacuum. The
//! 2026-07-21 constellation sweep found **78 such files across 11 repos, hiding
//! ~216 test functions**. Silence is not success.
//!
//! Three camouflage forms make it invisible to review:
//!
//! 1. **`all(default-on, default-on, default-OFF)`** — the file reads exactly
//!    like its many running siblings; only the last conjunct is dark.
//! 2. **Near-miss arm names** — the file needs `lineage-http`, CI passes
//!    `lineage`; a *different* feature that does not imply it.
//! 3. **Sibling asymmetry** — one test in a directory runs on default features,
//!    the eight next to it are dark. The directory reads as green.
//!
//! ## The guard
//!
//! [`audit_gated_tests`] walks every `test` target of every workspace member
//! (via `cargo metadata --no-deps`), extracts the leading crate-level `#![cfg]`,
//! resolves each crate's transitive `default` feature closure from its own
//! feature table, and asks: *does this target actually run under a plain
//! `cargo test`?* If not, it must be **rescued** by a declared matrix arm — an
//! explicit `cargo test -p P --features F --test T` re-invocation the repo runs.
//! Anything neither reachable-by-default nor rescued is a **RED**
//! [`TestResultRow`] on the `gated-tests` aspect, so it fails a gate instead of
//! merely printing.
//!
//! ## The SECOND gate on the same target: `required-features`
//!
//! A crate-level `#![cfg]` is only half the question. A `[[test]]` target may
//! also declare `required-features = ["gpu"]`, and cargo then **skips the target
//! outright** when `gpu` is off — no warning, no error, and not even the
//! `running 0 tests ... ok` line an empty binary prints. It is the same disease
//! one degree quieter, and it is declared in a place no source scan can see.
//!
//! Both halves are weighed together, against all four sources of "which feature
//! is on" — the crate's `default`, a declared arm's `--features`, the target's
//! `required-features`, and a feature switched on along a **dependency edge**
//! and unified across the workspace by cargo. See [`classify`] for what each
//! source was measured to be worth.
//!
//! The rescue model is holger's (`xtask/tests/holger_matrix.rs`), the
//! constellation's reference mitigation: per-file
//! `cargo test -p holger-ui --features gui --test robot_ui_app` cells. A repo
//! declares those arms once, in `.nornir/testmatrix-arms.json`:
//!
//! ```json
//! {
//!   "arms": [
//!     { "package": "holger-ui", "features": ["gui"], "test": "robot_ui_app" },
//!     { "package": "holger-ui", "features": ["gui"], "test": "demo_walk" },
//!     { "package": "holger-ui", "features": ["gui"] },
//!     { "features": ["testmatrix"] }
//!   ]
//! }
//! ```
//!
//! An arm with no `test` covers **every** test target of the package; an arm
//! with no `package` covers every package (a workspace-wide
//! `cargo test --workspace --features X`). `no_default_features` and
//! `all_features` mirror the cargo flags.
//!
//! ## Semantics, precisely
//!
//! Non-feature predicates (`unix`, `target_os = "linux"`, `test`) are
//! **unknown**, never false: the guard evaluates the cfg tree three-valued and
//! only reports a file whose gate is *definitively* false under the resolved
//! feature set. `not(feature = "x")` with `x` off is therefore satisfied, and
//! `not(target_os = "windows")` is never flagged. The guard reports
//! feature-driven silence and nothing else.
//!
//! ```no_run
//! use nornir_testmatrix::{audit_gated_tests, MatrixArms};
//! use std::path::Path;
//!
//! let arms = MatrixArms::load(Path::new(".")).unwrap_or_default();
//! let report = audit_gated_tests(Path::new("."), "myrepo", "run-1", &arms).unwrap();
//! assert!(report.is_green(), "{}", report.summary());
//! ```

use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::path::{Path, PathBuf};
use std::process::Command;

use anyhow::{anyhow, Context, Result};
use serde::{Deserialize, Serialize};

use crate::model::{status, TestResultRow};

/// The aspect tag every row this module emits carries.
pub const ASPECT_GATED_TESTS: &str = "gated-tests";

/// Where a repo declares the matrix arms that re-invoke its feature-gated tests.
pub const ARMS_FILE: &str = ".nornir/testmatrix-arms.json";

// ─── cfg expression ────────────────────────────────────────────────────────

/// A parsed `#![cfg(...)]` predicate tree.
///
/// `Other` is any predicate that is not `feature = "..."` (`unix`,
/// `target_os = "linux"`, `test`, `docsrs` …). It evaluates to **unknown**, so
/// it can never make a file look silenced.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum CfgExpr {
    /// `feature = "name"`.
    Feature(String),
    /// `all(a, b, …)`.
    All(Vec<CfgExpr>),
    /// `any(a, b, …)`.
    Any(Vec<CfgExpr>),
    /// `not(a)`.
    Not(Box<CfgExpr>),
    /// A non-feature predicate, kept verbatim for the report message.
    Other(String),
}

impl CfgExpr {
    /// Three-valued evaluation against an enabled-feature set. `None` = unknown
    /// (a non-feature predicate was decisive).
    pub fn eval(&self, enabled: &BTreeSet<String>) -> Option<bool> {
        match self {
            CfgExpr::Feature(f) => Some(enabled.contains(f)),
            CfgExpr::Other(_) => None,
            CfgExpr::Not(inner) => inner.eval(enabled).map(|b| !b),
            CfgExpr::All(items) => {
                let mut unknown = false;
                for it in items {
                    match it.eval(enabled) {
                        Some(false) => return Some(false),
                        None => unknown = true,
                        Some(true) => {}
                    }
                }
                if unknown {
                    None
                } else {
                    Some(true)
                }
            }
            CfgExpr::Any(items) => {
                let mut unknown = false;
                for it in items {
                    match it.eval(enabled) {
                        Some(true) => return Some(true),
                        None => unknown = true,
                        Some(false) => {}
                    }
                }
                if unknown {
                    None
                } else {
                    Some(false)
                }
            }
        }
    }

    /// Does the file behind this gate **compile** with `enabled` on? Unknown
    /// counts as yes — only a definitively-false gate silences a file.
    pub fn compiles_with(&self, enabled: &BTreeSet<String>) -> bool {
        self.eval(enabled) != Some(false)
    }

    /// Every `feature = "..."` name mentioned anywhere in the tree.
    pub fn features(&self) -> BTreeSet<String> {
        let mut out = BTreeSet::new();
        self.collect_features(&mut out);
        out
    }

    fn collect_features(&self, out: &mut BTreeSet<String>) {
        match self {
            CfgExpr::Feature(f) => {
                out.insert(f.clone());
            }
            CfgExpr::Other(_) => {}
            CfgExpr::Not(i) => i.collect_features(out),
            CfgExpr::All(v) | CfgExpr::Any(v) => {
                for i in v {
                    i.collect_features(out)
                }
            }
        }
    }

    /// Does this gate reference any cargo feature at all? A purely
    /// platform-conditional file is not this guard's business.
    pub fn mentions_features(&self) -> bool {
        !self.features().is_empty()
    }
}

// ─── source scanning ───────────────────────────────────────────────────────

/// Strip `//` / `/* */` (nesting-aware) comments, respecting string, char, and
/// raw-string literals, so an attribute-looking thing inside a doc comment or a
/// string is never mistaken for a real one.
pub(crate) fn strip_comments(src: &str) -> String {
    let b: Vec<char> = src.chars().collect();
    let mut out = String::with_capacity(src.len());
    let mut i = 0usize;
    while i < b.len() {
        let c = b[i];
        // raw string: r"..." / r#"..."#
        if c == 'r' && i + 1 < b.len() && (b[i + 1] == '"' || b[i + 1] == '#') {
            let mut j = i + 1;
            let mut hashes = 0usize;
            while j < b.len() && b[j] == '#' {
                hashes += 1;
                j += 1;
            }
            if j < b.len() && b[j] == '"' {
                out.push(c);
                out.extend(b[i + 1..=j].iter());
                let mut k = j + 1;
                loop {
                    if k >= b.len() {
                        i = k;
                        break;
                    }
                    if b[k] == '"' {
                        let mut h = 0usize;
                        while h < hashes && k + 1 + h < b.len() && b[k + 1 + h] == '#' {
                            h += 1;
                        }
                        if h == hashes {
                            out.extend(b[k..=k + hashes].iter());
                            i = k + hashes + 1;
                            break;
                        }
                    }
                    out.push(b[k]);
                    k += 1;
                }
                continue;
            }
        }
        if c == '"' {
            out.push(c);
            let mut k = i + 1;
            while k < b.len() {
                if b[k] == '\\' {
                    out.push(b[k]);
                    if k + 1 < b.len() {
                        out.push(b[k + 1]);
                    }
                    k += 2;
                    continue;
                }
                out.push(b[k]);
                if b[k] == '"' {
                    k += 1;
                    break;
                }
                k += 1;
            }
            i = k;
            continue;
        }
        if c == '/' && i + 1 < b.len() && b[i + 1] == '/' {
            while i < b.len() && b[i] != '\n' {
                i += 1;
            }
            out.push('\n');
            continue;
        }
        if c == '/' && i + 1 < b.len() && b[i + 1] == '*' {
            let mut depth = 1usize;
            i += 2;
            while i < b.len() && depth > 0 {
                if b[i] == '/' && i + 1 < b.len() && b[i + 1] == '*' {
                    depth += 1;
                    i += 2;
                } else if b[i] == '*' && i + 1 < b.len() && b[i + 1] == '/' {
                    depth -= 1;
                    i += 2;
                } else {
                    i += 1;
                }
            }
            out.push(' ');
            continue;
        }
        out.push(c);
        i += 1;
    }
    out
}

/// Skip whitespace and comments (`//`, `//!`, nesting-aware `/* */`) from `i`,
/// returning the index of the first character that is neither.
fn skip_trivia(t: &[char], mut i: usize) -> usize {
    loop {
        while i < t.len() && t[i].is_whitespace() {
            i += 1;
        }
        if t.get(i) == Some(&'/') && t.get(i + 1) == Some(&'/') {
            while i < t.len() && t[i] != '\n' {
                i += 1;
            }
            continue;
        }
        if t.get(i) == Some(&'/') && t.get(i + 1) == Some(&'*') {
            let mut depth = 1usize;
            i += 2;
            while i < t.len() && depth > 0 {
                if t[i] == '/' && t.get(i + 1) == Some(&'*') {
                    depth += 1;
                    i += 2;
                } else if t[i] == '*' && t.get(i + 1) == Some(&'/') {
                    depth -= 1;
                    i += 2;
                } else {
                    i += 1;
                }
            }
            continue;
        }
        return i;
    }
}

/// If a string literal starts at `i` (`"…"` with escapes, or `r"…"` / `r#"…"#`),
/// return the index just past its closing quote. `prev` guards the raw-string
/// form: the `r` of `feature` must not start one.
fn skip_string(t: &[char], i: usize, prev: Option<char>) -> Option<usize> {
    match t.get(i) {
        Some('"') => {
            let mut k = i + 1;
            while k < t.len() {
                if t[k] == '\\' {
                    k += 2;
                    continue;
                }
                if t[k] == '"' {
                    return Some(k + 1);
                }
                k += 1;
            }
            Some(t.len())
        }
        Some('r') if !prev.map(|c| c.is_alphanumeric() || c == '_').unwrap_or(false) => {
            let mut j = i + 1;
            let mut hashes = 0usize;
            while t.get(j) == Some(&'#') {
                hashes += 1;
                j += 1;
            }
            if t.get(j) != Some(&'"') {
                return None;
            }
            let mut k = j + 1;
            while k < t.len() {
                if t[k] == '"' {
                    let mut h = 0usize;
                    while h < hashes && t.get(k + 1 + h) == Some(&'#') {
                        h += 1;
                    }
                    if h == hashes {
                        return Some(k + 1 + hashes);
                    }
                }
                k += 1;
            }
            Some(t.len())
        }
        _ => None,
    }
}

/// Index of the delimiter closing the `(` or `[` at `open`, skipping string
/// literals and comments so a `)` or `]` inside `"…"` cannot close it.
fn match_delim(t: &[char], open: usize) -> Option<usize> {
    let (o, c) = match t.get(open)? {
        '(' => ('(', ')'),
        '[' => ('[', ']'),
        _ => return None,
    };
    let mut depth = 0usize;
    let mut i = open;
    let mut prev: Option<char> = None;
    while i < t.len() {
        if let Some(next) = skip_string(t, i, prev) {
            prev = Some('"');
            i = next;
            continue;
        }
        let after = skip_trivia(t, i);
        if after > i {
            prev = Some(' ');
            i = after;
            continue;
        }
        let ch = t[i];
        if ch == o {
            depth += 1;
        } else if ch == c {
            depth -= 1;
            if depth == 0 {
                return Some(i);
            }
        }
        prev = Some(ch);
        i += 1;
    }
    None
}

/// Extract the inner text of the crate-level `#![cfg(...)]` inner attribute of a
/// source file, e.g. `all(feature = "a", feature = "b")`.
///
/// Returns `None` when the file carries no crate-level cfg gate.
/// `#![cfg_attr(...)]` is deliberately NOT matched.
///
/// ## Why this walks the prologue instead of scanning for the text `#![cfg(`
///
/// A textual scan — even a comment-stripped one — reports files that merely
/// *talk about* gates. The case that showed it: an **ungated** tripwire test
/// that asserts a sibling file keeps its gate, so it contains the line
///
/// ```ignore
/// smoke.contains("#![cfg(feature = \"heavy-smoke\")]"),
/// ```
///
/// The old scan matched inside that string literal and reported the file as
/// SILENCED with missing feature `heavy-smoke"` — the trailing quote being the
/// fingerprint of having parsed an escaped literal. That file runs on default
/// features; the finding was pure noise, and a guard that cries wolf gets
/// ignored. The same naive match can equally MIS-ATTRIBUTE a real gate by
/// picking up a quoted one further down the file.
///
/// So the rule is structural, matching what rustc actually accepts: **inner
/// attributes may only appear in the file's prologue**, before the first item.
/// This walks that prologue — skipping whitespace and comments, stepping over
/// each `#![…]` whose path is not `cfg` — and stops dead at the first token
/// that is not an inner attribute. A `#![cfg(` inside a string literal, a
/// `//`/`//!` comment, or a `/* */` block therefore cannot be reached at all,
/// because by then the walk has already stopped at the first `use`/`fn`/`const`.
///
/// The mirror-image failure is guarded too: whitespace and comments are skipped
/// rather than required-absent, so an indented gate, a gate preceded by
/// `#![allow(...)]` or a doc-comment header, and a `#![cfg(all(feature = "a",
/// feature = "b"))]` broken across several lines are all still detected. Paren
/// matching is string-aware, so a feature name containing `)` cannot truncate
/// the capture.
pub fn extract_crate_cfg(src: &str) -> Option<String> {
    let t: Vec<char> = src.chars().collect();
    let mut i = 0usize;
    // A `#!/usr/bin/env …` shebang on line 1 is legal Rust and is not an
    // attribute; skip the line so it does not end the prologue walk.
    if t.first() == Some(&'#') && t.get(1) == Some(&'!') && t.get(2) != Some(&'[') {
        while i < t.len() && t[i] != '\n' {
            i += 1;
        }
    }
    loop {
        i = skip_trivia(&t, i);
        // First non-inner-attribute token ends the prologue: no gate can follow.
        if !(t.get(i) == Some(&'#') && t.get(i + 1) == Some(&'!') && t.get(i + 2) == Some(&'[')) {
            return None;
        }
        let bracket = i + 2;
        let mut j = skip_trivia(&t, i + 3);
        let start = j;
        while j < t.len() && (t[j].is_alphanumeric() || t[j] == '_') {
            j += 1;
        }
        let name: String = t[start..j].iter().collect();
        j = skip_trivia(&t, j);
        if name == "cfg" && t.get(j) == Some(&'(') {
            let close = match_delim(&t, j)?;
            let inner: String = t[j + 1..close].iter().collect();
            return Some(inner.trim().to_string());
        }
        // Some other inner attribute (`#![allow]`, `#![cfg_attr]`, …): step over
        // the whole thing — brackets matched string-aware — and keep walking.
        i = match_delim(&t, bracket)? + 1;
    }
}

/// Parse a cfg predicate list body (the text inside `cfg(...)`).
pub fn parse_cfg_expr(src: &str) -> Option<CfgExpr> {
    let toks = tokenize(src);
    let mut pos = 0usize;
    let e = parse_expr(&toks, &mut pos)?;
    Some(e)
}

#[derive(Debug, Clone, PartialEq, Eq)]
enum Tok {
    Ident(String),
    Str(String),
    Open,
    Close,
    Comma,
    Eq,
}

fn tokenize(src: &str) -> Vec<Tok> {
    let c: Vec<char> = src.chars().collect();
    let mut out = Vec::new();
    let mut i = 0usize;
    while i < c.len() {
        let ch = c[i];
        if ch.is_whitespace() {
            i += 1;
        } else if ch == '(' {
            out.push(Tok::Open);
            i += 1;
        } else if ch == ')' {
            out.push(Tok::Close);
            i += 1;
        } else if ch == ',' {
            out.push(Tok::Comma);
            i += 1;
        } else if ch == '=' {
            out.push(Tok::Eq);
            i += 1;
        } else if ch == '"' {
            let mut s = String::new();
            i += 1;
            while i < c.len() && c[i] != '"' {
                if c[i] == '\\' && i + 1 < c.len() {
                    i += 1;
                }
                s.push(c[i]);
                i += 1;
            }
            i += 1;
            out.push(Tok::Str(s));
        } else if ch.is_alphanumeric() || ch == '_' || ch == '-' {
            let mut s = String::new();
            while i < c.len() && (c[i].is_alphanumeric() || c[i] == '_' || c[i] == '-') {
                s.push(c[i]);
                i += 1;
            }
            out.push(Tok::Ident(s));
        } else {
            i += 1;
        }
    }
    out
}

fn parse_expr(toks: &[Tok], pos: &mut usize) -> Option<CfgExpr> {
    let name = match toks.get(*pos)? {
        Tok::Ident(s) => s.clone(),
        _ => return None,
    };
    *pos += 1;
    match toks.get(*pos) {
        Some(Tok::Open) => {
            *pos += 1;
            let mut items = Vec::new();
            loop {
                match toks.get(*pos) {
                    Some(Tok::Close) => {
                        *pos += 1;
                        break;
                    }
                    Some(Tok::Comma) => {
                        *pos += 1;
                    }
                    None => return None,
                    _ => {
                        let e = parse_expr(toks, pos)?;
                        items.push(e);
                    }
                }
            }
            match name.as_str() {
                "all" => Some(CfgExpr::All(items)),
                "any" => Some(CfgExpr::Any(items)),
                "not" => items.into_iter().next().map(|e| CfgExpr::Not(Box::new(e))),
                other => Some(CfgExpr::Other(other.to_string())),
            }
        }
        Some(Tok::Eq) => {
            *pos += 1;
            let val = match toks.get(*pos) {
                Some(Tok::Str(s)) => s.clone(),
                Some(Tok::Ident(s)) => s.clone(),
                _ => String::new(),
            };
            *pos += 1;
            if name == "feature" {
                Some(CfgExpr::Feature(val))
            } else {
                Some(CfgExpr::Other(format!("{name} = \"{val}\"")))
            }
        }
        _ => Some(CfgExpr::Other(name)),
    }
}

/// Count the `#[test]`-family attributes in a source file — the number of test
/// functions a silenced file is hiding. Comment-stripped, so a `#[test]` inside
/// a doc example is not counted.
///
/// Matches any attribute whose path's **last segment** is `test`, with or
/// without arguments: `#[test]`, `#[tokio::test]`,
/// `#[tokio::test(flavor = "multi_thread")]`, `#[actix_web::test]`. Does NOT
/// match `#[test_case(..)]` or `#[should_panic]` (different last segment).
pub fn count_test_fns(src: &str) -> usize {
    strip_comments(src).lines().filter(|l| is_test_attr(l.trim())).count()
}

/// Is this trimmed line an outer `#[…test…]` attribute?
pub(crate) fn is_test_attr(t: &str) -> bool {
    if !t.starts_with("#[") || t.starts_with("#![") {
        return false;
    }
    // The attribute path: everything up to the first `(` (args) or `]` (end).
    let body = &t[2..];
    let end = body.find(['(', ']']).unwrap_or(body.len());
    let path = body[..end].trim();
    path.rsplit("::").next().map(|seg| seg.trim() == "test").unwrap_or(false)
}

// ─── feature graph ─────────────────────────────────────────────────────────

/// The transitive closure of a crate's own features reachable from `seeds`.
///
/// `dep:x` entries and cross-crate `pkg/feat` (and the weak `pkg?/feat`) forms
/// enable features on *dependencies*, never on self, so they are skipped.
pub fn feature_closure(table: &BTreeMap<String, Vec<String>>, seeds: &[String]) -> BTreeSet<String> {
    let mut seen: BTreeSet<String> = BTreeSet::new();
    let mut queue: VecDeque<String> = seeds.iter().cloned().collect();
    while let Some(f) = queue.pop_front() {
        if f.starts_with("dep:") || f.contains('/') {
            continue;
        }
        if !seen.insert(f.clone()) {
            continue;
        }
        if let Some(children) = table.get(&f) {
            for c in children {
                queue.push_back(c.clone());
            }
        }
    }
    seen
}

/// The features a plain `cargo test` turns on: the closure of `default`.
pub fn default_features(table: &BTreeMap<String, Vec<String>>) -> BTreeSet<String> {
    let mut set = feature_closure(table, &["default".to_string()]);
    // `default` is itself a feature name; keep it, it is legal to `cfg` on.
    if !table.contains_key("default") {
        set.remove("default");
    }
    set
}

// ─── workspace model (cargo metadata) ──────────────────────────────────────

/// One `[[test]]` target of a crate (an integration test — a `tests/*.rs` file).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TestTarget {
    /// The target name (`foo` for `tests/foo.rs`).
    pub name: String,
    /// Absolute path to the test's root source file.
    pub src_path: PathBuf,
    /// The target's `required-features`. Cargo **silently skips** a target whose
    /// required features are off — no warning, no error, no line in `cargo test`
    /// output — so this list is the only place such a target is declared, and
    /// [`crate::configs_from_metadata`] turns each distinct set into its own
    /// compile configuration. Defaulted so older serialised manifests still load.
    #[serde(default)]
    pub required_features: Vec<String>,
}

/// A workspace member's feature table + test targets — everything the guard
/// needs, read once from `cargo metadata --no-deps`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CrateManifest {
    /// The package name (what `-p` takes).
    pub name: String,
    /// The raw `[features]` table, including cargo's implicit optional-dep
    /// features.
    pub features: BTreeMap<String, Vec<String>>,
    /// Every `test`-kind target of the package.
    pub tests: Vec<TestTarget>,
    /// The root source file of every **non-test** target — `src/lib.rs`, each
    /// `[[bin]]`, each `[[example]]`, each `[[bench]]`.
    ///
    /// This is the entry point for the third gate source. A
    /// `#[cfg(feature = "x")] pub mod m;` in `lib.rs` puts `m`'s `#[test]` fns in
    /// the LIBRARY target, not in a `tests/*.rs`, so [`tests`](Self::tests) — and
    /// therefore every walk built on it — cannot see them. Skidbladnir's
    /// `src/vmsuite.rs` held 23 of them behind `matrix` and nothing in the
    /// constellation had ever looked at one.
    ///
    /// Defaulted so manifests serialised before this field still load.
    #[serde(default)]
    pub src_roots: Vec<PathBuf>,
    /// The package's declared dependency EDGES — the third source of "what
    /// feature is on".
    ///
    /// A feature is not only turned on by `[build] offers`, a variant's
    /// `--features`, or a `default` chain. `holger/server/cli/Cargo.toml:71` carries
    /// `server-lib = { …, features = ["sign"] }` unconditionally, and
    /// `server/cli/Cargo.toml:99` + `server/ui/Cargo.toml:110` carry
    /// `bench-scenarios = { …, features = ["served"] }`. A feature-closure scan
    /// that reads only the variants reports `sign` and `served` as never built —
    /// which is exactly what `holger/.nornir/build-variants.toml:176` says in
    /// prose, and it is FALSE. See [`crate::testsurface`].
    ///
    /// Defaulted for the same back-compatibility reason.
    #[serde(default)]
    pub deps: Vec<DepEdge>,
    /// Absolute path to the package's own `Cargo.toml`. Defaulted.
    #[serde(default)]
    pub manifest_path: PathBuf,
}

/// Which dependency table an edge was declared in.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum DepKind {
    /// `[dependencies]` — built by `cargo build` and by `cargo test`.
    #[default]
    Normal,
    /// `[dev-dependencies]` — built by `cargo test`, NOT by `cargo build`. The
    /// distinction is load-bearing: a variant step is a build, so a dev edge's
    /// features are not on during it.
    Dev,
    /// `[build-dependencies]`.
    Build,
}

/// One declared dependency edge of a workspace member.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DepEdge {
    /// The **package** the edge points at (`holger-bench-scenarios`).
    pub package: String,
    /// The **token feature strings use** — the `rename` when the manifest renames
    /// the dep, otherwise the package name. holger writes
    /// `bench-scenarios = { package = "holger-bench-scenarios", … }` and then
    /// `bench = ["dep:bench-scenarios"]`, so resolving `dep:` against the package
    /// name would never match.
    pub alias: String,
    /// `optional = true` — the edge is inert until a feature activates it.
    pub optional: bool,
    /// `default-features` (cargo's `uses_default_features`).
    pub uses_default_features: bool,
    /// The features this edge turns on in the target package.
    pub features: Vec<String>,
    /// Which table the edge was declared in.
    pub kind: DepKind,
}

impl CrateManifest {
    /// The features a plain `cargo test -p <name>` enables.
    pub fn default_set(&self) -> BTreeSet<String> {
        default_features(&self.features)
    }

    /// The features an explicit `--features a,b` (plus default unless
    /// suppressed) enables.
    pub fn arm_set(&self, arm: &MatrixArm) -> BTreeSet<String> {
        if arm.all_features {
            return self.features.keys().cloned().collect();
        }
        let mut seeds: Vec<String> = arm.features.clone();
        if !arm.no_default_features {
            seeds.push("default".to_string());
        }
        let mut set = feature_closure(&self.features, &seeds);
        if !self.features.contains_key("default") {
            set.remove("default");
        }
        set
    }
}

/// Parse `cargo metadata --no-deps --format-version 1` output into the crates
/// the guard inspects. Pure — no subprocess — so it is unit-testable.
pub fn parse_metadata(json: &str) -> Result<Vec<CrateManifest>> {
    let v: serde_json::Value = serde_json::from_str(json).context("parse cargo metadata JSON")?;
    let pkgs = v
        .get("packages")
        .and_then(|p| p.as_array())
        .ok_or_else(|| anyhow!("cargo metadata has no `packages` array"))?;
    let mut out = Vec::new();
    for p in pkgs {
        let name = p.get("name").and_then(|n| n.as_str()).unwrap_or("").to_string();
        if name.is_empty() {
            continue;
        }
        let mut features = BTreeMap::new();
        if let Some(map) = p.get("features").and_then(|f| f.as_object()) {
            for (k, val) in map {
                let deps = val
                    .as_array()
                    .map(|a| {
                        a.iter().filter_map(|d| d.as_str().map(str::to_string)).collect::<Vec<_>>()
                    })
                    .unwrap_or_default();
                features.insert(k.clone(), deps);
            }
        }
        let mut tests = Vec::new();
        let mut src_roots = Vec::new();
        if let Some(tgts) = p.get("targets").and_then(|t| t.as_array()) {
            for t in tgts {
                let is_test = t
                    .get("kind")
                    .and_then(|k| k.as_array())
                    .map(|k| k.iter().any(|x| x.as_str() == Some("test")))
                    .unwrap_or(false);
                if !is_test {
                    // Every OTHER target's root file — lib, bin, example, bench.
                    // These are where a `#[cfg(feature = …)] mod` hides unit tests
                    // that no test-target walk can reach.
                    if let Some(s) = t.get("src_path").and_then(|s| s.as_str()) {
                        let p = PathBuf::from(s);
                        if !src_roots.contains(&p) {
                            src_roots.push(p);
                        }
                    }
                    continue;
                }
                tests.push(TestTarget {
                    name: t.get("name").and_then(|n| n.as_str()).unwrap_or("").to_string(),
                    src_path: PathBuf::from(
                        t.get("src_path").and_then(|s| s.as_str()).unwrap_or(""),
                    ),
                    required_features: t
                        .get("required-features")
                        .or_else(|| t.get("required_features"))
                        .and_then(|r| r.as_array())
                        .map(|a| {
                            a.iter().filter_map(|f| f.as_str().map(str::to_string)).collect()
                        })
                        .unwrap_or_default(),
                });
            }
        }
        // The declared dependency EDGES. `cargo metadata` reports each edge's
        // `features`, `optional` and `uses_default_features` verbatim, so this
        // needs no second manifest reader and no `toml` dependency.
        let mut deps = Vec::new();
        if let Some(ds) = p.get("dependencies").and_then(|d| d.as_array()) {
            for d in ds {
                let package = d.get("name").and_then(|n| n.as_str()).unwrap_or("").to_string();
                if package.is_empty() {
                    continue;
                }
                // `rename` is the token `dep:` and `pkg/feat` strings use.
                let alias = d
                    .get("rename")
                    .and_then(|r| r.as_str())
                    .unwrap_or(&package)
                    .to_string();
                let kind = match d.get("kind").and_then(|k| k.as_str()) {
                    Some("dev") => DepKind::Dev,
                    Some("build") => DepKind::Build,
                    // cargo emits `null` for a normal dependency.
                    _ => DepKind::Normal,
                };
                deps.push(DepEdge {
                    package,
                    alias,
                    optional: d.get("optional").and_then(|o| o.as_bool()).unwrap_or(false),
                    uses_default_features: d
                        .get("uses_default_features")
                        .and_then(|u| u.as_bool())
                        .unwrap_or(true),
                    features: d
                        .get("features")
                        .and_then(|f| f.as_array())
                        .map(|a| a.iter().filter_map(|f| f.as_str().map(str::to_string)).collect())
                        .unwrap_or_default(),
                    kind,
                });
            }
        }
        let manifest_path = PathBuf::from(
            p.get("manifest_path").and_then(|m| m.as_str()).unwrap_or(""),
        );
        out.push(CrateManifest {
            name,
            features,
            tests,
            src_roots,
            deps,
            manifest_path,
        });
    }
    out.sort_by(|a, b| a.name.cmp(&b.name));
    Ok(out)
}

/// Read the workspace's members (features + test targets) via
/// `cargo metadata --no-deps`.
pub fn workspace_crates(repo_root: &Path) -> Result<Vec<CrateManifest>> {
    let out = Command::new(std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
        .args(["metadata", "--no-deps", "--format-version", "1"])
        .current_dir(repo_root)
        .output()
        .with_context(|| format!("spawn cargo metadata in {}", repo_root.display()))?;
    if !out.status.success() {
        return Err(anyhow!(
            "cargo metadata failed in {}: {}",
            repo_root.display(),
            String::from_utf8_lossy(&out.stderr).trim()
        ));
    }
    parse_metadata(&String::from_utf8_lossy(&out.stdout))
}

// ─── declared rescue arms ──────────────────────────────────────────────────

/// One matrix arm a repo declares: an explicit re-invocation of `cargo test`
/// with extra features that rescues otherwise-silenced test files.
///
/// Mirrors what holger's `xtask/tests/holger_matrix.rs` cells actually run:
/// `cargo test -p holger-ui --features gui --test robot_ui_app`.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct MatrixArm {
    /// `-p <package>`. `None` = the arm runs across the whole workspace.
    #[serde(default)]
    pub package: Option<String>,
    /// `--test <name>`. `None` = the arm runs every test target of the package.
    #[serde(default)]
    pub test: Option<String>,
    /// `--features a,b`.
    #[serde(default)]
    pub features: Vec<String>,
    /// `--no-default-features`.
    #[serde(default)]
    pub no_default_features: bool,
    /// `--all-features`.
    #[serde(default)]
    pub all_features: bool,
    /// `-- --ignored`: this arm runs the `#[ignore]`d tests of its target.
    ///
    /// Default `false`, so every arm declared before this field keeps its exact
    /// meaning. It exists because until now an arm could not spell `--ignored` at
    /// all: [`command`](MatrixArm::command) emitted no `--` separator and there was
    /// no field to put after one, so **no declared arm in any repo could reach an
    /// `#[ignore]`d test**. `silenced_tests_guard` does not cover the gap — it
    /// catches tests that compile to nothing, not tests that compile and are then
    /// skipped. Measured cost in knut: a production fix landed, its anchor test was
    /// never turned round, and it sat RED on main for three weeks unseen.
    #[serde(default)]
    pub ignored: bool,
    /// Optional free-text note (where the arm lives, why it exists).
    #[serde(default)]
    pub note: String,
}

impl MatrixArm {
    /// `cargo test --features <features>` across the workspace.
    pub fn features<I, S>(feats: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        Self {
            features: feats.into_iter().map(Into::into).collect(),
            ..Default::default()
        }
    }

    /// Narrow this arm to one package (`-p`).
    pub fn package(mut self, pkg: impl Into<String>) -> Self {
        self.package = Some(pkg.into());
        self
    }

    /// Narrow this arm to one test target (`--test`).
    pub fn test(mut self, t: impl Into<String>) -> Self {
        self.test = Some(t.into());
        self
    }

    /// Attach a human note (surfaces in the report message).
    pub fn note(mut self, n: impl Into<String>) -> Self {
        self.note = n.into();
        self
    }

    /// Does this arm target the given package + test target at all (before
    /// asking whether its features actually satisfy the gate)?
    pub fn targets(&self, package: &str, test: &str) -> bool {
        self.package.as_deref().map(|p| p == package).unwrap_or(true)
            && self.test.as_deref().map(|t| t == test).unwrap_or(true)
    }

    /// Builder: this arm runs its target's `#[ignore]`d tests (`-- --ignored`).
    ///
    /// See the [`ignored`](Self::ignored) field for why an arm needed to be able to
    /// say this at all.
    pub fn ignored(mut self) -> Self {
        self.ignored = true;
        self
    }

    /// The `cargo test …` command line this arm stands for — what shows up in
    /// the report so the rescue is auditable.
    ///
    /// **Rendered FROM the argv the runner uses**, not written a second time beside
    /// it. It used to be a hand-built string that
    /// [`armcheck::arm_args`](crate::armcheck::arm_args) mirrored, with a test
    /// asserting the two agreed — a guard watching two copies, which is precisely
    /// the shape LAW 5 says to replace with one writer. They had already drifted
    /// once: neither could emit `--`, so no arm could name `--ignored`, and the
    /// mirror test was green throughout because both halves were equally wrong.
    pub fn command(&self) -> String {
        let mut parts = vec!["cargo".to_string()];
        parts.extend(crate::armcheck::arm_args(self));
        parts.extend(crate::armcheck::arm_harness_args(self));
        parts.join(" ")
    }
}

/// The set of arms a repo declares (`.nornir/testmatrix-arms.json`).
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct MatrixArms {
    /// The declared re-invocations.
    #[serde(default)]
    pub arms: Vec<MatrixArm>,
}

impl MatrixArms {
    /// Build from arms in memory (for repos that declare them in Rust).
    pub fn new(arms: impl IntoIterator<Item = MatrixArm>) -> Self {
        Self { arms: arms.into_iter().collect() }
    }

    /// Load `<repo_root>/.nornir/testmatrix-arms.json`. A repo with no
    /// declaration file gets an empty set (every gated file must then be
    /// default-reachable) — `Ok(None)` distinguishes "no file" from a parse
    /// error.
    pub fn read(repo_root: &Path) -> Result<Option<MatrixArms>> {
        let p = repo_root.join(ARMS_FILE);
        if !p.exists() {
            return Ok(None);
        }
        let text = std::fs::read_to_string(&p)
            .with_context(|| format!("read {}", p.display()))?;
        let arms: MatrixArms = serde_json::from_str(&text)
            .with_context(|| format!("parse {}", p.display()))?;
        Ok(Some(arms))
    }

    /// [`MatrixArms::read`] collapsing "no file" to an empty declaration.
    pub fn load(repo_root: &Path) -> Result<MatrixArms> {
        Ok(Self::read(repo_root)?.unwrap_or_default())
    }
}

// ─── findings ──────────────────────────────────────────────────────────────

/// Why a gated test file is (or is not) actually run.
///
/// The four verdicts answer the four ways a test can be turned on, in the order
/// a repo should prefer them. See [`classify`] for the sources.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum GateVerdict {
    /// The gate is satisfied by the crate's `default` features — a plain
    /// `cargo test -p <crate>` compiles and runs it.
    Default,
    /// Not default-reachable, but a declared matrix arm re-invokes it with the
    /// features it needs.
    Rescued,
    /// Not reachable from the crate's own `default`, and no declared arm — but a
    /// SIBLING's dependency edge turns the missing features on, so cargo's
    /// workspace-wide feature unification does build it under a plain
    /// `cargo test --workspace`.
    ///
    /// **PASS, and deliberately so**: reporting a live test as dark is the false
    /// positive that gets a detector switched off. It is nonetheless its own
    /// verdict and not folded into [`Default`](Self::Default), because nothing
    /// DECLARES this route: the day the sibling drops the feature from its edge,
    /// or the day someone runs `cargo test -p <crate>` on its own, the file goes
    /// dark in silence. facett measured exactly this on 2026-08-03 —
    /// `cargo test -p facett-map --test upload_replaces_geometry -- --list`
    /// printed `0 tests`, and the same command with `-p facett-demo` added
    /// printed 2, with no `--features` flag in either.
    ViaEdge,
    /// Not reachable by any of the three routes above. **RED** — the file
    /// compiles to zero tests (or is skipped outright) and reports green by
    /// vacuum.
    Silenced,
}

impl GateVerdict {
    /// The [`status`] tag this verdict records.
    pub fn status(self) -> &'static str {
        match self {
            GateVerdict::Silenced => status::FAIL,
            _ => status::PASS,
        }
    }
}

/// One test target that is gated — by a crate-level `#![cfg(...)]`, by the
/// target's `required-features`, or by both — and the guard's verdict on it.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GatedTestFile {
    /// The owning package.
    pub crate_name: String,
    /// The test target name (`--test <this>`).
    pub target: String,
    /// Path relative to the repo root, e.g. `server/ui/tests/robot_ui_app.rs`.
    pub path: String,
    /// The gate source text, e.g. `all(feature = "viz", feature = "testmatrix")`.
    /// **Empty** when the target's only gate is its `required-features`.
    pub cfg: String,
    /// The parsed crate-level gate. `All([])` — vacuously true — when the file
    /// carries no `#![cfg]` and is here only for its `required-features`.
    pub expr: CfgExpr,
    /// Feature names the gate mentions that are NOT reachable from `default` —
    /// the actual dark conjuncts.
    pub missing_features: Vec<String>,
    /// The target's declared `required-features` — the manifest half of the gate.
    ///
    /// Cargo does not merely compile such a target to an empty binary the way an
    /// unsatisfied `#![cfg]` does: it **skips the target entirely**, with no
    /// warning, no error and no line of output. Defaulted so reports serialised
    /// before this field still load.
    #[serde(default)]
    pub required_features: Vec<String>,
    /// The subset of [`required_features`](Self::required_features) not in the
    /// crate's own `default` closure — why cargo skipped the target.
    #[serde(default)]
    pub missing_required: Vec<String>,
    /// For [`GateVerdict::ViaEdge`]: the sibling workspace member whose
    /// dependency edge turns the missing features on.
    #[serde(default)]
    pub selected_via: Option<String>,
    /// How many `#[test]`-family functions the file holds (what is hidden).
    pub hidden_tests: usize,
    /// The verdict.
    pub verdict: GateVerdict,
    /// The `cargo test …` command of the arm that rescued it, when rescued.
    pub rescued_by: Option<String>,
}

impl GatedTestFile {
    /// Is this file dark (never compiled, or compiled to zero tests, and nothing
    /// re-invokes it)?
    pub fn is_silenced(&self) -> bool {
        self.verdict == GateVerdict::Silenced
    }

    /// The gate, as a human phrase — both halves when both are present, so a
    /// report line never attributes a `required-features` skip to a `#![cfg]`
    /// the file does not carry.
    pub fn gate_phrase(&self) -> String {
        match (self.cfg.is_empty(), self.required_features.is_empty()) {
            (true, true) => "no gate".to_string(),
            (false, true) => format!("#![cfg({})]", self.cfg),
            (true, false) => {
                format!("required-features = [{}]", self.required_features.join(", "))
            }
            (false, false) => format!(
                "required-features = [{}] + #![cfg({})]",
                self.required_features.join(", "),
                self.cfg
            ),
        }
    }

    /// Every feature that has to be on for this target to run and is not in the
    /// crate's own `default` closure — both gate sources, deduplicated.
    pub fn all_missing(&self) -> Vec<String> {
        let mut v: BTreeSet<String> = self.missing_features.iter().cloned().collect();
        v.extend(self.missing_required.iter().cloned());
        v.into_iter().collect()
    }

    /// The report line / row message.
    pub fn message(&self) -> String {
        let gate = self.gate_phrase();
        match self.verdict {
            GateVerdict::Default => {
                format!("{} · {gate} satisfied by default features", self.path)
            }
            GateVerdict::Rescued => format!(
                "{} · {gate} not default-reachable, rescued by `{}`",
                self.path,
                self.rescued_by.as_deref().unwrap_or("<arm>")
            ),
            GateVerdict::ViaEdge => format!(
                "{} · {gate} not reachable from `{}`'s own `default`, but `{}` turns [{}] on \
                 along a dependency edge, so `cargo test --workspace` DOES build it \
                 ({} test fn{}). UNDECLARED: `cargo test -p {}` on its own runs none of them, \
                 and the day that edge drops the feature this file goes dark in silence — \
                 declare the run in {}",
                self.path,
                self.crate_name,
                self.selected_via.as_deref().unwrap_or("a sibling"),
                self.all_missing().join(", "),
                self.hidden_tests,
                if self.hidden_tests == 1 { "" } else { "s" },
                self.crate_name,
                ARMS_FILE,
            ),
            GateVerdict::Silenced if self.cfg.is_empty() => format!(
                "{} · `required-features = [{}]` — cargo SILENTLY SKIPS this target on a \
                 plain `cargo test` (no warning, no error, no line of output), so its {} \
                 test fn{} {} never compiled at all; feature{} {} not in the crate's \
                 `default` closure, not turned on by any sibling's dependency edge, and no \
                 declared matrix arm re-invokes this target — declare one in {} or move the \
                 feature into `default`",
                self.path,
                self.required_features.join(", "),
                self.hidden_tests,
                if self.hidden_tests == 1 { "" } else { "s" },
                if self.hidden_tests == 1 { "is" } else { "are" },
                if self.missing_required.len() == 1 { "" } else { "s" },
                self.missing_required.join(", "),
                ARMS_FILE,
            ),
            GateVerdict::Silenced => format!(
                "{} · {gate} compiles to ZERO tests under `cargo test` \
                 ({} hidden test fn{}); feature{} {} not in the crate's `default` \
                 closure, not turned on by any sibling's dependency edge, and no declared \
                 matrix arm re-invokes this file — \
                 declare one in {} or move the feature into `default`",
                self.path,
                self.hidden_tests,
                if self.hidden_tests == 1 { "" } else { "s" },
                if self.all_missing().len() == 1 { "" } else { "s" },
                self.all_missing().join(", "),
                ARMS_FILE,
            ),
        }
    }
}

/// The guard's structured finding for a whole repo — the `gated-tests` sibling
/// of [`crate::GateReport`]: a verdict that can fail a gate, not just print.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GatedTestReport {
    /// The test-run identity these rows join on.
    pub run_id: String,
    /// The repo the scan ran in (partition key on every row).
    pub repo: String,
    /// Every gated test file found, in path order (all three verdicts).
    pub files: Vec<GatedTestFile>,
    /// How many test targets were inspected in total (gated or not).
    pub scanned: usize,
    /// The arms the repo declared (echoed so the excuse is visible).
    pub arms: Vec<MatrixArm>,
}

impl GatedTestReport {
    /// The dark files — the actionable set.
    pub fn silenced(&self) -> Vec<&GatedTestFile> {
        self.files.iter().filter(|f| f.is_silenced()).collect()
    }

    /// Total `#[test]` functions hidden by the dark files.
    pub fn hidden_tests(&self) -> usize {
        self.silenced().iter().map(|f| f.hidden_tests).sum()
    }

    /// The files that run ONLY through cargo's workspace feature unification —
    /// green today, undeclared, and one dependency-edge edit from dark.
    pub fn via_edge(&self) -> Vec<&GatedTestFile> {
        self.files.iter().filter(|f| f.verdict == GateVerdict::ViaEdge).collect()
    }

    /// The gated targets whose gate is (also) their `required-features` — the
    /// manifest half, which cargo skips SILENTLY rather than compiling empty.
    pub fn required_features_gated(&self) -> Vec<&GatedTestFile> {
        self.files.iter().filter(|f| !f.required_features.is_empty()).collect()
    }

    /// GREEN ⟺ no silenced file. The HARD-zero gate.
    pub fn is_green(&self) -> bool {
        self.silenced().is_empty()
    }

    /// One-line human summary for the CLI.
    pub fn summary(&self) -> String {
        let rescued = self.files.iter().filter(|f| f.verdict == GateVerdict::Rescued).count();
        format!(
            "{} test target{} scanned · {} feature-gated ({} of them by `required-features`) · \
             {} rescued by declared arms · {} run only through a dependency edge · \
             {} SILENCED ({} hidden test fns) — {}",
            self.scanned,
            if self.scanned == 1 { "" } else { "s" },
            self.files.len(),
            self.required_features_gated().len(),
            rescued,
            self.via_edge().len(),
            self.silenced().len(),
            self.hidden_tests(),
            if self.is_green() { "GREEN" } else { "RED" },
        )
    }

    /// The persisted rows: one [`TestResultRow`] per gated file on the
    /// `gated-tests` aspect. Silenced → `fail`, otherwise `pass`; `metric`
    /// carries the hidden-test count so the matrix can rank by damage.
    pub fn rows(&self) -> Vec<TestResultRow> {
        let ts = now_micros();
        self.files
            .iter()
            .map(|f| TestResultRow {
                run_id: self.run_id.clone(),
                repo: self.repo.clone(),
                suite: f.crate_name.clone(),
                test_name: format!("gated-tests::{}", f.target),
                status: f.verdict.status().to_string(),
                duration_ms: 0.0,
                ts_micros: ts,
                message: f.message(),
                aspect: ASPECT_GATED_TESTS.to_string(),
                metric: f.hidden_tests as f64,
            })
            .collect()
    }
}

fn now_micros() -> i64 {
    use std::time::{SystemTime, UNIX_EPOCH};
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_micros() as i64)
        .unwrap_or(0)
}

// ─── the audit ─────────────────────────────────────────────────────────────

/// Classify one already-read test target against a crate's features, the
/// declared arms, and (optionally) the workspace-resolved feature sets. Pure —
/// the unit-testable heart of the guard.
///
/// # The four sources, and why `required-features` is not optional
///
/// A test can be turned on FOUR ways, and this function has to weigh all four or
/// its verdict is a guess:
///
/// 1. the crate's own `default` closure — a plain `cargo test -p <crate>`;
/// 2. a declared matrix arm's `--features` ([`MatrixArms`]);
/// 3. the target's own **`required-features`** — the manifest half of the gate;
/// 4. a feature switched on along a **dependency edge**, which cargo's
///    workspace-wide feature unification then applies to the target's own
///    package ([`crate::testsurface::resolve`]).
///
/// Source 3 was parsed into [`TestTarget::required_features`] and **never
/// consulted in the verdict** until 2026-08-26, and the shape of that hole is
/// worse than a field nobody read: an unsatisfied `#![cfg]` compiles the file to
/// an empty binary that at least prints `running 0 tests`, while an unsatisfied
/// `required-features` makes cargo **skip the target outright** — no warning, no
/// error, no line of output, and no row here either, because a target with no
/// crate-level `#![cfg]` returned `None` at the first line. MEASURED across the
/// constellation on 2026-08-26: 9 live test targets in 4 repos carry
/// `required-features`, NONE of them carries a crate-level `#![cfg]`, so all 9
/// were invisible to this guard. Two are genuinely dark —
/// `holger/server/cli/tests/appliance_gate.rs` (`appliance`, 6 test fns) and
/// `dwarves/crates/dwarves/tests/min_req_floor_live.rs` (`release`, 4 fns).
///
/// Source 4 is what keeps the fix from crying wolf. The other seven of the nine
/// (facett's four umbrella targets, gunnar-lfs `interop`) are NOT dark: a
/// `default_set`-only reading would have called them silenced, and
/// `facett-demo`'s and `gunnar-cli`'s dependency edges turn their features on
/// under `cargo test --workspace`. They get [`GateVerdict::ViaEdge`], which
/// passes and says so.
///
/// `resolved` is [`crate::testsurface::resolve`] run on the baseline
/// `cargo test --workspace` configuration. Pass `None` when the caller has only
/// one manifest in hand; source 4 is then simply not available and a target that
/// would be `ViaEdge` reports `Silenced`.
pub fn classify(
    krate: &CrateManifest,
    target: &TestTarget,
    src: &str,
    rel_path: &str,
    arms: &[MatrixArm],
    resolved: Option<&crate::testsurface::Resolved>,
) -> Option<GatedTestFile> {
    // The crate-level `#![cfg]` half. Absent is not "no finding" any more — the
    // target may still be gated by its manifest — so it becomes the vacuously
    // true gate `all()`.
    let cfg = extract_crate_cfg(src);
    let expr = match &cfg {
        // An unparseable gate stays out of the report exactly as before: this
        // guard reports what it can read, and guesses at nothing.
        Some(c) => parse_cfg_expr(c)?,
        None => CfgExpr::All(Vec::new()),
    };
    let required = &target.required_features;
    if !expr.mentions_features() && required.is_empty() {
        // Neither gate source mentions a feature: a purely platform-conditional
        // (or entirely ungated) file is not this guard's business.
        return None;
    }

    let defaults = krate.default_set();
    let hidden_tests = count_test_fns(src);
    let missing_features: Vec<String> = expr
        .features()
        .into_iter()
        .filter(|f| !defaults.contains(f))
        .collect();
    let missing_required: Vec<String> =
        required.iter().filter(|f| !defaults.contains(*f)).cloned().collect();

    // Both halves must hold for the target to actually run: cargo has to SELECT
    // it (required-features) and the file has to COMPILE to something (the cfg).
    let runs_under = |enabled: &BTreeSet<String>| {
        required.iter().all(|f| enabled.contains(f)) && expr.compiles_with(enabled)
    };

    let mut selected_via = None;
    let (verdict, rescued_by) = if runs_under(&defaults) {
        (GateVerdict::Default, None)
    } else if let Some(a) = arms
        .iter()
        .filter(|a| a.targets(&krate.name, &target.name))
        .find(|a| runs_under(&krate.arm_set(a)))
    {
        (GateVerdict::Rescued, Some(a.command()))
    } else {
        // The fourth source. Only ever consulted to AVOID a red, never to make
        // one: `resolved` is a superset of `defaults`, and a gate that already
        // passed above is never re-evaluated against it.
        let via = resolved.and_then(|r| {
            let enabled = r.enabled.get(&krate.name)?;
            if !runs_under(enabled) {
                return None;
            }
            // Name the sibling responsible for the first feature `defaults`
            // lacked — that is the edge whose removal takes the file dark.
            Some(
                missing_required
                    .iter()
                    .chain(missing_features.iter())
                    .find_map(|f| r.via_edge.get(&(krate.name.clone(), f.clone())).cloned())
                    .unwrap_or_else(|| "a sibling".to_string()),
            )
        });
        match via {
            Some(who) => {
                selected_via = Some(who);
                (GateVerdict::ViaEdge, None)
            }
            None => (GateVerdict::Silenced, None),
        }
    };

    Some(GatedTestFile {
        crate_name: krate.name.clone(),
        target: target.name.clone(),
        path: rel_path.to_string(),
        cfg: cfg.unwrap_or_default(),
        expr,
        missing_features,
        required_features: required.clone(),
        missing_required,
        selected_via,
        hidden_tests,
        verdict,
        rescued_by,
    })
}

/// **The guard.** Walk every `tests/*.rs` of every workspace member, resolve
/// each crate's `default` feature closure, and report every file whose
/// crate-level `#![cfg]` is not satisfied by default and that no declared
/// matrix arm re-invokes.
///
/// The result is a structured [`GatedTestReport`]; call
/// [`GatedTestReport::is_green`] to fail a gate and [`GatedTestReport::rows`]
/// to persist it through any [`crate::TestSink`].
pub fn audit_gated_tests(
    repo_root: &Path,
    repo: &str,
    run_id: &str,
    arms: &MatrixArms,
) -> Result<GatedTestReport> {
    let crates = workspace_crates(repo_root)?;
    // The FOURTH source, resolved once: what `cargo test --workspace` really has
    // on in each member after feature unification across the dependency edges.
    // Pure — it compiles nothing and spawns nothing.
    let resolved = crate::testsurface::resolve(&crates, &crate::testsurface::default_config(&crates));
    let mut files = Vec::new();
    let mut scanned = 0usize;
    for krate in &crates {
        for target in &krate.tests {
            scanned += 1;
            let Ok(src) = std::fs::read_to_string(&target.src_path) else {
                continue;
            };
            let rel = target
                .src_path
                .strip_prefix(repo_root)
                .unwrap_or(&target.src_path)
                .to_string_lossy()
                .to_string();
            if let Some(f) = classify(krate, target, &src, &rel, &arms.arms, Some(&resolved)) {
                files.push(f);
            }
        }
    }
    files.sort_by(|a, b| a.path.cmp(&b.path));
    Ok(GatedTestReport {
        run_id: run_id.to_string(),
        repo: repo.to_string(),
        files,
        scanned,
        arms: arms.arms.clone(),
    })
}

/// Convenience: audit `repo_root` with the arms it declares in
/// `.nornir/testmatrix-arms.json`, using the directory name as the repo tag.
pub fn audit_repo(repo_root: &Path, run_id: &str) -> Result<GatedTestReport> {
    let repo = repo_root
        .canonicalize()
        .ok()
        .and_then(|p| p.file_name().map(|n| n.to_string_lossy().to_string()))
        .unwrap_or_else(|| "unknown".to_string());
    let arms = MatrixArms::load(repo_root)?;
    audit_gated_tests(repo_root, &repo, run_id, &arms)
}

/// **The whole adoption, in one call** — a repo's own `cargo test` goes RED when
/// any of its test files compiles to zero tests.
///
/// This exists because the guard above was, on 2026-08-01, consumed by **nobody**.
/// Ten repos carried a hand-written `.nornir/testmatrix-arms.json` from the
/// 2026-07-21 sweep, and not one of them ever ran [`audit_repo`]: the declarations
/// were prose. In the eleven days that followed, 38 fresh dark test targets
/// appeared across the fleet, hiding 122 `#[test]` fns, and nothing said a word.
/// A guard that ships and is never invoked is indistinguishable from a guard that
/// does not exist.
///
/// So the adoption had to cost four lines, not forty, or it would not happen. Each
/// repo writes exactly:
///
/// ```no_run
/// #[test]
/// fn this_repo_has_no_silenced_test_files() {
///     nornir_testmatrix::assert_no_silenced_tests(env!("CARGO_MANIFEST_DIR").as_ref());
/// }
/// ```
///
/// # Why this cannot pass vacuously
///
/// Three ways an "all green" could be a lie, all three refused here:
///
/// 1. **Wrong root.** Point it at a directory with no workspace and `cargo
///    metadata` fails — an `Err` is a panic, never a green.
/// 2. **Zero targets scanned.** A root that resolves but contains no `tests/*.rs`
///    at all (a stale path, a `--exclude`d member set, a metadata call that
///    silently degraded) reports `0 silenced` because it looked at nothing. That
///    is the exact shape LAW 2 calls "check output, not exit code", so
///    `scanned == 0` is a FAILURE, not a pass.
/// 3. **Arms that rescue nothing.** Declaring an arm makes a dark file green. This
///    call deliberately does NOT verify the arms compile — that is
///    [`crate::armcheck::verify_arms`], which costs a build. A repo that declares
///    arms should run BOTH; the panic message says so when arms are present.
///
/// The one thing it cannot decide for you is where the repo root is. Pass a path
/// that contains the workspace `Cargo.toml`; from a root-package test that is
/// `env!("CARGO_MANIFEST_DIR")`, from a crate `N` levels down it is that path's
/// `.ancestors().nth(N)`.
pub fn assert_no_silenced_tests(repo_root: &Path) {
    let rep = match audit_repo(repo_root, "self-audit") {
        Ok(r) => r,
        Err(e) => panic!(
            "the silenced-test audit could not run at {} — this is a RED, not a skip: {e:#}",
            repo_root.display()
        ),
    };

    assert!(
        rep.scanned > 0,
        "the silenced-test audit scanned ZERO test targets under {} — it proved nothing. \
         Either the path is not the workspace root, or `cargo metadata --no-deps` \
         returned no members with test targets. A guard that looks at nothing and \
         reports green is the disease it exists to cure.",
        repo_root.display()
    );

    if rep.is_green() {
        return;
    }

    let mut msg = format!("SILENCED TESTS in `{}` — {}\n", rep.repo, rep.summary());
    for f in rep.silenced() {
        msg.push_str(&format!("  · {}\n", f.message()));
    }
    msg.push_str(
        "\nEach target above either compiles to an EMPTY test binary under a plain \
         `cargo test` and prints `running 0 tests ... ok`, or — when the gate is the \
         target's own `required-features` — is SKIPPED by cargo with no output at all, \
         which is worse: there is not even a `running 0 tests` line to notice. Fix by \
         either (a) making the feature default-reachable, or (b) declaring the \
         re-invocation that runs it in .nornir/testmatrix-arms.json — and then proving \
         that arm real with `nornir_testmatrix::verify_arms`, because a declared arm \
         nobody compiles is silence wearing a badge.",
    );
    panic!("{msg}");
}

#[cfg(test)]
mod tests {
    use super::*;

    fn table(pairs: &[(&str, &[&str])]) -> BTreeMap<String, Vec<String>> {
        pairs
            .iter()
            .map(|(k, v)| (k.to_string(), v.iter().map(|s| s.to_string()).collect()))
            .collect()
    }

    #[test]
    fn extracts_the_crate_level_cfg_past_doc_comments() {
        let src = r#"
//! A doc comment that says #![cfg(feature = "decoy")] in prose.
/* block /* nested */ #![cfg(feature = "decoy2")] */
#![allow(dead_code)]
#![cfg(all(feature = "viz", feature = "server", feature = "testmatrix"))]

#[test]
fn t() {}
"#;
        assert_eq!(
            extract_crate_cfg(src).as_deref(),
            Some(r#"all(feature = "viz", feature = "server", feature = "testmatrix")"#)
        );
    }

    /// The false positive that motivated the prologue walk: an UNGATED tripwire
    /// test that asserts a sibling file keeps its gate — so the text `#![cfg(`
    /// appears in a string literal and a doc comment. The old textual scan
    /// reported it SILENCED with missing feature `heavy-smoke"`, trailing quote
    /// and all.
    #[test]
    fn a_gate_quoted_in_a_string_or_doc_comment_is_not_the_files_gate() {
        let src = r####"
//! Tripwire: the smoke test must stay `#![cfg(feature = "heavy-smoke")]`.
use std::fs;

#[test]
fn smoke_stays_gated() {
    let smoke = fs::read_to_string("tests/heavy_smoke.rs").unwrap();
    assert!(smoke.contains("#![cfg(feature = \"heavy-smoke\")]"));
    assert!(smoke.contains(r#"#![cfg(feature = "raw-quoted")]"#));
    /* even in a block comment: #![cfg(feature = "blocked")] */
}
"####;
        assert_eq!(
            extract_crate_cfg(src),
            None,
            "the file has no crate-level gate; every `#![cfg(` in it is quoted or commented"
        );
        assert!(
            classify(&krate(), &target("wiring"), src, "tests/wiring.rs", &[], None).is_none(),
            "an ungated tripwire test must not be a finding at all"
        );
    }

    /// The mirror-image failure: do not tighten so far that a real gate is
    /// missed. Indentation, a doc header, a preceding `#![allow]`, and a
    /// multi-line `all(...)` must all still be detected.
    #[test]
    fn an_indented_multi_line_gate_after_other_inner_attrs_is_still_found() {
        let src = r#"
//! Header prose.
#![allow(dead_code)]
#![cfg_attr(docsrs, feature(doc_cfg))]
   #![cfg(all(
       feature = "light",
       // a comment mid-gate
       feature = "heavy"
   ))]

#[test]
fn a() {}
"#;
        let cfg = extract_crate_cfg(src).expect("multi-line indented gate must be found");
        let e = parse_cfg_expr(&cfg).expect("and must parse");
        assert_eq!(e.features(), ["heavy", "light"].iter().map(|s| s.to_string()).collect());

        let f = classify(&krate(), &target("dark"), src, "tests/dark.rs", &[], None).unwrap();
        assert_eq!(f.verdict, GateVerdict::Silenced, "`heavy` is not in default");
        assert_eq!(f.missing_features, vec!["heavy".to_string()]);
    }

    /// A `)` or `]` inside a feature name must not truncate the capture, and the
    /// prologue walk must step over earlier attributes whose brackets are
    /// themselves quoted.
    #[test]
    fn bracket_matching_is_string_aware() {
        let src = "#![doc = \"a ] bracket in prose\"]\n#![cfg(feature = \"we)ird\")]\n#[test]\nfn a() {}\n";
        assert_eq!(extract_crate_cfg(src).as_deref(), Some(r#"feature = "we)ird""#));
    }

    #[test]
    fn cfg_attr_is_not_a_cfg_gate() {
        let src = "#![cfg_attr(docsrs, feature(doc_cfg))]\n#[test]\nfn t() {}\n";
        assert_eq!(extract_crate_cfg(src), None);
    }

    #[test]
    fn all_any_not_and_nesting_parse_and_evaluate() {
        let e = parse_cfg_expr(
            r#"all(feature = "a", any(feature = "b", feature = "c"), not(feature = "d"))"#,
        )
        .unwrap();
        let on = |v: &[&str]| -> BTreeSet<String> {
            v.iter().map(|s| s.to_string()).collect()
        };
        assert_eq!(e.eval(&on(&["a", "b"])), Some(true));
        assert_eq!(e.eval(&on(&["a", "b", "d"])), Some(false), "not(d) fails");
        assert_eq!(e.eval(&on(&["b"])), Some(false), "a missing");
        assert_eq!(e.eval(&on(&["a"])), Some(false), "neither b nor c");
        assert_eq!(
            e.features(),
            ["a", "b", "c", "d"].iter().map(|s| s.to_string()).collect()
        );
    }

    #[test]
    fn the_camouflage_shape_two_on_one_off_is_definitively_false() {
        // `all(default-on, default-on, default-OFF)` — reads like its running
        // siblings, compiles to nothing.
        let e =
            parse_cfg_expr(r#"all(feature = "viz", feature = "server", feature = "testmatrix")"#)
                .unwrap();
        let defaults: BTreeSet<String> =
            ["viz", "server"].iter().map(|s| s.to_string()).collect();
        assert!(!e.compiles_with(&defaults));
    }

    #[test]
    fn non_feature_predicates_are_unknown_never_silenced() {
        let e = parse_cfg_expr(r#"not(target_os = "windows")"#).unwrap();
        assert_eq!(e.eval(&BTreeSet::new()), None);
        assert!(e.compiles_with(&BTreeSet::new()));

        let e = parse_cfg_expr(r#"all(unix, feature = "a")"#).unwrap();
        assert!(!e.compiles_with(&BTreeSet::new()), "the feature conjunct is decisive");
        assert!(e.compiles_with(&["a".to_string()].into_iter().collect()));
    }

    #[test]
    fn default_closure_is_transitive_and_skips_cross_crate_entries() {
        let t = table(&[
            ("default", &["light", "dep:serde", "other/feat"]),
            ("light", &["tiny"]),
            ("tiny", &[]),
            ("heavy", &[]),
        ]);
        let d = default_features(&t);
        assert!(d.contains("light") && d.contains("tiny"), "transitive: {d:?}");
        assert!(!d.contains("heavy"));
        assert!(!d.iter().any(|f| f.contains('/') || f.starts_with("dep:")));
    }

    fn krate() -> CrateManifest {
        CrateManifest {
            name: "demo".into(),
            // Widened by the gated-tests work: these three carry the SECOND and THIRD
            // sources of "which feature is on" — the library target's own `#[cfg]`ed
            // modules, and features turned on along a dependency EDGE. This fixture
            // exercises neither, so empty is the honest value, not a placeholder.
            src_roots: Vec::new(),
            deps: Vec::new(),
            manifest_path: Default::default(),
            features: table(&[
                ("default", &["light"]),
                ("light", &[]),
                ("heavy", &[]),
                ("bundle", &["heavy"]),
            ]),
            tests: vec![],
        }
    }

    fn target(n: &str) -> TestTarget {
        TestTarget {
            name: n.into(),
            src_path: PathBuf::from(format!("tests/{n}.rs")),
            required_features: Vec::new(),
        }
    }

    /// A target carrying `required-features`, the way `cargo metadata` reports
    /// holger's `appliance_gate` and facett's `matrix`.
    fn target_rf(n: &str, rf: &[&str]) -> TestTarget {
        TestTarget {
            name: n.into(),
            src_path: PathBuf::from(format!("tests/{n}.rs")),
            required_features: rf.iter().map(|s| (*s).to_string()).collect(),
        }
    }

    /// A [`crate::testsurface::Resolved`] that says `feat` is on in `demo`
    /// because `who`'s dependency edge turns it on — the FOURTH source.
    fn edge(feat: &str, who: &str) -> crate::testsurface::Resolved {
        let mut r = crate::testsurface::Resolved::default();
        r.enabled.insert(
            "demo".to_string(),
            ["default", "light", feat].iter().map(|s| s.to_string()).collect(),
        );
        r.via_edge.insert(("demo".to_string(), feat.to_string()), who.to_string());
        r
    }

    const DARK: &str = "#![cfg(all(feature = \"light\", feature = \"heavy\"))]\n#[test]\nfn a() {}\n#[tokio::test]\nasync fn b() {}\n";
    const LIT: &str = "#![cfg(feature = \"light\")]\n#[test]\nfn a() {}\n";
    /// No crate-level gate at all — the shape ALL NINE real `required-features`
    /// test targets in the constellation have.
    const NOCFG: &str = "//! No `#![cfg]` anywhere.\n#[test]\nfn a() {}\n#[test]\nfn b() {}\n";

    /// **The hole.** `required_features` was parsed into [`TestTarget`] and never
    /// consulted, and a target with no crate-level `#![cfg]` bailed out of
    /// `classify` on its first line — so it produced no row of any kind. This is
    /// `holger/server/cli/tests/appliance_gate.rs`: `required-features =
    /// ["appliance"]`, six `#[test]` fns, no `#![cfg]`, no arm, no sibling edge,
    /// and holger's own `silenced_tests_guard` GREEN over it.
    #[test]
    fn a_required_features_target_with_no_cfg_is_a_finding_and_is_silenced() {
        let f = classify(
            &krate(),
            &target_rf("appliance_gate", &["heavy"]),
            NOCFG,
            "tests/appliance_gate.rs",
            &[],
            None,
        )
        .expect("a manifest-gated target IS a finding, cfg or no cfg");
        assert_eq!(f.verdict, GateVerdict::Silenced);
        assert_eq!(f.cfg, "", "there is no crate-level gate to report");
        assert_eq!(f.required_features, vec!["heavy".to_string()]);
        assert_eq!(f.missing_required, vec!["heavy".to_string()]);
        assert_eq!(f.hidden_tests, 2);
        assert_eq!(f.gate_phrase(), "required-features = [heavy]");
        let m = f.message();
        assert!(m.contains("SILENTLY SKIPS"), "names the real mechanism: {m}");
        assert!(!m.contains("#![cfg"), "must not invent a gate the file lacks: {m}");
    }

    /// **The sharpest regression**: a file whose `#![cfg]` IS satisfied by
    /// default while its `required-features` is not. Before the fix this was
    /// `Default` — a green PASS row on a target cargo never compiles, which is
    /// precisely the vacuum this guard exists to refuse.
    #[test]
    fn a_satisfied_cfg_does_not_excuse_an_unsatisfied_required_features() {
        let f = classify(
            &krate(),
            &target_rf("lit", &["heavy"]),
            LIT,
            "tests/lit.rs",
            &[],
            None,
        )
        .unwrap();
        assert_eq!(
            f.verdict,
            GateVerdict::Silenced,
            "the cfg passes and cargo still skips the target"
        );
        assert!(f.missing_features.is_empty(), "the cfg misses nothing");
        assert_eq!(f.missing_required, vec!["heavy".to_string()]);
        assert_eq!(
            f.gate_phrase(),
            "required-features = [heavy] + #![cfg(feature = \"light\")]",
            "both halves are named, so the reader is not sent to the wrong one"
        );
    }

    /// `required-features` all in `default` — gunnar-server's two `wg-client`
    /// targets. A PASS row, not silence and not a red.
    #[test]
    fn required_features_inside_the_default_closure_are_a_pass() {
        let f = classify(&krate(), &target_rf("ok", &["light"]), NOCFG, "tests/ok.rs", &[], None)
            .unwrap();
        assert_eq!(f.verdict, GateVerdict::Default);
        assert!(f.missing_required.is_empty());
        assert_eq!(f.verdict.status(), status::PASS);
    }

    /// A declared arm must turn on the `required-features` too, not just satisfy
    /// the `#![cfg]` — otherwise the arm's own cargo invocation skips the target
    /// and the "rescue" rescues nothing.
    #[test]
    fn an_arm_rescues_a_required_features_target_only_if_it_names_the_feature() {
        let good = MatrixArm::features(["heavy"]).package("demo").test("gpu");
        let f = classify(
            &krate(),
            &target_rf("gpu", &["heavy"]),
            NOCFG,
            "tests/gpu.rs",
            &[good],
            None,
        )
        .unwrap();
        assert_eq!(f.verdict, GateVerdict::Rescued);
        assert_eq!(
            f.rescued_by.as_deref(),
            Some("cargo test -p demo --features heavy --test gpu")
        );

        // An arm that satisfies nothing the target needs is not a rescue: cargo
        // would run it and silently skip the target, printing a green run.
        let near = MatrixArm::features(["light"]).package("demo").test("gpu");
        let f = classify(
            &krate(),
            &target_rf("gpu", &["heavy"]),
            NOCFG,
            "tests/gpu.rs",
            &[near],
            None,
        )
        .unwrap();
        assert_eq!(f.verdict, GateVerdict::Silenced);
    }

    /// **The FOURTH source.** facett's four umbrella targets and
    /// `gunnar-lfs/interop` are lit by a sibling's dependency edge, so cargo's
    /// workspace-wide unification really does run them. Calling those SILENCED
    /// would be five false reds out of nine findings — and a detector that
    /// reports a live test as dark is a detector people switch off.
    #[test]
    fn a_dependency_edge_saves_a_required_features_target_from_a_false_red() {
        let r = edge("heavy", "demo-driver");
        let f = classify(
            &krate(),
            &target_rf("matrix", &["heavy"]),
            NOCFG,
            "tests/matrix.rs",
            &[],
            Some(&r),
        )
        .unwrap();
        assert_eq!(f.verdict, GateVerdict::ViaEdge);
        assert_eq!(f.verdict.status(), status::PASS, "green — it really does run");
        assert_eq!(f.selected_via.as_deref(), Some("demo-driver"));
        let m = f.message();
        assert!(m.contains("demo-driver"), "names the sibling holding it up: {m}");
        assert!(m.contains("UNDECLARED"), "and does not pretend it is declared: {m}");

        // Same target, no resolver in hand: the fourth source is simply not
        // available and the honest answer is the conservative one.
        let f = classify(
            &krate(),
            &target_rf("matrix", &["heavy"]),
            NOCFG,
            "tests/matrix.rs",
            &[],
            None,
        )
        .unwrap();
        assert_eq!(f.verdict, GateVerdict::Silenced);

        // And an edge that turns on a DIFFERENT feature rescues nothing.
        let r = edge("bundle-only-elsewhere", "demo-driver");
        let f = classify(
            &krate(),
            &target_rf("matrix", &["heavy"]),
            NOCFG,
            "tests/matrix.rs",
            &[],
            Some(&r),
        )
        .unwrap();
        assert_eq!(f.verdict, GateVerdict::Silenced);
    }

    /// A DECLARED arm outranks an undeclared edge: the repo said how it runs the
    /// file, and that statement is the one worth reporting.
    #[test]
    fn a_declared_arm_outranks_a_dependency_edge() {
        let r = edge("heavy", "demo-driver");
        let arm = MatrixArm::features(["heavy"]).package("demo").test("matrix");
        let f = classify(
            &krate(),
            &target_rf("matrix", &["heavy"]),
            NOCFG,
            "tests/matrix.rs",
            &[arm],
            Some(&r),
        )
        .unwrap();
        assert_eq!(f.verdict, GateVerdict::Rescued);
        assert!(f.selected_via.is_none());
    }

    #[test]
    fn default_on_gate_is_not_reported() {
        let f = classify(&krate(), &target("lit"), LIT, "tests/lit.rs", &[], None).unwrap();
        assert_eq!(f.verdict, GateVerdict::Default);
        assert!(!f.is_silenced());
    }

    #[test]
    fn default_off_gate_is_silenced_and_counts_its_hidden_tests() {
        let f = classify(&krate(), &target("dark"), DARK, "tests/dark.rs", &[], None).unwrap();
        assert_eq!(f.verdict, GateVerdict::Silenced);
        assert_eq!(f.hidden_tests, 2, "counts #[test] and #[tokio::test]");
        assert_eq!(f.missing_features, vec!["heavy".to_string()]);
        assert!(f.message().contains("ZERO tests"));
    }

    #[test]
    fn a_declared_arm_rescues_the_file_and_a_near_miss_arm_does_not() {
        // holger's shape: `cargo test -p demo --features heavy --test dark`.
        let good = MatrixArm::features(["heavy"]).package("demo").test("dark");
        let f = classify(&krate(), &target("dark"), DARK, "tests/dark.rs", &[good], None).unwrap();
        assert_eq!(f.verdict, GateVerdict::Rescued);
        assert_eq!(
            f.rescued_by.as_deref(),
            Some("cargo test -p demo --features heavy --test dark")
        );

        // Near-miss (skade's `lineage` vs `lineage-http`): a different feature
        // that does not imply the needed one rescues NOTHING.
        let near = MatrixArm::features(["light"]).package("demo").test("dark");
        let f = classify(&krate(), &target("dark"), DARK, "tests/dark.rs", &[near], None).unwrap();
        assert_eq!(f.verdict, GateVerdict::Silenced);

        // Wrong test target — the arm does not target this file.
        let other = MatrixArm::features(["heavy"]).package("demo").test("something_else");
        let f = classify(&krate(), &target("dark"), DARK, "tests/dark.rs", &[other], None).unwrap();
        assert_eq!(f.verdict, GateVerdict::Silenced);
    }

    #[test]
    fn workspace_wide_and_transitive_arms_rescue() {
        // No `package`, no `test` → `cargo test --workspace --features bundle`,
        // and `bundle` transitively enables `heavy`.
        let wide = MatrixArm::features(["bundle"]);
        let f = classify(&krate(), &target("dark"), DARK, "tests/dark.rs", &[wide], None).unwrap();
        assert_eq!(f.verdict, GateVerdict::Rescued);

        let all = MatrixArm { all_features: true, ..Default::default() };
        let f = classify(&krate(), &target("dark"), DARK, "tests/dark.rs", &[all], None).unwrap();
        assert_eq!(f.verdict, GateVerdict::Rescued);
    }

    #[test]
    fn no_default_features_arm_can_lose_a_conjunct() {
        // `--no-default-features --features heavy` drops `light` → still dark.
        let arm = MatrixArm {
            features: vec!["heavy".into()],
            no_default_features: true,
            ..Default::default()
        };
        let f = classify(&krate(), &target("dark"), DARK, "tests/dark.rs", &[arm], None).unwrap();
        assert_eq!(f.verdict, GateVerdict::Silenced);
    }

    #[test]
    fn every_test_attribute_shape_is_counted_and_lookalikes_are_not() {
        // The real shapes the constellation's silenced files carry — knut's
        // `robot_web_viewer.rs` is `#[tokio::test(flavor = "multi_thread")]`,
        // which a naive `ends_with("test]")` match misses entirely.
        let src = r#"
#[test]
fn a() {}
#[tokio::test]
async fn b() {}
#[tokio::test(flavor = "multi_thread")]
async fn c() {}
#[actix_web::test]
async fn d() {}
    #[test]
    fn nested_and_indented() {}
// #[test] in a comment does not count
#[test_case(1, 2)]
fn not_a_test_attr() {}
#[should_panic]
#[ignore = "test"]
#[cfg(test)]
fn neither() {}
"#;
        assert_eq!(count_test_fns(src), 5);
    }

    #[test]
    fn ungated_files_are_not_findings() {
        assert!(
            classify(&krate(), &target("plain"), "#[test]\nfn a() {}\n", "t.rs", &[], None)
                .is_none()
        );
    }

    #[test]
    fn metadata_parses_features_and_test_targets() {
        let json = r#"{"packages":[{"name":"demo","features":{"default":["light"],"light":[],"heavy":[]},
            "targets":[{"kind":["lib"],"name":"demo","src_path":"/r/src/lib.rs"},
                       {"kind":["test"],"name":"dark","src_path":"/r/tests/dark.rs"}]}]}"#;
        let c = parse_metadata(json).unwrap();
        assert_eq!(c.len(), 1);
        assert_eq!(c[0].tests, vec![TestTarget {
            name: "dark".into(),
            src_path: PathBuf::from("/r/tests/dark.rs"),
            required_features: Vec::new(),
        }]);
        assert_eq!(c[0].default_set(), ["default", "light"].iter().map(|s| s.to_string()).collect());

        // `required-features` must survive the parse: cargo SKIPS such a target
        // silently, so the manifest is the only place it is ever declared, and
        // `configs_from_metadata` builds a whole configuration out of it.
        let json = r#"{"packages":[{"name":"demo","features":{},
            "targets":[{"kind":["test"],"name":"gpu","src_path":"/r/tests/gpu.rs",
                        "required-features":["map3d-gpu","client"]}]}]}"#;
        let c = parse_metadata(json).unwrap();
        assert_eq!(c[0].tests[0].required_features, vec!["map3d-gpu", "client"]);
    }

    #[test]
    fn report_rows_are_red_for_silenced_and_carry_the_hidden_count() {
        let k = krate();
        let files = vec![
            classify(&k, &target("dark"), DARK, "tests/dark.rs", &[], None).unwrap(),
            classify(&k, &target("lit"), LIT, "tests/lit.rs", &[], None).unwrap(),
        ];
        let rep = GatedTestReport {
            run_id: "r1".into(),
            repo: "demo".into(),
            files,
            scanned: 2,
            arms: vec![],
        };
        assert!(!rep.is_green());
        assert_eq!(rep.hidden_tests(), 2);
        let rows = rep.rows();
        assert_eq!(rows.len(), 2);
        let dark = rows.iter().find(|r| r.test_name == "gated-tests::dark").unwrap();
        assert_eq!(dark.status, status::FAIL);
        assert!(status::is_red(&dark.status));
        assert_eq!(dark.aspect, ASPECT_GATED_TESTS);
        assert_eq!(dark.metric, 2.0);
        assert_eq!(dark.repo, "demo");
        assert_eq!(dark.suite, "demo");
        let lit = rows.iter().find(|r| r.test_name == "gated-tests::lit").unwrap();
        assert_eq!(lit.status, status::PASS);
    }

    #[test]
    fn arms_round_trip_through_the_declaration_file_shape() {
        let json = r#"{"arms":[
            {"package":"holger-ui","features":["gui"],"test":"robot_ui_app"},
            {"features":["testmatrix"],"note":"workspace-wide functional lane"}
        ]}"#;
        let a: MatrixArms = serde_json::from_str(json).unwrap();
        assert_eq!(a.arms.len(), 2);
        assert_eq!(a.arms[0].command(), "cargo test -p holger-ui --features gui --test robot_ui_app");
        assert_eq!(a.arms[1].command(), "cargo test --features testmatrix");
        assert!(a.arms[1].targets("anything", "anything"));
    }
}