crates/nornir-build-thing/tests/iso_boot.rs
//! **Pure-Rust ISO — master + BOOT-under-QEMU/OVMF MATRIX TEST.**
//!
//! Closes the "no ISO-boot proof" gap: masters a bootable UEFI ISO-9660 image in
//! **pure Rust** (via [`nornir_build_thing::PureRustIsoBuilder`] →
//! [`nornir_build_thing::iso_writer::IsoWriter`], zero external ISO tool, zero
//! subprocess in the ISO path), then **boots it under QEMU + OVMF (UEFI)** off a
//! `-cdrom` and asserts the appliance payload reached `KORP-APPLIANCE BOOT-OK` in
//! the captured serial log. A real booted firmware producing real, asserted serial
//! output — not "the bytes were written".
//!
//! ## What is and isn't a subprocess here
//!
//! The **ISO is mastered 100% in Rust** — no `xorriso`/`mkisofs`/`genisoimage`,
//! no `Command::new` on the mastering path (the constellation LAW). This test
//! then shells to exactly two SANCTIONED tools that are NOT ISO tools:
//! * `cargo … --target x86_64-unknown-uefi` — the Rust COMPILER, building the
//! `tests/fixtures/korp-efistub` `no_std` UEFI payload from source (no vendored
//! binary blob); and
//! * `qemu-system-x86_64` — the one accepted hypervisor for a KVM/OVMF boot.
//!
//! ## Heavy / `#[ignore]`
//!
//! Self-gates on `qemu-system-x86_64`, an OVMF CODE+VARS pair, and a buildable
//! `x86_64-unknown-uefi` target; SKIPs with a loud note (never a faked green) when
//! any is absent. `#[ignore]`'d so it joins the `--ignored` heavy pass, exactly
//! like the sibling `tunnr` `boot_matrix` / `korp` `korp_matrix` boots.
//!
//! ```bash
//! cargo test -p nornir-build-thing --test iso_boot -- --ignored --nocapture
//! ```
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{Duration, Instant};
use nornir_build_thing::iso::{IsoBuild, IsoFormat};
use nornir_build_thing::{build_iso, bt_assert_emit, PureRustIsoBuilder};
/// The boot marker the korp appliance payload prints to serial on success.
const BOOT_MARKER: &str = "KORP-APPLIANCE BOOT-OK";
#[test]
#[ignore = "heavy: masters a pure-Rust ISO then boots it under QEMU+OVMF (UEFI), asserting BOOT-OK"]
fn pure_rust_iso_boots_under_qemu_ovmf_to_boot_ok() {
// ── self-gate: skip loudly (never fake green) when the host can't boot ──
let Some(qemu) = which("qemu-system-x86_64") else {
eprintln!("SKIP pure_rust_iso_boots: no qemu-system-x86_64 on PATH");
return;
};
let Some((ovmf_code, ovmf_vars)) = locate_ovmf() else {
eprintln!("SKIP pure_rust_iso_boots: no OVMF CODE+VARS pair (install edk2/ovmf)");
return;
};
let tmp = tempfile::tempdir().expect("tempdir");
let root = tmp.path();
// ── 1. build the no_std UEFI payload from source (Rust compiler, not an ISO tool) ──
let Some(efi_app) = build_efi_stub(root) else {
eprintln!(
"SKIP pure_rust_iso_boots: could not build the x86_64-unknown-uefi payload \
(run `rustup target add x86_64-unknown-uefi`)"
);
return;
};
// ── 2. build the EFI System Partition (FAT) in pure Rust (fatfs) ──
let staging = root.join("iso-root");
std::fs::create_dir_all(&staging).unwrap();
build_esp(&staging.join("efiboot.img"), &efi_app);
// ── 3. master the bootable UEFI ISO through the REAL builder — PURE RUST ──
let iso = IsoBuild {
name: "korp-iso".into(),
staging: "iso-root".into(),
volume_id: "KORP_UEFI".into(),
boot_params: vec!["console=ttyS0".into()],
format: IsoFormat::Uefi,
..Default::default()
};
// `into_result`, not a bare `?`: a target build now RECORDS its failure instead
// of propagating it, and this test wants the builder's own reason at the point it
// failed rather than an `is_file()` assertion three lines down that says only
// that no ISO appeared.
let out = build_iso(&PureRustIsoBuilder::new(), &iso, "korp", root)
.and_then(|o| o.into_result())
.unwrap_or_else(|e| panic!("pure-Rust ISO master failed: {e:#}"));
assert_eq!(out.attestation.record.engine, "pure-rust", "mastered by the pure-Rust builder");
let iso_path = root.join(".iso-out").join("korp-iso.iso");
assert!(iso_path.is_file(), "a real .iso was mastered at {}", iso_path.display());
// ── 4. BOOT it under QEMU + OVMF, capture the serial log, assert BOOT-OK ──
let serial = boot_and_capture(&qemu, &ovmf_code, &ovmf_vars, &iso_path, root, Duration::from_secs(90));
eprintln!("─── OVMF serial log ───\n{serial}\n───────────────────────");
let booted = serial.contains(BOOT_MARKER);
bt_assert_emit!(
"iso-build",
"pure-rust-iso-boots-under-ovmf",
booted,
"pure-Rust ISO booted under QEMU/OVMF and reached '{BOOT_MARKER}' (serial {} bytes)",
serial.len()
);
}
/// Build the `tests/fixtures/korp-efistub` UEFI payload; `None` if the target is
/// unavailable. Returns the path to the produced `bootx64.efi`.
fn build_efi_stub(root: &Path) -> Option<PathBuf> {
let fixture = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/korp-efistub/Cargo.toml");
let target_dir = root.join("efi-target");
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
let status = Command::new(&cargo)
.args([
"build",
"--release",
"--target",
"x86_64-unknown-uefi",
"--manifest-path",
])
.arg(&fixture)
.arg("--target-dir")
.arg(&target_dir)
.status()
.ok()?;
if !status.success() {
return None;
}
let efi = target_dir.join("x86_64-unknown-uefi/release/bootx64.efi");
efi.is_file().then_some(efi)
}
/// Format a FAT EFI System Partition at `path` carrying `EFI/BOOT/BOOTX64.EFI` —
/// pure Rust via the `fatfs` crate (no `mkfs`/`mtools` subprocess).
fn build_esp(path: &Path, efi_app: &Path) {
let f = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.expect("create ESP image");
f.set_len(1440 * 1024).expect("size ESP"); // 1.44 MiB FAT12
fatfs::format_volume(&f, fatfs::FormatVolumeOptions::new().volume_label(*b"EFIESP "))
.expect("format FAT ESP");
let fs = fatfs::FileSystem::new(&f, fatfs::FsOptions::new()).expect("mount ESP");
let root = fs.root_dir();
let boot = root.create_dir("EFI").unwrap().create_dir("BOOT").unwrap();
let mut out = boot.create_file("BOOTX64.EFI").unwrap();
let bytes = std::fs::read(efi_app).expect("read efi payload");
out.write_all(&bytes).unwrap();
out.flush().unwrap();
}
/// Boot the ISO as a UEFI `-cdrom` under OVMF pflash, capturing the serial console
/// to a file. Polls for [`BOOT_MARKER`] up to `timeout`, then kills QEMU (the guest
/// hlt-loops after printing, so it never exits on its own).
fn boot_and_capture(
qemu: &Path,
ovmf_code: &Path,
ovmf_vars_tmpl: &Path,
iso: &Path,
workdir: &Path,
timeout: Duration,
) -> String {
// A per-boot writable OVMF NVRAM copy (the vars store must not be read-only).
let vars = workdir.join("OVMF_VARS.fd");
std::fs::copy(ovmf_vars_tmpl, &vars).expect("stage writable OVMF_VARS");
let log = workdir.join("serial.log");
let mut child = Command::new(qemu)
.args(["-machine", "q35,accel=kvm:tcg", "-m", "512", "-display", "none", "-no-reboot"])
.arg("-drive")
.arg(format!("if=pflash,format=raw,readonly=on,file={}", ovmf_code.display()))
.arg("-drive")
.arg(format!("if=pflash,format=raw,file={}", vars.display()))
.arg("-cdrom")
.arg(iso)
.args(["-boot", "d"])
.arg("-serial")
.arg(format!("file:{}", log.display()))
.spawn()
.expect("spawn qemu");
let deadline = Instant::now() + timeout;
let mut captured = String::new();
while Instant::now() < deadline {
if let Ok(bytes) = std::fs::read(&log) {
captured = String::from_utf8_lossy(&bytes).replace('\u{0}', "");
if captured.contains(BOOT_MARKER) {
break;
}
}
if let Ok(Some(_)) = child.try_wait() {
break; // qemu exited on its own
}
std::thread::sleep(Duration::from_millis(400));
}
let _ = child.kill();
let _ = child.wait();
if let Ok(bytes) = std::fs::read(&log) {
captured = String::from_utf8_lossy(&bytes).replace('\u{0}', "");
}
captured
}
/// Resolve a bare binary name on `PATH`.
fn which(bin: &str) -> Option<PathBuf> {
let path = std::env::var_os("PATH")?;
std::env::split_paths(&path)
.map(|d| d.join(bin))
.find(|c| c.is_file())
}
/// Locate an OVMF firmware CODE+VARS pair (the common Debian/Fedora/Arch spots).
fn locate_ovmf() -> Option<(PathBuf, PathBuf)> {
let pairs = [
("/usr/share/OVMF/OVMF_CODE_4M.fd", "/usr/share/OVMF/OVMF_VARS_4M.fd"),
("/usr/share/OVMF/OVMF_CODE.fd", "/usr/share/OVMF/OVMF_VARS.fd"),
("/usr/share/edk2/x64/OVMF_CODE.4m.fd", "/usr/share/edk2/x64/OVMF_VARS.4m.fd"),
("/usr/share/edk2/ovmf/OVMF_CODE.fd", "/usr/share/edk2/ovmf/OVMF_VARS.fd"),
("/usr/share/qemu/OVMF_CODE.fd", "/usr/share/qemu/OVMF_VARS.fd"),
];
pairs.iter().find_map(|(c, v)| {
let (c, v) = (PathBuf::from(c), PathBuf::from(v));
(c.is_file() && v.is_file()).then_some((c, v))
})
}