ci / rust (push) Successful in 2m8s
New `crates/maestro-e2e` exercises the full headless stack — registry, two daemons, real HTTP+WS — as actual subprocesses. Harness pieces: - `Registry::spawn` and `Daemon::spawn` launch the real built binaries with `env_clear()` plus an isolated `$HOME`, `XDG_CONFIG_HOME`, `XDG_STATE_HOME`, and (for daemons) `TMUX_TMPDIR`. Drop guards kill the child and clean the temp dirs. - `Daemon::stop` + `Daemon::restart` swap the process without losing the state dirs, so the recovery test exercises a real tmux re-adoption. - A `build::workspace_target_bin` helper re-invokes cargo from the test process to build `maestrod` / `maestro-registry` on demand (cargo only ships libs as path-dep of e2e by default). - `TestClient` wraps reqwest + tokio-tungstenite for the public HTTP+WS surface — list/spawn/kill/connect-info/attach + events WS. Scenarios covered (`tests/e2e.rs`): - list sessions from two daemons through one registry - multi-attach: two WS clients see the same PTY output - worktree with `cleanup_on_kill: true` is removed after kill - daemon restart: catalog drops then repopulates from `recover()` - concurrent spawns all get distinct ids All five pass in ~0.7s in parallel; cleanup confirmed (no leftover processes or temp dirs).
32 lines
1.1 KiB
Rust
32 lines
1.1 KiB
Rust
//! Helpers shared between harness modules and tests.
|
|
|
|
use std::future::Future;
|
|
use std::net::{Ipv4Addr, SocketAddr, TcpListener};
|
|
use std::time::{Duration, Instant};
|
|
|
|
/// Pick a free TCP port by binding to `127.0.0.1:0` and immediately releasing.
|
|
///
|
|
/// There is a small race: another process may grab the port between drop and
|
|
/// the child re-binding, but for local single-user test runs it's negligible.
|
|
pub fn free_port() -> u16 {
|
|
let l = TcpListener::bind(SocketAddr::from((Ipv4Addr::LOCALHOST, 0))).expect("bind :0");
|
|
l.local_addr().expect("local_addr").port()
|
|
}
|
|
|
|
/// Poll `cond` every 50ms until it returns `true` or `timeout` elapses.
|
|
/// Panics on timeout with `label`.
|
|
pub async fn wait_for_async<F, Fut>(label: &str, timeout: Duration, mut cond: F)
|
|
where
|
|
F: FnMut() -> Fut,
|
|
Fut: Future<Output = bool>,
|
|
{
|
|
let deadline = Instant::now() + timeout;
|
|
while Instant::now() < deadline {
|
|
if cond().await {
|
|
return;
|
|
}
|
|
tokio::time::sleep(Duration::from_millis(50)).await;
|
|
}
|
|
panic!("timed out after {timeout:?} waiting for: {label}");
|
|
}
|