sim/tests/cluster.rs

87 lines
2.9 KiB
Rust

//! Runs the real binary as a gateway with two shard processes and checks that the UI sees one
//! world spanning both shards.
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::process::{Child, Command, Stdio};
use std::time::{Duration, Instant};
struct Gateway(Child);
impl Drop for Gateway {
fn drop(&mut self) {
let _ = self.0.kill();
let _ = self.0.wait();
}
}
fn get(address: &str, path: &str) -> Option<serde_json::Value> {
let mut stream = TcpStream::connect(address).ok()?;
stream.set_read_timeout(Some(Duration::from_secs(5))).ok()?;
write!(
stream,
"GET {path} HTTP/1.1\r\nHost: {address}\r\nConnection: close\r\n\r\n"
)
.ok()?;
let mut response = String::new();
stream.read_to_string(&mut response).ok()?;
let (_, body) = response.split_once("\r\n\r\n")?;
serde_json::from_str(body).ok()
}
#[test]
fn gateway_presents_the_shards_as_one_world() {
let address = {
let probe = TcpListener::bind("127.0.0.1:0").unwrap();
probe.local_addr().unwrap().to_string()
};
let directory = std::env::temp_dir().join(format!("sim-cluster-test-{}", std::process::id()));
std::fs::create_dir_all(&directory).unwrap();
let database = format!(
"sqlite://{}/shard-{{shard}}.db?mode=rwc",
directory.display()
);
let _gateway = Gateway(
Command::new(env!("CARGO_BIN_EXE_sim"))
.arg("5")
.env("SIM_SHARDS", "2")
.env("SIM_ADDR", &address)
.env("SHARD_DATABASE_URL", &database)
.env(
"SIM_CONFIG",
concat!(env!("CARGO_MANIFEST_DIR"), "/config.rn"),
)
.env("RUST_LOG", "warn")
.stdout(Stdio::null())
.spawn()
.unwrap(),
);
let deadline = Instant::now() + Duration::from_secs(120);
let mut first_tick = None;
loop {
assert!(Instant::now() < deadline, "the cluster never got going");
std::thread::sleep(Duration::from_millis(500));
let Some(state) = get(&address, "/api/state") else {
continue;
};
let Some(tick) = state["tick"].as_u64() else {
continue;
};
let start = *first_tick.get_or_insert(tick);
let shards = state["shards"].as_array().unwrap();
assert_eq!(shards.len(), 2);
let border = shards[1][0].as_u64().unwrap();
let people = state["people"].as_array().unwrap();
let west = people.iter().any(|p| p[1].as_u64() < Some(border));
let east = people.iter().any(|p| p[1].as_u64() >= Some(border));
if tick > start + 5 && west && east {
assert_eq!(state["homes"].as_array().unwrap().len(), 2);
break;
}
}
let terrain = get(&address, "/api/terrain").unwrap();
assert!(terrain["w"].as_u64().unwrap() > 0);
let _ = std::fs::remove_dir_all(&directory);
}