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/target_compile_guard.rs

//! **The end-to-end red proof for [`nornir_testmatrix::targetcheck`].**
//!
//! The unit tests in the module prove the *accounting* — what the parser does
//! with a stream, what `is_green` does with a set of verdicts. They cannot prove
//! the thing that actually matters: that pointing the guard at a real repo whose
//! test targets do not compile makes it name them. A guard whose subprocess half
//! is wrong passes every unit test it has, and three agents built exactly that
//! kind of hollow guard on 2026-07-31 *while hunting hollow guards*.
//!
//! So this drives real `cargo` against `tests/fixtures/broken_target`, a crate
//! whose library compiles cleanly (`cargo check` exits 0) and whose four test
//! targets are, by construction:
//!
//! | target | expected |
//! |---|---|
//! | `compiles` | builds — the control that keeps the guard from being blindly red |
//! | `does_not` | `E0425` |
//! | `also_does_not` | `E0308` — only visible because of `--keep-going` |
//! | `gpu_only` | `required-features = ["gpu"]`: skipped by default, built by its own config |
//!
//! and against `tests/fixtures/no_test_targets`, which declares none at all and
//! must therefore FAIL as an empty scan rather than pass by looking at nothing.

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

use nornir_testmatrix::{
    check_targets, configs_from_metadata, workspace_crates, ConfigSource, Depth, TargetState,
};

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

/// **THE RED PROOF.** Two of four declared test targets do not compile; the
/// guard must name both, with their configuration and their first error, and
/// must leave the working one alone.
#[test]
fn the_guard_names_every_test_target_that_does_not_compile() {
    let root = fixture("broken_target");
    let crates = workspace_crates(&root).expect("cargo metadata on the fixture");
    assert_eq!(crates.len(), 1, "the fixture is a standalone one-crate workspace");
    assert_eq!(crates[0].tests.len(), 4, "four declared test targets: {:?}", crates[0].tests);

    let configs = configs_from_metadata(&crates);
    // default + all-features + the one `required-features` set gpu_only declares.
    assert_eq!(
        configs.iter().filter(|c| c.source == ConfigSource::RequiredFeatures).count(),
        1,
        "gpu_only's required-features became its own configuration: {configs:?}"
    );

    let report = check_targets(&root, &crates, &configs);

    assert!(!report.is_green(), "a repo with two uncompilable test targets is NOT green");
    assert_eq!(report.declared_test_targets, 4);

    let broken = report.broken();
    let named: Vec<&str> = broken.iter().map(|v| v.target.as_str()).collect();
    assert!(named.contains(&"does_not"), "the first broken target is named: {named:?}");
    assert!(
        named.contains(&"also_does_not"),
        "the SECOND broken target is named too — this is what --keep-going buys, and \
         without it cargo aborts after the first and this one is invisible: {named:?}"
    );
    assert!(
        !named.contains(&"compiles"),
        "the working target is NOT reported — a guard that reds everything detects \
         nothing: {named:?}"
    );

    // The error itself, not merely the fact of one.
    let e0425 = broken.iter().find(|v| v.target == "does_not").unwrap();
    assert!(e0425.detail.contains("E0425"), "the first error is quoted: {}", e0425.detail);
    let e0308 = broken.iter().find(|v| v.target == "also_does_not").unwrap();
    assert!(e0308.detail.contains("E0308"), "the first error is quoted: {}", e0308.detail);

    // And the summary a human reads carries all of it.
    let s = report.summary();
    for want in ["does_not", "also_does_not", "E0425", "E0308", "default"] {
        assert!(s.contains(want), "summary is missing {want:?}:\n{s}");
    }

    // The control compiled — under the DEFAULT configuration, by name.
    let control: Vec<&nornir_testmatrix::TargetVerdict> = report
        .verdicts
        .iter()
        .filter(|v| v.target == "compiles" && v.source == ConfigSource::Default)
        .collect();
    assert_eq!(control.len(), 1, "{control:?}");
    assert_eq!(control[0].state, TargetState::Compiled, "the fixture's good target builds");

    // `gpu_only` is SKIPPED by default (cargo says nothing about it at all) and
    // COMPILED under the configuration its own `required-features` implied. That
    // pair is the whole reason required-features are enumerated.
    let gpu: Vec<(&str, TargetState)> = report
        .verdicts
        .iter()
        .filter(|v| v.target == "gpu_only")
        .map(|v| (v.config.as_str(), v.state))
        .collect();
    assert!(
        gpu.iter().any(|(c, s)| *c == "default" && *s == TargetState::NotSelected),
        "cargo skips it silently on a default build: {gpu:?}"
    );
    assert!(
        gpu.iter().any(|(_, s)| *s == TargetState::Compiled),
        "and it DOES compile under its own required-features config: {gpu:?}"
    );
    assert!(
        report.never_compiled().iter().all(|(_, t)| t != "gpu_only"),
        "so it is not reported as never-built: {:?}",
        report.never_compiled()
    );

    // Rows carry the same verdict the summary does — a sink must not see green.
    let rows = report.rows();
    let red: Vec<&str> = rows
        .iter()
        .filter(|r| r.status == "fail")
        .map(|r| r.test_name.as_str())
        .collect();
    assert!(red.iter().any(|n| n.contains("does_not")), "{red:?}");
    assert!(rows.iter().all(|r| r.aspect == nornir_testmatrix::ASPECT_TARGET_COMPILE));
}

/// **The empty scan.** A repo declaring zero test targets must FAIL. A run that
/// examined nothing is shaped exactly like a clean one, and reading it as a pass
/// is this constellation's signature bug.
#[test]
fn a_repo_with_no_test_targets_fails_instead_of_passing_vacuously() {
    let root = fixture("no_test_targets");
    let crates = workspace_crates(&root).expect("cargo metadata on the fixture");
    let configs = configs_from_metadata(&crates);
    let report = check_targets(&root, &crates, &configs);

    assert_eq!(report.declared_test_targets, 0);
    assert!(
        !report.is_green(),
        "zero declared test targets is an EMPTY SCAN — it must not pass:\n{}",
        report.summary()
    );
    assert!(report.summary().contains("EMPTY SCAN"), "{}", report.summary());
    let rows = report.rows();
    assert!(
        rows.iter().any(|r| r.status == "fail" && r.message.contains("empty scan")),
        "the empty scan publishes its own RED row rather than an empty row set: {rows:?}"
    );
}

/// **The cost claim, asserted rather than assumed.** `Depth::Check` is the
/// default because on nornir it found the same twenty-six broken targets as a
/// full build at 4.2× less wall-clock and 60× less disk (2 min 20 s / 2.8 GB vs
/// 9 min 51 s / ~170 GB, oden, cold target dir, 2026-08-22).
///
/// That measurement is one repo. This pins the part that must hold everywhere:
/// the two depths must reach the SAME verdict on the same breakage, or "check
/// is the affordable default" is a claim that the guard itself disproves.
#[test]
fn checking_and_building_reach_the_same_verdict_on_the_same_breakage() {
    let root = fixture("broken_target");
    let crates = workspace_crates(&root).expect("cargo metadata on the fixture");

    let mut named = Vec::new();
    for depth in [Depth::Check, Depth::Build] {
        let configs: Vec<_> = configs_from_metadata(&crates)
            .into_iter()
            .map(|mut c| {
                c.depth = depth;
                c
            })
            .collect();
        let report = check_targets(&root, &crates, &configs);
        assert!(!report.is_green(), "[{}] {}", depth.tag(), report.summary());
        let mut broken: Vec<String> =
            report.broken().iter().map(|v| v.target.clone()).collect();
        broken.sort();
        broken.dedup();
        named.push((depth, broken));
    }
    assert_eq!(
        named[0].1, named[1].1,
        "check and build must name the same broken targets — otherwise the cheap \
         default is cheap because it sees less"
    );
    assert_eq!(
        named[0].1,
        vec!["also_does_not".to_string(), "does_not".to_string()],
        "and both of them, not one"
    );
}