#![cfg(unix)]
#[path = "../src/test_process.rs"]
mod test_process;
use async_trait::async_trait;
use khive_runtime::daemon::run_daemon_in_process_test;
use khive_runtime::{DaemonDispatch, RequestIdentity};
use serial_test::serial;
#[derive(Clone)]
struct NeverDispatch;
#[async_trait]
impl DaemonDispatch for NeverDispatch {
fn plan(&self, ops: &str) -> String {
khive_request::plan_request(ops, &Default::default()).to_string()
}
async fn dispatch(
&self,
_ops: String,
_presentation: Option<String>,
_presentation_per_op: Option<Vec<Option<String>>>,
_format: Option<String>,
_format_per_op: Option<Vec<Option<String>>>,
_from_wire: bool,
_identity: Option<RequestIdentity>,
) -> Result<String, String> {
Err("dispatch not exercised by this test".to_string())
}
async fn warm_all(&self) {}
fn namespace(&self) -> &str {
"test"
}
fn config_id(&self) -> &str {
"test-config"
}
}
#[tokio::test]
#[serial]
async fn second_daemon_boot_refuses_loudly_while_first_is_live() {
if test_process::run_in_child() {
return;
}
let dir = tempfile::tempdir().expect("tempdir");
std::env::set_var("KHIVE_SOCKET", dir.path().join("khived.sock"));
std::env::set_var("KHIVE_PID", dir.path().join("khived.pid"));
std::env::set_var("KHIVE_LOCK", dir.path().join("khived.recovery.lock"));
let first = tokio::spawn(run_daemon_in_process_test(NeverDispatch));
let sock = dir.path().join("khived.sock");
let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(5);
while !sock.exists() {
if tokio::time::Instant::now() >= deadline {
panic!("first daemon never bound its socket within the deadline");
}
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
let second = run_daemon_in_process_test(NeverDispatch).await;
let err = second.expect_err(
"a second boot attempt while the first is live and responsive must return Err, \
not silently succeed",
);
let message = format!("{err:#}");
assert!(
message.contains(&std::process::id().to_string()),
"the refusal must name the incumbent's pid so an operator can act on it, got: {message}"
);
assert!(
message.to_lowercase().contains("already running"),
"the refusal must say plainly that a daemon is already running, got: {message}"
);
first.abort();
let _ = first.await;
std::env::remove_var("KHIVE_SOCKET");
std::env::remove_var("KHIVE_PID");
std::env::remove_var("KHIVE_LOCK");
}