Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153#![allow(clippy::unwrap_used, clippy::expect_used, dead_code)]
//! A BEAM node that lives as long as the test using it.
use beamdev::connection::ConnectionManager;use std::process::{Child, Command, Stdio};use std::sync::Arc;use std::sync::atomic::{AtomicU32, Ordering};use std::time::{Duration, Instant};
/// How long to wait for a freshly spawned node to accept a connection.const BOOT_TIMEOUT: Duration = Duration::from_secs(30);
pub const COOKIE: &str = "beamdev_test";
/// Which runtime starts the node. Both are BEAM nodes; only Elixir has the/// Elixir standard library loaded.#[derive(Debug, Clone, Copy)]pub enum Runtime { Erlang, Elixir,}
impl Runtime { fn command(&self, node: &str) -> Command { let mut command = match self { Runtime::Erlang => { let mut c = Command::new("erl"); c.args(["-sname", node, "-setcookie", COOKIE, "-noshell", "-noinput"]); c } Runtime::Elixir => { let mut c = Command::new("elixir"); c.args(["--sname", node, "--cookie", COOKIE, "--no-halt", "-e", ""]); c } }; command .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::piped()); command }
fn binary(&self) -> &'static str { match self { Runtime::Erlang => "erl", Runtime::Elixir => "elixir", } }}
/// A node started for one test, and the manager bound to it.////// Both die when the test drops this: holding the manager means holding the/// node, so a test cannot outlive the BEAM it is talking to.pub struct TestNode { child: Child, pub name: String, manager: Arc<ConnectionManager>,}
impl Drop for TestNode { fn drop(&mut self) { let _ = self.child.kill(); let _ = self.child.wait(); }}
/// The short hostname `-sname` will graft onto the node name.fn short_hostname() -> String { let out = Command::new("hostname") .output() .expect("`hostname` should be available to work out the node name"); String::from_utf8_lossy(&out.stdout) .trim() .split('.') .next() .expect("hostname should not be empty") .to_string()}
/// A name no other test, and no other run on this machine, is using.fn unique(prefix: &str) -> String { static COUNTER: AtomicU32 = AtomicU32::new(0); format!( "{}_{}_{}", prefix, std::process::id(), COUNTER.fetch_add(1, Ordering::Relaxed) )}
impl TestNode { /// Starts a node, waits until it answers, and connects to it. /// /// Each test connects under its own local node name, so tests can run in /// parallel: a node rejects concurrent handshakes from the same peer name. pub async fn start(runtime: Runtime) -> Self { let short = unique("beamdev_test"); let name = format!("{}@{}", short, short_hostname());
let child = runtime.command(&short).spawn().unwrap_or_else(|e| { panic!( "could not start `{}`: {}. These tests need a BEAM runtime on PATH; \ `nix develop` provides one.", runtime.binary(), e ) });
let manager = Arc::new(ConnectionManager::new( format!("{}@{}", unique("mcp"), short_hostname()), name.clone(), ));
let node = Self { child, name, manager, }; node.await_connection().await;
node }
/// The manager connected to this node. pub fn manager(&self) -> &ConnectionManager { &self.manager }
/// The manager, for a test that needs to move it into a task. pub fn shared(&self) -> Arc<ConnectionManager> { Arc::clone(&self.manager) }
/// Retries until the node has finished booting and registered with epmd. async fn await_connection(&self) { let deadline = Instant::now() + BOOT_TIMEOUT; let mut last = String::new();
while Instant::now() < deadline { match self.manager.connect(COOKIE).await { Ok(()) => return, Err(e) => last = e.to_string(), } tokio::time::sleep(Duration::from_millis(200)).await; }
panic!("{} never accepted a connection: {}", self.name, last); }}