//! Shared helpers for integration tests: spawn the real daemon binary on //! ephemeral paths and speak the JSON-lines protocol to it. // Each test binary compiles its own copy of this module and none of them // uses every helper. #![allow(dead_code)] use std::io::{BufRead, BufReader, Write}; use std::os::unix::net::UnixStream; use std::path::{Path, PathBuf}; use std::process::{Child, Command}; use std::time::{Duration, Instant}; use varde_proto::{Envelope, Request}; /// A running daemon process, killed on drop. pub struct DaemonProc { pub child: Child, pub socket: PathBuf, pub store: PathBuf, // Kept alive so the store/socket dirs survive as long as the daemon. _dir: Option, } impl DaemonProc { /// SIGKILL the daemon — no cleanup handlers run, simulating a crash. pub fn kill_dash_nine(&mut self) { let _ = self.child.kill(); let _ = self.child.wait(); } } impl Drop for DaemonProc { fn drop(&mut self) { let _ = self.child.kill(); let _ = self.child.wait(); } } /// Spawn the compiled varde-daemon on a fresh tempdir. pub fn spawn_daemon() -> DaemonProc { let dir = tempfile::tempdir().expect("tempdir"); let mut proc = spawn_daemon_at(dir.path()); proc._dir = Some(dir); proc } /// Spawn the daemon against an existing directory (for restart tests). pub fn spawn_daemon_at(dir: &Path) -> DaemonProc { spawn_daemon_with_env(dir, &[]) } /// Spawn the daemon with extra environment variables (config overrides). pub fn spawn_daemon_with_env(dir: &Path, envs: &[(&str, &str)]) -> DaemonProc { let socket = dir.join("varde.sock"); let store = dir.join("store"); let mut command = Command::new(env!("CARGO_BIN_EXE_varde-daemon")); command .arg("--user") .arg("--socket") .arg(&socket) .arg("--store") .arg(&store) // Isolate from any config file on the developer's machine. .env("VARDE_CONFIG", dir.join("no-config.toml")) .env("VARDE_DISCOVERY", "false"); for (key, value) in envs { command.env(key, value); } let child = command.spawn().expect("spawning varde-daemon"); // An explicitly named config must exist for the daemon to start. std::fs::write(dir.join("no-config.toml"), "").expect("writing empty config"); let proc = DaemonProc { child, socket, store, _dir: None, }; wait_for_socket(&proc.socket); proc } fn wait_for_socket(path: &Path) { let deadline = Instant::now() + Duration::from_secs(10); while Instant::now() < deadline { if UnixStream::connect(path).is_ok() { return; } std::thread::sleep(Duration::from_millis(20)); } panic!("daemon socket {} never appeared", path.display()); } /// One connected API client. pub struct Client { reader: BufReader, writer: UnixStream, } impl Client { pub fn connect(socket: &Path) -> Client { let stream = UnixStream::connect(socket).expect("connecting to daemon"); let reader = BufReader::new(stream.try_clone().expect("cloning stream")); Client { reader, writer: stream, } } /// Send a request and read the response envelope. pub fn request(&mut self, req: &Request) -> Envelope { let mut line = serde_json::to_string(req).expect("serializing request"); line.push('\n'); self.writer .write_all(line.as_bytes()) .expect("writing request"); self.read_envelope() } /// Send a raw line (for malformed-input tests). pub fn send_raw(&mut self, raw: &str) -> Envelope { self.writer .write_all(raw.as_bytes()) .expect("writing raw line"); self.writer.write_all(b"\n").expect("writing newline"); self.read_envelope() } fn read_envelope(&mut self) -> Envelope { let mut reply = String::new(); self.reader.read_line(&mut reply).expect("reading reply"); assert!( !reply.is_empty(), "daemon closed connection without replying" ); serde_json::from_str(&reply).expect("parsing reply envelope") } /// Bound how long reads may block (applies to the shared socket). pub fn set_read_timeout(&mut self, timeout: Duration) { self.writer .set_read_timeout(Some(timeout)) .expect("setting read timeout"); } /// Read one line of the event stream (after Subscribe). pub fn read_event_line(&mut self) -> Option { let mut line = String::new(); match self.reader.read_line(&mut line) { Ok(0) => None, Ok(_) => Some(line), Err(e) => panic!("reading event line: {e}"), } } }