#[cfg(all(unix, feature = "a2a"))]
struct KillOnDrop(std::process::Child);
#[cfg(all(unix, feature = "a2a"))]
impl Drop for KillOnDrop {
fn drop(&mut self) {
let _ = self.0.kill();
let _ = self.0.wait();
}
}
#[cfg(all(unix, feature = "a2a"))]
#[test]
fn daemon_boots_under_constrained_stack_ulimit() {
let tmp = tempfile::tempdir().expect("create tempdir");
let config_path = tmp.path().join("daemon-boot.toml");
let mut doc: toml_edit::DocumentMut = zeph_core::config::Config::dump_defaults()
.expect("dump default config")
.parse()
.expect("parse default config toml");
doc["memory"]["sqlite_path"] =
toml_edit::value(tmp.path().join("zeph.db").display().to_string());
doc["daemon"]["pid_file"] = toml_edit::value(tmp.path().join("zeph.pid").display().to_string());
doc["skills"]["paths"] = toml_edit::value(toml_edit::Array::from_iter([tmp
.path()
.join("skills")
.display()
.to_string()]));
doc["vault"]["backend"] = toml_edit::value("env");
std::fs::write(&config_path, doc.to_string()).expect("write test config");
let bin = zeph_bin_path();
let child = std::process::Command::new("bash")
.arg("-c")
.arg(format!(
"ulimit -s 4096 && exec {bin} --config {config} --daemon --bare",
bin = shell_escape(&bin),
config = shell_escape(&config_path.display().to_string()),
))
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.expect("spawn zeph --daemon under bash");
let mut child = KillOnDrop(child);
std::thread::sleep(std::time::Duration::from_secs(3));
match child.0.try_wait().expect("try_wait") {
None => {} Some(status) => panic!(
"daemon exited early with {status:?} instead of staying up \
(pre-fix symptom: stack overflow, exit 134)"
),
}
}
#[test]
fn main_rs_drives_runtime_from_dedicated_stack_thread() {
let main_rs = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/src/main.rs"))
.expect("read src/main.rs");
assert!(
main_rs.contains("stack_size("),
"src/main.rs must spawn the runtime on a thread with an explicit stack_size (see #5394)"
);
assert!(
!main_rs.contains("#[tokio::main]"),
"src/main.rs must not run the async runtime via #[tokio::main] on the OS main \
thread — its stack size is bounded by the caller's ulimit -s and can overflow \
(see #5394); use the dedicated stack_size thread instead"
);
}
#[cfg(all(unix, feature = "a2a"))]
fn zeph_bin_path() -> String {
std::env::var("NEXTEST_BIN_EXE_zeph")
.or_else(|_| std::env::var("CARGO_BIN_EXE_zeph"))
.expect(
"NEXTEST_BIN_EXE_zeph or CARGO_BIN_EXE_zeph must be set by the test runner \
(cargo test / cargo nextest run / cargo nextest run --archive-file)",
)
}
#[cfg(all(unix, feature = "a2a"))]
fn shell_escape(s: &str) -> String {
format!("'{}'", s.replace('\'', r"'\''"))
}