use std::net::{SocketAddr, TcpListener, TcpStream};
use std::process::Command;
use std::thread;
use std::time::{Duration, Instant};
use pijul_srv_lite::server::{ServerConfig, resolve_repo_path, serve};
use anyhow::{Context, Result, bail};
use crate::support::RepoFixture;
pub struct TestServer {
port: u16,
_repo: RepoFixture,
}
const BIND_ADDR: &str = "127.0.0.1";
impl TestServer {
pub fn start(repo: RepoFixture) -> Result<Self> {
let repo_path = resolve_repo_path(repo.repo_root().to_path_buf())?;
// Binds to port 0, which asks OS for port
let listener =
TcpListener::bind("127.0.0.1:0").context("failed to bind ephemeral test listener")?;
let port = listener
.local_addr()
.context("failed to read ephemeral listener address")?
.port();
thread::spawn(move || {
if let Err(err) = serve(listener, ServerConfig { repo_path }) {
eprintln!("Server exited with error: {err:#}");
}
});
wait_until_ready(port).context("server did not become ready")?;
Ok(Self { port, _repo: repo })
}
pub fn base_url(&self) -> String {
format!("http://{BIND_ADDR}:{}", self.port)
}
pub fn repo_root(&self) -> &std::path::Path {
self._repo.repo_root()
}
pub fn apply_repo_env(&self, cmd: &mut Command) {
self._repo.apply_env(cmd);
}
}
fn wait_until_ready(port: u16) -> Result<()> {
let addr: SocketAddr = ([127, 0, 0, 1], port).into();
let deadline = Instant::now() + Duration::from_secs(5);
loop {
if TcpStream::connect_timeout(&addr, Duration::from_millis(50)).is_ok() {
return Ok(());
}
if Instant::now() >= deadline {
bail!("timed out waiting for server to listen on {addr}",);
}
std::thread::sleep(Duration::from_millis(20));
}
}