use std::path::{Path, PathBuf};
use super::super::cache_ops::{artifact_matches_lockfile, parse_lockfile_crates, warm_target};
fn make_test_store(dir: &Path) -> (PathBuf, PathBuf) {
let cache_dir = dir.join("cache");
let artifact_dir = cache_dir.join("artifacts");
let index_path = cache_dir.join("index.bin");
std::fs::create_dir_all(&artifact_dir).unwrap();
let store = crate::artifact::ArtifactStore::open(&index_path).unwrap();
let k1 = "aaaa0001";
store.insert(
k1,
&crate::artifact::ArtifactIndex::new(
vec![
"libserde-abc123.rlib".into(),
"libserde-abc123.rmeta".into(),
"serde-abc123.d".into(),
],
vec![100, 50, 10],
vec![],
vec![],
0,
),
);
std::fs::write(artifact_dir.join(format!("{k1}_0")), b"serde-rlib").unwrap();
std::fs::write(artifact_dir.join(format!("{k1}_1")), b"serde-rmeta").unwrap();
std::fs::write(artifact_dir.join(format!("{k1}_2")), b"serde-d").unwrap();
let k2 = "aaaa0002";
store.insert(
k2,
&crate::artifact::ArtifactIndex::new(
vec!["libproc_macro2-def456.rlib".into()],
vec![200],
vec![],
vec![],
0,
),
);
std::fs::write(artifact_dir.join(format!("{k2}_0")), b"proc-macro2-rlib").unwrap();
let k3 = "aaaa0003";
store.insert(
k3,
&crate::artifact::ArtifactIndex::new(
vec!["libtokio-ghi789.rlib".into()],
vec![300],
vec![],
vec![],
0,
),
);
std::fs::write(artifact_dir.join(format!("{k3}_0")), b"tokio-rlib").unwrap();
let k4 = "aaaa0004";
store.insert(
k4,
&crate::artifact::ArtifactIndex::new(vec!["foo.o".into()], vec![50], vec![], vec![], 0),
);
std::fs::write(artifact_dir.join(format!("{k4}_0")), b"cpp-object").unwrap();
store.flush().unwrap();
drop(store);
(index_path, artifact_dir)
}
fn write_lockfile(dir: &Path, crates: &[&str]) -> PathBuf {
let lockfile = dir.join("Cargo.lock");
let mut content = String::from("# This file is automatically @generated\nversion = 3\n\n");
for name in crates {
content.push_str(&format!(
"[[package]]\nname = \"{name}\"\nversion = \"1.0.0\"\n\n"
));
}
std::fs::write(&lockfile, &content).unwrap();
lockfile
}
#[test]
fn parse_lockfile_extracts_crate_names() {
let dir = tempfile::tempdir().unwrap();
let lf = write_lockfile(dir.path(), &["serde", "proc-macro2", "unicode-ident"]);
let crates = parse_lockfile_crates(&lf).unwrap();
assert!(crates.contains("serde"));
assert!(
crates.contains("proc_macro2"),
"hyphens should be underscores"
);
assert!(crates.contains("unicode_ident"));
assert!(!crates.contains("tokio"), "tokio not in lockfile");
}
#[test]
fn artifact_matches_lockfile_basic() {
let mut allowed = std::collections::HashSet::new();
allowed.insert("serde".to_string());
allowed.insert("proc_macro2".to_string());
assert!(artifact_matches_lockfile("libserde-abc123.rlib", &allowed));
assert!(artifact_matches_lockfile("libserde-abc123.rmeta", &allowed));
assert!(artifact_matches_lockfile("serde-abc123.d", &allowed));
assert!(artifact_matches_lockfile(
"libproc_macro2-def456.rlib",
&allowed
));
assert!(!artifact_matches_lockfile("libtokio-ghi789.rlib", &allowed));
assert!(artifact_matches_lockfile("build_script_build", &allowed));
}
#[test]
fn warm_without_lockfile_restores_everything() {
let dir = tempfile::tempdir().unwrap();
let (index_path, artifact_dir) = make_test_store(dir.path());
let target_dir = dir.path().join("target");
let (restored, _, _) =
warm_target(&index_path, &artifact_dir, &target_dir, "debug", None).unwrap();
let deps = target_dir.join("debug").join("deps");
assert_eq!(restored, 6, "without lockfile: restore all 6 files");
assert!(deps.join("libserde-abc123.rlib").exists());
assert!(
deps.join("libtokio-ghi789.rlib").exists(),
"tokio restored without filter"
);
assert!(
deps.join("foo.o").exists(),
"C++ file restored without filter"
);
}
#[test]
fn warm_with_lockfile_filters_to_matching_crates() {
let dir = tempfile::tempdir().unwrap();
let (index_path, artifact_dir) = make_test_store(dir.path());
let target_dir = dir.path().join("target");
let lockfile = write_lockfile(dir.path(), &["serde", "proc-macro2"]);
let (restored, skipped, _) = warm_target(
&index_path,
&artifact_dir,
&target_dir,
"debug",
Some(&lockfile),
)
.unwrap();
let deps = target_dir.join("debug").join("deps");
assert_eq!(restored, 5);
assert!(deps.join("libserde-abc123.rlib").exists());
assert!(deps.join("libproc_macro2-def456.rlib").exists());
assert!(
!deps.join("libtokio-ghi789.rlib").exists(),
"tokio NOT in lockfile"
);
assert!(
deps.join("foo.o").exists(),
"no hash separator = allowed through"
);
assert!(skipped > 0, "tokio should be skipped");
}
#[test]
fn adversarial_crate_removed_from_lockfile() {
let dir = tempfile::tempdir().unwrap();
let (index_path, artifact_dir) = make_test_store(dir.path());
let target_dir = dir.path().join("target");
let lockfile = write_lockfile(dir.path(), &["serde"]);
let (restored, _, _) = warm_target(
&index_path,
&artifact_dir,
&target_dir,
"debug",
Some(&lockfile),
)
.unwrap();
let deps = target_dir.join("debug").join("deps");
assert!(deps.join("libserde-abc123.rlib").exists());
assert!(
!deps.join("libtokio-ghi789.rlib").exists(),
"removed crate must NOT be restored"
);
assert_eq!(restored, 4);
}
#[test]
fn adversarial_stale_file_in_target_from_previous_warm() {
let dir = tempfile::tempdir().unwrap();
let (index_path, artifact_dir) = make_test_store(dir.path());
let target_dir = dir.path().join("target");
let deps = target_dir.join("debug").join("deps");
std::fs::create_dir_all(&deps).unwrap();
std::fs::write(deps.join("libtokio-ghi789.rlib"), b"stale").unwrap();
let lockfile = write_lockfile(dir.path(), &["serde"]);
warm_target(
&index_path,
&artifact_dir,
&target_dir,
"debug",
Some(&lockfile),
)
.unwrap();
assert!(
deps.join("libtokio-ghi789.rlib").exists(),
"warm doesn't clean up stale files — cargo ignores them"
);
assert_eq!(
std::fs::read(deps.join("libtokio-ghi789.rlib")).unwrap(),
b"stale",
"stale file content unchanged"
);
}
#[test]
fn adversarial_version_bump_old_artifact_in_cache() {
let dir = tempfile::tempdir().unwrap();
let cache_dir = dir.path().join("cache");
let artifact_dir = cache_dir.join("artifacts");
let index_path = cache_dir.join("index.bin");
std::fs::create_dir_all(&artifact_dir).unwrap();
let store = crate::artifact::ArtifactStore::open(&index_path).unwrap();
let k_old = "bbbb0001";
store.insert(
k_old,
&crate::artifact::ArtifactIndex::new(
vec!["libserde-old111.rlib".into()],
vec![100],
vec![],
vec![],
0,
),
);
std::fs::write(artifact_dir.join(format!("{k_old}_0")), b"old-serde").unwrap();
let k_new = "bbbb0002";
store.insert(
k_new,
&crate::artifact::ArtifactIndex::new(
vec!["libserde-new222.rlib".into()],
vec![100],
vec![],
vec![],
0,
),
);
std::fs::write(artifact_dir.join(format!("{k_new}_0")), b"new-serde").unwrap();
store.flush().unwrap();
drop(store);
let target_dir = dir.path().join("target");
let lockfile = write_lockfile(dir.path(), &["serde"]);
let (restored, _, _) = warm_target(
&index_path,
&artifact_dir,
&target_dir,
"debug",
Some(&lockfile),
)
.unwrap();
let deps = target_dir.join("debug").join("deps");
assert_eq!(restored, 2);
assert!(deps.join("libserde-old111.rlib").exists());
assert!(deps.join("libserde-new222.rlib").exists());
}
#[test]
fn adversarial_corrupted_cache_file() {
let dir = tempfile::tempdir().unwrap();
let cache_dir = dir.path().join("cache");
let artifact_dir = cache_dir.join("artifacts");
let index_path = cache_dir.join("index.bin");
std::fs::create_dir_all(&artifact_dir).unwrap();
let store = crate::artifact::ArtifactStore::open(&index_path).unwrap();
let key = "cccc0001";
store.insert(
key,
&crate::artifact::ArtifactIndex::new(
vec!["libserde-abc123.rlib".into()],
vec![1000], vec![],
vec![],
0,
),
);
std::fs::write(artifact_dir.join(format!("{key}_0")), b"short").unwrap();
store.flush().unwrap();
drop(store);
let target_dir = dir.path().join("target");
let (restored, _, errors) =
warm_target(&index_path, &artifact_dir, &target_dir, "debug", None).unwrap();
assert_eq!(restored, 1);
assert_eq!(errors, 0);
let deps = target_dir.join("debug").join("deps");
assert_eq!(
std::fs::read(deps.join("libserde-abc123.rlib")).unwrap(),
b"short"
);
}
#[test]
fn adversarial_empty_lockfile() {
let dir = tempfile::tempdir().unwrap();
let (index_path, artifact_dir) = make_test_store(dir.path());
let target_dir = dir.path().join("target");
let lockfile = write_lockfile(dir.path(), &[]);
let (restored, skipped, _) = warm_target(
&index_path,
&artifact_dir,
&target_dir,
"debug",
Some(&lockfile),
)
.unwrap();
assert_eq!(restored, 1, "only foo.o (no hash separator) passes");
assert!(skipped > 0);
}