use std::io::{BufRead, BufReader};
use std::net::TcpListener;
use std::process::{Child, Command, Stdio};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::{Duration, Instant};
static TEST_COUNTER: AtomicUsize = AtomicUsize::new(0);
pub struct TestServer {
pub url: String,
pub data_dir: String,
child: Option<Child>,
}
impl TestServer {
pub fn spawn() -> Self {
let port = pick_port();
let unique = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
let pid = std::process::id();
let data_dir = format!("/tmp/microresolve_test_{}_{}_{}", pid, unique, port);
let _ = std::fs::remove_dir_all(&data_dir);
let bin = if std::path::Path::new("./target/release/server").exists() {
"./target/release/server"
} else {
"./target/debug/server"
};
let child = Command::new(bin)
.args([
"--port",
&port.to_string(),
"--no-open",
"--data",
&data_dir,
])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect(
"failed to spawn server (build with `cargo build --release --features server`)",
);
let url = format!("http://localhost:{}", port);
wait_for_ready(&url, Duration::from_secs(10));
Self {
url,
data_dir,
child: Some(child),
}
}
pub fn client(&self) -> reqwest::blocking::Client {
reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.build()
.unwrap()
}
}
impl Drop for TestServer {
fn drop(&mut self) {
if let Some(mut child) = self.child.take() {
let _ = child.kill();
let _ = child.wait();
}
let _ = std::fs::remove_dir_all(&self.data_dir);
}
}
fn pick_port() -> u16 {
let listener = TcpListener::bind("127.0.0.1:0").expect("failed to find free port");
let port = listener.local_addr().unwrap().port();
drop(listener);
port
}
fn wait_for_ready(url: &str, timeout: Duration) {
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_millis(500))
.build()
.unwrap();
let deadline = Instant::now() + timeout;
while Instant::now() < deadline {
if let Ok(resp) = client.get(format!("{}/api/health", url)).send() {
if resp.status().is_success() {
return;
}
}
std::thread::sleep(Duration::from_millis(100));
}
panic!(
"server at {} did not become ready within {:?}",
url, timeout
);
}
pub fn post_json<T: serde::Serialize>(
client: &reqwest::blocking::Client,
url: &str,
headers: &[(&str, &str)],
body: &T,
) -> (u16, String) {
let mut req = client.post(url).json(body);
for (k, v) in headers {
req = req.header(*k, *v);
}
let resp = req.send().expect("request failed");
(resp.status().as_u16(), resp.text().unwrap_or_default())
}
pub fn get(
client: &reqwest::blocking::Client,
url: &str,
headers: &[(&str, &str)],
) -> (u16, String) {
let mut req = client.get(url);
for (k, v) in headers {
req = req.header(*k, *v);
}
let resp = req.send().expect("request failed");
(resp.status().as_u16(), resp.text().unwrap_or_default())
}
pub fn delete_json<T: serde::Serialize>(
client: &reqwest::blocking::Client,
url: &str,
headers: &[(&str, &str)],
body: &T,
) -> u16 {
let mut req = client.delete(url).json(body);
for (k, v) in headers {
req = req.header(*k, *v);
}
let resp = req.send().expect("request failed");
resp.status().as_u16()
}
pub fn patch_json<T: serde::Serialize>(
client: &reqwest::blocking::Client,
url: &str,
headers: &[(&str, &str)],
body: &T,
) -> u16 {
let mut req = client.patch(url).json(body);
for (k, v) in headers {
req = req.header(*k, *v);
}
let resp = req.send().expect("request failed");
resp.status().as_u16()
}
#[allow(dead_code)]
fn _unused() {
let _ = BufReader::new(std::io::empty()).lines();
}