use super::super::*;
use super::CacheDirEnvGuard;
async fn start_daemon() -> (String, tokio::task::JoinHandle<()>, Arc<Notify>) {
let endpoint = crate::ipc::unique_test_endpoint();
let mut server = DaemonServer::bind(&endpoint).unwrap();
let shutdown = server.shutdown_handle();
let handle = tokio::spawn(async move {
server.run(0).await.unwrap();
});
(endpoint, handle, shutdown)
}
#[tokio::test]
#[ignore] async fn test_server_ping_pong() {
crate::test_support::test_timeout(async {
let (endpoint, server_task, shutdown) = start_daemon().await;
let mut client = crate::ipc::connect(&endpoint).await.unwrap();
client.send(&Request::Ping).await.unwrap();
let resp: Option<Response> = client.recv().await.unwrap();
assert_eq!(resp, Some(Response::Pong));
shutdown.notify_one();
server_task.await.unwrap();
})
.await;
}
#[tokio::test]
#[ignore] async fn test_server_shutdown_request() {
crate::test_support::test_timeout(async {
let (endpoint, server_task, shutdown) = start_daemon().await;
let mut client = crate::ipc::connect(&endpoint).await.unwrap();
client.send(&Request::Shutdown).await.unwrap();
let resp: Option<Response> = client.recv().await.unwrap();
assert_eq!(resp, Some(Response::ShuttingDown));
shutdown.notify_one();
server_task.await.unwrap();
})
.await;
}
#[tokio::test]
#[ignore] async fn test_server_clear_empty() {
crate::test_support::test_timeout(async {
let (endpoint, server_task, shutdown) = start_daemon().await;
let mut client = crate::ipc::connect(&endpoint).await.unwrap();
client.send(&Request::Clear).await.unwrap();
let resp: Option<Response> = client.recv().await.unwrap();
match resp {
Some(Response::Cleared {
metadata_cleared,
dep_graph_contexts_cleared,
..
}) => {
assert_eq!(metadata_cleared, 0);
assert_eq!(dep_graph_contexts_cleared, 0);
}
other => panic!("expected Cleared, got: {other:?}"),
}
shutdown.notify_one();
server_task.await.unwrap();
})
.await;
}
#[tokio::test]
#[ignore] async fn test_server_status() {
crate::test_support::test_timeout(async {
let (endpoint, server_task, shutdown) = start_daemon().await;
let mut client = crate::ipc::connect(&endpoint).await.unwrap();
client.send(&Request::Status).await.unwrap();
let resp: Option<Response> = client.recv().await.unwrap();
assert!(matches!(resp, Some(Response::Status(_))));
shutdown.notify_one();
server_task.await.unwrap();
})
.await;
}
#[tokio::test]
#[ignore] async fn cli_session_lifecycle() {
let clang = match crate::test_support::find_clang() {
Some(p) => p,
None => return,
};
crate::test_support::test_timeout(async move {
let tmp = tempfile::tempdir().unwrap();
let src = tmp.path().join("hello.cpp");
let obj = tmp.path().join("hello.o");
let log = tmp.path().join("session.log");
let cwd = tmp.path().to_string_lossy().into_owned();
std::fs::write(
&src,
"#include <stdio.h>\nint main() { printf(\"hello\\n\"); return 0; }\n",
)
.unwrap();
let (endpoint, server_handle, shutdown) = start_daemon().await;
let mut client = crate::ipc::connect(&endpoint).await.unwrap();
client
.send(&Request::SessionStart {
client_pid: std::process::id(),
working_dir: cwd.clone().into(),
log_file: Some(log.to_string_lossy().into_owned().into()),
track_stats: false,
journal_path: None,
profile: false,
})
.await
.unwrap();
let session_id = match client.recv().await.unwrap() {
Some(Response::SessionStarted { session_id, .. }) => session_id,
other => panic!("expected SessionStarted, got: {other:?}"),
};
client
.send(&Request::Compile {
session_id: session_id.clone(),
args: vec![
"-c".to_string(),
src.to_string_lossy().into_owned(),
"-o".to_string(),
obj.to_string_lossy().into_owned(),
],
cwd: cwd.clone().into(),
compiler: clang.to_string_lossy().into_owned().into(),
env: None,
stdin: Vec::new(),
})
.await
.unwrap();
match client.recv().await.unwrap() {
Some(Response::CompileResult {
exit_code, cached, ..
}) => {
assert_eq!(exit_code, 0, "first compile should succeed");
assert!(!cached, "first compile should be a miss");
}
other => panic!("expected CompileResult, got: {other:?}"),
}
assert!(obj.exists(), ".o should exist after first compile");
let obj_data = std::fs::read(&obj).unwrap();
std::fs::remove_file(&obj).unwrap();
client
.send(&Request::Compile {
session_id: session_id.clone(),
args: vec![
"-c".to_string(),
src.to_string_lossy().into_owned(),
"-o".to_string(),
obj.to_string_lossy().into_owned(),
],
cwd: cwd.clone().into(),
compiler: clang.to_string_lossy().into_owned().into(),
env: None,
stdin: Vec::new(),
})
.await
.unwrap();
match client.recv().await.unwrap() {
Some(Response::CompileResult {
exit_code, cached, ..
}) => {
assert_eq!(exit_code, 0, "cached compile should succeed");
assert!(cached, "second compile should be a hit");
}
other => panic!("expected CompileResult, got: {other:?}"),
}
assert!(obj.exists(), ".o should exist after cached compile");
let cached_data = std::fs::read(&obj).unwrap();
assert_eq!(obj_data.len(), cached_data.len(), "cached .o should match");
client
.send(&Request::SessionEnd {
session_id: session_id.clone(),
})
.await
.unwrap();
match client.recv().await.unwrap() {
Some(Response::SessionEnded { .. }) => {}
other => panic!("expected SessionEnded, got: {other:?}"),
}
client
.send(&Request::Compile {
session_id,
args: vec!["-c".to_string(), src.to_string_lossy().into_owned()],
cwd: cwd.clone().into(),
compiler: clang.to_string_lossy().into_owned().into(),
env: None,
stdin: Vec::new(),
})
.await
.unwrap();
match client.recv().await.unwrap() {
Some(Response::Error { message }) => {
assert!(
message.contains("unknown session"),
"should report unknown session after end: {message}"
);
}
other => panic!("expected Error after session-end, got: {other:?}"),
}
let log_text = std::fs::read_to_string(&log).unwrap();
assert!(log_text.contains("[MISS]"), "log should show miss");
assert!(log_text.contains("[HIT]"), "log should show hit");
shutdown.notify_one();
server_handle.await.unwrap();
})
.await;
}
#[tokio::test]
#[ignore] async fn cli_session_end_invalid_id() {
crate::test_support::test_timeout(async {
let (endpoint, server_handle, shutdown) = start_daemon().await;
let mut client = crate::ipc::connect(&endpoint).await.unwrap();
client
.send(&Request::SessionEnd {
session_id: 999999.to_string(),
})
.await
.unwrap();
match client.recv().await.unwrap() {
Some(Response::Error { message }) => {
assert!(
message.contains("unknown session") || message.contains("invalid session"),
"expected session error, got: {message}"
);
}
other => panic!("expected Error, got: {other:?}"),
}
shutdown.notify_one();
server_handle.await.unwrap();
})
.await;
}
#[tokio::test]
#[ignore] async fn cli_session_end_unknown_uuid_is_idempotent() {
crate::test_support::test_timeout(async {
let (endpoint, server_handle, shutdown) = start_daemon().await;
let mut client = crate::ipc::connect(&endpoint).await.unwrap();
client
.send(&Request::SessionEnd {
session_id: "00000000-0000-0000-0000-000000000000".to_string(),
})
.await
.unwrap();
match client.recv().await.unwrap() {
Some(Response::SessionEnded { stats }) => {
assert!(
stats.is_none(),
"no stats expected for unknown session, got: {stats:?}"
);
}
other => panic!("expected SessionEnded for unknown UUID, got: {other:?}"),
}
shutdown.notify_one();
server_handle.await.unwrap();
})
.await;
}
#[tokio::test]
#[ignore] async fn cli_compile_unknown_uuid_is_idempotent() {
crate::test_support::test_timeout(async {
let tmp = tempfile::tempdir().unwrap();
let _cache_dir = CacheDirEnvGuard::set(&tmp.path().join("zccache-cache"));
let (endpoint, server_handle, shutdown) = start_daemon().await;
let mut client = crate::ipc::connect(&endpoint).await.unwrap();
let cwd = tmp.path().to_string_lossy().into_owned();
client
.send(&Request::Compile {
session_id: "00000000-0000-0000-0000-000000000000".to_string(),
args: vec!["--version".to_string()],
cwd: cwd.clone().into(),
compiler: "/nonexistent/compiler".to_string().into(),
env: None,
stdin: Vec::new(),
})
.await
.unwrap();
if let Some(Response::Error { message }) = client.recv().await.unwrap() {
assert!(
!message.contains("unknown session"),
"Compile must not fail with 'unknown session' on an unknown UUID, got: {message}"
);
}
shutdown.notify_one();
server_handle.await.unwrap();
})
.await;
}
#[tokio::test]
#[ignore] async fn cli_clear_resets_cache() {
let clang = match crate::test_support::find_clang() {
Some(p) => p,
None => return,
};
crate::test_support::test_timeout(async move {
let tmp = tempfile::tempdir().unwrap();
let src = tmp.path().join("clear_test.cpp");
let obj = tmp.path().join("clear_test.o");
let cwd = tmp.path().to_string_lossy().into_owned();
std::fs::write(&src, "int main() { return 0; }\n").unwrap();
let (endpoint, server_handle, shutdown) = start_daemon().await;
let mut client = crate::ipc::connect(&endpoint).await.unwrap();
client
.send(&Request::SessionStart {
client_pid: std::process::id(),
working_dir: cwd.clone().into(),
log_file: None,
track_stats: false,
journal_path: None,
profile: false,
})
.await
.unwrap();
let session_id = match client.recv().await.unwrap() {
Some(Response::SessionStarted { session_id, .. }) => session_id,
other => panic!("expected SessionStarted, got: {other:?}"),
};
let compile_args = vec![
"-c".to_string(),
src.to_string_lossy().into_owned(),
"-o".to_string(),
obj.to_string_lossy().into_owned(),
];
client
.send(&Request::Compile {
session_id: session_id.clone(),
args: compile_args.clone(),
cwd: cwd.clone().into(),
compiler: clang.to_string_lossy().into_owned().into(),
env: None,
stdin: Vec::new(),
})
.await
.unwrap();
match client.recv().await.unwrap() {
Some(Response::CompileResult {
exit_code, cached, ..
}) => {
assert_eq!(exit_code, 0);
assert!(!cached, "first compile should be a miss");
}
other => panic!("expected CompileResult, got: {other:?}"),
}
std::fs::remove_file(&obj).unwrap();
client
.send(&Request::Compile {
session_id: session_id.clone(),
args: compile_args.clone(),
cwd: cwd.clone().into(),
compiler: clang.to_string_lossy().into_owned().into(),
env: None,
stdin: Vec::new(),
})
.await
.unwrap();
match client.recv().await.unwrap() {
Some(Response::CompileResult {
exit_code, cached, ..
}) => {
assert_eq!(exit_code, 0);
assert!(cached, "second compile should be a hit");
}
other => panic!("expected CompileResult, got: {other:?}"),
}
client.send(&Request::Clear).await.unwrap();
match client.recv().await.unwrap() {
Some(Response::Cleared {
artifacts_removed, ..
}) => {
assert!(
artifacts_removed > 0,
"should have cleared at least one artifact"
);
}
other => panic!("expected Cleared, got: {other:?}"),
}
client
.send(&Request::SessionEnd { session_id })
.await
.unwrap();
let _: Option<Response> = client.recv().await.unwrap();
client
.send(&Request::SessionStart {
client_pid: std::process::id(),
working_dir: cwd.clone().into(),
log_file: None,
track_stats: false,
journal_path: None,
profile: false,
})
.await
.unwrap();
let session_id2 = match client.recv().await.unwrap() {
Some(Response::SessionStarted { session_id, .. }) => session_id,
other => panic!("expected SessionStarted, got: {other:?}"),
};
std::fs::remove_file(&obj).unwrap();
client
.send(&Request::Compile {
session_id: session_id2,
args: compile_args,
cwd: cwd.into(),
compiler: clang.to_string_lossy().into_owned().into(),
env: None,
stdin: Vec::new(),
})
.await
.unwrap();
match client.recv().await.unwrap() {
Some(Response::CompileResult {
exit_code, cached, ..
}) => {
assert_eq!(exit_code, 0);
assert!(!cached, "compile after clear should be a miss");
}
other => panic!("expected CompileResult, got: {other:?}"),
}
shutdown.notify_one();
server_handle.await.unwrap();
})
.await;
}
#[tokio::test]
#[ignore] async fn cli_multi_file_compilation_runs_directly() {
let clang = match crate::test_support::find_clang() {
Some(p) => p,
None => return,
};
crate::test_support::test_timeout(async move {
let tmp = tempfile::tempdir().unwrap();
let src_a = tmp.path().join("multi_a.cpp");
let src_b = tmp.path().join("multi_b.cpp");
let cwd = tmp.path().to_string_lossy().into_owned();
std::fs::write(&src_a, "int foo() { return 1; }\n").unwrap();
std::fs::write(&src_b, "int bar() { return 2; }\n").unwrap();
let (endpoint, server_handle, shutdown) = start_daemon().await;
let mut client = crate::ipc::connect(&endpoint).await.unwrap();
client
.send(&Request::SessionStart {
client_pid: std::process::id(),
working_dir: cwd.clone().into(),
log_file: None,
track_stats: true,
journal_path: None,
profile: false,
})
.await
.unwrap();
let session_id = match client.recv().await.unwrap() {
Some(Response::SessionStarted { session_id, .. }) => session_id,
other => panic!("expected SessionStarted, got: {other:?}"),
};
let multi_args = vec![
"-c".to_string(),
src_a.to_string_lossy().into_owned(),
src_b.to_string_lossy().into_owned(),
];
client
.send(&Request::Compile {
session_id: session_id.clone(),
args: multi_args.clone(),
cwd: cwd.clone().into(),
compiler: clang.to_string_lossy().into_owned().into(),
env: None,
stdin: Vec::new(),
})
.await
.unwrap();
match client.recv().await.unwrap() {
Some(Response::CompileResult {
exit_code, cached, ..
}) => {
assert_eq!(exit_code, 0, "multi-file compile should succeed");
assert!(!cached, "first multi-file compile should be a miss");
}
other => panic!("expected CompileResult, got: {other:?}"),
}
let obj_a = tmp.path().join("multi_a.o");
let obj_b = tmp.path().join("multi_b.o");
assert!(obj_a.exists(), "multi_a.o should exist");
assert!(obj_b.exists(), "multi_b.o should exist");
client
.send(&Request::Compile {
session_id: session_id.clone(),
args: multi_args,
cwd: cwd.clone().into(),
compiler: clang.to_string_lossy().into_owned().into(),
env: None,
stdin: Vec::new(),
})
.await
.unwrap();
match client.recv().await.unwrap() {
Some(Response::CompileResult {
exit_code, cached, ..
}) => {
assert_eq!(exit_code, 0, "second multi-file compile should succeed");
assert!(cached, "second multi-file compile should be all cache hits");
}
other => panic!("expected CompileResult, got: {other:?}"),
}
client
.send(&Request::SessionEnd { session_id })
.await
.unwrap();
match client.recv().await.unwrap() {
Some(Response::SessionEnded { stats }) => {
if let Some(s) = stats {
assert!(
s.misses >= 2,
"first multi-file compile should have 2 misses, got: {}",
s.misses
);
assert!(
s.hits >= 2,
"second multi-file compile should have 2 hits, got: {}",
s.hits
);
}
}
other => panic!("expected SessionEnded, got: {other:?}"),
}
shutdown.notify_one();
server_handle.await.unwrap();
})
.await;
}