crates/nornir-build-thing/tests/image_writer.rs
//! **Cloud medium writers — the CHEAP proofs.** Byte-level structure, the
//! read-back, and the five red probes, all unignored: they need no QEMU, no
//! network and no root, so they run on every `cargo test`.
//!
//! The heavy half — booting each produced medium back under QEMU — is
//! `crates/cloud-media-bootproof/tests/image_writer_boot.rs`, `#[ignore]`d.
//!
//! # What "proven" means here
//!
//! Every assertion below is on APPLIED OUTPUT: bytes that exist in a file on
//! disk, read back through a reader that does not share the writer's variables.
//! Nothing asserts that a struct still holds the field it was just given.
//!
//! In particular the qcow2 writer is proven by RECONSTRUCTING the raw disk out
//! of the L1/L2/refcount structure and comparing it byte-for-byte to the source
//! — a header-field check would pass over an image with an unreadable mapping.
//!
//! # The red probes, and why each exists
//!
//! Each `red_probe_*` breaks exactly one thing and asserts the named refusal
//! ([`nornir_build_thing::image_writer::ERR_*`]) fires. They were themselves
//! proven by neutering the guard they cover in `image_writer.rs`, watching the
//! probe fail, and restoring — a probe nobody has seen red is not a probe.
use std::io::Write;
use std::path::{Path, PathBuf};
use nornir_build_thing::image_writer::{
self, CloudMedium, Qcow2Reader, VhdFooter, VhdOptions, ERR_GCE_MEMBER, ERR_QCOW2_FEATURES,
ERR_QCOW2_MAGIC, ERR_QCOW2_VERSION, ERR_SOURCE, ERR_VHD_CHECKSUM, ERR_VHD_COOKIE,
ERR_VHD_NOT_MIB, ERR_VHD_SHORT, GCE_MEMBER_NAME, MIB, SECTOR, VHD_FOOTER_LEN,
};
use nornir_build_thing::target::presets::{MediumBring, PRESETS};
// ── fixtures ────────────────────────────────────────────────────────────────
/// A synthetic "installed disk": a protective-MBR signature and a GPT header
/// where a real one has them, a recognisable data run, and a large zero tail so
/// the qcow2 sparseness is measurable rather than asserted.
///
/// Deliberately shaped like [`image_writer::probe_disk`]'s two facts — a
/// converter that drops the first two sectors, or shifts the image by one, is
/// caught by content comparison AND by the probe, which are independent.
fn fake_installed_disk(path: &Path, size: u64) -> Vec<u8> {
assert_eq!(size % SECTOR, 0, "the fixture must be whole sectors");
let mut d = vec![0u8; size as usize];
// LBA 0: protective MBR with the 0x55AA signature.
d[440..444].copy_from_slice(&0xDEAD_BEEFu32.to_le_bytes());
d[510] = 0x55;
d[511] = 0xAA;
// LBA 1: a GPT header.
d[512..520].copy_from_slice(b"EFI PART");
d[520..524].copy_from_slice(&0x0001_0000u32.to_le_bytes());
// A data run in the middle, then zeros to the end (the sparse tail).
let run = (size as usize / 4).min(300 * 1024);
for (i, b) in d[64 * 1024..64 * 1024 + run].iter_mut().enumerate() {
*b = (i % 251) as u8 + 1;
}
std::fs::write(path, &d).expect("write the fixture disk");
d
}
fn tmp() -> tempfile::TempDir {
tempfile::tempdir().expect("tempdir")
}
fn sha256(bytes: &[u8]) -> [u8; 32] {
use sha2::Digest;
sha2::Sha256::digest(bytes).into()
}
/// Overwrite `len` bytes at `at` in the file — the red probes' one corruption
/// primitive, so "how a probe breaks a file" has one definition.
fn poke(path: &Path, at: u64, bytes: &[u8]) {
use std::io::{Seek, SeekFrom};
let mut f = std::fs::OpenOptions::new().write(true).open(path).expect("open for poke");
f.seek(SeekFrom::Start(at)).expect("seek");
f.write_all(bytes).expect("poke");
}
// ─────────────────────────────────────────────────────────────────────────────
// RAW — the passthrough that is still a path
// ─────────────────────────────────────────────────────────────────────────────
#[test]
fn raw_is_a_validated_named_path_and_reproduces_the_disk_byte_for_byte() {
let t = tmp();
let src = t.path().join("installed.raw");
let want = fake_installed_disk(&src, 4 * MIB);
let dst = t.path().join("aws.raw");
let p = image_writer::write_raw(&src, &dst).expect("write the raw medium");
let got = std::fs::read(&dst).expect("read back the raw medium");
assert_eq!(got, want, "the raw medium is the installed disk, byte for byte");
assert_eq!(p.virtual_size, 4 * MIB);
assert_eq!(p.padded, 0, "raw pads nothing");
assert_eq!(p.file_size, 4 * MIB);
assert_eq!(p.content_sha256, sha256(&want), "the content id is over the real bytes");
assert_eq!(p.medium.qemu_format(), "raw");
// The partition table survived — a converter that shifts by a sector passes
// a length check and fails this.
let probe = image_writer::probe_disk(&dst).expect("probe the raw medium");
assert!(probe.mbr_signature && probe.gpt_header, "the raw medium still carries its GPT");
}
#[test]
fn red_probe_a_source_that_is_not_a_whole_disk_is_refused_by_name() {
let t = tmp();
// Not a whole number of sectors.
let ragged = t.path().join("ragged.raw");
std::fs::write(&ragged, vec![7u8; 1000]).unwrap();
let e = image_writer::write_raw(&ragged, &t.path().join("out.raw")).unwrap_err();
assert!(e.to_string().contains(ERR_SOURCE), "a partial sector is refused: {e:#}");
// Empty.
let empty = t.path().join("empty.raw");
std::fs::write(&empty, b"").unwrap();
let e = image_writer::write_vhd(&empty, &t.path().join("out.vhd")).unwrap_err();
assert!(e.to_string().contains(ERR_SOURCE), "an empty image is refused: {e:#}");
// Missing.
let e = image_writer::write_qcow2(&t.path().join("nope.raw"), &t.path().join("o.qcow2"))
.unwrap_err();
assert!(e.to_string().contains(ERR_SOURCE), "a missing source is refused: {e:#}");
}
// ─────────────────────────────────────────────────────────────────────────────
// VHD — Azure
// ─────────────────────────────────────────────────────────────────────────────
#[test]
fn the_vhd_is_a_fixed_disk_whose_footer_reads_back_through_the_validator() {
let t = tmp();
let src = t.path().join("installed.raw");
let want = fake_installed_disk(&src, 8 * MIB);
let dst = t.path().join("azure.vhd");
let p = image_writer::write_vhd(&src, &dst).expect("write the VHD medium");
// APPLIED OUTPUT: the file is data + exactly 512 footer bytes.
assert_eq!(p.file_size, 8 * MIB + VHD_FOOTER_LEN as u64, "data + one 512-byte footer");
let bytes = std::fs::read(&dst).unwrap();
assert_eq!(&bytes[..want.len()], &want[..], "the VHD body is the installed disk verbatim");
// The footer is read back by the VALIDATOR, not by re-reading our own struct.
let f = image_writer::read_vhd_footer(&dst).expect("the footer validates");
assert_eq!(&f.cookie, b"conectix");
assert_eq!(f.disk_type, image_writer::VHD_TYPE_FIXED, "disk type 2 — fixed");
assert_eq!(f.data_offset, image_writer::VHD_DATA_OFFSET_FIXED, "no dynamic-disk header");
assert_eq!(f.features, image_writer::VHD_FEATURES);
assert_eq!(f.format_version, image_writer::VHD_FORMAT_VERSION);
assert_eq!(f.current_size, 8 * MIB);
assert_eq!(f.original_size, 8 * MIB);
assert_eq!(f.saved_state, 0);
assert_eq!(f.timestamp, image_writer::VHD_TIMESTAMP_DEFAULT);
// The unique id is a well-formed v4-shaped UUID, not sixteen raw bytes.
assert_eq!(f.unique_id[6] & 0xF0, 0x40, "RFC-4122 version nibble");
assert_eq!(f.unique_id[8] & 0xC0, 0x80, "RFC-4122 variant bits");
// The geometry covers the disk (Hyper-V sizes from CHS).
assert!(
f.chs_bytes() >= 8 * MIB - SECTOR * 64,
"CHS {}x{}x{} = {} B must cover the 8 MiB disk",
f.cylinders,
f.heads,
f.sectors_per_track,
f.chs_bytes()
);
}
#[test]
fn azure_mib_rounding_is_applied_to_the_bytes_not_merely_documented() {
let t = tmp();
// 1 MiB + one sector: the shape that costs a whole upload to be told about.
let src = t.path().join("odd.raw");
let want = fake_installed_disk(&src, MIB + SECTOR);
let dst = t.path().join("odd.vhd");
let p = image_writer::write_vhd(&src, &dst).expect("write the VHD medium");
assert_eq!(p.source_size, MIB + SECTOR);
assert_eq!(p.virtual_size, 2 * MIB, "rounded UP to the next whole MiB");
assert_eq!(p.padded, 2 * MIB - (MIB + SECTOR));
assert_eq!(p.file_size, 2 * MIB + VHD_FOOTER_LEN as u64);
// The padding is really zeros ON DISK — the applied output, not the field.
let bytes = std::fs::read(&dst).unwrap();
assert_eq!(&bytes[..want.len()], &want[..], "the body is unchanged");
assert!(
bytes[want.len()..(2 * MIB) as usize].iter().all(|&b| b == 0),
"every padded byte is zero"
);
let f = image_writer::read_vhd_footer(&dst).expect("the padded footer validates");
assert_eq!(f.current_size % MIB, 0, "Azure's rule holds on the way back in");
assert_eq!(f.current_size, 2 * MIB);
}
#[test]
fn the_vhd_is_deterministic_for_the_same_installed_disk() {
let t = tmp();
let src = t.path().join("installed.raw");
fake_installed_disk(&src, 3 * MIB);
let a = t.path().join("a.vhd");
let b = t.path().join("b.vhd");
image_writer::write_vhd(&src, &a).unwrap();
image_writer::write_vhd(&src, &b).unwrap();
assert_eq!(
std::fs::read(&a).unwrap(),
std::fs::read(&b).unwrap(),
"the same install converts byte-identically — the property a content-addressed build needs"
);
}
#[test]
fn vhd_chs_follows_the_spec_and_clamps_where_the_spec_clamps() {
// Small disk: the 17-sector branch, heads at least 4.
let (c, h, s) = image_writer::vhd_chs(8 * MIB / SECTOR);
assert!(h >= 4 && s > 0 && c > 0, "small-disk geometry {c}x{h}x{s}");
assert!(
c as u64 * h as u64 * s as u64 * SECTOR >= 8 * MIB - 64 * SECTOR,
"geometry covers the disk"
);
// Big disk: the 255-sector branch.
let (c, h, s) = image_writer::vhd_chs(200 * 1024 * MIB / SECTOR);
assert_eq!((h, s), (16, 255), "the large-disk branch is 16 heads x 255 sectors");
assert!(c > 0);
// Past the CHS ceiling everything clamps rather than wrapping.
let (c, h, s) = image_writer::vhd_chs(u64::MAX);
assert_eq!((c, h, s), (65535, 16, 255), "the spec's own clamp");
}
// ── the VHD red probes ──────────────────────────────────────────────────────
#[test]
fn red_probe_a_footer_cookie_that_is_not_conectix_is_refused() {
let t = tmp();
let src = t.path().join("installed.raw");
fake_installed_disk(&src, MIB);
let dst = t.path().join("bad-cookie.vhd");
image_writer::write_vhd(&src, &dst).unwrap();
// Green first, so the probe is known to be testing the corruption and not a
// fixture that never worked.
image_writer::read_vhd_footer(&dst).expect("the untouched footer validates");
let footer_at = std::fs::metadata(&dst).unwrap().len() - VHD_FOOTER_LEN as u64;
poke(&dst, footer_at, b"cxxnectix");
let e = image_writer::read_vhd_footer(&dst).unwrap_err();
assert!(e.to_string().contains(ERR_VHD_COOKIE), "cookie refusal by name: {e:#}");
}
#[test]
fn red_probe_a_footer_checksum_that_does_not_verify_is_refused() {
let t = tmp();
let src = t.path().join("installed.raw");
fake_installed_disk(&src, MIB);
let dst = t.path().join("bad-sum.vhd");
image_writer::write_vhd(&src, &dst).unwrap();
image_writer::read_vhd_footer(&dst).expect("the untouched footer validates");
let footer_at = std::fs::metadata(&dst).unwrap().len() - VHD_FOOTER_LEN as u64;
// Flip the creator timestamp: the cookie stays right, the SUM stops matching.
// A checksum guard that only looked at the checksum FIELD would not see this.
poke(&dst, footer_at + 24, &0xAABB_CCDDu32.to_be_bytes());
let e = image_writer::read_vhd_footer(&dst).unwrap_err();
assert!(e.to_string().contains(ERR_VHD_CHECKSUM), "checksum refusal by name: {e:#}");
// …and the other direction: corrupt the checksum field itself.
let dst2 = t.path().join("bad-sum2.vhd");
image_writer::write_vhd(&src, &dst2).unwrap();
let at2 = std::fs::metadata(&dst2).unwrap().len() - VHD_FOOTER_LEN as u64 + 64;
poke(&dst2, at2, &[0, 0, 0, 0]);
let e = image_writer::read_vhd_footer(&dst2).unwrap_err();
assert!(e.to_string().contains(ERR_VHD_CHECKSUM), "a zeroed checksum is refused: {e:#}");
}
#[test]
fn red_probe_a_virtual_size_that_is_not_mib_aligned_is_refused_both_ways() {
// On the way OUT: the constructor refuses to mint such a footer at all.
let e = VhdFooter::fixed(MIB + SECTOR, [0u8; 16], &VhdOptions::default()).unwrap_err();
assert!(e.to_string().contains(ERR_VHD_NOT_MIB), "the constructor refuses: {e:#}");
// On the way IN: a footer that claims a non-MiB size is refused by the parser,
// even with a perfectly valid checksum over the lie. This is the one Azure
// finds AFTER the upload.
let mut good = VhdFooter::fixed(2 * MIB, [0x11u8; 16], &VhdOptions::default()).unwrap();
good.current_size = 2 * MIB + SECTOR;
let bytes = good.to_bytes();
// The checksum is recomputed by `to_bytes`, so the image is internally
// consistent — only the MiB rule catches it.
assert_eq!(
u32::from_be_bytes(bytes[64..68].try_into().unwrap()),
image_writer::vhd_checksum(&bytes),
"the corrupted footer still checksums correctly — so this probe tests the MiB rule alone"
);
let e = VhdFooter::parse(&bytes).unwrap_err();
assert!(e.to_string().contains(ERR_VHD_NOT_MIB), "the parser refuses: {e:#}");
}
#[test]
fn red_probe_a_footer_truncated_by_one_byte_is_refused() {
let t = tmp();
let src = t.path().join("installed.raw");
fake_installed_disk(&src, MIB);
let dst = t.path().join("short.vhd");
image_writer::write_vhd(&src, &dst).unwrap();
image_writer::read_vhd_footer(&dst).expect("the untouched footer validates");
// Chop exactly one byte. The file is still 1 MiB + 511 bytes, so anything
// that only checked "is there a footer-sized tail" would pass it.
let full = std::fs::read(&dst).unwrap();
std::fs::write(&dst, &full[..full.len() - 1]).unwrap();
let e = image_writer::read_vhd_footer(&dst).unwrap_err();
let msg = e.to_string();
assert!(
msg.contains(ERR_VHD_COOKIE) || msg.contains(ERR_VHD_CHECKSUM),
"a one-byte truncation shifts the whole footer and is refused: {e:#}"
);
// And a file too short to hold a footer at all names the short-file refusal.
let stub = t.path().join("stub.vhd");
std::fs::write(&stub, vec![0u8; VHD_FOOTER_LEN - 1]).unwrap();
let e = image_writer::read_vhd_footer(&stub).unwrap_err();
assert!(e.to_string().contains(ERR_VHD_SHORT), "a sub-footer file is refused: {e:#}");
}
// ─────────────────────────────────────────────────────────────────────────────
// GCE — disk.raw in a gzipped tar
// ─────────────────────────────────────────────────────────────────────────────
#[test]
fn the_gce_tarball_holds_exactly_one_member_named_disk_raw() {
let t = tmp();
let src = t.path().join("installed.raw");
let want = fake_installed_disk(&src, 4 * MIB);
let dst = t.path().join("gce.tar.gz");
let p = image_writer::write_gce_tar_gz(&src, &dst).expect("write the GCE medium");
// THE contract, read off the produced artifact.
let names = image_writer::tar_gz_member_names(&dst).expect("list the tarball");
assert_eq!(names, vec![GCE_MEMBER_NAME.to_string()], "exactly one member, named disk.raw");
// …and it unpacks to the installed disk, byte for byte.
let out = t.path().join("disk.raw");
let n = image_writer::unpack_gce_tar_gz(&dst, &out).expect("unpack disk.raw");
assert_eq!(n, 4 * MIB);
assert_eq!(std::fs::read(&out).unwrap(), want, "the unpacked disk is the installed disk");
assert_eq!(p.content_sha256, sha256(&want));
// Compression really happened (a mostly-zero disk is highly compressible) —
// a "gzip" writer that emitted stored blocks would pass every name check.
assert!(
p.file_size < p.virtual_size / 4,
"the tarball is compressed: {} B for a {} B disk",
p.file_size,
p.virtual_size
);
}
#[test]
fn red_probe_a_tarball_whose_member_is_not_disk_raw_is_refused() {
let t = tmp();
let src = t.path().join("installed.raw");
let body = fake_installed_disk(&src, MIB);
// Hand-build the WRONG tarball — the same tar+flate2 libraries, one field
// changed. A probe that cannot construct the failure is not a probe.
let wrong = t.path().join("wrong-name.tar.gz");
write_tar_gz(&wrong, &[("image.raw", &body)]);
let names = image_writer::tar_gz_member_names(&wrong).unwrap();
assert_eq!(names, vec!["image.raw".to_string()], "the fixture really is misnamed");
let e = image_writer::unpack_gce_tar_gz(&wrong, &t.path().join("o.raw")).unwrap_err();
assert!(e.to_string().contains(ERR_GCE_MEMBER), "a misnamed member is refused: {e:#}");
// The near-miss that looks right in a listing: the disk one directory down.
//
// MEASURED, and worth recording: `./disk.raw` is NOT a near-miss at all —
// `tar::Header::set_path` normalises the leading `./` away, so the member
// really is called `disk.raw` and the writer would have been right. The
// first draft of this probe asserted it was refused and went RED for the
// wrong reason. `disk/disk.raw` is the real one: a member inside a
// directory, which GCE rejects and a name check that only looked for the
// suffix would accept.
let dotted = t.path().join("nested.tar.gz");
write_tar_gz(&dotted, &[("disk/disk.raw", &body)]);
let names = image_writer::tar_gz_member_names(&dotted).unwrap();
assert_eq!(names, vec!["disk/disk.raw".to_string()], "the fixture really is nested");
let e = image_writer::unpack_gce_tar_gz(&dotted, &t.path().join("o2.raw")).unwrap_err();
assert!(e.to_string().contains(ERR_GCE_MEMBER), "`disk/disk.raw` is not `disk.raw`: {e:#}");
// A second member is as wrong as a wrong name.
let extra = t.path().join("extra.tar.gz");
write_tar_gz(&extra, &[(GCE_MEMBER_NAME, &body), ("README", &body[..512].to_vec())]);
let e = image_writer::unpack_gce_tar_gz(&extra, &t.path().join("o3.raw")).unwrap_err();
assert!(e.to_string().contains(ERR_GCE_MEMBER), "a second member is refused: {e:#}");
// And the control: the RIGHT name through the same fixture builder passes,
// so the probe is failing on the name and not on the fixture.
let right = t.path().join("right.tar.gz");
write_tar_gz(&right, &[(GCE_MEMBER_NAME, &body)]);
let n = image_writer::unpack_gce_tar_gz(&right, &t.path().join("o4.raw"))
.expect("the correctly named fixture unpacks");
assert_eq!(n, MIB);
}
/// Build a gzipped tar with the given `(name, bytes)` members — the red probes'
/// fixture builder, using the same libraries the writer does.
fn write_tar_gz(path: &Path, members: &[(&str, &Vec<u8>)]) {
let f = std::fs::File::create(path).expect("create the fixture tarball");
let gz = flate2::write::GzEncoder::new(f, flate2::Compression::fast());
let mut b = tar::Builder::new(gz);
for (name, bytes) in members {
let mut h = tar::Header::new_ustar();
h.set_path(name).expect("name the fixture member");
h.set_size(bytes.len() as u64);
h.set_mode(0o644);
h.set_entry_type(tar::EntryType::Regular);
h.set_cksum();
b.append(&h, bytes.as_slice()).expect("append the fixture member");
}
b.into_inner().expect("finish tar").finish().expect("finish gzip");
}
// ─────────────────────────────────────────────────────────────────────────────
// qcow2 — OVH / Scaleway / OpenStack
// ─────────────────────────────────────────────────────────────────────────────
#[test]
fn the_qcow2_reconstructs_the_installed_disk_through_its_own_l1_l2_mapping() {
let t = tmp();
let src = t.path().join("installed.raw");
let want = fake_installed_disk(&src, 6 * MIB);
let dst = t.path().join("ovh.qcow2");
let p = image_writer::write_qcow2(&src, &dst).expect("write the qcow2 medium");
let mut r = Qcow2Reader::open(&dst).expect("the qcow2 header validates");
let h = r.header().clone();
assert_eq!(h.version, 3, "qcow2 v3");
assert_eq!(h.size, 6 * MIB, "the guest disk size");
assert_eq!(h.cluster_bits, 16, "64 KiB clusters");
assert_eq!(h.refcount_order, 4, "16-bit refcounts");
assert_eq!(h.header_length, 112);
assert_eq!(h.backing_file_offset, 0, "unbacked");
assert_eq!(h.crypt_method, 0, "unencrypted");
assert_eq!(h.nb_snapshots, 0);
assert_eq!(h.incompatible_features, 0, "nothing a reader must implement to open it");
// THE proof: reconstruct the guest disk out of the mapping and compare.
let back = t.path().join("back.raw");
let n = r.to_raw(&back).expect("reconstruct the raw disk");
assert_eq!(n, 6 * MIB);
assert_eq!(std::fs::read(&back).unwrap(), want, "the qcow2 reconstructs the installed disk");
assert_eq!(r.content_sha256().unwrap(), p.content_sha256, "the content id round-trips");
assert_eq!(p.content_sha256, sha256(&want));
// The partition table survived the mapping.
let probe = image_writer::probe_disk(&back).expect("probe the reconstruction");
assert!(probe.mbr_signature && probe.gpt_header, "the reconstruction still carries its GPT");
}
#[test]
fn the_qcow2_is_sparse_and_the_saving_is_measured_not_claimed() {
let t = tmp();
let src = t.path().join("mostly-zero.raw");
// 64 MiB of which ~364 KiB is non-zero.
fake_installed_disk(&src, 64 * MIB);
let dst = t.path().join("sparse.qcow2");
let p = image_writer::write_qcow2(&src, &dst).expect("write the qcow2 medium");
assert_eq!(p.virtual_size, 64 * MIB);
assert!(
p.file_size < 2 * MIB,
"a 64 MiB disk with ~364 KiB of data must not cost 64 MiB: file is {} B ({:.1}% of virtual)",
p.file_size,
100.0 * p.file_size as f64 / p.virtual_size as f64
);
// Sparse must still read back identically — a small file that lost data
// would pass the size assertion above on its own.
let mut r = Qcow2Reader::open(&dst).unwrap();
assert_eq!(r.content_sha256().unwrap(), p.content_sha256, "sparse round-trips exactly");
}
#[test]
fn a_qcow2_larger_than_one_l2_table_maps_every_cluster() {
// One L2 table covers 8192 x 64 KiB = 512 MiB. 600 MiB forces a second L1
// slot, which is where an off-by-one in the L1 walk shows up and a small
// fixture never would.
let t = tmp();
let src = t.path().join("big.raw");
let size = 600 * MIB;
let mut d = vec![0u8; size as usize];
// Data on BOTH sides of the 512 MiB L2 boundary.
for (i, b) in d[..128 * 1024].iter_mut().enumerate() {
*b = (i % 251) as u8 + 1;
}
for (i, b) in d[(520 * MIB) as usize..(520 * MIB) as usize + 128 * 1024].iter_mut().enumerate()
{
*b = (i % 241) as u8 + 3;
}
std::fs::write(&src, &d).unwrap();
let dst = t.path().join("big.qcow2");
let p = image_writer::write_qcow2(&src, &dst).unwrap();
let mut r = Qcow2Reader::open(&dst).unwrap();
assert!(r.header().l1_size >= 2, "600 MiB needs more than one L2 table");
assert_eq!(r.content_sha256().unwrap(), p.content_sha256);
assert_eq!(p.content_sha256, sha256(&d), "both sides of the L2 boundary survive");
}
#[test]
fn red_probe_a_qcow2_with_a_bad_magic_version_or_incompatible_feature_is_refused() {
let t = tmp();
let src = t.path().join("installed.raw");
fake_installed_disk(&src, MIB);
let good = t.path().join("good.qcow2");
image_writer::write_qcow2(&src, &good).unwrap();
Qcow2Reader::open(&good).expect("the untouched image opens");
// Magic.
let bad_magic = t.path().join("magic.qcow2");
std::fs::copy(&good, &bad_magic).unwrap();
poke(&bad_magic, 0, b"QFIx");
let e = Qcow2Reader::open(&bad_magic).unwrap_err();
assert!(e.to_string().contains(ERR_QCOW2_MAGIC), "bad magic refused: {e:#}");
// Version 2 — a real format this writer does not emit and this reader must
// not pretend to read.
let v2 = t.path().join("v2.qcow2");
std::fs::copy(&good, &v2).unwrap();
poke(&v2, 4, &2u32.to_be_bytes());
let e = Qcow2Reader::open(&v2).unwrap_err();
assert!(e.to_string().contains(ERR_QCOW2_VERSION), "version 2 refused: {e:#}");
// incompatible_features bit 0 = "dirty". Anything set here means the reader
// below is not entitled to interpret the mapping.
let dirty = t.path().join("dirty.qcow2");
std::fs::copy(&good, &dirty).unwrap();
poke(&dirty, 72, &1u64.to_be_bytes());
let e = Qcow2Reader::open(&dirty).unwrap_err();
assert!(e.to_string().contains(ERR_QCOW2_FEATURES), "an incompatible feature refused: {e:#}");
}
// ─────────────────────────────────────────────────────────────────────────────
// The matrix, and the join back to the roster
// ─────────────────────────────────────────────────────────────────────────────
#[test]
fn one_install_four_media_and_a_single_content_identity() {
let t = tmp();
let src = t.path().join("installed.raw");
let want = fake_installed_disk(&src, 8 * MIB); // MiB-aligned: the VHD pads nothing
let out = t.path().join("media");
let products = image_writer::write_all_media(&src, &out, "korp-appliance").expect("all media");
assert_eq!(products.len(), 4, "one install, four media");
let id = sha256(&want);
for p in &products {
assert!(p.path.is_file(), "{} really exists", p.path.display());
assert_eq!(
p.content_sha256,
id,
"{} carries the ONE content identity of this install",
p.medium.as_str()
);
assert_eq!(p.padded, 0, "{}: an 8 MiB disk needs no padding", p.medium.as_str());
}
// The names are the ones a cloud console will show.
let names: Vec<String> = products
.iter()
.map(|p| p.path.file_name().unwrap().to_string_lossy().into_owned())
.collect();
assert_eq!(
names,
vec![
"korp-appliance.raw",
"korp-appliance.vhd",
"korp-appliance.tar.gz",
"korp-appliance.qcow2",
]
);
}
#[test]
fn every_roster_row_that_imports_a_disk_reaches_a_writer() {
// The bug this module exists to fix, as a standing guard: a preset declaring
// an IMPORT medium with no writer is a row that can only ever be read.
let mut unreachable: Vec<&str> = Vec::new();
let mut reached = 0usize;
for p in PRESETS {
let imports = !matches!(
p.medium,
MediumBring::IsoCdrom | MediumBring::Metal(_) | MediumBring::Unknown
);
match (imports, CloudMedium::for_preset(p)) {
(true, None) => unreachable.push(p.name),
(true, Some(_)) => reached += 1,
(false, _) => {}
}
}
assert!(unreachable.is_empty(), "presets declaring an import medium with no writer: {unreachable:?}");
assert!(reached >= 8, "at least the eight known import rows reach a writer, got {reached}");
// Spot-check the join in both directions on the rows that named the clouds.
let azure: Vec<&str> = CloudMedium::Vhd.presets().iter().map(|p| p.name).collect();
assert!(azure.contains(&"azure-gen1") && azure.contains(&"azure-gen2"), "Azure takes VHD: {azure:?}");
let gce: Vec<&str> = CloudMedium::GceTarGz.presets().iter().map(|p| p.name).collect();
assert!(gce.contains(&"gce-virtio") && gce.contains(&"gce-gvnic"), "GCE takes disk.raw.tar.gz: {gce:?}");
let q: Vec<&str> = CloudMedium::Qcow2.presets().iter().map(|p| p.name).collect();
assert!(q.contains(&"ovh-public-cloud") && q.contains(&"scaleway-instance"), "qcow2 clouds: {q:?}");
let raw: Vec<&str> = CloudMedium::Raw.presets().iter().map(|p| p.name).collect();
assert!(raw.contains(&"aws-nitro") && raw.contains(&"aws-xen-hvm"), "AWS takes raw: {raw:?}");
// An ISO platform must NOT be claimed by a disk-image writer.
for p in PRESETS.iter().filter(|p| p.medium.is_iso_attach()) {
assert_eq!(CloudMedium::for_preset(p), None, "{} is iso_writer's, not ours", p.name);
}
}
#[test]
fn the_qemu_format_word_matches_what_each_medium_actually_is() {
assert_eq!(CloudMedium::Raw.qemu_format(), "raw");
assert_eq!(CloudMedium::Vhd.qemu_format(), "vpc", "QEMU calls the VHD format `vpc`");
assert_eq!(CloudMedium::Qcow2.qemu_format(), "qcow2");
assert_eq!(CloudMedium::GceTarGz.qemu_format(), "raw", "after unpacking, which is what GCE does too");
assert!(!CloudMedium::GceTarGz.is_attachable(), "a tarball is not a disk");
for m in CloudMedium::ALL {
assert!(m.is_attachable() || m == CloudMedium::GceTarGz);
assert!(!m.describe().is_empty());
assert!(!m.file_extension().is_empty());
}
}
/// A path helper the boot test also wants: where `write_all_media` puts things.
/// Kept here so both suites agree on one answer.
#[allow(dead_code)]
pub fn medium_path(dir: &Path, stem: &str, m: CloudMedium) -> PathBuf {
dir.join(format!("{stem}.{}", m.file_extension()))
}
#[test]
fn the_drive_argv_names_each_medium_s_own_format_word() {
// The rendered argv is applied output: it is the exact string QEMU (and a
// ⚒ Build Thing boot cell) receives, and `format=vpc` is the one word a
// filename sniff cannot produce.
//
// Proven RED under QEMU on 2026-09-05: a VHD written WITHOUT its footer is
// refused by `format=vpc` with "invalid VPC image", so this word really is
// the one that makes the container be parsed rather than ignored.
let p = Path::new("/srv/media/korp-cloud");
assert_eq!(
CloudMedium::Vhd.qemu_drive_arg(&p.with_extension("vhd")).unwrap(),
vec!["-drive".to_string(), "file=/srv/media/korp-cloud.vhd,if=virtio,format=vpc".to_string()]
);
assert_eq!(
CloudMedium::Qcow2.qemu_drive_arg(&p.with_extension("qcow2")).unwrap(),
vec![
"-drive".to_string(),
"file=/srv/media/korp-cloud.qcow2,if=virtio,format=qcow2".to_string()
]
);
assert_eq!(
CloudMedium::Raw.qemu_drive_arg(&p.with_extension("raw")).unwrap(),
vec!["-drive".to_string(), "file=/srv/media/korp-cloud.raw,if=virtio,format=raw".to_string()]
);
assert!(
CloudMedium::GceTarGz.qemu_drive_arg(p).is_none(),
"a tarball renders NO -drive — it must be unpacked first, not attached"
);
}