use std::sync::Mutex;
use tempfile::TempDir;
use super::manager::{ManagedError, ManagedTmuxDriver, SessionManager};
use super::tests::FakeTmuxDriver;
#[tokio::test]
async fn graceful_terminate_runtime_signals_then_kills() {
let dir = TempDir::new().unwrap();
let fake = FakeTmuxDriver::new();
let mgr = SessionManager::new(dir.path(), fake.clone()).await.unwrap();
fake.seeded_names
.lock()
.unwrap()
.push("tm-drain-1".to_string());
mgr.graceful_terminate_runtime("tm-drain-1").await;
assert_eq!(
*fake.interrupt_calls.lock().unwrap(),
vec!["tm-drain-1".to_string()],
"expected a Ctrl-C interrupt (SIGTERM fallback) before the pane is reclaimed"
);
assert_eq!(
*fake.kill_calls.lock().unwrap(),
vec!["tm-drain-1".to_string()],
"expected the pane to be reclaimed after the grace window"
);
}
#[tokio::test]
async fn graceful_terminate_runtime_noop_when_session_gone() {
let dir = TempDir::new().unwrap();
let fake = FakeTmuxDriver::new();
let mgr = SessionManager::new(dir.path(), fake.clone()).await.unwrap();
mgr.graceful_terminate_runtime("tm-already-gone").await;
assert!(
fake.interrupt_calls.lock().unwrap().is_empty(),
"must not signal a session that no longer exists"
);
assert!(
fake.kill_calls.lock().unwrap().is_empty(),
"must not kill a session that no longer exists"
);
}
#[cfg(unix)]
struct DefaultGsDriver {
kill_calls: Mutex<Vec<String>>,
}
#[cfg(unix)]
impl ManagedTmuxDriver for DefaultGsDriver {
fn create_session(&self, _name: &str, _workdir: &str) -> Result<(), ManagedError> {
Ok(())
}
fn kill_session(&self, name: &str) -> Result<(), ManagedError> {
self.kill_calls.lock().unwrap().push(name.to_owned());
Ok(())
}
fn send_line(&self, _name: &str, _text: &str) -> Result<(), ManagedError> {
Ok(())
}
fn capture(&self, _name: &str, _lines: usize) -> Result<String, ManagedError> {
Ok(String::new())
}
fn list_sessions(&self) -> Result<Vec<String>, ManagedError> {
Ok(Vec::new())
}
}
#[cfg(unix)]
#[test]
fn default_graceful_stop_sends_sigterm() {
use std::process::{Child, Command};
use std::time::{Duration, Instant};
struct ChildGuard(Child);
impl Drop for ChildGuard {
fn drop(&mut self) {
let _ = self.0.kill();
let _ = self.0.wait();
}
}
let child = Command::new("sleep")
.arg("30")
.spawn()
.expect("spawn sleep child");
let pid = child.id();
let mut guard = ChildGuard(child);
let driver = DefaultGsDriver {
kill_calls: Mutex::new(Vec::new()),
};
driver
.graceful_stop("tmpm-default-gs", Some(pid))
.expect("graceful_stop");
assert!(
driver
.kill_calls
.lock()
.unwrap()
.iter()
.any(|n| n == "tmpm-default-gs"),
"default graceful_stop must call kill_session after signalling"
);
let deadline = Instant::now() + Duration::from_secs(5);
let exited = loop {
match guard.0.try_wait().expect("try_wait") {
Some(_status) => break true,
None if Instant::now() >= deadline => break false,
None => std::thread::sleep(Duration::from_millis(20)),
}
};
assert!(
exited,
"default graceful_stop must deliver SIGTERM — the sleep child should exit"
);
}