#![cfg(test)]
use super::*;
#[test]
fn cache_key_renders_capacity_in_mib_and_version_fp() {
let key = template_cache_key(Filesystem::Btrfs, 256 * 1024 * 1024, "deadbeef");
assert_eq!(key, "btrfs-256m-deadbeef");
let key = template_cache_key(Filesystem::Raw, 1024 * 1024 * 1024, NOVERSION_FP);
assert_eq!(key, "raw-1024m-noversion");
}
#[test]
fn cache_key_truncates_sub_mib_capacity_to_zero() {
let key = template_cache_key(Filesystem::Btrfs, 1024, "deadbeef");
assert_eq!(key, "btrfs-0m-deadbeef");
}
#[test]
fn cache_key_rotates_with_version_fp() {
let v1 = template_cache_key(Filesystem::Btrfs, 256 * 1024 * 1024, "fp_v1");
let v2 = template_cache_key(Filesystem::Btrfs, 256 * 1024 * 1024, "fp_v2");
assert_ne!(v1, v2, "cache key must rotate when version_fp changes");
assert_eq!(v1, "btrfs-256m-fp_v1");
assert_eq!(v2, "btrfs-256m-fp_v2");
}
#[test]
fn template_path_includes_filename_constant() {
let tmp = tempfile::tempdir().expect("create tempdir");
let _guard = crate::test_support::test_helpers::EnvVarGuard::set("KTSTR_CACHE_DIR", tmp.path());
let path = template_path_for_key("btrfs-256m").expect("resolve template path");
assert!(path.ends_with(format!("btrfs-256m/{TEMPLATE_FILENAME}")));
}
#[test]
fn lookup_missing_returns_none() {
let tmp = tempfile::tempdir().expect("create tempdir");
let _guard = crate::test_support::test_helpers::EnvVarGuard::set("KTSTR_CACHE_DIR", tmp.path());
let result = lookup("missing-key").expect("lookup must not error on miss");
assert!(result.is_none());
}
#[test]
fn store_atomic_publishes_then_lookup_finds() {
let tmp = tempfile::tempdir().expect("create tempdir");
let _guard = crate::test_support::test_helpers::EnvVarGuard::set("KTSTR_CACHE_DIR", tmp.path());
let cache_root_path = cache_root().unwrap();
std::fs::create_dir_all(&cache_root_path).unwrap();
let staged = cache_root_path.join("staged.img");
std::fs::write(&staged, b"FAKE_TEMPLATE_BODY").unwrap();
let key = "test-key";
let installed = store_atomic(key, &staged).expect("store_atomic publishes");
assert!(installed.ends_with(format!("{key}/{TEMPLATE_FILENAME}")));
let found = lookup(key).expect("lookup ok").expect("lookup must hit");
assert_eq!(found, installed);
let body = std::fs::read(&found).unwrap();
assert_eq!(body, b"FAKE_TEMPLATE_BODY");
}
#[test]
fn store_atomic_idempotent_on_existing_entry() {
let tmp = tempfile::tempdir().expect("create tempdir");
let _guard = crate::test_support::test_helpers::EnvVarGuard::set("KTSTR_CACHE_DIR", tmp.path());
let cache_root_path = cache_root().unwrap();
std::fs::create_dir_all(&cache_root_path).unwrap();
let staged1 = cache_root_path.join("staged1.img");
std::fs::write(&staged1, b"FIRST").unwrap();
let key = "idem-key";
let installed1 = store_atomic(key, &staged1).unwrap();
let staged2 = cache_root_path.join("staged2.img");
std::fs::write(&staged2, b"SECOND").unwrap();
let installed2 = store_atomic(key, &staged2).unwrap();
assert_eq!(installed1, installed2);
let body = std::fs::read(&installed2).unwrap();
assert_eq!(body, b"FIRST");
}
#[test]
fn store_atomic_unlinks_src_on_idempotent_early_return() {
let tmp = tempfile::tempdir().expect("create tempdir");
let _guard = crate::test_support::test_helpers::EnvVarGuard::set("KTSTR_CACHE_DIR", tmp.path());
let cache_root_path = cache_root().unwrap();
std::fs::create_dir_all(&cache_root_path).unwrap();
let staged1 = cache_root_path.join("staged1.img");
std::fs::write(&staged1, b"FIRST").unwrap();
let key = "early-return-key";
store_atomic(key, &staged1).unwrap();
let staged2 = cache_root_path.join("staged2.img");
std::fs::write(&staged2, b"SECOND").unwrap();
store_atomic(key, &staged2).unwrap();
assert!(
!staged2.exists(),
"early-return path must unlink the obsolete staging image \
at {staged2:?}; without this cleanup the cache root \
accumulates orphan staging files across every concurrent \
peer that loses the publish race",
);
}
#[test]
fn locate_host_binary_actionable_error_when_missing() {
let tmp = tempfile::tempdir().expect("create tempdir");
let _guard = crate::test_support::test_helpers::EnvVarGuard::set("PATH", tmp.path());
let err = locate_host_binary("nonexistent-binary-9242", "imagined-package")
.expect_err("must error when binary absent");
let msg = err.to_string();
assert!(
msg.contains("nonexistent-binary-9242"),
"error names the binary: {msg}",
);
assert!(
msg.contains("imagined-package"),
"error names the package hint: {msg}",
);
}
#[test]
fn locate_host_mkfs_raw_returns_none() {
let tmp = tempfile::tempdir().expect("create tempdir");
let _path_guard = crate::test_support::test_helpers::EnvVarGuard::set("PATH", tmp.path());
let result =
locate_host_mkfs(Filesystem::Raw).expect("Raw must short-circuit before any PATH walk");
assert!(
result.is_none(),
"Filesystem::Raw has no userspace formatter; \
locate_host_mkfs must return Ok(None) without consulting \
PATH. Got: {result:?}",
);
}
#[test]
fn mkfs_version_fingerprint_is_deterministic() {
let path_var = match std::env::var_os("PATH") {
Some(p) => p,
None => return,
};
let mut working_binary: Option<PathBuf> = None;
for name in &["cat", "ls", "true"] {
for dir in std::env::split_paths(&path_var) {
let candidate = dir.join(name);
if !std::fs::metadata(&candidate)
.map(|m| m.is_file())
.unwrap_or(false)
{
continue;
}
let probe = std::process::Command::new(&candidate)
.arg("--version")
.output();
let Ok(output) = probe else {
continue;
};
if !output.stdout.is_empty() || !output.stderr.is_empty() {
working_binary = Some(candidate);
break;
}
}
if working_binary.is_some() {
break;
}
}
let Some(binary_path) = working_binary else {
return;
};
let fp1 =
mkfs_version_fingerprint(&binary_path).expect("first --version invocation must succeed");
let fp2 =
mkfs_version_fingerprint(&binary_path).expect("second --version invocation must succeed");
assert_eq!(
fp1, fp2,
"fingerprint must be deterministic across repeated \
invocations of the same binary"
);
assert_eq!(
fp1.len(),
16,
"fingerprint must render as 16 hex chars (64 bits): {fp1}",
);
assert!(
fp1.chars().all(|c| c.is_ascii_hexdigit()),
"fingerprint must be hex-only: {fp1}",
);
let cached = mkfs_version_fingerprint_cache()
.lock()
.expect("cache mutex")
.get(&binary_path)
.cloned();
assert_eq!(
cached.as_deref(),
Some(fp1.as_str()),
"first call must populate the per-process fingerprint cache; \
without the cache, ensure_template re-execs `--version` on \
every VM boot",
);
}
#[test]
fn build_template_via_vm_rejects_raw_filesystem() {
let tmp = tempfile::tempdir().expect("create tempdir");
let _guard = crate::test_support::test_helpers::EnvVarGuard::set("KTSTR_CACHE_DIR", tmp.path());
let err = build_template_via_vm(Filesystem::Raw, 256 * 1024 * 1024, tmp.path(), "raw-256m")
.expect_err("Raw must be rejected");
let msg = err.to_string();
assert!(
msg.contains("Filesystem::Raw"),
"error must name the rejected variant: {msg}",
);
assert!(
msg.contains("init_virtio_blk"),
"error must name the gate location for the operator: {msg}",
);
}
#[test]
fn verify_cache_dir_walks_up_to_existing_ancestor() {
let tmp = tempfile::tempdir().expect("create tempdir");
let nonexistent = tmp.path().join("nonexistent/sub/dir");
match verify_cache_dir_supports_reflink(&nonexistent) {
Ok(()) => { }
Err(e) => {
let msg = e.to_string();
assert!(
msg.contains("statfs.f_type") || msg.contains("FICLONE"),
"unexpected error wording: {msg}",
);
}
}
}
#[test]
fn verify_cache_dir_probe_note_fires_when_probe_differs_from_dir() {
let tmp = tempfile::tempdir().expect("create tempdir");
let nonexistent = tmp.path().join("nonexistent/sub/dir");
match verify_cache_dir_supports_reflink(&nonexistent) {
Ok(()) => {
}
Err(e) => {
let msg = e.to_string();
assert!(
msg.contains("ancestor") && msg.contains("no part of"),
"walk-up diagnostic must surface the probed \
ancestor when probe != dir; got: {msg}",
);
}
}
}
#[test]
fn verify_cache_dir_probe_note_absent_when_probe_equals_dir() {
let tmp = tempfile::tempdir().expect("create tempdir");
match verify_cache_dir_supports_reflink(tmp.path()) {
Ok(()) => {
}
Err(e) => {
let msg = e.to_string();
assert!(
!msg.contains("ancestor") && !msg.contains("no part of"),
"probe == dir branch must NOT emit the probe_note \
text; got: {msg}",
);
assert!(
msg.contains("statfs.f_type") || msg.contains("FICLONE"),
"diagnostic must still name the f_type; got: {msg}",
);
}
}
}
#[cfg(target_os = "linux")]
#[test]
fn verify_cache_dir_walks_through_dangling_symlink() {
let tmp = tempfile::tempdir().expect("create tempdir");
let symlink_path = tmp.path().join("dangling");
std::os::unix::fs::symlink("/nonexistent-symlink-target-9242", &symlink_path)
.expect("create dangling symlink");
let probe_path = symlink_path.join("sub");
match verify_cache_dir_supports_reflink(&probe_path) {
Ok(()) => {
}
Err(e) => {
let msg = e.to_string();
assert!(
msg.contains("statfs.f_type") || msg.contains("FICLONE"),
"symlink walk-up must produce an f_type-named \
diagnostic, not a symlink-resolution error; got: {msg}",
);
}
}
}
#[test]
fn staging_image_path_is_unique_per_key_and_pid() {
let cache_root = std::path::Path::new("/tmp/ktstr-fake-cache-root");
let pid = 12_345u32;
let p_256 = staging_image_path(cache_root, "btrfs-256m", pid);
let p_1024 = staging_image_path(cache_root, "btrfs-1024m", pid);
assert_ne!(
p_256, p_1024,
"cache_key qualifier missing from staging-image path: \
distinct keys collided",
);
assert!(
p_256
.to_string_lossy()
.contains("template.img.in-flight.btrfs-256m.12345"),
"256m staging path missing key/pid token: {p_256:?}",
);
assert!(
p_1024
.to_string_lossy()
.contains("template.img.in-flight.btrfs-1024m.12345"),
"1024m staging path missing key/pid token: {p_1024:?}",
);
let p_256_other_pid = staging_image_path(cache_root, "btrfs-256m", 67_890);
assert_ne!(p_256, p_256_other_pid);
assert_eq!(
p_256,
staging_image_path(cache_root, "btrfs-256m", pid),
"staging_image_path must be a pure function of its inputs",
);
}
#[test]
fn create_and_size_staging_image_cleans_up_on_set_len_failure() {
let tmp = tempfile::tempdir().expect("create tempdir");
let staging_path = tmp.path().join("template.img.in-flight.btrfs-256m.0");
let err = create_and_size_staging_image(&staging_path, u64::MAX)
.expect_err("set_len(u64::MAX) must fail at the i64 cast");
let msg = err.to_string();
assert!(
msg.contains("set staging image length"),
"error must surface the set_len-failed context: {msg}",
);
match std::fs::metadata(&staging_path) {
Err(e) if e.kind() == io::ErrorKind::NotFound => { }
Ok(m) => panic!(
"staging image not cleaned up after set_len failure: \
still exists at {staging_path:?} ({} bytes)",
m.len(),
),
Err(e) => panic!("unexpected stat error: {e}"),
}
}
#[test]
fn fsid_bytes_is_deterministic_for_same_path() {
let tmp = tempfile::tempdir().expect("create tempdir");
let buf1 = statfs_path(tmp.path()).expect("first statfs");
let buf2 = statfs_path(tmp.path()).expect("second statfs");
assert_eq!(
fsid_bytes(&buf1),
fsid_bytes(&buf2),
"fsid_bytes must be deterministic across repeated statfs \
calls against the same path; a mismatch would indicate \
the bytewise f_fsid read produces different output for \
the same input on this host",
);
}
#[test]
fn fsid_bytes_distinguishes_different_filesystems() {
let tmp = tempfile::tempdir().expect("create tempdir");
let tmp_buf = statfs_path(tmp.path()).expect("statfs tempdir");
let tmp_fsid = fsid_bytes(&tmp_buf);
let candidates: &[&str] = &["/proc", "/sys", "/dev", "/"];
let mut probe_outcomes: Vec<String> = Vec::with_capacity(candidates.len());
for cand in candidates {
let path = std::path::Path::new(cand);
match statfs_path(path) {
Ok(buf) => {
let fsid = fsid_bytes(&buf);
if buf.f_type != tmp_buf.f_type || fsid != tmp_fsid {
assert_ne!(
tmp_fsid, fsid,
"fsid_bytes must differ across distinct filesystems \
(tempdir f_type=0x{:x}, {cand} f_type=0x{:x}); a match \
would indicate the bytewise f_fsid read is producing a \
constant byte pattern instead of the real fsid_t — \
e.g. reading from a wrong offset within libc::statfs",
tmp_buf.f_type, buf.f_type,
);
return;
}
probe_outcomes.push(format!(
"{cand}: same fs (f_type=0x{:x}, fsid==tempdir)",
buf.f_type,
));
}
Err(e) => {
probe_outcomes.push(format!("{cand}: statfs error ({e})"));
}
}
}
panic!(
"fsid_bytes_distinguishes_different_filesystems found no candidate path \
that resolves to a different filesystem from tempdir (f_type=0x{:x}). \
At least one of the standard pseudo filesystems should mount \
independently of /tmp; the absence of any distinguishing path is \
anomalous — the cross-fs property at store_atomic depends on \
distinguishability, so silent-skip would falsely report green. \
Probe outcomes: {probe_outcomes:?}",
tmp_buf.f_type,
);
}
#[test]
fn clean_orphaned_tmp_dirs_handles_missing_root() {
let tmp = tempfile::tempdir().expect("create tempdir");
let nonexistent = tmp.path().join("never-created");
let count = clean_orphaned_tmp_dirs(&nonexistent).expect("missing root must not error");
assert_eq!(count, 0, "missing root sweeps zero entries");
}
#[test]
fn clean_orphaned_tmp_dirs_removes_dead_pid_staging_image() {
let tmp = tempfile::tempdir().expect("create tempdir");
let cache_root = tmp.path();
let dead_pid = i32::MAX;
let leaked = cache_root.join(format!("template.img.in-flight.btrfs-256m.{dead_pid}",));
std::fs::write(&leaked, b"FAKE_STAGING_IMG").unwrap();
let count = clean_orphaned_tmp_dirs(cache_root).expect("sweep must succeed");
assert_eq!(count, 1, "exactly one debris entry removed");
assert!(!leaked.exists(), "dead-pid staging image must be unlinked",);
}
#[test]
fn clean_orphaned_tmp_dirs_removes_dead_pid_staging_directory() {
let tmp = tempfile::tempdir().expect("create tempdir");
let cache_root = tmp.path();
let dead_pid = i32::MAX;
let leaked = cache_root.join(format!("btrfs-256m.tmp.{dead_pid}"));
std::fs::create_dir_all(&leaked).unwrap();
std::fs::write(leaked.join("template.img"), b"PARTIAL").unwrap();
let count = clean_orphaned_tmp_dirs(cache_root).expect("sweep must succeed");
assert_eq!(count, 1, "exactly one debris entry removed");
assert!(
!leaked.exists(),
"dead-pid staging directory must be removed",
);
}
#[test]
fn clean_orphaned_tmp_dirs_removes_dead_pid_per_test_image() {
let tmp = tempfile::tempdir().expect("create tempdir");
let cache_root = tmp.path();
let dead_pid = i32::MAX;
let leaked = cache_root.join(format!(".per-test-{dead_pid}-deadbeef-cafe.img"));
std::fs::write(&leaked, b"FAKE_PER_TEST_IMG").unwrap();
let count = clean_orphaned_tmp_dirs(cache_root).expect("sweep must succeed");
assert_eq!(count, 1, "exactly one debris entry removed");
assert!(
!leaked.exists(),
"dead-pid per-test backing file must be unlinked",
);
}
#[test]
fn clean_orphaned_tmp_dirs_preserves_live_pid_per_test_image() {
let tmp = tempfile::tempdir().expect("create tempdir");
let cache_root = tmp.path();
let live_pid = std::process::id();
let live_file = cache_root.join(format!(".per-test-{live_pid}-deadbeef-cafe.img"));
std::fs::write(&live_file, b"LIVE_PER_TEST_BACKING").unwrap();
let count = clean_orphaned_tmp_dirs(cache_root).expect("sweep must succeed");
assert_eq!(
count, 0,
"live-pid per-test backing must not be removed by sweep",
);
assert!(
live_file.exists(),
"live-pid per-test backing must survive the sweep",
);
}
#[test]
fn clean_orphaned_tmp_dirs_preserves_live_pid_debris() {
let tmp = tempfile::tempdir().expect("create tempdir");
let cache_root = tmp.path();
let live_pid = std::process::id();
let live_image = cache_root.join(format!("template.img.in-flight.btrfs-256m.{live_pid}",));
std::fs::write(&live_image, b"LIVE_PEER_DEBRIS").unwrap();
let count = clean_orphaned_tmp_dirs(cache_root).expect("sweep must succeed");
assert_eq!(
count, 0,
"no entries removed when only live-pid debris exists",
);
assert!(
live_image.exists(),
"live-pid debris must be preserved across sweep",
);
}
#[test]
fn clean_orphaned_tmp_dirs_preserves_published_entries() {
let tmp = tempfile::tempdir().expect("create tempdir");
let cache_root = tmp.path();
let published = cache_root.join("btrfs-256m");
std::fs::create_dir_all(&published).unwrap();
std::fs::write(published.join(TEMPLATE_FILENAME), b"GOOD").unwrap();
let count = clean_orphaned_tmp_dirs(cache_root).expect("sweep must succeed");
assert_eq!(
count, 0,
"published cache entries must not be swept by debris GC",
);
assert!(published.is_dir(), "published entry must survive");
assert!(
published.join(TEMPLATE_FILENAME).is_file(),
"published template.img must survive",
);
}
#[test]
fn clean_orphaned_tmp_dirs_preserves_lock_subdirectory() {
let tmp = tempfile::tempdir().expect("create tempdir");
let cache_root = tmp.path();
let locks = cache_root.join(LOCK_DIR_NAME);
std::fs::create_dir_all(&locks).unwrap();
std::fs::write(locks.join("btrfs-256m.lock"), b"").unwrap();
let count = clean_orphaned_tmp_dirs(cache_root).expect("sweep must succeed");
assert_eq!(count, 0, ".locks/ must be invisible to the debris sweep",);
assert!(locks.is_dir(), ".locks/ subdirectory must survive");
assert!(
locks.join("btrfs-256m.lock").is_file(),
"individual lockfiles must survive",
);
}
#[test]
fn clean_all_removes_published_entry() {
let tmp = tempfile::tempdir().expect("create tempdir");
let _guard = crate::test_support::test_helpers::EnvVarGuard::set("KTSTR_CACHE_DIR", tmp.path());
let cache_root_path = cache_root().unwrap();
std::fs::create_dir_all(&cache_root_path).unwrap();
let staged = cache_root_path.join("staged.img");
std::fs::write(&staged, b"FAKE_TEMPLATE").unwrap();
let installed = store_atomic("btrfs-256m", &staged).expect("store_atomic publishes");
assert!(installed.is_file());
let count = clean_all().expect("clean_all must succeed");
assert_eq!(count, 1, "exactly one published entry removed");
assert!(
lookup("btrfs-256m").expect("lookup ok").is_none(),
"published entry must be gone after clean_all",
);
let lock_path = lock_path_for_key("btrfs-256m").unwrap();
if lock_path.exists() {
assert!(lock_path.is_file(), "lockfile inode must survive clean_all",);
}
}
#[test]
fn clean_all_reports_zero_on_empty_cache() {
let tmp = tempfile::tempdir().expect("create tempdir");
let _guard = crate::test_support::test_helpers::EnvVarGuard::set("KTSTR_CACHE_DIR", tmp.path());
let count = clean_all().expect("clean_all must succeed on empty");
assert_eq!(count, 0);
}
#[test]
fn clean_all_handles_missing_cache_root() {
let tmp = tempfile::tempdir().expect("create tempdir");
let nonexistent = tmp.path().join("never-created");
let _guard =
crate::test_support::test_helpers::EnvVarGuard::set("KTSTR_CACHE_DIR", &nonexistent);
let count = clean_all().expect("missing cache root must not error");
assert_eq!(count, 0);
}
#[test]
fn clean_all_skips_entry_locked_by_live_peer() {
let tmp = tempfile::tempdir().expect("create tempdir");
let _guard = crate::test_support::test_helpers::EnvVarGuard::set("KTSTR_CACHE_DIR", tmp.path());
let cache_root_path = cache_root().unwrap();
std::fs::create_dir_all(&cache_root_path).unwrap();
let staged = cache_root_path.join("staged.img");
std::fs::write(&staged, b"FAKE_TEMPLATE").unwrap();
let installed = store_atomic("btrfs-256m", &staged).expect("store_atomic publishes");
assert!(installed.is_file());
let _hold = acquire_template_lock("btrfs-256m").expect("acquire template lock");
let count = clean_all().expect("clean_all must succeed");
assert_eq!(count, 0, "locked entry must not be removed by clean_all",);
assert!(
lookup("btrfs-256m").expect("lookup ok").is_some(),
"locked entry must survive clean_all",
);
}
#[test]
fn clean_all_sweeps_debris_alongside_published_entries() {
let tmp = tempfile::tempdir().expect("create tempdir");
let _guard = crate::test_support::test_helpers::EnvVarGuard::set("KTSTR_CACHE_DIR", tmp.path());
let cache_root_path = cache_root().unwrap();
std::fs::create_dir_all(&cache_root_path).unwrap();
let staged = cache_root_path.join("staged.img");
std::fs::write(&staged, b"FAKE_TEMPLATE").unwrap();
store_atomic("btrfs-256m", &staged).unwrap();
let dead_pid = i32::MAX;
let debris = cache_root_path.join(format!("template.img.in-flight.btrfs-1024m.{dead_pid}",));
std::fs::write(&debris, b"DEBRIS").unwrap();
assert!(debris.is_file());
assert!(lookup("btrfs-256m").unwrap().is_some());
let count = clean_all().expect("clean_all must succeed");
assert_eq!(count, 1, "one published entry removed");
assert!(
!debris.exists(),
"debris must be removed by the embedded sweep",
);
assert!(
lookup("btrfs-256m").unwrap().is_none(),
"published entry must be removed by clean_all",
);
}