use super::*;
use std::time::{Duration, SystemTime};
fn write_with_mtime(path: &Path, contents: &[u8], mtime: SystemTime) {
std::fs::write(path, contents).unwrap();
let ft = filetime::FileTime::from_system_time(mtime);
filetime::set_file_mtime(path, ft).unwrap();
}
fn mtime_of(path: &Path) -> SystemTime {
std::fs::metadata(path).unwrap().modified().unwrap()
}
fn epoch_plus(secs: u64) -> SystemTime {
SystemTime::UNIX_EPOCH + Duration::from_secs(secs)
}
#[test]
fn floor_noop_when_target_dir_is_empty() {
let dir = tempfile::tempdir().unwrap();
let target = dir.path().join("only.rlib");
let before = epoch_plus(1_000_000);
write_with_mtime(&target, b"x", before);
floor_artifact_mtime_to_sibling_max(&target).unwrap();
assert_eq!(mtime_of(&target), before);
}
#[test]
fn floor_noop_when_already_newest() {
let dir = tempfile::tempdir().unwrap();
let target = dir.path().join("newer.rlib");
let older = dir.path().join("older.rlib");
write_with_mtime(&target, b"t", epoch_plus(2_000_000));
write_with_mtime(&older, b"o", epoch_plus(1_000_000));
floor_artifact_mtime_to_sibling_max(&target).unwrap();
assert_eq!(mtime_of(&target), epoch_plus(2_000_000));
}
#[test]
fn floor_bumps_when_sibling_is_newer() {
let dir = tempfile::tempdir().unwrap();
let target = dir.path().join("dependent.rlib");
let dep = dir.path().join("dep.rlib");
write_with_mtime(&target, b"t", epoch_plus(1_000_000));
write_with_mtime(&dep, b"d", epoch_plus(2_000_000));
floor_artifact_mtime_to_sibling_max(&target).unwrap();
assert_eq!(mtime_of(&target), epoch_plus(2_000_000));
assert_eq!(mtime_of(&dep), epoch_plus(2_000_000));
}
#[test]
fn floor_ignores_non_artifact_files() {
let dir = tempfile::tempdir().unwrap();
let target = dir.path().join("art.rlib");
let dep_file = dir.path().join("dep.d");
let json_sidecar = dir.path().join("meta.json");
write_with_mtime(&target, b"t", epoch_plus(1_000_000));
write_with_mtime(&dep_file, b"d", epoch_plus(5_000_000));
write_with_mtime(&json_sidecar, b"j", epoch_plus(5_000_000));
floor_artifact_mtime_to_sibling_max(&target).unwrap();
assert_eq!(mtime_of(&target), epoch_plus(1_000_000));
}
#[test]
fn floor_idempotent_under_repeated_application() {
let dir = tempfile::tempdir().unwrap();
let target = dir.path().join("art.rlib");
let dep = dir.path().join("dep.rlib");
write_with_mtime(&target, b"t", epoch_plus(1_000_000));
write_with_mtime(&dep, b"d", epoch_plus(2_000_000));
floor_artifact_mtime_to_sibling_max(&target).unwrap();
let first = mtime_of(&target);
floor_artifact_mtime_to_sibling_max(&target).unwrap();
let second = mtime_of(&target);
floor_artifact_mtime_to_sibling_max(&target).unwrap();
let third = mtime_of(&target);
assert_eq!(first, epoch_plus(2_000_000));
assert_eq!(second, first);
assert_eq!(third, first);
}
#[test]
fn batch_floor_bumps_build_script_output_to_extern_mtime() {
let dir = tempfile::tempdir().unwrap();
let build_dir = dir.path().join("target/debug/build/blake3-abc");
let deps_dir = dir.path().join("target/debug/deps");
std::fs::create_dir_all(&build_dir).unwrap();
std::fs::create_dir_all(&deps_dir).unwrap();
let cache = dir.path().join("cache/build-script-cache");
std::fs::create_dir_all(cache.parent().unwrap()).unwrap();
std::fs::write(&cache, b"build script exe").unwrap();
let old_time = filetime::FileTime::from_unix_time(1_000_000, 0);
filetime::set_file_mtime(&cache, old_time).unwrap();
let extern_dep = deps_dir.join("libcc-new.rlib");
write_with_mtime(
&extern_dep,
b"cc rlib",
SystemTime::UNIX_EPOCH + Duration::new(2_000_000, 123_456_700),
);
let dep_mtime = mtime_of(&extern_dep);
let output = build_dir.join("build-script-build");
let targets = vec![(output.clone(), cache.clone())];
let payloads = vec![CachedPayload::File(cache.clone().into())];
let floor_paths = vec![extern_dep.clone()];
assert!(write_payloads_par_with_mtime_floor(
&targets,
&payloads,
&floor_paths,
));
let output_mtime = mtime_of(&output);
assert!(
output_mtime >= dep_mtime,
"extensionless build-script output must be at least as new as extern dependency; \
output={output_mtime:?}, dep={dep_mtime:?}",
);
}
#[test]
fn batch_floor_freshens_materialized_outputs_without_floor_paths() {
let dir = tempfile::tempdir().unwrap();
let cache = dir.path().join("cache/libcrate-cache.rlib");
std::fs::create_dir_all(cache.parent().unwrap()).unwrap();
std::fs::write(&cache, b"rlib").unwrap();
let old_mtime = epoch_plus(1_000_000);
filetime::set_file_mtime(&cache, filetime::FileTime::from_system_time(old_mtime)).unwrap();
let output = dir.path().join("target/debug/deps/libcrate.rlib");
let targets = vec![(output.clone(), cache.clone())];
let payloads = vec![CachedPayload::File(cache.clone().into())];
let floor_paths: Vec<PathBuf> = Vec::new();
assert!(write_payloads_par_with_mtime_floor(
&targets,
&payloads,
&floor_paths,
));
let output_mtime = mtime_of(&output);
assert!(
output_mtime > old_mtime,
"compile-hit output must not inherit the stale cache mtime; \
output={output_mtime:?}, old_cache={old_mtime:?}",
);
}