mod common;
use std::io::{BufRead, BufReader};
use std::net::TcpListener;
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
use tempfile::tempdir;
const SALVOR_BIN: &str = env!("CARGO_BIN_EXE_salvor");
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn kill_by_pid_stops_a_real_serve_process_and_frees_the_port() {
let dir = tempdir().expect("tempdir");
let store_path = dir.path().join("salvor.db");
let mut child = Command::new(SALVOR_BIN)
.args(["--store", store_path.to_str().unwrap()])
.args(["serve", "--bind", "127.0.0.1:0"])
.env("RUST_LOG", "off")
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.expect("spawn salvor serve");
let pid = child.id();
let stdout = child.stdout.take().expect("piped stdout");
let listening_line = tokio::task::spawn_blocking(move || {
let mut reader = BufReader::new(stdout);
let mut line = String::new();
reader
.read_line(&mut line)
.expect("read the listening line");
line
})
.await
.expect("blocking read joins");
let addr = listening_line
.trim()
.strip_prefix("salvor control plane listening on http://")
.unwrap_or_else(|| panic!("unexpected serve banner: {listening_line:?}"))
.to_owned();
let pid_arg = pid.to_string();
let kill = tokio::task::spawn_blocking(move || {
Command::new(SALVOR_BIN)
.args(["serve", "--kill", &pid_arg])
.env("RUST_LOG", "off")
.output()
.expect("run salvor serve --kill")
})
.await
.expect("blocking task joins");
assert!(
kill.status.success(),
"salvor serve --kill exits 0: {kill:?}"
);
let kill_stdout = String::from_utf8_lossy(&kill.stdout);
assert!(
kill_stdout.contains(&format!("killed pid {pid}")),
"kill output names the pid: {kill_stdout}"
);
assert!(
kill_stdout.contains("bind 127.0.0.1:0"),
"kill output names the argv bind: {kill_stdout}"
);
let deadline = Instant::now() + Duration::from_secs(5);
loop {
if child.try_wait().expect("try_wait").is_some() {
break;
}
assert!(
Instant::now() < deadline,
"the killed salvor serve process did not exit in time"
);
tokio::time::sleep(Duration::from_millis(50)).await;
}
let rebound = TcpListener::bind(&addr);
assert!(
rebound.is_ok(),
"port {addr} should be free after the kill: {rebound:?}"
);
}