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"
);
}