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/tests/gated_tests_guard.rs

//! **The silenced-test guard, proved against real fixture repos.**
//!
//! Three standalone fixture crates under `tests/fixtures/` stand in for the
//! three cases the guard must separate. Each is driven through the REAL
//! [`audit_repo`] path — `cargo metadata` on the fixture, the fixture's own
//! `Cargo.toml` feature table, the fixture's own `.nornir/testmatrix-arms.json`:
//!
//! | fixture | shape | expected |
//! |---|---|---|
//! | `gated_dark` | `#![cfg(all(feature="light", feature="heavy"))]`, `heavy` NOT in `default` | **REPORTED** (RED), 3 hidden tests; its default-ON sibling is not |
//! | `gated_default_on` | gates satisfied through a transitive `default` chain + a platform-only gate | **NOT reported** (GREEN) |
//! | `gated_rescued` | same dark gate as `gated_dark`, plus a declared matrix arm | **NOT reported** (GREEN, `Rescued`) |
//!
//! RED-when-broken: with the classifier's dark branch stubbed to
//! `GateVerdict::Default` (i.e. the guard not shipped), `gated_dark` reports
//! GREEN and the first assertion here fails. With the arm-matching branch
//! removed, `gated_rescued` reports RED and the third fails. The three cases
//! pin each other.
//!
//! Deliberately NOT feature-gated — a guard against silenced tests that is
//! itself silenced would be the joke that writes itself.

use std::path::{Path, PathBuf};

use nornir_testmatrix::{audit_repo, status, GateVerdict, ASPECT_GATED_TESTS};

fn fixture(name: &str) -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests").join("fixtures").join(name)
}

/// `cargo metadata` on a fixture writes a `Cargo.lock` beside it; the fixtures
/// have zero dependencies so this is offline and instant.
fn audit(name: &str) -> nornir_testmatrix::GatedTestReport {
    let dir = fixture(name);
    assert!(dir.join("Cargo.toml").exists(), "fixture {name} exists at {}", dir.display());
    audit_repo(&dir, "guard-run").unwrap_or_else(|e| panic!("audit {name}: {e:#}"))
}

#[test]
fn a_default_off_gated_test_file_is_reported_red_with_its_hidden_count() {
    let rep = audit("gated_dark");

    assert!(
        !rep.is_green(),
        "the dark fixture must FAIL the gate — {}",
        rep.summary()
    );

    let silenced = rep.silenced();
    assert_eq!(
        silenced.len(),
        1,
        "exactly one silenced file (got {:?}) — {}",
        silenced.iter().map(|f| &f.path).collect::<Vec<_>>(),
        rep.summary()
    );
    let f = silenced[0];
    assert_eq!(f.target, "dark_gate");
    assert_eq!(f.crate_name, "gated_dark");
    assert!(f.path.ends_with("tests/dark_gate.rs"), "path is repo-relative: {}", f.path);
    assert_eq!(f.hidden_tests, 3, "all three #[test] fns are hidden");
    assert_eq!(
        f.missing_features,
        vec!["heavy".to_string()],
        "only the dark conjunct is named, not its default-on siblings"
    );
    assert_eq!(rep.hidden_tests(), 3);
    assert!(f.message().contains("ZERO tests"), "message: {}", f.message());
    assert!(f.message().contains("heavy"), "message names the dark feature");

    // Sibling asymmetry: the default-ON gate next to it is classified, not
    // reported. This is the camouflage the audit called out.
    let lit = rep
        .files
        .iter()
        .find(|f| f.target == "lit_gate")
        .expect("the default-on sibling is classified");
    assert_eq!(lit.verdict, GateVerdict::Default);
    assert!(!lit.is_silenced());

    // The ungated file is not a finding at all.
    assert!(
        rep.files.iter().all(|f| f.target != "plain"),
        "ungated files are not listed: {:?}",
        rep.files.iter().map(|f| &f.target).collect::<Vec<_>>()
    );
    assert_eq!(rep.scanned, 3, "all three test targets were inspected");

    // The finding is a structured, persistable row — not a println.
    let rows = rep.rows();
    let dark = rows
        .iter()
        .find(|r| r.test_name == "gated-tests::dark_gate")
        .expect("a row per gated file");
    assert_eq!(dark.aspect, ASPECT_GATED_TESTS);
    assert_eq!(dark.status, status::FAIL);
    assert!(status::is_red(&dark.status), "the row is RED, so it can fail a gate");
    assert_eq!(dark.metric, 3.0, "metric carries the hidden-test count");
    assert_eq!(dark.repo, "gated_dark");
    assert_eq!(dark.suite, "gated_dark");
    assert_eq!(dark.run_id, "guard-run");
    let lit_row = rows.iter().find(|r| r.test_name == "gated-tests::lit_gate").unwrap();
    assert_eq!(lit_row.status, status::PASS);
}

#[test]
fn a_default_on_gate_is_never_reported() {
    let rep = audit("gated_default_on");
    assert!(
        rep.is_green(),
        "a repo whose gates are all default-reachable is GREEN — {} · silenced={:?}",
        rep.summary(),
        rep.silenced().iter().map(|f| &f.path).collect::<Vec<_>>()
    );
    assert_eq!(rep.hidden_tests(), 0);

    // The transitive chain `default → bundle → light` must resolve.
    let t = rep
        .files
        .iter()
        .find(|f| f.target == "transitive_gate")
        .expect("the transitively-default gate is classified");
    assert_eq!(t.verdict, GateVerdict::Default);

    // A platform-only gate mentions no feature → not this guard's business.
    assert!(
        rep.files.iter().all(|f| f.target != "platform_gate"),
        "non-feature gates are not listed"
    );
}

#[test]
fn a_gate_rescued_by_a_declared_arm_is_not_reported() {
    let rep = audit("gated_rescued");
    assert!(
        rep.is_green(),
        "a declared matrix arm covers the file — {} · silenced={:?}",
        rep.summary(),
        rep.silenced().iter().map(|f| &f.path).collect::<Vec<_>>()
    );
    assert_eq!(rep.arms.len(), 2, "the repo's declaration file was read");

    let r = rep.files.iter().find(|f| f.target == "rescued_gate").expect("classified");
    assert_eq!(r.verdict, GateVerdict::Rescued);
    assert_eq!(
        r.rescued_by.as_deref(),
        Some("cargo test -p gated_rescued --features heavy --test rescued_gate"),
        "the report names the exact command that rescues it"
    );
    assert!(r.message().contains("rescued by"), "message: {}", r.message());

    // The workspace-wide arm's feature closure (`bundle → heavy`) rescues the
    // second file — arm features are resolved transitively, not compared literally.
    let t = rep
        .files
        .iter()
        .find(|f| f.target == "transitively_rescued_gate")
        .expect("classified");
    assert_eq!(t.verdict, GateVerdict::Rescued);
    assert_eq!(t.rescued_by.as_deref(), Some("cargo test --features bundle"));

    // Rescued files still emit rows — the excuse is visible, never silent.
    let rows = rep.rows();
    assert_eq!(rows.len(), 2);
    assert!(rows.iter().all(|r| r.status == status::PASS && r.aspect == ASPECT_GATED_TESTS));
}

/// Run `assert_no_silenced_tests` and return its panic message, or `None` if it
/// passed. The hook is silenced so a *deliberate* failure does not print a
/// backtrace into the middle of a green run.
fn panic_message_of(dir: &Path) -> Option<String> {
    let prev = std::panic::take_hook();
    std::panic::set_hook(Box::new(|_| {}));
    let dir = dir.to_path_buf();
    let r = std::panic::catch_unwind(move || {
        nornir_testmatrix::assert_no_silenced_tests(&dir);
    });
    std::panic::set_hook(prev);
    match r {
        Ok(()) => None,
        Err(e) => Some(
            e.downcast_ref::<String>()
                .cloned()
                .or_else(|| e.downcast_ref::<&str>().map(|s| s.to_string()))
                .unwrap_or_else(|| "<non-string panic>".to_string()),
        ),
    }
}

/// **The four-line adoption must actually be able to fail.**
///
/// `assert_no_silenced_tests` is the call every repo in the fleet is being asked
/// to add, so it is exactly the place a hollow guard would do the most damage: one
/// blind helper would make twenty repos report green at once. Its three exits are
/// pinned here against real fixtures.
#[test]
fn the_four_line_adoption_goes_red_on_a_dark_repo() {
    let msg = panic_message_of(&fixture("gated_dark"))
        .expect("the dark fixture MUST panic — a helper that cannot fail is the disease");
    assert!(msg.contains("SILENCED TESTS"), "message: {msg}");
    assert!(msg.contains("dark_gate"), "the message names the file: {msg}");
    assert!(msg.contains("heavy"), "the message names the dark feature: {msg}");
    assert!(
        msg.contains("testmatrix-arms.json"),
        "the message says how to fix it: {msg}"
    );
    // Sibling asymmetry survives into the message: the default-ON file next to it
    // is not reported as a problem.
    assert!(
        !msg.contains("lit_gate"),
        "only the dark file is listed, not its running sibling: {msg}"
    );
}

/// A repo whose gates are all default-reachable, and one whose dark gate is
/// covered by a declared arm, both pass. Without this the helper could "work" by
/// always panicking.
#[test]
fn the_four_line_adoption_passes_a_clean_repo_and_a_rescued_one() {
    assert_eq!(panic_message_of(&fixture("gated_default_on")), None);
    assert_eq!(panic_message_of(&fixture("gated_rescued")), None);
}

/// **The vacuous green.** A root that resolves but has no test targets reports
/// "0 silenced" because it inspected nothing — the same shape as a stale path or a
/// degraded `cargo metadata`. LAW 2: check output, not exit code.
#[test]
fn a_repo_with_no_test_targets_at_all_is_a_red_not_a_pass() {
    let dir = fixture("no_test_targets");
    // Precondition, stated rather than assumed: the fixture really has none.
    assert!(!dir.join("tests").exists(), "the fixture must have no tests/ dir");
    let rep = audit_repo(&dir, "vacuity").expect("the audit itself succeeds");
    assert_eq!(rep.scanned, 0, "…scanning nothing…");
    assert!(rep.is_green(), "…and reporting green, which is the trap");

    let msg = panic_message_of(&dir)
        .expect("scanning ZERO targets must panic — otherwise the guard proves nothing");
    assert!(msg.contains("ZERO test targets"), "message: {msg}");
}

/// A path that is not a cargo workspace at all is a RED, never a skip: an audit
/// that could not run has not cleared anything.
#[test]
fn an_unresolvable_root_is_a_red_not_a_skip() {
    let msg = panic_message_of(&fixture("this_fixture_does_not_exist"))
        .expect("a root `cargo metadata` cannot read must panic");
    assert!(
        msg.contains("could not run") && msg.contains("RED, not a skip"),
        "message: {msg}"
    );
}

#[test]
fn the_declaration_file_is_optional_and_a_missing_one_is_not_an_error() {
    // `gated_dark` declares nothing; the audit still runs and simply rescues
    // nothing.
    let rep = audit("gated_dark");
    assert!(rep.arms.is_empty());
    assert!(!Path::new(&fixture("gated_dark"))
        .join(nornir_testmatrix::ARMS_FILE)
        .exists());
}