Skip to content

Commit

Permalink
kinda builder pattern + bring back old signature
Browse files Browse the repository at this point in the history
  • Loading branch information
bragov4ik committed Feb 20, 2025
1 parent 3908e8b commit a0a770e
Showing 1 changed file with 81 additions and 44 deletions.
125 changes: 81 additions & 44 deletions libs/blockscout-service-launcher/src/test_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use reqwest::Url;
use std::{
future::Future,
net::{SocketAddr, TcpListener},
pin::Pin,
str::FromStr,
time::Duration,
};
Expand All @@ -22,62 +23,98 @@ pub fn get_test_server_settings() -> (ServerSettings, Url) {
(server, base)
}

/// `check_health_response` - additional logic to verify if healthcheck
/// was successful. `true` - success
pub async fn init_server<F, R, FCheck, RCheck>(
run: F,
base: &Url,
healthcheck_timeout: Option<Duration>,
check_health_response: Option<FCheck>,
) -> JoinHandle<Result<(), anyhow::Error>>
pub fn health_always_valid(_: reqwest::Response) -> DefaultRCheck {
Box::pin(async { true })
}

pub struct TestServerSettings<RCheck, FCheckHealth = fn(reqwest::Response) -> RCheck>
where
F: FnOnce() -> R + Send + 'static,
R: Future<Output = Result<(), anyhow::Error>> + Send,
FCheck: Fn(reqwest::Response) -> RCheck + Send + 'static,
RCheck: Future<Output = bool> + Send,
FCheckHealth: Fn(reqwest::Response) -> RCheck + Send + 'static,
{
let server_handle = tokio::spawn(async move { run().await });

let client = reqwest::Client::new();
let health_endpoint = base.join("health").unwrap();

let wait_health_check = async {
loop {
if let Ok(response) = client
.request(reqwest::Method::GET, health_endpoint.clone())
.query(&[("service", "")])
.send()
.await
{
if response.status() == reqwest::StatusCode::OK {
if let Some(check_health_response) = &check_health_response {
if check_health_response(response).await {
healthcheck_timeout: Duration,
/// additional logic to verify if healthcheck
/// was successful. `true` - success
check_health_response: Option<FCheckHealth>,
base: Url,
}

type DefaultRCheck = Pin<Box<dyn Future<Output = bool> + Send>>;

impl TestServerSettings<DefaultRCheck, fn(reqwest::Response) -> DefaultRCheck> {
pub fn new(base: Url) -> Self {
Self {
healthcheck_timeout: Duration::from_secs(15),
check_health_response: None,
base,
}
}
}

impl<RCheck, FCheckHealth> TestServerSettings<RCheck, FCheckHealth>
where
RCheck: Future<Output = bool> + Send,
FCheckHealth: Fn(reqwest::Response) -> RCheck + Send + 'static,
{
pub async fn init<F, R>(self, run: F) -> JoinHandle<Result<(), anyhow::Error>>
where
F: FnOnce() -> R + Send + 'static,
R: Future<Output = Result<(), anyhow::Error>> + Send,
{
let server_handle = tokio::spawn(async move { run().await });

let client = reqwest::Client::new();
let health_endpoint = self.base.join("health").unwrap();

let wait_health_check = async {
loop {
if let Ok(response) = client
.request(reqwest::Method::GET, health_endpoint.clone())
.query(&[("service", "")])
.send()
.await
{
if response.status() == reqwest::StatusCode::OK {
if let Some(check_health_response) = &self.check_health_response {
if check_health_response(response).await {
break;
}
} else {
break;
}
} else {
break;
}
}
}
}
};
// Wait for the server to start
let timeout_duration = healthcheck_timeout.unwrap_or(Duration::from_secs(15));
if (timeout(timeout_duration, wait_health_check).await).is_err() {
match timeout(Duration::from_secs(1), server_handle).await {
Ok(Ok(result)) => {
panic!("Server terminated with: {result:?}")
}
Ok(Err(_)) => {
panic!("Server start terminated with exit error")
}
Err(_) => {
panic!("Server did not start in time, and did not terminate");
};
// Wait for the server to start
if (timeout(self.healthcheck_timeout, wait_health_check).await).is_err() {
match timeout(Duration::from_secs(1), server_handle).await {
Ok(Ok(result)) => {
panic!("Server terminated with: {result:?}")
}
Ok(Err(_)) => {
panic!("Server start terminated with exit error")
}
Err(_) => {
panic!("Server did not start in time, and did not terminate");
}
}
}

server_handle
}
}

server_handle
/// Use [`TestServerSettings`] for more configurable interface
pub async fn init_server<F, R, FCheck, RCheck>(
run: F,
base: &Url,
) -> JoinHandle<Result<(), anyhow::Error>>
where
F: FnOnce() -> R + Send + 'static,
R: Future<Output = Result<(), anyhow::Error>> + Send,
{
TestServerSettings::new(base.clone()).init(run).await
}

async fn send_annotated_request<Response: for<'a> serde::Deserialize<'a>>(
Expand Down

0 comments on commit a0a770e

Please sign in to comment.