#![cfg(windows)]
use std::net::TcpStream;
use std::time::{Duration, Instant};
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Command;
fn wait_for(port: u16, want_connectable: bool, timeout: Duration) -> bool {
let deadline = Instant::now() + timeout;
loop {
let connected = TcpStream::connect(("127.0.0.1", port)).is_ok();
if connected == want_connectable {
return true;
}
if Instant::now() >= deadline {
return false;
}
std::thread::sleep(Duration::from_millis(50));
}
}
#[tokio::test]
async fn a_hard_killed_parents_replacement_is_also_killed_by_the_os() {
let parent_exe = env!("CARGO_BIN_EXE_supervisor_parent_fixture");
let replacement_exe = env!("CARGO_BIN_EXE_zero_downtime_fixture");
let app_name = format!("supervisor_test_{}", std::process::id());
let mut parent = Command::new(parent_exe)
.arg(replacement_exe)
.env("APP_NAME", &app_name)
.stderr(std::process::Stdio::piped())
.spawn()
.expect("failed to spawn supervisor_parent_fixture");
let stderr = parent.stderr.take().expect("stderr was piped");
let mut lines = BufReader::new(stderr).lines();
let ready_line = tokio::time::timeout(Duration::from_secs(10), lines.next_line())
.await
.expect("timed out waiting for the parent fixture's READY line")
.expect("reading the parent fixture's stderr failed")
.expect("parent fixture should print a READY line before blocking forever");
let port: u16 = ready_line
.split("port=")
.nth(1)
.and_then(|rest| rest.split_whitespace().next())
.and_then(|s| s.parse().ok())
.unwrap_or_else(|| panic!("couldn't parse port from: {ready_line}"));
assert!(
wait_for(port, true, Duration::from_secs(5)),
"the replacement should be reachable before the parent is ever touched"
);
parent.kill().await.expect("failed to kill parent fixture");
let _ = parent.wait().await;
assert!(
wait_for(port, false, Duration::from_secs(10)),
"the replacement should have been killed by the OS once its supervising parent was \
hard-killed, but it's still accepting connections on port {port}"
);
}