use std::path::Path;
use super::super::*;
use super::CacheDirEnvGuard;
#[cfg(unix)]
fn write_fake_linker(dir: &Path) -> std::path::PathBuf {
use std::os::unix::fs::PermissionsExt;
let tool = dir.join("clang");
std::fs::write(
&tool,
r#"#!/bin/sh
out=
while [ "$#" -gt 0 ]; do
if [ "$1" = "-o" ]; then
shift
out=$1
fi
shift || true
done
if [ -z "$out" ]; then
exit 2
fi
out_dir=$(dirname "$out")
printf 'binary\n' > "$out"
printf 'debug\n' > "$out_dir/app.pdb"
printf 'map\n' > "$out_dir/app.wasm.map"
"#,
)
.unwrap();
let mut perms = std::fs::metadata(&tool).unwrap().permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&tool, perms).unwrap();
tool
}
#[cfg(windows)]
fn write_fake_linker(dir: &Path) -> std::path::PathBuf {
let tool = dir.join("clang.cmd");
std::fs::write(
&tool,
r#"@echo off
set "OUT=%~2"
if "%OUT%"=="" exit /b 2
> "%OUT%" echo binary
for %%I in ("%OUT%") do set "OUTDIR=%%~dpI"
> "%OUTDIR%app.pdb" echo debug
> "%OUTDIR%app.wasm.map" echo map
exit /b 0
"#,
)
.unwrap();
tool
}
#[tokio::test]
async fn link_cache_hit_restores_sibling_side_effects() {
let tmp = tempfile::tempdir().unwrap();
let fake_linker = write_fake_linker(tmp.path());
let input = tmp.path().join("main.o");
let output = tmp.path().join("app.exe");
let pdb = tmp.path().join("app.pdb");
let wasm_map = tmp.path().join("app.wasm.map");
std::fs::write(&input, b"fake object").unwrap();
let _cache_dir = CacheDirEnvGuard::set(&tmp.path().join("zccache-cache"));
let server = DaemonServer::bind(&crate::ipc::unique_test_endpoint()).unwrap();
let args = vec![
"-o".to_string(),
output.to_string_lossy().into_owned(),
input.to_string_lossy().into_owned(),
];
let first = handle_link_ephemeral(
&server.state,
std::process::id(),
&fake_linker,
&args,
tmp.path(),
None,
)
.await;
match first {
Response::LinkResult {
exit_code, cached, ..
} => {
assert_eq!(exit_code, 0);
assert!(!cached, "first link should populate the cache");
}
other => panic!("expected LinkResult, got: {other:?}"),
}
assert!(
output.exists(),
"fresh link should create the primary output"
);
assert!(pdb.exists(), "fresh link should create a PDB sidecar");
assert!(
wasm_map.exists(),
"fresh link should create a wasm map sidecar"
);
std::fs::remove_file(&pdb).unwrap();
std::fs::remove_file(&wasm_map).unwrap();
let second = handle_link_ephemeral(
&server.state,
std::process::id(),
&fake_linker,
&args,
tmp.path(),
None,
)
.await;
match second {
Response::LinkResult {
exit_code, cached, ..
} => {
assert_eq!(exit_code, 0);
assert!(cached, "second link should be served from cache");
}
other => panic!("expected LinkResult, got: {other:?}"),
}
assert!(output.exists(), "cache hit should keep the primary output");
assert!(pdb.exists(), "cache hit should restore the PDB sidecar");
assert!(
wasm_map.exists(),
"cache hit should restore the wasm map sidecar"
);
}
#[tokio::test]
async fn link_cache_key_preserves_input_order_under_parallel_hashing() {
let tmp = tempfile::tempdir().unwrap();
let fake_linker = write_fake_linker(tmp.path());
let output = tmp.path().join("app.exe");
let mut input_paths: Vec<std::path::PathBuf> = Vec::with_capacity(12);
for i in 0..12 {
let p = tmp.path().join(format!("input-{i}.o"));
std::fs::write(&p, format!("payload-bytes-{i}-{}", "x".repeat(64))).unwrap();
input_paths.push(p);
}
let _cache_dir = CacheDirEnvGuard::set(&tmp.path().join("zccache-cache"));
let server = DaemonServer::bind(&crate::ipc::unique_test_endpoint()).unwrap();
let make_args = |inputs: &[std::path::PathBuf]| -> Vec<String> {
let mut a = vec!["-o".to_string(), output.to_string_lossy().into_owned()];
for p in inputs {
a.push(p.to_string_lossy().into_owned());
}
a
};
let first_args = make_args(&input_paths);
let first = handle_link_ephemeral(
&server.state,
std::process::id(),
&fake_linker,
&first_args,
tmp.path(),
None,
)
.await;
assert!(
matches!(
first,
Response::LinkResult {
cached: false,
exit_code: 0,
..
}
),
"first link must be a miss + 0 exit, got: {first:?}"
);
let second = handle_link_ephemeral(
&server.state,
std::process::id(),
&fake_linker,
&first_args,
tmp.path(),
None,
)
.await;
assert!(
matches!(second, Response::LinkResult { cached: true, exit_code: 0, .. }),
"same-order repeat must HIT (parallel hash must preserve input order in cache key), got: {second:?}"
);
let mut reversed = input_paths.clone();
reversed.reverse();
let reversed_args = make_args(&reversed);
let third = handle_link_ephemeral(
&server.state,
std::process::id(),
&fake_linker,
&reversed_args,
tmp.path(),
None,
)
.await;
assert!(
matches!(
third,
Response::LinkResult {
cached: false,
exit_code: 0,
..
}
),
"reversed-order link must MISS (input order is part of the cache key), got: {third:?}"
);
}