use std::path::Path;
use tempfile::TempDir;
mod common;
use common::{build_kache, hermetic_command, isolated_config_path, kache_binary, scratch_dir};
fn copy_dir(src: &Path, dst: &Path) {
std::fs::create_dir_all(dst).unwrap();
for entry in std::fs::read_dir(src).unwrap() {
let entry = entry.unwrap();
let dest_path = dst.join(entry.file_name());
if entry.file_type().unwrap().is_dir() {
copy_dir(&entry.path(), &dest_path);
} else {
std::fs::copy(entry.path(), &dest_path).unwrap();
}
}
}
fn copy_fixture() -> TempDir {
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("test-projects/custom-harness");
let tmp = TempDir::new_in(scratch_dir()).unwrap();
copy_dir(&fixture, tmp.path());
tmp
}
fn cargo_test(project: &Path, cache_dir: &Path, target_dir: &Path) -> std::process::Output {
hermetic_command("cargo", cache_dir, Some(&isolated_config_path(cache_dir)))
.args(["test", "--test", "harness"])
.current_dir(project)
.env("RUSTC_WRAPPER", kache_binary())
.env("CARGO_TARGET_DIR", target_dir)
.env("CARGO_INCREMENTAL", "0")
.output()
.expect("failed to run cargo test")
}
fn kache_report(cache_dir: &Path) -> serde_json::Value {
let output = hermetic_command(
kache_binary(),
cache_dir,
Some(&isolated_config_path(cache_dir)),
)
.args(["report", "--format", "json", "--since", "1h"])
.output()
.expect("failed to run kache report");
assert!(
output.status.success(),
"kache report failed.\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
serde_json::from_slice(&output.stdout).expect("report should be valid json")
}
fn last_event_for<'a>(report: &'a serde_json::Value, crate_name: &str) -> &'a serde_json::Value {
report["all_events"]
.as_array()
.expect("report should include all_events")
.iter()
.rfind(|event| event["crate_name"].as_str() == Some(crate_name))
.unwrap_or_else(|| {
panic!(
"no `{crate_name}` event in report: {}",
serde_json::to_string_pretty(report).unwrap()
)
})
}
#[cfg(unix)]
fn strip_executable_bits(dir: &Path) -> usize {
use std::os::unix::fs::PermissionsExt;
let mut stripped = 0;
let mut stack = vec![dir.to_path_buf()];
while let Some(current) = stack.pop() {
for entry in std::fs::read_dir(¤t).expect("reading store dir") {
let entry = entry.unwrap();
let path = entry.path();
if entry.file_type().unwrap().is_dir() {
stack.push(path);
} else {
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o444))
.expect("chmod store blob");
stripped += 1;
}
}
}
stripped
}
#[test]
fn custom_harness_test_binary_is_executable_after_restore() {
build_kache();
let project = copy_fixture();
let cache = TempDir::new_in(scratch_dir()).unwrap();
let target = TempDir::new_in(scratch_dir()).unwrap();
let cold = cargo_test(project.path(), cache.path(), target.path());
assert!(
cold.status.success(),
"cold build failed.\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&cold.stdout),
String::from_utf8_lossy(&cold.stderr),
);
let report = kache_report(cache.path());
let cold_event = last_event_for(&report, "harness");
assert_eq!(
cold_event["compiler_runs"].as_u64(),
Some(1),
"cold run should have compiled `harness`: {cold_event}"
);
std::fs::remove_dir_all(target.path()).unwrap();
std::fs::create_dir_all(target.path()).unwrap();
#[cfg(unix)]
{
let store = cache.path().join("store");
let stripped = strip_executable_bits(&store);
assert!(
stripped > 0,
"no files under {} — store layout changed?",
store.display()
);
}
let warm = cargo_test(project.path(), cache.path(), target.path());
assert!(
warm.status.success(),
"restored harness=false test binary could not be run.\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&warm.stdout),
String::from_utf8_lossy(&warm.stderr),
);
let report = kache_report(cache.path());
let warm_event = last_event_for(&report, "harness");
assert_eq!(
warm_event["result"].as_str(),
Some("local_hit"),
"warm run should have restored `harness` from the cache: {warm_event}"
);
assert_eq!(
warm_event["compiler_runs"].as_u64(),
Some(0),
"warm run should not have spawned the compiler: {warm_event}"
);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let binary = std::fs::read_dir(target.path().join("debug").join("deps"))
.unwrap()
.filter_map(Result::ok)
.map(|entry| entry.path())
.find(|path| {
path.extension().is_none()
&& path
.file_name()
.and_then(|name| name.to_str())
.is_some_and(|name| name.starts_with("harness-"))
})
.expect("restored harness test binary");
let mode = std::fs::metadata(&binary).unwrap().permissions().mode();
assert_ne!(
mode & 0o111,
0,
"{} was restored without its executable bit (mode {mode:o})",
binary.display()
);
}
}