From cbfd972be5fa7412593c2b57a1d12bd9e0b299ff Mon Sep 17 00:00:00 2001 From: Albert Hui Date: Sun, 2 Aug 2026 08:35:24 +0800 Subject: [PATCH] fix(tests): resolve qemu-img via PATH so the differential runs off macOS The oracle was pinned to the macOS-arm64 Homebrew path: const QEMU_IMG: &str = "/opt/homebrew/bin/qemu-img"; That path does not exist on the ubuntu-latest runner, so every qemu differential in this crate resolved nothing and returned early on every CI run -- despite ci.yml installing qemu-utils in two jobs specifically to feed them. The tests reported "4 passed" while validating nothing. Resolve through PATH first (covering any install location), falling back to the known absolute prefixes for a stripped PATH, with a QEMU_IMG_BIN override. A candidate counts only if `--version` succeeds, which proves the binary runs rather than merely exists. Verified on Linux (rust:1-slim-bookworm + apt qemu-utils 7.2.22), three controls: A real qemu-img on PATH -> 4 passed (0.20s -- real work) B QEMU_IMG_BIN=/bin/true -> 4 FAILED (proves the body runs the oracle and consumes its output) C QEMU_IMG_BIN=/nonexistent -> 4 passed (0.00s -- clean skip) Control C's 0.00s is the signature of the old bug; control A's 0.20s is what CI gets now. The reader agrees with qemu-img byte-for-byte on Linux -- no parser defect behind the skip. No RED commit: a test-plumbing change admits no honest failing test. The verification is the control matrix above. Co-Authored-By: Claude Opus 5 (1M context) --- core/tests/qemu_differential.rs | 56 ++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/core/tests/qemu_differential.rs b/core/tests/qemu_differential.rs index 396c512..ae39110 100644 --- a/core/tests/qemu_differential.rs +++ b/core/tests/qemu_differential.rs @@ -7,8 +7,47 @@ 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 { + 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 { + 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 @@ -16,10 +55,9 @@ use vhd::VhdReader; #[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. @@ -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", @@ -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", @@ -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",