use crate::{
Future, Result,
client::{
Client, Config, CustomTransport, ServerConfig, TransportFactory, TransportReader,
TransportWriter,
},
commands::{ConnectionCommands, StringCommands},
resp::cmd,
tests::{fake_server::FakeServer, log_try_init},
};
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
fn duplex_pair(server: FakeServer) -> (TransportReader, TransportWriter) {
let (client_side, server_side) = tokio::io::duplex(4096);
tokio::spawn(async move { server.serve(server_side).await });
let (reader, writer) = tokio::io::split(client_side);
(Box::new(reader), Box::new(writer))
}
struct DuplexTransport {
server: FakeServer,
dials: Arc<AtomicUsize>,
}
impl TransportFactory for DuplexTransport {
fn connect(&self) -> Future<'_, (TransportReader, TransportWriter)> {
let server = self.server.clone();
let dials = Arc::clone(&self.dials);
Box::pin(async move {
dials.fetch_add(1, Ordering::SeqCst);
Ok(duplex_pair(server))
})
}
}
fn duplex_config(server: FakeServer, dials: Arc<AtomicUsize>) -> Config {
Config {
server: ServerConfig::Custom(CustomTransport::new(DuplexTransport { server, dials })),
..Default::default()
}
}
#[cfg(unix)]
fn serve_on_unix_socket(name: &str, server: FakeServer) -> Result<std::path::PathBuf> {
let dir = std::env::temp_dir().join(format!("rustis-uds-{}-{name}", std::process::id()));
std::fs::create_dir_all(&dir)?;
let path = dir.join("redis.sock");
let _ = std::fs::remove_file(&path);
let listener = tokio::net::UnixListener::bind(&path)?;
tokio::spawn(async move {
while let Ok((stream, _)) = listener.accept().await {
let server = server.clone();
tokio::spawn(async move { server.serve(stream).await });
}
});
Ok(path)
}
#[tokio::test]
async fn a_custom_transport_carries_the_whole_client_stack() -> Result<()> {
log_try_init();
let config = duplex_config(
FakeServer::new().reply("PING", b"+PONG\r\n"),
Arc::new(AtomicUsize::new(0)),
);
let client = Client::connect(config).await?;
assert_eq!("PONG", client.ping::<String>(()).await?);
Ok(())
}
#[tokio::test]
async fn a_closure_is_a_transport_factory() -> Result<()> {
log_try_init();
let config = Config {
server: ServerConfig::Custom(CustomTransport::new(|| async {
Ok(duplex_pair(
FakeServer::new().reply("GET", b"$5\r\nvalue\r\n"),
))
})),
..Default::default()
};
let client = Client::connect(config).await?;
assert_eq!("value", client.get::<String>("key").await?);
Ok(())
}
#[tokio::test]
async fn the_factory_is_asked_again_on_each_reconnection() -> Result<()> {
log_try_init();
let dials = Arc::new(AtomicUsize::new(0));
let client = Client::connect(duplex_config(
FakeServer::new().reply("PING", b"+PONG\r\n"),
Arc::clone(&dials),
))
.await?;
assert_eq!(1, dials.load(Ordering::SeqCst));
assert_eq!("PONG", client.ping::<String>(()).await?);
let mut reconnections = client.on_reconnect();
client.send_and_forget(cmd("PING").kill_connection_on_read(1), None)?;
reconnections.recv().await.unwrap();
assert_eq!(2, dials.load(Ordering::SeqCst));
assert_eq!("PONG", client.ping::<String>(()).await?);
Ok(())
}
#[cfg(unix)]
#[tokio::test]
async fn a_unix_socket_reaches_the_server() -> Result<()> {
log_try_init();
let path = serve_on_unix_socket("config", FakeServer::new().reply("PING", b"+PONG\r\n"))?;
let client = Client::connect(Config {
server: ServerConfig::UnixSocket { path },
..Default::default()
})
.await?;
assert_eq!("PONG", client.ping::<String>(()).await?);
Ok(())
}
#[cfg(unix)]
#[tokio::test]
async fn a_unix_socket_is_reachable_from_its_uri() -> Result<()> {
log_try_init();
let path = serve_on_unix_socket("uri", FakeServer::new().reply("PING", b"+PONG\r\n"))?;
let client = Client::connect(format!("unix://{}", path.display())).await?;
assert_eq!("PONG", client.ping::<String>(()).await?);
Ok(())
}