use std::time::Duration;
use vox_core::SessionError;
#[vox::service]
trait Echo {
async fn echo(&self, value: u32) -> u32;
}
#[derive(Clone)]
struct EchoService;
impl Echo for EchoService {
async fn echo(&self, value: u32) -> u32 {
value
}
}
#[cfg(unix)]
#[tokio::test]
async fn wait_for_service_retries_until_service_appears() {
let dir = tempfile::tempdir().expect("tempdir");
let sock_path = dir.path().join("echo.sock");
let addr = format!("local://{}", sock_path.display());
let server = {
let addr = addr.clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(50)).await;
vox::serve(&addr, EchoDispatcher::new(EchoService))
.await
.expect("serve");
})
};
let client: EchoClient = vox::connect(&addr)
.connect_timeout(Duration::from_millis(100))
.wait_for_service(Duration::from_secs(5))
.await
.expect("should connect once service starts");
let result = client.echo(42).await.expect("echo");
assert_eq!(result, 42);
server.abort();
}
#[tokio::test]
async fn wait_for_service_times_out_when_service_never_starts() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
.await
.expect("bind");
let addr = listener.local_addr().expect("local_addr");
drop(listener);
let result = vox::connect::<EchoClient>(format!("tcp://{addr}"))
.connect_timeout(Duration::from_millis(50))
.wait_for_service(Duration::from_millis(200))
.establish()
.await;
let err = match result {
Err(e) => e,
Ok(_) => panic!("expected failure when service never starts"),
};
assert!(
matches!(err, SessionError::Io(_) | SessionError::ConnectTimeout),
"error should be retryable kind (Io or ConnectTimeout), got: {err:?}"
);
}
#[tokio::test]
async fn wait_for_service_deadline_caps_individual_attempt() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
.await
.expect("bind");
let addr = listener.local_addr().expect("local_addr");
let server = tokio::spawn(async move {
loop {
let _ = listener.accept().await;
tokio::time::sleep(Duration::from_secs(60)).await;
}
});
let start = std::time::Instant::now();
let result = vox::connect::<EchoClient>(format!("tcp://{addr}"))
.connect_timeout(Duration::from_secs(5)) .wait_for_service(Duration::from_millis(200))
.establish()
.await;
let elapsed = start.elapsed();
server.abort();
assert!(result.is_err(), "should fail");
assert!(
elapsed < Duration::from_secs(2),
"waiting timeout must bound individual attempts; elapsed: {elapsed:?}"
);
}
#[tokio::test]
async fn wait_for_service_fails_immediately_on_protocol_error() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
.await
.expect("bind");
let addr = listener.local_addr().expect("local_addr");
let server = tokio::spawn(async move {
loop {
let (socket, _) = listener.accept().await.expect("accept");
drop(socket);
}
});
let start = std::time::Instant::now();
let result = vox::connect::<EchoClient>(format!("tcp://{addr}"))
.connect_timeout(Duration::from_millis(100))
.wait_for_service(Duration::from_secs(10)) .establish()
.await;
let elapsed = start.elapsed();
server.abort();
let err = match result {
Err(e) => e,
Ok(_) => panic!("should fail with protocol error"),
};
assert!(
elapsed < Duration::from_secs(2),
"non-retryable error should fail fast, not wait the full timeout; elapsed: {elapsed:?}"
);
assert!(
matches!(err, SessionError::Protocol(_)),
"expected SessionError::Protocol for peer that closed connection, got: {err:?}"
);
}