use std::sync::{Arc, Mutex};
use tokio::sync::Notify;
use tokio::task::JoinHandle;
use zccache::core::NormalizedPath;
use zccache::daemon::DaemonServer;
use zccache::depgraph::{CompileContext, DepGraph, IncludeSearchPaths};
use zccache::protocol::{DaemonStatus, Request, Response};
static ENV_SERIAL: Mutex<()> = Mutex::new(());
struct CacheDirGuard {
_tmp: tempfile::TempDir,
prev: Option<std::ffi::OsString>,
_lock: std::sync::MutexGuard<'static, ()>,
}
impl CacheDirGuard {
fn new() -> Self {
let lock = ENV_SERIAL.lock().unwrap_or_else(|p| p.into_inner());
let tmp = tempfile::TempDir::new().unwrap();
let prev = std::env::var_os(zccache::core::config::CACHE_DIR_ENV);
std::env::set_var(zccache::core::config::CACHE_DIR_ENV, tmp.path());
Self {
_tmp: tmp,
prev,
_lock: lock,
}
}
}
impl Drop for CacheDirGuard {
fn drop(&mut self) {
match self.prev.take() {
Some(v) => std::env::set_var(zccache::core::config::CACHE_DIR_ENV, v),
None => std::env::remove_var(zccache::core::config::CACHE_DIR_ENV),
}
}
}
async fn start_daemon(endpoint: &str) -> (JoinHandle<()>, Arc<Notify>) {
let mut server = DaemonServer::bind(endpoint).unwrap();
let shutdown = server.shutdown_handle();
let handle = tokio::spawn(async move {
server.run(0).await.unwrap();
});
(handle, shutdown)
}
async fn start_daemon_with_preloaded_graph(
endpoint: &str,
graph: DepGraph,
) -> (JoinHandle<()>, Arc<Notify>) {
let mut server = DaemonServer::bind(endpoint).unwrap();
server.set_dep_graph(graph);
let shutdown = server.shutdown_handle();
let handle = tokio::spawn(async move {
server.run(0).await.unwrap();
});
(handle, shutdown)
}
async fn get_status(endpoint: &str) -> DaemonStatus {
let mut client = zccache::ipc::connect(endpoint).await.unwrap();
client.send(&Request::Status).await.unwrap();
match client.recv().await.unwrap() {
Some(Response::Status(s)) => s,
other => panic!("unexpected response: {other:?}"),
}
}
fn make_ctx(source: &str) -> CompileContext {
CompileContext {
source_file: NormalizedPath::from(source),
include_search: IncludeSearchPaths::default(),
defines: Vec::new(),
flags: Vec::new(),
force_includes: Vec::new(),
unknown_flags: Vec::new(),
}
}
#[tokio::test]
#[ignore] async fn fresh_daemon_reports_not_persisted() {
let _guard = CacheDirGuard::new();
let endpoint = zccache::ipc::unique_test_endpoint();
let (handle, shutdown) = start_daemon(&endpoint).await;
let status = get_status(&endpoint).await;
assert!(
!status.dep_graph_persisted,
"fresh daemon must report dep_graph_persisted = false, got: {status:?}",
);
assert_eq!(status.dep_graph_disk_size, 0);
assert_eq!(
status.dep_graph_version,
zccache::depgraph::DEPGRAPH_VERSION
);
assert_eq!(status.cache_hits, 0);
assert_eq!(status.cache_misses, 0);
assert_eq!(status.non_cacheable, 0);
shutdown.notify_one();
handle.await.unwrap();
}
#[tokio::test]
#[ignore]
async fn preloaded_graph_reports_persisted() {
let _guard = CacheDirGuard::new();
let graph = DepGraph::new();
let _ = graph.register(make_ctx("/src/main.cpp"));
let path = zccache::depgraph::depgraph_file_path();
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).unwrap();
}
zccache::depgraph::save_to_file(&graph, &path).unwrap();
let loaded = zccache::depgraph::load_from_file(&path).unwrap();
assert_eq!(loaded.stats().context_count, 1);
let endpoint = zccache::ipc::unique_test_endpoint();
let (handle, shutdown) = start_daemon_with_preloaded_graph(&endpoint, loaded).await;
let status = get_status(&endpoint).await;
assert!(
status.dep_graph_persisted,
"preloaded daemon must report dep_graph_persisted = true, got: {status:?}",
);
assert!(
status.dep_graph_disk_size > 0,
"snapshot file must be visible on disk, got size 0",
);
assert_eq!(status.dep_graph_contexts, 1, "context survived the load");
assert_eq!(status.cache_hits, 0);
assert_eq!(status.cache_misses, 0);
assert_eq!(status.non_cacheable, 0);
shutdown.notify_one();
handle.await.unwrap();
}
#[tokio::test]
#[ignore]
async fn shutdown_save_restore_roundtrip() {
let guard1 = CacheDirGuard::new();
let graph = DepGraph::new();
let _ = graph.register(make_ctx("/src/foo.cpp"));
let _ = graph.register(make_ctx("/src/bar.cpp"));
assert_eq!(graph.stats().context_count, 2);
let endpoint1 = zccache::ipc::unique_test_endpoint();
let (handle1, shutdown1) = start_daemon_with_preloaded_graph(&endpoint1, graph).await;
let status1 = get_status(&endpoint1).await;
assert_eq!(status1.dep_graph_contexts, 2);
assert!(
status1.dep_graph_persisted,
"preloaded daemon should already report persisted=true",
);
let saved_path = zccache::depgraph::depgraph_file_path();
shutdown1.notify_one();
handle1.await.unwrap();
assert!(
saved_path.exists(),
"shutdown should have flushed depgraph to {}",
saved_path.display(),
);
let saved_bytes = std::fs::read(&saved_path).unwrap();
drop(guard1);
let guard2 = CacheDirGuard::new();
let new_path = zccache::depgraph::depgraph_file_path();
if let Some(parent) = new_path.parent() {
std::fs::create_dir_all(parent).unwrap();
}
std::fs::write(&new_path, &saved_bytes).unwrap();
let loaded = zccache::depgraph::load_from_file(&new_path).unwrap();
assert_eq!(loaded.stats().context_count, 2, "loaded graph survived");
let endpoint2 = zccache::ipc::unique_test_endpoint();
let (handle2, shutdown2) = start_daemon_with_preloaded_graph(&endpoint2, loaded).await;
let status2 = get_status(&endpoint2).await;
assert!(
status2.dep_graph_persisted,
"restarted daemon must report dep_graph_persisted = true after loading from disk",
);
assert_eq!(
status2.dep_graph_contexts, 2,
"restarted graph keeps both contexts",
);
assert_eq!(status2.cache_hits, 0);
assert_eq!(status2.cache_misses, 0);
assert_eq!(status2.non_cacheable, 0);
assert!(status2.dep_graph_disk_size > 0);
shutdown2.notify_one();
handle2.await.unwrap();
drop(guard2);
}