This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/crates/maestro-e2e/src/util.rs
T
naps62-yolo 91f252b4e2
ci / rust (push) Successful in 2m8s
feat(maestro): add maestro-e2e cross-process test harness (NAP-217)
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).
2026-05-26 07:38:19 +00:00

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}");
}