crates/nornir-testmatrix/tests/appliance_boot.rs
//! **The `appliance-boot/<preset>` roster, against the REAL declarations.**
//!
//! `src/appliance.rs` owns the grid; this file is the binding to
//! `nornir-build-thing` (a dev-dependency, for the cycle reason
//! `tests/chain_matrix.rs` records) and proves that the matrix is EMITTED from
//! the one preset roster — `target::presets::PRESETS` — rather than typed out
//! anywhere in this crate.
use nornir_build_thing::target::presets::{Mimic, PresetStatus, PRESETS};
use nornir_testmatrix::appliance::{
ApplianceBootGrid, BootPreset, BootReach, BootSubstitution, ProofVerdict, APPLIANCE_BOOT_PROOFS,
};
use nornir_testmatrix::functional::functional_row;
/// The ~10-line adapter every caller writes: the roster FROM the code. The
/// library's own `presets::boot_roster()` (behind `--features testmatrix`) is
/// the same lines; this copy keeps the test independent of that feature.
fn roster_from_code() -> Vec<BootPreset> {
PRESETS
.iter()
.map(|p| BootPreset {
name: p.name.to_string(),
reach: match p.mimic {
Mimic::Qemu => BootReach::Kvm,
Mimic::NeedsRealCloud(why) => BootReach::RealCloud(why.to_string()),
},
measured: p.measured.map(|e| e.label()),
substitutions: p
.axes()
.substitutions()
.into_iter()
.map(|(facet, missing, with)| BootSubstitution {
facet: facet.to_string(),
missing: missing.to_string(),
with: with.to_string(),
})
.collect(),
})
.collect()
}
/// One row per preset, reach derived from `Mimic`, and the named gaps are
/// visible holes — never green, never missing.
#[test]
fn every_declared_preset_is_a_row_and_its_reach_follows_mimic() {
let grid = ApplianceBootGrid::from_rows(roster_from_code(), &[]);
assert_eq!(grid.presets.len(), PRESETS.len());
for (row, p) in grid.presets.iter().zip(PRESETS) {
assert_eq!(row.name, p.name, "roster order is the code's order");
match (p.status(), &row.reach) {
(PresetStatus::NeedsRealCloud, BootReach::RealCloud(why)) => {
assert_eq!(Some(why.as_str()), p.gap());
assert!(grid.detail(p.name, "install_ok").starts_with("needs real cloud: "), "{}", p.name);
}
(PresetStatus::MimicMeasured | PresetStatus::CloudMeasured | PresetStatus::Stub, BootReach::Kvm) => {
assert!(p.gap().is_none());
}
(s, r) => panic!("{}: status {s:?} vs reach {r:?} disagree", p.name),
}
for proof in APPLIANCE_BOOT_PROOFS {
assert_eq!(grid.verdict(p.name, proof), ProofVerdict::Absent, "{}: nothing ran", p.name);
}
}
let rendered = grid.render();
for p in PRESETS {
assert!(rendered.contains(p.name), "{}: missing from the wall:\n{rendered}", p.name);
}
assert!(rendered.contains("needs real cloud: ENA"), "{rendered}");
assert!(!grid.kvm_all_green());
}
/// The door's rows land on the code's names: a measured UpCloud B1/B2/B3 turns
/// exactly its row green and no other.
#[test]
fn the_installtest_rows_fold_onto_the_code_roster() {
let rows: Vec<_> = APPLIANCE_BOOT_PROOFS
.iter()
.map(|proof| functional_row("appliance-boot/upcloud", proof, true, "SeaBIOS + -serial none"))
.collect();
let grid = ApplianceBootGrid::from_rows(roster_from_code(), &rows);
for proof in APPLIANCE_BOOT_PROOFS {
assert_eq!(grid.verdict("upcloud", proof), ProofVerdict::Green);
assert_eq!(grid.verdict("qemu-local", proof), ProofVerdict::Absent);
}
assert_eq!(grid.presets.len(), PRESETS.len(), "no row invented");
let emitted = grid.rows();
assert_eq!(emitted.len(), PRESETS.len() * APPLIANCE_BOOT_PROOFS.len());
assert_eq!(emitted.iter().filter(|r| r.status == "pass").count(), 3);
}
/// **The substitutions reach the grid from the REAL roster** — not a fixture.
///
/// `azure-gen2` presents a Hyper-V synthetic disk and NIC, neither of which
/// QEMU models, so `MimicAxes` says both are stood in for. A `pass` row that
/// does not NAME the swap is refused: the KVM run proved the shape (firmware
/// handoff, boot order, partition scan, root mount) and not the machine, and a
/// row that hides which devices were swapped is the exact silent green this
/// crate exists to refuse.
#[test]
fn a_real_azure_pass_must_name_hv_netvsc_or_it_is_red() {
let roster = roster_from_code();
let azure = roster.iter().find(|p| p.name == "azure-gen2").expect("azure-gen2 is on the roster");
let labels: Vec<String> = azure.substitutions.iter().map(|s| s.label()).collect();
assert!(
labels.iter().any(|l| l == "nic: hv-netvsc -> virtio-net-pci"),
"the roster itself must derive the NIC substitution: {labels:?}"
);
// RED: a bare pass.
let silent = vec![functional_row("appliance-boot/azure-gen2", "install_ok", true, "B1 INSTALL-OK")];
let grid = ApplianceBootGrid::from_rows(roster_from_code(), &silent);
assert_eq!(grid.verdict("azure-gen2", "install_ok"), ProofVerdict::Red, "a silent green must not stand");
assert!(grid.detail("azure-gen2", "install_ok").contains("nic: hv-netvsc -> virtio-net-pci"));
// GREEN: the same run, reported honestly.
let honest = vec![functional_row(
"appliance-boot/azure-gen2",
"install_ok",
true,
&format!("B1 INSTALL-OK (shape only — {})", labels.join("; ")),
)];
let grid = ApplianceBootGrid::from_rows(roster_from_code(), &honest);
assert_eq!(grid.verdict("azure-gen2", "install_ok"), ProofVerdict::Green);
}
/// **The roster's evidence says which KIND it is.** `upcloud` was booted on a
/// real UpCloud instance; `qemu-local-bios` was booted here. Before the split
/// both were the word `measured`, and "3 of 13 measured" read as three clouds.
#[test]
fn the_roster_separates_a_cloud_boot_from_a_rig_run() {
let cloud: Vec<&str> = PRESETS.iter().filter(|p| p.status() == PresetStatus::CloudMeasured).map(|p| p.name).collect();
assert_eq!(cloud, vec!["upcloud"]);
let rig: Vec<&str> = PRESETS.iter().filter(|p| p.status() == PresetStatus::MimicMeasured).map(|p| p.name).collect();
assert_eq!(rig, vec!["qemu-local", "qemu-local-bios"]);
// The word travels onto the emitted row, so a warehouse reader never has to
// guess which kind of evidence a date is.
let roster = roster_from_code();
let up = roster.iter().find(|p| p.name == "upcloud").unwrap();
assert!(up.measured.as_deref().unwrap().starts_with("real cloud: "), "{:?}", up.measured);
let rig_row = roster.iter().find(|p| p.name == "qemu-local-bios").unwrap();
assert!(rig_row.measured.as_deref().unwrap().starts_with("qemu rig: "), "{:?}", rig_row.measured);
// `NeedsRealCloud` keeps its name and meaning — gunnar's installtest gates
// `--target` on exactly this comparison, and the split must not move it.
let renderable: Vec<&str> =
PRESETS.iter().filter(|p| p.status() != PresetStatus::NeedsRealCloud).map(|p| p.name).collect();
assert_eq!(
renderable,
vec!["qemu-local", "qemu-local-bios", "upcloud", "gce-virtio", "hetzner-cloud", "ovh-public-cloud"],
"the presets gunnar-installtest will still launch"
);
}