use std::path::{Path, PathBuf};
use std::time::Duration;
use anyhow::{Context as _, Result};
const ENV_NO_AUTO_START: &str = "SQRY_DAEMON_NO_AUTO_START";
const SQRYD_ALREADY_RUNNING_EXIT_CODE: i32 = 75;
#[derive(Debug, PartialEq)]
pub enum DaemonParseResult {
Daemon { socket: Option<PathBuf> },
MissingDaemon,
MissingSocketPath,
UnknownInDaemonMode { token: String },
NotDaemonMode,
}
pub fn parse_daemon_args(args: &[String]) -> DaemonParseResult {
let tail: Vec<&str> = args.iter().skip(1).map(String::as_str).collect();
let mut has_daemon = false;
let mut socket_path: Option<PathBuf> = None;
let mut unknown_token: Option<String> = None;
let mut i = 0usize;
while i < tail.len() {
match tail[i] {
"--daemon" => {
has_daemon = true;
}
"--daemon-socket" => {
match tail.get(i + 1) {
Some(&path_str) if !path_str.starts_with("--") => {
socket_path = Some(PathBuf::from(path_str));
i += 1;
}
_ => return DaemonParseResult::MissingSocketPath,
}
}
other => {
if unknown_token.is_none() {
unknown_token = Some(other.to_string());
}
}
}
i += 1;
}
if socket_path.is_some() && !has_daemon {
return DaemonParseResult::MissingDaemon;
}
if has_daemon {
if let Some(token) = unknown_token {
return DaemonParseResult::UnknownInDaemonMode { token };
}
return DaemonParseResult::Daemon {
socket: socket_path,
};
}
DaemonParseResult::NotDaemonMode
}
pub fn resolve_daemon_socket(override_path: Option<&Path>) -> PathBuf {
if let Some(p) = override_path {
return p.to_path_buf();
}
if let Some(env_val) = std::env::var_os("SQRYD_SOCKET") {
return PathBuf::from(env_val);
}
platform_default_daemon_socket()
}
#[cfg(unix)]
fn platform_default_daemon_socket() -> PathBuf {
if let Some(xdg) = std::env::var_os("XDG_RUNTIME_DIR") {
return PathBuf::from(xdg).join("sqry").join("sqryd.sock");
}
let uid = unsafe { libc::getuid() };
let dir_name = format!("sqry-{uid}");
if let Some(tmp) = std::env::var_os("TMPDIR") {
return PathBuf::from(tmp).join(&dir_name).join("sqryd.sock");
}
PathBuf::from("/tmp").join(&dir_name).join("sqryd.sock")
}
#[cfg(windows)]
fn platform_default_daemon_socket() -> PathBuf {
PathBuf::from(r"\\.\pipe\sqry")
}
pub async fn run_daemon_shim(socket: Option<PathBuf>) -> Result<()> {
let socket_path = resolve_daemon_socket(socket.as_deref());
tracing::info!(
"sqry-mcp connecting to sqryd daemon at {}",
socket_path.display()
);
let conn = match sqry_daemon_client::connect_shim(
&socket_path,
sqry_daemon_client::ShimProtocol::Mcp,
std::process::id(),
)
.await
{
Ok(conn) => conn,
Err(ref e) if is_connect_failure(e) => {
if std::env::var_os(ENV_NO_AUTO_START).as_deref() == Some(std::ffi::OsStr::new("1")) {
anyhow::bail!("daemon shim connect failed: {e}");
}
tracing::info!(
"daemon not reachable at {}; attempting auto-start",
socket_path.display()
);
auto_start_daemon(&socket_path).await?;
sqry_daemon_client::connect_shim(
&socket_path,
sqry_daemon_client::ShimProtocol::Mcp,
std::process::id(),
)
.await
.map_err(|e| anyhow::anyhow!("daemon shim connect failed after auto-start: {e}"))?
}
Err(e) => {
anyhow::bail!("daemon shim connect failed: {e}");
}
};
tracing::info!(
"sqry-mcp shim connected (daemon version {})",
conn.daemon_version()
);
let (bytes_up, bytes_down) = sqry_daemon_client::pump_stdio(conn)
.await
.map_err(|e| anyhow::anyhow!("daemon byte-pump failed: {e}"))?;
tracing::info!("sqry-mcp shim pump complete: {bytes_up} bytes up, {bytes_down} bytes down");
Ok(())
}
pub(crate) fn is_connect_failure(err: &sqry_daemon_client::ClientError) -> bool {
matches!(
err,
sqry_daemon_client::ClientError::Connect { .. }
| sqry_daemon_client::ClientError::ConnectTimeout { .. }
)
}
fn resolve_sqryd_binary() -> Result<PathBuf> {
if let Some(val) = std::env::var_os("SQRYD_PATH") {
let path = PathBuf::from(val);
if path.exists() {
return Ok(path);
}
anyhow::bail!("SQRYD_PATH={} does not exist", path.display());
}
if let Ok(exe) = std::env::current_exe() {
let canonical = std::fs::canonicalize(&exe).unwrap_or(exe);
if let Some(dir) = canonical.parent() {
let sibling = dir.join("sqryd");
if sibling.exists() {
return Ok(sibling);
}
let sibling_exe = dir.join("sqryd.exe");
if sibling_exe.exists() {
return Ok(sibling_exe);
}
}
}
which::which("sqryd").with_context(|| {
"sqryd binary not found. \
Install sqryd alongside sqry-mcp, set SQRYD_PATH to its path, \
or start sqryd manually before launching the MCP server."
.to_owned()
})
}
async fn auto_start_daemon(socket_path: &Path) -> Result<()> {
let binary = resolve_sqryd_binary()?;
tracing::info!("auto-starting sqryd from {}", binary.display());
spawn_sqryd_start_detach(&binary, socket_path).await?;
poll_socket_reachable(socket_path, Duration::from_secs(5)).await?;
tracing::info!("sqryd auto-started successfully");
Ok(())
}
async fn spawn_sqryd_start_detach(binary: &Path, socket_path: &Path) -> Result<()> {
let binary = binary.to_path_buf();
let socket_path = socket_path.to_path_buf();
#[cfg(unix)]
{
let binary_for_err = binary.clone();
let status = tokio::task::spawn_blocking(move || {
std::process::Command::new(&binary)
.args(["start", "--detach"])
.env("SQRY_DAEMON_SOCKET", socket_path.as_os_str())
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::inherit())
.status()
})
.await
.with_context(|| "auto-start: spawn_blocking task panicked")?
.with_context(|| {
format!(
"auto-start: failed to exec sqryd at {}",
binary_for_err.display()
)
})?;
if !status.success() {
let code = status.code().unwrap_or(1);
if code != SQRYD_ALREADY_RUNNING_EXIT_CODE {
anyhow::bail!("auto-start: sqryd start --detach exited with code {code}");
}
tracing::info!("sqryd is already running (exit code 75)");
}
}
#[cfg(not(unix))]
{
let binary_for_err = binary.clone();
tokio::task::spawn_blocking(move || {
let mut cmd = std::process::Command::new(&binary);
cmd.args(["start", "--detach"])
.env("SQRY_DAEMON_SOCKET", socket_path.as_os_str())
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::inherit());
#[cfg(windows)]
{
use std::os::windows::process::CommandExt as _;
const DETACHED_PROCESS: u32 = 0x0000_0008;
const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
cmd.creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP);
}
cmd.spawn().map(|_child| ()) })
.await
.with_context(|| "auto-start: spawn_blocking task panicked")?
.with_context(|| {
format!(
"auto-start: failed to spawn sqryd at {}",
binary_for_err.display()
)
})?;
}
Ok(())
}
async fn poll_socket_reachable(socket_path: &Path, budget: Duration) -> Result<()> {
let deadline = tokio::time::Instant::now() + budget;
loop {
if try_connect_async(socket_path).await {
return Ok(());
}
if tokio::time::Instant::now() >= deadline {
anyhow::bail!(
"auto-start: sqryd started but socket {} not reachable within {}s",
socket_path.display(),
budget.as_secs()
);
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
async fn try_connect_async(socket_path: &Path) -> bool {
#[cfg(unix)]
{
tokio::net::UnixStream::connect(socket_path).await.is_ok()
}
#[cfg(windows)]
{
use tokio::net::windows::named_pipe::ClientOptions;
ClientOptions::new().open(socket_path.as_os_str()).is_ok()
}
#[cfg(not(any(unix, windows)))]
{
let _ = socket_path;
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
use std::path::Path;
fn args(v: &[&str]) -> Vec<String> {
v.iter().map(|s| s.to_string()).collect()
}
#[test]
fn parse_daemon_alone() {
let r = parse_daemon_args(&args(&["sqry-mcp", "--daemon"]));
assert_eq!(r, DaemonParseResult::Daemon { socket: None });
}
#[test]
fn parse_daemon_with_socket() {
let r = parse_daemon_args(&args(&[
"sqry-mcp",
"--daemon",
"--daemon-socket",
"/custom.sock",
]));
assert_eq!(
r,
DaemonParseResult::Daemon {
socket: Some(PathBuf::from("/custom.sock"))
}
);
}
#[test]
fn parse_daemon_flags_order_independent() {
let r = parse_daemon_args(&args(&[
"sqry-mcp",
"--daemon-socket",
"/reorder.sock",
"--daemon",
]));
assert_eq!(
r,
DaemonParseResult::Daemon {
socket: Some(PathBuf::from("/reorder.sock"))
}
);
}
#[test]
fn parse_daemon_socket_without_daemon() {
let r = parse_daemon_args(&args(&["sqry-mcp", "--daemon-socket", "/some/path"]));
assert_eq!(r, DaemonParseResult::MissingDaemon);
}
#[test]
fn parse_daemon_socket_with_no_path() {
let r = parse_daemon_args(&args(&["sqry-mcp", "--daemon", "--daemon-socket"]));
assert_eq!(r, DaemonParseResult::MissingSocketPath);
}
#[test]
fn parse_daemon_socket_with_flag_token_as_path_is_missing_path() {
let r = parse_daemon_args(&args(&["sqry-mcp", "--daemon-socket", "--daemon"]));
assert_eq!(
r,
DaemonParseResult::MissingSocketPath,
"--daemon-socket --daemon must be MissingSocketPath (not MissingDaemon)"
);
let r2 = parse_daemon_args(&args(&[
"sqry-mcp",
"--daemon",
"--daemon-socket",
"--help",
]));
assert_eq!(
r2,
DaemonParseResult::MissingSocketPath,
"--daemon --daemon-socket --help must be MissingSocketPath"
);
}
#[test]
fn parse_no_daemon_flags() {
let r = parse_daemon_args(&args(&["sqry-mcp"]));
assert_eq!(r, DaemonParseResult::NotDaemonMode);
let r2 = parse_daemon_args(&args(&["sqry-mcp", "--help"]));
assert_eq!(r2, DaemonParseResult::NotDaemonMode);
}
#[test]
fn parse_daemon_with_unknown_trailing_flag_is_rejected() {
let r = parse_daemon_args(&args(&["sqry-mcp", "--daemon", "--bogus"]));
assert_eq!(
r,
DaemonParseResult::UnknownInDaemonMode {
token: "--bogus".to_string()
}
);
}
#[test]
fn parse_unknown_before_daemon_flag_is_rejected() {
let r = parse_daemon_args(&args(&["sqry-mcp", "--bogus", "--daemon"]));
assert_eq!(
r,
DaemonParseResult::UnknownInDaemonMode {
token: "--bogus".to_string()
}
);
}
#[test]
fn parse_multiple_unknowns_reports_first() {
let r = parse_daemon_args(&args(&[
"sqry-mcp",
"--daemon",
"--first-bad",
"--second-bad",
]));
assert_eq!(
r,
DaemonParseResult::UnknownInDaemonMode {
token: "--first-bad".to_string()
}
);
}
#[test]
fn parse_daemon_with_positional_unknown_is_rejected() {
let r = parse_daemon_args(&args(&["sqry-mcp", "--daemon", "extra-positional"]));
assert_eq!(
r,
DaemonParseResult::UnknownInDaemonMode {
token: "extra-positional".to_string()
}
);
}
#[test]
fn parse_unknown_without_daemon_falls_through_to_not_daemon_mode() {
let r = parse_daemon_args(&args(&["sqry-mcp", "--bogus"]));
assert_eq!(r, DaemonParseResult::NotDaemonMode);
}
#[test]
fn resolve_explicit_override_wins() {
let path = Path::new("/explicit/sqryd.sock");
let result = resolve_daemon_socket(Some(path));
assert_eq!(result, PathBuf::from("/explicit/sqryd.sock"));
}
#[serial]
#[test]
fn resolve_env_var_wins_over_platform_default() {
struct EnvGuard {
key: &'static str,
prior: Option<std::ffi::OsString>,
}
impl Drop for EnvGuard {
fn drop(&mut self) {
unsafe {
match &self.prior {
Some(v) => std::env::set_var(self.key, v),
None => std::env::remove_var(self.key),
}
}
}
}
let prior = std::env::var_os("SQRYD_SOCKET");
let _guard = EnvGuard {
key: "SQRYD_SOCKET",
prior,
};
unsafe { std::env::set_var("SQRYD_SOCKET", "/env/sqryd.sock") };
let result = resolve_daemon_socket(None);
assert_eq!(
result,
PathBuf::from("/env/sqryd.sock"),
"SQRYD_SOCKET env var should take precedence over platform default"
);
}
#[serial]
#[test]
fn resolve_explicit_wins_over_env_var() {
struct EnvGuard {
key: &'static str,
prior: Option<std::ffi::OsString>,
}
impl Drop for EnvGuard {
fn drop(&mut self) {
unsafe {
match &self.prior {
Some(v) => std::env::set_var(self.key, v),
None => std::env::remove_var(self.key),
}
}
}
}
let prior = std::env::var_os("SQRYD_SOCKET");
let _guard = EnvGuard {
key: "SQRYD_SOCKET",
prior,
};
unsafe { std::env::set_var("SQRYD_SOCKET", "/env/should-be-ignored.sock") };
let explicit = Path::new("/explicit/wins.sock");
let result = resolve_daemon_socket(Some(explicit));
assert_eq!(
result,
PathBuf::from("/explicit/wins.sock"),
"explicit path must win over SQRYD_SOCKET env var"
);
}
#[cfg(unix)]
#[serial]
#[test]
fn resolve_unix_platform_default_structure() {
struct EnvGuard {
key: &'static str,
prior: Option<std::ffi::OsString>,
}
impl Drop for EnvGuard {
fn drop(&mut self) {
unsafe {
match &self.prior {
Some(v) => std::env::set_var(self.key, v),
None => std::env::remove_var(self.key),
}
}
}
}
let prior_socket = std::env::var_os("SQRYD_SOCKET");
let prior_xdg = std::env::var_os("XDG_RUNTIME_DIR");
let _g1 = EnvGuard {
key: "SQRYD_SOCKET",
prior: prior_socket,
};
let _g2 = EnvGuard {
key: "XDG_RUNTIME_DIR",
prior: prior_xdg,
};
unsafe {
std::env::remove_var("SQRYD_SOCKET");
std::env::remove_var("XDG_RUNTIME_DIR");
}
let result = resolve_daemon_socket(None);
let s = result.to_string_lossy();
assert!(
s.contains("sqry"),
"unix platform default should contain 'sqry'; got: {s}"
);
assert!(
s.ends_with("sqryd.sock"),
"unix platform default should end with 'sqryd.sock'; got: {s}"
);
}
struct EnvGuard {
key: &'static str,
prior: Option<std::ffi::OsString>,
}
impl Drop for EnvGuard {
fn drop(&mut self) {
unsafe {
match &self.prior {
Some(v) => std::env::set_var(self.key, v),
None => std::env::remove_var(self.key),
}
}
}
}
#[test]
fn is_connect_failure_true_for_connect_error() {
let err = sqry_daemon_client::ClientError::Connect {
path: PathBuf::from("/tmp/sqryd.sock"),
source: std::io::Error::new(std::io::ErrorKind::ConnectionRefused, "refused"),
};
assert!(is_connect_failure(&err));
}
#[test]
fn is_connect_failure_true_for_connect_timeout() {
let err = sqry_daemon_client::ClientError::ConnectTimeout {
path: PathBuf::from("/tmp/sqryd.sock"),
after: Duration::from_millis(100),
};
assert!(is_connect_failure(&err));
}
#[test]
fn is_connect_failure_false_for_handshake_error() {
let err = sqry_daemon_client::ClientError::HelloRejected;
assert!(!is_connect_failure(&err));
}
#[test]
fn is_connect_failure_false_for_shim_rejected() {
let err = sqry_daemon_client::ClientError::ShimRejected("cap limit".to_string());
assert!(!is_connect_failure(&err));
}
#[test]
fn is_connect_failure_false_for_envelope_version_mismatch() {
let err = sqry_daemon_client::ClientError::EnvelopeVersionMismatch {
got: 0,
expected: 1,
};
assert!(!is_connect_failure(&err));
}
#[serial]
#[test]
fn resolve_sqryd_binary_finds_env_var() {
let tmp = tempfile::NamedTempFile::new().expect("temp file");
let prior = std::env::var_os("SQRYD_PATH");
let _guard = EnvGuard {
key: "SQRYD_PATH",
prior,
};
unsafe { std::env::set_var("SQRYD_PATH", tmp.path()) };
let result = resolve_sqryd_binary();
assert!(result.is_ok(), "expected Ok, got: {result:?}");
assert_eq!(result.unwrap(), tmp.path());
}
#[serial]
#[test]
fn resolve_sqryd_binary_returns_error_when_not_found() {
let absent = PathBuf::from(
"/tmp/sqryd_test_absent_aaaaabbbbccccddddeeeef0001/definitely-does-not-exist",
);
assert!(!absent.exists(), "test precondition: path must not exist");
let prior = std::env::var_os("SQRYD_PATH");
let _guard = EnvGuard {
key: "SQRYD_PATH",
prior,
};
unsafe { std::env::set_var("SQRYD_PATH", &absent) };
let result = resolve_sqryd_binary();
assert!(result.is_err(), "expected Err for absent SQRYD_PATH");
let msg = format!("{:?}", result.unwrap_err());
assert!(
msg.contains("does not exist"),
"error should cite 'does not exist'; got: {msg}"
);
}
#[cfg(unix)]
#[serial]
#[test]
fn resolve_sqryd_binary_finds_sibling_of_current_exe() {
let exe = std::env::current_exe().expect("current_exe");
let canonical = std::fs::canonicalize(&exe).unwrap_or(exe);
let dir = canonical.parent().expect("exe has parent");
let sibling = dir.join("sqryd");
let created = !sibling.exists();
if created {
std::fs::write(&sibling, b"").expect("create mock sqryd sibling");
}
let prior_path = std::env::var_os("SQRYD_PATH");
let _guard_path = EnvGuard {
key: "SQRYD_PATH",
prior: prior_path,
};
unsafe { std::env::remove_var("SQRYD_PATH") };
let result = resolve_sqryd_binary();
if created {
let _ = std::fs::remove_file(&sibling);
}
assert!(
result.is_ok(),
"expected Ok for sibling resolution; got: {result:?}"
);
assert_eq!(result.unwrap(), sibling);
}
#[cfg(unix)]
#[serial]
#[test]
fn resolve_sqryd_binary_finds_via_path_lookup() {
use std::os::unix::fs::PermissionsExt as _;
let tmp_dir = tempfile::TempDir::new().expect("temp dir");
let mock_sqryd = tmp_dir.path().join("sqryd");
std::fs::write(&mock_sqryd, b"#!/bin/sh\n").expect("write mock sqryd");
std::fs::set_permissions(&mock_sqryd, std::fs::Permissions::from_mode(0o755))
.expect("chmod mock sqryd");
let exe = std::env::current_exe().expect("current_exe");
let canonical = std::fs::canonicalize(&exe).unwrap_or(exe);
let sibling = canonical.parent().map(|d| d.join("sqryd"));
let sibling_exists = sibling.as_ref().is_some_and(|p| p.exists());
let prior_sqryd_path = std::env::var_os("SQRYD_PATH");
let _guard_sqryd = EnvGuard {
key: "SQRYD_PATH",
prior: prior_sqryd_path,
};
unsafe { std::env::remove_var("SQRYD_PATH") };
let prior_path_env = std::env::var_os("PATH");
let new_path = if let Some(ref p) = prior_path_env {
let mut s = std::ffi::OsString::from(tmp_dir.path());
s.push(":");
s.push(p);
s
} else {
std::ffi::OsString::from(tmp_dir.path())
};
let _guard_path = EnvGuard {
key: "PATH",
prior: prior_path_env,
};
unsafe { std::env::set_var("PATH", &new_path) };
let result = resolve_sqryd_binary();
if sibling_exists {
assert!(
result.is_ok(),
"sibling exists, expected Ok; got: {result:?}"
);
} else {
assert!(
result.is_ok(),
"expected Ok from PATH lookup; got: {result:?}"
);
assert_eq!(
result.unwrap(),
mock_sqryd,
"PATH lookup should find the temp-dir mock sqryd"
);
}
}
#[tokio::test]
async fn poll_socket_reachable_times_out() {
let absent = Path::new("/tmp/sqryd_poll_test_no_such_socket_zzz999.sock");
let result = poll_socket_reachable(absent, Duration::from_millis(10)).await;
assert!(result.is_err(), "expected timeout error");
let msg = format!("{:?}", result.unwrap_err());
assert!(
msg.contains("not reachable"),
"error should contain 'not reachable'; got: {msg}"
);
}
#[cfg(unix)]
#[tokio::test]
async fn poll_socket_reachable_succeeds_on_live_socket() {
use tokio::net::UnixListener;
let tmp = tempfile::NamedTempFile::new().expect("temp file");
let sock_path = tmp.path().to_path_buf();
drop(tmp);
let _listener = UnixListener::bind(&sock_path).expect("bind unix listener");
let result = poll_socket_reachable(&sock_path, Duration::from_secs(5)).await;
assert!(
result.is_ok(),
"expected Ok on live socket; got: {result:?}"
);
}
#[serial]
#[tokio::test]
async fn auto_start_daemon_fails_when_binary_not_found() {
let absent = PathBuf::from("/tmp/sqryd_autostart_test_absent_binary_xyz999/sqryd-no-exist");
let prior = std::env::var_os("SQRYD_PATH");
let _guard = EnvGuard {
key: "SQRYD_PATH",
prior,
};
unsafe { std::env::set_var("SQRYD_PATH", &absent) };
let result = auto_start_daemon(Path::new("/tmp/irrelevant.sock")).await;
assert!(result.is_err(), "expected Err when binary not found");
let msg = format!("{:?}", result.unwrap_err());
assert!(
msg.contains("does not exist"),
"error should cite 'does not exist'; got: {msg}"
);
}
#[serial]
#[tokio::test]
async fn auto_start_opt_out_via_env() {
let absent_socket = PathBuf::from("/tmp/sqryd_optout_test_no_sock_qqq888/sqryd.sock");
let prior_no_start = std::env::var_os("SQRY_DAEMON_NO_AUTO_START");
let _guard = EnvGuard {
key: "SQRY_DAEMON_NO_AUTO_START",
prior: prior_no_start,
};
unsafe { std::env::set_var("SQRY_DAEMON_NO_AUTO_START", "1") };
let result = run_daemon_shim(Some(absent_socket)).await;
assert!(result.is_err(), "expected connect error");
let msg = format!("{}", result.unwrap_err());
assert!(
msg.contains("daemon shim connect failed"),
"error should be the original connect error (not auto-start); got: {msg}"
);
assert!(
!msg.contains("auto-start"),
"opt-out must suppress auto-start; got: {msg}"
);
}
#[tokio::test]
async fn spawn_sqryd_start_detach_fails_for_missing_binary() {
let missing_binary = Path::new("/tmp/sqryd_spawn_test_no_binary_mmm777/sqryd-missing");
let socket = Path::new("/tmp/irrelevant_sock_for_spawn_test.sock");
let result = spawn_sqryd_start_detach(missing_binary, socket).await;
assert!(result.is_err(), "expected Err for missing binary");
let msg = format!("{:?}", result.unwrap_err());
assert!(
msg.contains("exec") || msg.contains("spawn") || msg.contains("failed"),
"error should describe exec/spawn failure; got: {msg}"
);
}
#[cfg(unix)]
#[serial]
#[tokio::test]
async fn run_daemon_shim_attempts_auto_start_on_connect_failure() {
let absent_socket =
PathBuf::from("/tmp/sqryd_shim_autostart_wiring_test_zzz444/sqryd.sock");
let prior_no_start = std::env::var_os("SQRY_DAEMON_NO_AUTO_START");
let _guard_no_start = EnvGuard {
key: "SQRY_DAEMON_NO_AUTO_START",
prior: prior_no_start,
};
unsafe { std::env::remove_var("SQRY_DAEMON_NO_AUTO_START") };
let absent_binary =
PathBuf::from("/tmp/sqryd_shim_autostart_wiring_test_zzz444/sqryd-no-exist");
let prior_sqryd_path = std::env::var_os("SQRYD_PATH");
let _guard_sqryd = EnvGuard {
key: "SQRYD_PATH",
prior: prior_sqryd_path,
};
unsafe { std::env::set_var("SQRYD_PATH", &absent_binary) };
let result = run_daemon_shim(Some(absent_socket)).await;
assert!(result.is_err(), "expected error from auto-start failure");
let msg = format!("{}", result.unwrap_err());
assert!(
msg.contains("does not exist"),
"error should be from binary resolution inside auto_start_daemon; got: {msg}"
);
assert!(
!msg.contains("auto-start suppressed"),
"auto-start must not have been suppressed; got: {msg}"
);
}
#[cfg(unix)]
#[serial]
#[tokio::test]
async fn run_daemon_shim_does_not_auto_start_on_protocol_failure() {
use tokio::net::UnixListener;
let prior_no_start = std::env::var_os("SQRY_DAEMON_NO_AUTO_START");
let _guard = EnvGuard {
key: "SQRY_DAEMON_NO_AUTO_START",
prior: prior_no_start,
};
unsafe { std::env::remove_var("SQRY_DAEMON_NO_AUTO_START") };
let tmp = tempfile::NamedTempFile::new().expect("temp file");
let sock_path = tmp.path().to_path_buf();
drop(tmp);
let listener = UnixListener::bind(&sock_path).expect("bind listener");
tokio::spawn(async move {
if let Ok((_stream, _)) = listener.accept().await {
}
});
let result = run_daemon_shim(Some(sock_path)).await;
assert!(result.is_err(), "expected protocol error");
let msg = format!("{}", result.unwrap_err());
assert!(
msg.contains("daemon shim connect failed"),
"expected raw protocol error; got: {msg}"
);
assert!(
!msg.contains("auto-start"),
"protocol failure must NOT trigger auto-start; got: {msg}"
);
}
}