use super::*;
#[test]
fn volume_key_boot_volume() {
assert_eq!(
volume_key(Path::new("/tmp/trusty-test")),
PathBuf::from("/"),
"/tmp/... must produce volume key /"
);
assert_eq!(
volume_key(Path::new("/usr/local/bin")),
PathBuf::from("/"),
"/usr/... must produce volume key /"
);
assert_eq!(
volume_key(Path::new("/")),
PathBuf::from("/"),
"root itself must produce volume key /"
);
assert_eq!(
volume_key(Path::new("/home/user/projects")),
PathBuf::from("/"),
"/home/... must produce volume key /"
);
}
#[test]
fn volume_key_external_volume() {
assert_eq!(
volume_key(Path::new("/Volumes/SSD1/Projects/trusty-tools")),
PathBuf::from("/Volumes/SSD1"),
"/Volumes/SSD1/... must produce volume key /Volumes/SSD1"
);
assert_eq!(
volume_key(Path::new("/Volumes/ExternalDrive/code")),
PathBuf::from("/Volumes/ExternalDrive"),
"/Volumes/ExternalDrive/... must produce volume key /Volumes/ExternalDrive"
);
assert_eq!(
volume_key(Path::new("/Volumes/SSD1")),
PathBuf::from("/Volumes/SSD1"),
"/Volumes/SSD1 itself must produce volume key /Volumes/SSD1"
);
}
#[test]
fn volume_key_linux_lowercase_volumes_is_root() {
assert_eq!(
volume_key(Path::new("/volumes/ssd1/projects/myrepo")),
PathBuf::from("/"),
"/volumes/... (lowercase) must produce volume key / on all platforms"
);
assert_eq!(
volume_key(Path::new("/VOLUMES/SSD1/projects/myrepo")),
PathBuf::from("/"),
"/VOLUMES/... (uppercase) must produce volume key / — not a canonical macOS path"
);
}
#[test]
fn probe_volume_accessible_tempdir() {
let tmp = tempfile::tempdir().unwrap();
let result = probe_volume(tmp.path(), tmp.path(), Duration::from_secs(5));
assert_eq!(
result,
VolumeAccessibility::Accessible,
"a real tmpdir must be accessible within 5s"
);
}
#[test]
fn probe_volume_nonexistent_path_returns_accessible() {
let nonexistent = Path::new("/tmp/trusty-723-definitely-not-here-xyz99999");
let result = probe_volume(nonexistent, nonexistent, Duration::from_secs(5));
assert_eq!(
result,
VolumeAccessibility::Accessible,
"a NotFound metadata call must return promptly (kernel answered), not time out"
);
}
#[test]
fn probe_uses_sample_path_not_volume_root() {
let tmp = tempfile::tempdir().unwrap();
let volume_root = tmp.path();
let inner_dir = tmp.path().join("inner-index");
std::fs::create_dir_all(&inner_dir).unwrap();
let result = probe_volume(volume_root, &inner_dir, Duration::from_secs(5));
assert_eq!(
result,
VolumeAccessibility::Accessible,
"probe of accessible inner dir must return Accessible"
);
let deep_nonexistent = tmp.path().join("a").join("b").join("c").join("never-here");
let result2 = probe_volume(volume_root, &deep_nonexistent, Duration::from_secs(5));
assert_eq!(
result2,
VolumeAccessibility::Accessible,
"ENOENT on probe_path must return Accessible (kernel answered fast, not hung)"
);
}
#[test]
#[serial_test::serial]
fn probe_timeout_increments_leaked_thread_count() {
let before = LEAKED_PROBE_THREAD_COUNT.load(Ordering::Relaxed);
let tmp = tempfile::tempdir().unwrap();
let result = probe_volume(tmp.path(), tmp.path(), Duration::ZERO);
let after = LEAKED_PROBE_THREAD_COUNT.load(Ordering::Relaxed);
assert_eq!(
result,
VolumeAccessibility::Inaccessible,
"zero-duration deadline must produce Inaccessible"
);
assert!(
after > before,
"LEAKED_PROBE_THREAD_COUNT must increment on timeout; before={before} after={after}"
);
}
#[test]
fn probe_all_volumes_accessible_returns_empty() {
let paths = vec![
PathBuf::from("/tmp/a"),
PathBuf::from("/tmp/b"),
PathBuf::from("/usr/local"),
];
let inaccessible = probe_all_volumes(&paths, Duration::from_secs(5));
assert!(
inaccessible.is_empty(),
"all boot-volume paths must be accessible; got: {inaccessible:?}"
);
}
#[test]
fn probe_all_volumes_distinct_keys() {
let paths = vec![
PathBuf::from("/tmp/proj-a"),
PathBuf::from("/tmp/proj-b"),
PathBuf::from("/usr/local/bin"),
];
let mut keys: std::collections::HashSet<PathBuf> = std::collections::HashSet::new();
for p in &paths {
keys.insert(volume_key(p));
}
assert_eq!(keys.len(), 1, "3 boot-volume paths must yield 1 unique key");
assert!(keys.contains(&PathBuf::from("/")));
}
#[test]
fn probe_all_volumes_parallel_bounded_time() {
let inaccessible = probe_all_volumes(&[], Duration::from_secs(5));
assert!(
inaccessible.is_empty(),
"empty input must return empty inaccessible set"
);
let paths = vec![
PathBuf::from("/tmp/proj-a"),
PathBuf::from("/tmp/proj-b"),
PathBuf::from("/usr/local"),
];
let inaccessible = probe_all_volumes(&paths, Duration::from_secs(5));
assert!(
inaccessible.is_empty(),
"all boot-volume paths must be accessible (parallel probe); got: {inaccessible:?}"
);
}
fn probe_with_injected_delays(
entries: Vec<(PathBuf, PathBuf, Duration)>,
deadline: Duration,
) -> std::collections::HashSet<PathBuf> {
use std::collections::HashSet;
use std::sync::mpsc;
use std::time::Instant;
if entries.is_empty() {
return HashSet::new();
}
let n = entries.len();
let end = Instant::now() + deadline;
let (tx, rx) = mpsc::channel::<PathBuf>();
let mut all_keys: HashSet<PathBuf> = HashSet::with_capacity(n);
let mut key_to_sample: std::collections::HashMap<PathBuf, PathBuf> =
std::collections::HashMap::with_capacity(n);
for (vol_key, sample_path, probe_delay) in entries {
all_keys.insert(vol_key.clone());
key_to_sample.insert(vol_key.clone(), sample_path);
let tx = tx.clone();
let key = vol_key;
let _ = std::thread::spawn(move || {
std::thread::sleep(probe_delay);
let _ = tx.send(key);
});
}
drop(tx);
let mut reported: HashSet<PathBuf> = HashSet::with_capacity(n);
loop {
if reported.len() == n {
break;
}
let remaining = end.saturating_duration_since(Instant::now());
match rx.recv_timeout(remaining) {
Ok(vol_key) => {
reported.insert(vol_key);
}
Err(_) => break,
}
}
let mut inaccessible: HashSet<PathBuf> = HashSet::new();
for vol_key in &all_keys {
if reported.contains(vol_key) {
continue;
}
let _sample = key_to_sample
.get(vol_key)
.map(|p| p.as_path())
.unwrap_or(vol_key.as_path());
LEAKED_PROBE_THREAD_COUNT.fetch_add(1, Ordering::Relaxed);
inaccessible.insert(vol_key.clone());
}
inaccessible
}
#[test]
#[serial_test::serial]
fn probe_all_volumes_multi_volume_no_fast_starvation() {
let deadline = Duration::from_millis(50);
let fast_delay = Duration::from_millis(5);
let slow_delay = Duration::from_millis(250);
let fast_vol_a = PathBuf::from("/tmp/trusty-723-fast-a");
let fast_vol_b = PathBuf::from("/tmp/trusty-723-fast-b");
let slow_vol = PathBuf::from("/tmp/trusty-723-slow");
let entries = vec![
(fast_vol_a.clone(), fast_vol_a.clone(), fast_delay),
(fast_vol_b.clone(), fast_vol_b.clone(), fast_delay),
(slow_vol.clone(), slow_vol.clone(), slow_delay),
];
let before_leaked = LEAKED_PROBE_THREAD_COUNT.load(Ordering::Relaxed);
let start = std::time::Instant::now();
let inaccessible = probe_with_injected_delays(entries, deadline);
let elapsed = start.elapsed();
let after_leaked = LEAKED_PROBE_THREAD_COUNT.load(Ordering::Relaxed);
assert!(
inaccessible.contains(&slow_vol),
"slow volume must be inaccessible; inaccessible={inaccessible:?}"
);
assert!(
!inaccessible.contains(&fast_vol_a),
"fast volume A must NOT be inaccessible (starvation bug); inaccessible={inaccessible:?}"
);
assert!(
!inaccessible.contains(&fast_vol_b),
"fast volume B must NOT be inaccessible (starvation bug); inaccessible={inaccessible:?}"
);
assert_eq!(
inaccessible.len(),
1,
"exactly 1 volume must be inaccessible; got={inaccessible:?}"
);
let upper_bound = deadline * 2;
assert!(
elapsed < upper_bound,
"total elapsed {elapsed:?} must be < 2× deadline {upper_bound:?} \
(shared-channel should NOT stall for each volume sequentially)"
);
assert_eq!(
after_leaked,
before_leaked + 1,
"LEAKED_PROBE_THREAD_COUNT must increase by exactly 1 for the one blocked volume; \
before={before_leaked} after={after_leaked}"
);
}
#[test]
#[serial_test::serial]
fn volume_probe_timeout_parses_env_var() {
unsafe { std::env::set_var("TRUSTY_WARMBOOT_VOLUME_PROBE_SECS", "7") };
assert_eq!(
volume_probe_timeout(),
Duration::from_secs(7),
"must parse 7 from env var"
);
unsafe { std::env::remove_var("TRUSTY_WARMBOOT_VOLUME_PROBE_SECS") };
assert_eq!(
volume_probe_timeout(),
Duration::from_secs(5),
"must fall back to 5s default when env var is absent"
);
}