tests/support/ephemeral_server.rs
//! **One ephemeral `nornir-server`, for any test that needs a live wire.**
//!
//! Three test files had grown their own byte-identical `spawn_server` —
//! `data_submit_rpcs.rs`, `viz_ops_buttons.rs`, `server_scip_ingest.rs` — and every
//! one of them is `#![cfg(all(feature = "viz", feature = "server"))]`. That is not a
//! tidiness complaint, it is *why a bug lived here*: nothing in this repo ever stood
//! a server up in the configuration the fat forge actually ships
//! (`--no-default-features --features server`), so a service gated on the GUI feature
//! could go missing from the daemon and every server test stayed green. This module
//! is the shared body, and it is feature-neutral on purpose: it needs only
//! `wire-client`, which `server` already turns on.
//!
//! The workspace is registered through the redb registry BEFORE the server boots (the
//! handle is dropped so the server can take the single-writer lock), with a *path*
//! member so the fetch loop skips cloning and the workspace is served immediately.
//! The warehouse itself is created lazily — so a non-zero read-back can only have come
//! from a write this test made over the wire.
#![allow(dead_code)]
use std::io::Write as _;
use std::net::TcpListener;
use std::path::PathBuf;
use std::process::{Child, Command};
use std::time::{Duration, Instant};
pub struct Server {
child: Child,
root: PathBuf,
pub endpoint: String,
pub token: String,
pub workspace: String,
}
impl Drop for Server {
fn drop(&mut self) {
let _ = self.child.kill();
let _ = self.child.wait();
let _ = std::fs::remove_dir_all(&self.root);
}
}
fn free_port() -> u16 {
TcpListener::bind("127.0.0.1:0")
.unwrap()
.local_addr()
.unwrap()
.port()
}
/// Spawn a server serving ONE workspace named `ws`, over a warehouse that starts
/// EMPTY. `tag` only keeps parallel spawns from sharing a root/HOME.
pub fn spawn(ws: &str, tag: &str) -> Server {
let token = format!("{tag}-ephemeral-token-0123456789");
let port = free_port();
let endpoint = format!("http://127.0.0.1:{port}");
static N: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
let uniq = N.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
let root = std::env::temp_dir().join(format!("nornir-{tag}-{}-{uniq}", std::process::id()));
std::fs::create_dir_all(&root).unwrap();
let server_home = root.join("home");
let registry_root = server_home.join(".nornir").join("workspaces");
std::fs::create_dir_all(®istry_root).unwrap();
let desc = root.join("ws.toml");
let mut f = std::fs::File::create(&desc).unwrap();
writeln!(f, "[workspace]\nname = \"{ws}\"\n").unwrap();
writeln!(
f,
"[repos.\"{ws}-core\"]\npath = \"{}\"\n",
env!("CARGO_MANIFEST_DIR")
)
.unwrap();
f.flush().unwrap();
drop(f);
{
use nornir::registry::{Mode, Registry, Workspace};
let reg = Registry::open(®istry_root).expect("open registry");
reg.upsert(&Workspace::new(
ws.to_string(),
desc.to_string_lossy().into_owned(),
Mode::Monitored,
"3600s".to_string(),
None,
))
.expect("register workspace");
} // dropped → releases registry.redb before the server boots
let child = Command::new(env!("CARGO_BIN_EXE_nornir-server"))
.env("NORNIR_SERVER_ADDR", format!("127.0.0.1:{port}"))
.env("NORNIR_SERVER_TOKEN", &token)
.env("HOME", &server_home)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.expect("spawn nornir-server");
let srv = Server {
child,
root,
endpoint: endpoint.clone(),
token: token.clone(),
workspace: ws.to_string(),
};
let deadline = Instant::now() + Duration::from_secs(45);
loop {
if let Ok(names) = nornir::viz::remote::list_workspaces(&endpoint, &token) {
if names.iter().any(|s| s == ws) {
break;
}
}
assert!(
Instant::now() < deadline,
"server never served workspace `{ws}`"
);
std::thread::sleep(Duration::from_millis(300));
}
srv
}