//! A throwaway `nats-server` with `JetStream`, for tests whose subject is the //! server's own behaviour: stream creation, consumer delivery order. //! //! The binary comes from [`NATS_SERVER_ENV`], which `nix/checks.nix` and //! `nix/devshell.nix` export. A test that needs it panics when it is unset //! rather than skipping, so it never shows green without having run. use std::path::PathBuf; use std::process::{Child, Command, Stdio}; use std::time::Duration; /// The variable naming the `nats-server` binary. pub(crate) const NATS_SERVER_ENV: &str = "HIVE_NATS_SERVER"; /// A running server on a loopback port, with its store in a fresh directory. /// Dropping it kills the server and removes the store. pub(crate) struct TestNats { child: Child, store: PathBuf, url: String, } impl TestNats { pub(crate) fn start() -> Self { let bin = std::env::var(NATS_SERVER_ENV).unwrap_or_else(|_| { panic!("{NATS_SERVER_ENV} is exported by nix/checks.nix and nix/devshell.nix") }); // Bound and released to learn a free port; the server binds it next. let port = std::net::TcpListener::bind("127.0.0.1:0") .and_then(|l| l.local_addr()) .expect("a free loopback port") .port(); let store = std::env::temp_dir().join(format!("test-nats-{}-{port}", std::process::id())); let child = Command::new(&bin) .args(["-a", "127.0.0.1", "-p", &port.to_string(), "-js", "-sd"]) .arg(&store) .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn() .unwrap_or_else(|e| panic!("starting {bin}: {e}")); Self { child, store, url: format!("nats://127.0.0.1:{port}"), } } /// A client connected to the server, once it accepts connections. pub(crate) async fn client(&self) -> async_nats::Client { for _ in 0..100 { if let Ok(client) = async_nats::connect(&self.url).await { return client; } tokio::time::sleep(Duration::from_millis(100)).await; } panic!("nats-server at {} never accepted a connection", self.url); } } impl Drop for TestNats { fn drop(&mut self) { let _ = self.child.kill(); let _ = self.child.wait(); let _ = std::fs::remove_dir_all(&self.store); } }