use glide::{GlideClient, GlideClientConfiguration, ProtocolVersion};
use std::net::{TcpListener, TcpStream};
use std::process::{Child, Command, Stdio};
use std::time::{Duration, Instant};
pub fn server_binary() -> Option<String> {
if let Ok(p) = std::env::var("VALKEY_SERVER_PATH")
&& std::path::Path::new(&p).exists()
{
return Some(p);
}
for name in ["valkey-server", "redis-server"] {
if let Ok(output) = Command::new("which").arg(name).output()
&& output.status.success()
{
let p = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !p.is_empty() {
return Some(p);
}
}
}
None
}
pub fn free_port() -> u16 {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind ephemeral port");
listener.local_addr().unwrap().port()
}
pub(crate) fn wait_for_port(port: u16, timeout: Duration) -> bool {
let deadline = Instant::now() + timeout;
while Instant::now() < deadline {
if TcpStream::connect(("127.0.0.1", port)).is_ok() {
return true;
}
std::thread::sleep(Duration::from_millis(30));
}
false
}
pub struct TestServer {
child: Child,
pub port: u16,
}
impl TestServer {
pub fn start() -> Option<TestServer> {
Self::start_with_args(&[])
}
pub fn start_with_args(extra: &[&str]) -> Option<TestServer> {
let bin = server_binary()?;
let port = free_port();
let mut args: Vec<String> = vec![
"--port".into(),
port.to_string(),
"--bind".into(),
"127.0.0.1".into(),
"--save".into(),
"".into(),
"--appendonly".into(),
"no".into(),
"--daemonize".into(),
"no".into(),
];
args.extend(extra.iter().map(|s| s.to_string()));
let child = Command::new(&bin)
.args(&args)
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.ok()?;
let mut server = TestServer { child, port };
if wait_for_port(port, Duration::from_secs(10)) {
std::thread::sleep(Duration::from_millis(100));
Some(server)
} else {
let _ = server.child.kill();
None
}
}
pub async fn client(&self) -> GlideClient {
self.client_with_protocol(ProtocolVersion::RESP3).await
}
pub async fn client_with_protocol(&self, protocol: ProtocolVersion) -> GlideClient {
let config = GlideClientConfiguration::with_address("127.0.0.1", self.port)
.protocol(protocol)
.connection_timeout(Duration::from_secs(10))
.request_timeout(Duration::from_secs(10));
connect_standalone_with_retry(config).await
}
pub async fn try_connect(
&self,
config: GlideClientConfiguration,
) -> glide::Result<GlideClient> {
GlideClient::connect(config).await
}
}
impl Drop for TestServer {
fn drop(&mut self) {
let _ = self.child.kill();
let _ = self.child.wait();
}
}
async fn connect_standalone_with_retry(config: GlideClientConfiguration) -> GlideClient {
let mut last_err = None;
for attempt in 0..10u32 {
match GlideClient::connect(config.clone()).await {
Ok(c) => return c,
Err(e) => {
last_err = Some(e);
tokio::time::sleep(Duration::from_millis(100 * (attempt + 1) as u64)).await;
}
}
}
panic!("connect to test server failed after retries: {last_err:?}");
}