mod common;
#[test]
fn single_run_non_regression_exits_zero() {
common::agent_block_cmd()
.args(["-s", &common::fixture("hello.lua")])
.assert()
.success();
}
#[cfg(unix)]
mod unix {
use super::common;
use nix::sys::signal::{kill, Signal};
use nix::unistd::Pid;
use std::io::Write;
use std::process::{Child, Stdio};
use std::thread;
use std::time::{Duration, Instant};
use tempfile::NamedTempFile;
fn wait_with_timeout(
child: &mut Child,
timeout: Duration,
) -> Result<std::process::ExitStatus, Duration> {
let start = Instant::now();
loop {
match child.try_wait() {
Ok(Some(status)) => return Ok(status),
Ok(None) => {
if start.elapsed() >= timeout {
return Err(start.elapsed());
}
thread::sleep(Duration::from_millis(50));
}
Err(e) => panic!("try_wait error: {e}"),
}
}
}
fn spawn_bus_serve(script_path: &str) -> (Child, tempfile::TempDir) {
let home = tempfile::tempdir().expect("mktempdir");
let child = common::agent_block_std_cmd()
.args(["-s", script_path])
.env("AGENT_BLOCK_TASK_GRACE_MS", "1000")
.env("AGENT_BLOCK_HOME", home.path())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn agent-block");
(child, home)
}
fn assert_graceful_exit(mut child: Child, sig: Signal, deadline: Duration) {
thread::sleep(Duration::from_millis(1500));
let pid = Pid::from_raw(child.id() as i32);
kill(pid, sig).expect("kill");
match wait_with_timeout(&mut child, deadline) {
Ok(status) => {
assert!(
status.success(),
"child exited {:?} after {:?}, expected success",
status,
sig
);
}
Err(elapsed) => {
let _ = child.kill();
panic!(
"child did not exit within {:?} after {:?} (elapsed {:?})",
deadline, sig, elapsed
);
}
}
}
#[test]
fn bus_serve_sigterm_exits_zero_within_grace() {
let script = common::example("test_bus.lua");
let (child, _home) = spawn_bus_serve(&script);
assert_graceful_exit(child, Signal::SIGTERM, Duration::from_secs(5));
}
#[test]
fn bus_serve_sigint_exits_zero_within_grace() {
let script = common::example("test_bus.lua");
let (child, _home) = spawn_bus_serve(&script);
assert_graceful_exit(child, Signal::SIGINT, Duration::from_secs(5));
}
#[test]
fn bus_serve_with_no_handlers_exits_on_signal() {
let mut tmp = NamedTempFile::new().expect("tempfile");
writeln!(
tmp,
r#"log.info("idle serve start")
bus.serve()
log.info("idle serve stop")
"#
)
.expect("write tmp script");
let path = tmp.path().to_string_lossy().to_string();
let (child, _home) = spawn_bus_serve(&path);
assert_graceful_exit(child, Signal::SIGTERM, Duration::from_secs(5));
}
#[test]
fn double_bus_serve_second_call_errors() {
let mut tmp = NamedTempFile::new().expect("tempfile");
writeln!(
tmp,
r#"bus.serve()
local ok, err = pcall(function() bus.serve() end)
if ok then
print("double-serve unexpectedly succeeded")
error("double-serve unexpectedly succeeded")
else
print("double-serve error: " .. tostring(err))
error("intended: double serve blocked")
end
"#
)
.expect("write tmp script");
let path = tmp.path().to_string_lossy().to_string();
let home = tempfile::tempdir().expect("mktempdir");
let mut child = common::agent_block_std_cmd()
.args(["-s", &path])
.env("AGENT_BLOCK_TASK_GRACE_MS", "1000")
.env("AGENT_BLOCK_HOME", home.path())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn agent-block");
thread::sleep(Duration::from_millis(1500));
let pid = Pid::from_raw(child.id() as i32);
kill(pid, Signal::SIGTERM).expect("kill SIGTERM");
match wait_with_timeout(&mut child, Duration::from_secs(5)) {
Ok(status) => {
assert!(
!status.success(),
"double-serve test should exit non-zero, got {status:?}"
);
let out = child.wait_with_output().ok();
if let Some(out) = out {
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("double-serve error"),
"expected 'double-serve error' in stdout, got: {stdout}"
);
assert!(
stdout.contains("already running"),
"expected 'already running' in double-serve error, got: {stdout}"
);
}
}
Err(elapsed) => {
let _ = child.kill();
panic!("double-serve test did not terminate within 5s (elapsed {elapsed:?})");
}
}
}
}