use std::net::SocketAddr;
use std::sync::mpsc::{Receiver, channel};
use std::time::Duration;
use camber::http::{HostRouter, Router};
use camber::{JoinHandle, RuntimeError, http, runtime, spawn};
use super::http::wait_for_http_response;
const READINESS_BOUND: Duration = Duration::from_secs(5);
const EXIT_REPORT_BOUND: Duration = Duration::from_millis(100);
pub fn test_runtime() -> runtime::RuntimeBuilder {
runtime::builder()
.keepalive_timeout(Duration::from_millis(100))
.shutdown_timeout(Duration::from_secs(1))
}
pub fn block_on<F: std::future::Future>(future: F) -> F::Output {
tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(future))
}
fn spawn_bound(
serve: impl FnOnce(camber::net::Listener) -> Result<(), RuntimeError> + Send + 'static,
) -> SocketAddr {
let listener = camber::net::listen("127.0.0.1:0").unwrap();
let local_addr = listener.local_addr().unwrap().tcp().unwrap();
let (exited_tx, exited) = channel();
let served = spawn(move || -> Result<(), RuntimeError> {
let outcome = serve(listener);
let _ = exited_tx.send(());
outcome
});
match wait_for_http_response(local_addr, READINESS_BOUND) {
Ok(_) => local_addr,
Err(unready) => panic!("{}", unready_cause(served, &exited, &unready)),
}
}
fn unready_cause(
served: JoinHandle<Result<(), RuntimeError>>,
exited: &Receiver<()>,
unready: &std::io::Error,
) -> String {
match exited.recv_timeout(EXIT_REPORT_BOUND) {
Ok(()) => format!(
"the fixture server never answered within {READINESS_BOUND:?}: {unready}; \
serving had already returned {:?}",
served.join()
),
Err(_) => format!(
"the fixture server never answered within {READINESS_BOUND:?}: {unready}; \
the serve call has not returned within {EXIT_REPORT_BOUND:?}, so it \
either never started or is still running"
),
}
}
pub fn spawn_server(router: Router) -> SocketAddr {
spawn_bound(move |listener| http::serve_listener(listener, router))
}
pub fn spawn_host_server(hosts: HostRouter) -> SocketAddr {
spawn_bound(move |listener| http::serve_hosts(listener, hosts))
}