crates/nornir-testmatrix/tests/arm_run.rs
//! **`run_arm` runs a declared arm and records the REAL per-test verdict.**
//!
//! `verify_arm` (the build-target check) proves an arm COMPILES and lists tests;
//! `run_arm` (the test-target run) is the last mile — it EXECUTES the arm's
//! feature set and reports PASS/FAIL. This test proves, RED before green (LAW 2),
//! that it reports each real outcome and never the `0 tests ... ok` silent green:
//!
//! * a passing arm → GREEN, real passed count;
//! * an arm whose file has a FAILING test → not green, `failed() >= 1` (the
//! proof a broken test in the matrix is not swallowed);
//! * a dark baseline (the gate's feature OFF) → 0 cases, and `green()` is FALSE
//! because an arm that rescued nothing is not a pass;
//! * an arm that does not compile (bogus feature) → `compiled_and_ran == false`
//! and `green()` FALSE — a compile failure is caught, not read as empty-green.
//!
//! One test fn, run sequentially, so the process-global arm target-dir env is set
//! once with no cross-thread race, and the tiny fixture compiles once and is
//! reused across the four arms.
use std::path::PathBuf;
use nornir_testmatrix::{run_arm, status, MatrixArm};
fn fixture_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests").join("fixtures").join("arm_run_probe")
}
#[test]
fn run_arm_records_pass_fail_dark_and_compile_failure() {
let dir = fixture_dir();
// Keep the per-arm build OUT of the source tree and off the tight md1 root:
// a unique scratch target dir this run owns. `run_arm` reads it via
// `arm_target_dir` (NORNIR_ARM_TARGET_DIR takes precedence).
let target = std::env::temp_dir().join(format!(
"nornir-testmatrix-arm-run-{}",
nornir_testmatrix::new_run_id()
));
// SAFETY: single-threaded — this is the only test fn in this binary.
unsafe { std::env::set_var("NORNIR_ARM_TARGET_DIR", &target) };
// ── RED baseline: the gated file is DARK under default, so an arm that does
// not turn its feature on runs zero of its tests — and that is NOT green. ──
let dark = MatrixArm::features::<[&str; 0], &str>([])
.package("arm_run_probe")
.test("good_gate");
let dark = run_arm(&dir, &dark).expect("arm runs");
assert!(
dark.compiled_and_ran,
"an empty-but-compiling arm still exits 0: {}",
dark.run.cases.len()
);
assert_eq!(dark.ran(), 0, "good_gate is dark without --features good: {:?}", dark.run.cases);
assert!(!dark.green(), "an arm that ran zero tests rescued nothing — not green");
// ── GREEN: the arm turns the feature on → the two passing tests run. ──
let good = MatrixArm::features(["good"]).package("arm_run_probe").test("good_gate");
let good = run_arm(&dir, &good).expect("arm runs");
assert!(good.compiled_and_ran, "the good arm compiles and runs");
assert_eq!(good.ran(), 2, "good_gate has two tests: {:?}", good.run.cases);
assert_eq!(good.passed(), 2, "both pass");
assert_eq!(good.failed(), 0, "none fail");
assert!(good.green(), "a two-passing-test arm is green");
// ── RED, the load-bearing one: a FAILING test in the arm reports FAIL, it is
// NOT swallowed as a silent green. ──
let bad = MatrixArm::features(["bad"]).package("arm_run_probe").test("bad_gate");
let bad = run_arm(&dir, &bad).expect("arm runs");
assert_eq!(bad.ran(), 2, "bad_gate has two tests: {:?}", bad.run.cases);
assert_eq!(bad.passed(), 1, "one passes");
assert_eq!(bad.failed(), 1, "one fails — the matrix SEES it");
assert!(!bad.green(), "an arm with a failing test is RED, not silent-green");
assert!(
bad.run.cases.iter().any(|c| c.status == status::FAIL && c.name.contains("bad_fails")),
"the failing case is named in the rows: {:?}",
bad.run.cases
);
// ── RED: an arm that does not compile (a feature that does not exist) is
// caught as a compile failure, never read as an empty green. ──
let broken = MatrixArm::features(["no_such_feature_zz"]).package("arm_run_probe");
let broken = run_arm(&dir, &broken).expect("arm runs (cargo exits non-zero, no panic)");
assert!(
!broken.compiled_and_ran,
"a bogus-feature arm does NOT compile — exit is non-zero"
);
assert!(!broken.green(), "a non-compiling arm is never green");
// Cleanup: drop the scratch target dir (best-effort).
let _ = std::fs::remove_dir_all(&target);
unsafe { std::env::remove_var("NORNIR_ARM_TARGET_DIR") };
}