use oxia::{OxiaClient, OxiaClientBuilder};
use std::time::Duration;
use testcontainers::core::ports::ContainerPort;
use testcontainers::core::wait::WaitFor;
use testcontainers::core::{CmdWaitFor, ExecCommand};
use testcontainers::runners::AsyncRunner;
use testcontainers::{ContainerAsync, GenericImage, ImageExt};
const OXIA_PORT: u16 = 6648;
const DEFAULT_OXIA_IMAGE: &str = "oxia/oxia";
const DEFAULT_OXIA_TAG: &str = "main";
const SHARDS: u32 = 10;
const KEY_COUNT: usize = 24;
async fn start_oxia_multishard() -> (ContainerAsync<GenericImage>, String) {
let image = std::env::var("OXIA_IMAGE").unwrap_or_else(|_| DEFAULT_OXIA_IMAGE.to_string());
let tag = std::env::var("OXIA_TAG").unwrap_or_else(|_| DEFAULT_OXIA_TAG.to_string());
let container = GenericImage::new(image, tag)
.with_exposed_port(ContainerPort::Tcp(OXIA_PORT))
.with_wait_for(WaitFor::message_on_stdout("Started Grpc server"))
.with_cmd(vec![
"oxia".to_string(),
"standalone".to_string(),
"--shards".to_string(),
SHARDS.to_string(),
])
.start()
.await
.expect("Failed to start Oxia container");
let host_port = container
.get_host_port_ipv4(OXIA_PORT)
.await
.expect("Failed to get host port");
(container, format!("http://127.0.0.1:{}", host_port))
}
async fn new_client(address: &str) -> OxiaClient {
OxiaClientBuilder::new()
.service_address(address.to_string())
.request_timeout(Duration::from_secs(10))
.build()
.await
.unwrap()
}
async fn oxia_cli(container: &ContainerAsync<GenericImage>, args: &[&str]) -> String {
let mut cmd: Vec<String> = vec!["oxia".to_string(), "client".to_string()];
cmd.extend(args.iter().map(|s| s.to_string()));
let mut result = container
.exec(ExecCommand::new(cmd).with_cmd_ready_condition(CmdWaitFor::exit_code(0)))
.await
.unwrap_or_else(|e| panic!("`oxia client {}` failed: {e}", args.join(" ")));
let out = result.stdout_to_vec().await.expect("read exec stdout");
String::from_utf8(out)
.expect("exec stdout is not utf-8")
.trim_end()
.to_string()
}
#[tokio::test]
async fn rust_writes_go_reads_across_shards() {
let (container, address) = start_oxia_multishard().await;
let client = new_client(&address).await;
for i in 0..KEY_COUNT {
let key = format!("interop/rust-{i}");
let value = format!("rust-value-{i}");
client
.put(key, value.into_bytes())
.await
.expect("rust put failed");
}
for i in 0..KEY_COUNT {
let key = format!("interop/rust-{i}");
let expected = format!("rust-value-{i}");
let got = oxia_cli(&container, &["get", &key]).await;
assert_eq!(got, expected, "Go CLI read the wrong value for {key}");
}
client.close().await.unwrap();
}
#[tokio::test]
async fn go_writes_rust_reads_across_shards() {
let (container, address) = start_oxia_multishard().await;
let client = new_client(&address).await;
for i in 0..KEY_COUNT {
let key = format!("interop/go-{i}");
let value = format!("go-value-{i}");
oxia_cli(&container, &["put", &key, &value]).await;
}
for i in 0..KEY_COUNT {
let key = format!("interop/go-{i}");
let expected = format!("go-value-{i}");
let got = client.get(key.clone()).await.expect("rust get failed");
assert_eq!(
got.value.as_deref(),
Some(expected.as_bytes()),
"Rust read the wrong value for {key}"
);
}
client.close().await.unwrap();
}