use super::super::*;
#[test]
fn write_cached_output_overwrites_same_size_different_content() {
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("output.o");
let cache = dir.path().join("cached.o");
let old_content = b"AAAA_symbols_v1_xxxx";
std::fs::write(&out, old_content).unwrap();
let new_content = b"BBBB_symbols_v2_yyyy";
assert_eq!(
old_content.len(),
new_content.len(),
"test requires same size"
);
std::fs::write(&cache, new_content).unwrap();
write_cached_output(&out, &cache, new_content).unwrap();
let result = std::fs::read(&out).unwrap();
assert_eq!(
result, new_content,
"output must contain new content, not stale old content"
);
}
#[test]
fn write_cached_output_creates_new_file() {
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("output.o");
let cache = dir.path().join("cached.o");
let content = b"fresh object file data";
std::fs::write(&cache, content).unwrap();
write_cached_output(&out, &cache, content).unwrap();
let result = std::fs::read(&out).unwrap();
assert_eq!(result, content.as_slice());
}
#[test]
fn write_cached_output_fallback_to_memory_copy() {
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("output.o");
let cache = dir.path().join("nonexistent_cache.o");
let content = b"data from memory";
write_cached_output(&out, &cache, content).unwrap();
let result = std::fs::read(&out).unwrap();
assert_eq!(result, content.as_slice());
}
#[test]
fn write_cached_output_skips_when_already_hardlinked() {
let dir = tempfile::tempdir().unwrap();
let cache = dir.path().join("cached.o");
let out = dir.path().join("output.o");
let content = b"cached artifact content";
std::fs::write(&cache, content).unwrap();
write_cached_output(&out, &cache, content).unwrap();
assert_eq!(std::fs::read(&out).unwrap(), content.as_slice());
assert!(
same_file(&out, &cache),
"output should be a hardlink to cache file after first write"
);
write_cached_output(&out, &cache, content).unwrap();
assert_eq!(std::fs::read(&out).unwrap(), content.as_slice());
}
#[test]
fn persist_artifact_output_does_not_mutate_existing_hardlink() {
let dir = tempfile::tempdir().unwrap();
let cache = dir.path().join("artifact-key_0");
let out = dir.path().join("output.rlib");
persist_artifact_output(&cache, b"first").unwrap();
write_cached_output(&out, &cache, b"first").unwrap();
assert!(
same_file(&out, &cache),
"cache hit should initially hardlink output to cache payload"
);
persist_artifact_output(&cache, b"second").unwrap();
assert_eq!(
std::fs::read(&out).unwrap(),
b"first",
"publishing a later cache payload must not mutate existing target outputs"
);
assert_eq!(std::fs::read(&cache).unwrap(), b"second");
assert!(
!same_file(&out, &cache),
"cache path replacement should break the hardlink relationship"
);
}
#[test]
fn persist_artifact_file_reports_hardlink_snapshot_stats() {
let dir = tempfile::tempdir().unwrap();
let source = dir.path().join("libunit.rlib");
let cache = dir.path().join("artifact-key_0");
let content = b"compiled rust artifact";
std::fs::write(&source, content).unwrap();
let stats = persist_artifact_file(&cache, &source).unwrap();
assert_eq!(std::fs::read(&cache).unwrap(), content);
assert!(
same_file(&source, &cache),
"same-directory snapshots should use a hardlink"
);
assert_eq!(stats.hardlink_count, 1);
assert_eq!(stats.copy_count, 0);
assert_eq!(stats.copy_bytes, 0);
}
#[test]
fn break_output_hardlink_before_compile_prevents_cache_poisoning() {
let dir = tempfile::tempdir().unwrap();
let cache = dir.path().join("cached.rlib");
let out = dir.path().join("libapp.rlib");
let cached_content = b"cached artifact from worktree a";
let rebuilt_content = b"rebuilt artifact in worktree b";
std::fs::write(&cache, cached_content).unwrap();
write_cached_output(&out, &cache, cached_content).unwrap();
assert!(same_file(&out, &cache), "cache hit should hardlink output");
break_output_hardlink_before_compile(&out).unwrap();
assert!(
!same_file(&out, &cache),
"compile miss must detach output from cache hardlink first"
);
std::fs::write(&out, rebuilt_content).unwrap();
assert_eq!(
std::fs::read(&cache).unwrap(),
cached_content,
"compiler overwrite of output must not mutate shared cache artifact"
);
assert_eq!(std::fs::read(&out).unwrap(), rebuilt_content);
}
#[test]
fn write_cached_output_preserves_cache_mtime_on_hardlink() {
let dir = tempfile::tempdir().unwrap();
let cache = dir.path().join("cached.rlib");
let out = dir.path().join("output.rlib");
let content = b"cached rlib data";
std::fs::write(&cache, content).unwrap();
let old_time = filetime::FileTime::from_unix_time(1_000_000_000, 0); filetime::set_file_mtime(&cache, old_time).unwrap();
write_cached_output(&out, &cache, content).unwrap();
let out_mtime =
filetime::FileTime::from_last_modification_time(&std::fs::metadata(&out).unwrap());
assert_eq!(
out_mtime.unix_seconds(),
old_time.unix_seconds(),
"cache hit must preserve cache file mtime (cargo's fingerprint depends on it); \
got {out_mtime:?}, expected {old_time:?}"
);
}
#[test]
fn write_cached_output_preserves_mtime_on_existing_hardlink() {
let dir = tempfile::tempdir().unwrap();
let cache = dir.path().join("cached.rlib");
let out = dir.path().join("output.rlib");
let content = b"cached rlib data";
std::fs::write(&cache, content).unwrap();
write_cached_output(&out, &cache, content).unwrap();
let old_time = filetime::FileTime::from_unix_time(1_000_000_000, 0);
filetime::set_file_mtime(&out, old_time).unwrap();
write_cached_output(&out, &cache, content).unwrap();
let out_mtime =
filetime::FileTime::from_last_modification_time(&std::fs::metadata(&out).unwrap());
assert_eq!(
out_mtime.unix_seconds(),
old_time.unix_seconds(),
"mtime must be preserved across repeated cache hits on the same file"
);
}
#[test]
fn write_cached_output_fallback_has_fresh_mtime() {
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("output.rlib");
let cache = dir.path().join("nonexistent_cache.rlib");
let content = b"data from memory";
write_cached_output(&out, &cache, content).unwrap();
let out_mtime =
filetime::FileTime::from_last_modification_time(&std::fs::metadata(&out).unwrap());
let now = filetime::FileTime::now();
let diff = now.unix_seconds() - out_mtime.unix_seconds();
assert!(
diff < 5,
"fallback path should produce fresh mtime — {diff}s old"
);
}