Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 48 additions & 8 deletions core/tests/qemu_differential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,57 @@

use std::io::{Read, Seek, SeekFrom};
use std::path::Path;
use std::process::Command;
use vhd::VhdReader;

/// Resolve a usable `qemu-img`, or `None` (the differential then skips).
///
/// `PATH` is probed first, so any install location works — including ones a
/// fixed list cannot anticipate: another package manager's prefix, or a
/// hand-built install. The absolute
/// candidates are the fallback for a stripped `PATH`: Homebrew on Apple silicon
/// and Intel, then `/usr/bin`, where the Linux CI runner's `qemu-utils` package
/// lands it. `QEMU_IMG_BIN` overrides both.
///
/// The hardcoded Homebrew path this replaces resolved only on a macOS-arm64 dev
/// machine, so every qemu differential here skipped silently on the Linux CI
/// runner despite CI installing `qemu-utils` for exactly these tests.
fn qemu_img_bin() -> Option<String> {
if let Ok(explicit) = std::env::var("QEMU_IMG_BIN") {
return usable(&explicit);
}
[
"qemu-img",
"/opt/homebrew/bin/qemu-img",
"/usr/local/bin/qemu-img",
"/usr/bin/qemu-img",
]
.into_iter()
.find_map(usable)
}

/// A candidate counts only if it actually executes: a successful `--version`
/// proves both that the name resolved and that the binary runs on this host,
/// which a bare `Path::exists()` check does not.
fn usable(candidate: &str) -> Option<String> {
Command::new(candidate)
.arg("--version")
.output()
.ok()
.filter(|o| o.status.success())
.map(|_| candidate.to_string())
}

// VHD uses CHS geometry, so the virtual size gets rounded from the source.
// Strategy: raw → VHD → raw_reference (both via qemu-img), then compare
// our reader output against raw_reference. qemu-img is authoritative for
// both conversions; we need not predict the CHS-rounded size ourselves.

#[test]
fn reads_match_qemu_raw_convert() {
const QEMU_IMG: &str = "/opt/homebrew/bin/qemu-img";
if !Path::new(QEMU_IMG).exists() {
let Some(qemu_img) = qemu_img_bin() else {
return;
}
};
let tmp = tempfile::tempdir().expect("tempdir");

// 1 MiB source with a deterministic non-trivial pattern.
Expand All @@ -30,7 +68,7 @@ fn reads_match_qemu_raw_convert() {

// raw → VHD (dynamic, qemu default VPC format).
let vhd_path = tmp.path().join("test.vhd");
let ok = std::process::Command::new(QEMU_IMG)
let ok = Command::new(&qemu_img)
.args([
"convert",
"-O",
Expand All @@ -45,7 +83,7 @@ fn reads_match_qemu_raw_convert() {

// VHD → reference raw (qemu-img resolves CHS rounding authoritatively).
let ref_path = tmp.path().join("reference.raw");
let ok = std::process::Command::new(QEMU_IMG)
let ok = Command::new(&qemu_img)
.args([
"convert",
"-O",
Expand Down Expand Up @@ -96,13 +134,15 @@ fn reads_match_qemu_raw_convert() {
// ── Corpus differential tests: real qemu-img generated VHDs ──────────────

fn corpus_vhd_matches_raw(corpus: &Path) {
const QEMU_IMG: &str = "/opt/homebrew/bin/qemu-img";
if !Path::new(QEMU_IMG).exists() || !corpus.exists() {
let Some(qemu_img) = qemu_img_bin() else {
return;
};
if !corpus.exists() {
return;
}
let tmp = tempfile::tempdir().expect("tempdir");
let ref_path = tmp.path().join("reference.raw");
let ok = std::process::Command::new(QEMU_IMG)
let ok = Command::new(&qemu_img)
.args([
"convert",
"-O",
Expand Down
Loading