ci / rust (push) Failing after 3s
Local primitives for `maestrod`, no network code yet: - `tmux`: subprocess wrapper around new-session, kill-session, list-sessions (filtered to maestro-*), send-keys -l, pipe-pane -o, resize-window, kill-server. - `sidecar`: atomic JSON metadata store keyed by session UUID, with load_all for daemon restart recovery. - `worktree`: `git worktree add -b` with collision-proof generated paths and `git worktree remove --force` for cleanup_on_kill. Integration tests use real tmux (isolated via unique socket + temp TMUX_TMPDIR) and real git (tmpdir repo). Closes NAP-212.
129 lines
3.5 KiB
Rust
129 lines
3.5 KiB
Rust
//! Integration tests for the tmux wrapper against a real `tmux` binary.
|
|
//!
|
|
//! Each test spins up an isolated tmux server (unique socket name + temp
|
|
//! `TMUX_TMPDIR`) so they can run in parallel and never touch the user's
|
|
//! actual tmux session.
|
|
|
|
mod common;
|
|
|
|
use std::process::Command;
|
|
use std::time::Duration;
|
|
|
|
use common::{wait_for, TmuxFixture};
|
|
use tempfile::TempDir;
|
|
use uuid::Uuid;
|
|
|
|
#[test]
|
|
fn create_list_kill_round_trip() {
|
|
let fx = TmuxFixture::new();
|
|
let cwd = TempDir::new().unwrap();
|
|
let id = Uuid::new_v4();
|
|
|
|
assert!(fx.tmux.list_sessions().unwrap().is_empty());
|
|
|
|
fx.tmux
|
|
.new_session(id, cwd.path(), "sleep 30")
|
|
.expect("new_session");
|
|
|
|
wait_for(Duration::from_secs(3), || {
|
|
fx.tmux.list_sessions().unwrap().contains(&id)
|
|
});
|
|
|
|
fx.tmux.kill_session(id).expect("kill_session");
|
|
|
|
wait_for(Duration::from_secs(3), || {
|
|
!fx.tmux.list_sessions().unwrap().contains(&id)
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn list_sessions_ignores_foreign_names() {
|
|
let fx = TmuxFixture::new();
|
|
let cwd = TempDir::new().unwrap();
|
|
let maestro_id = Uuid::new_v4();
|
|
|
|
// Drop a non-maestro session onto the same tmux server. list_sessions
|
|
// should still report only the maestro-prefixed ones.
|
|
let out = Command::new("tmux")
|
|
.arg("-L")
|
|
.arg(fx.tmux.socket_name().unwrap())
|
|
.env("TMUX_TMPDIR", fx.tmux.tmpdir().unwrap())
|
|
.args(["new-session", "-d", "-s", "foreign", "-c"])
|
|
.arg(cwd.path())
|
|
.arg("sleep 30")
|
|
.output()
|
|
.expect("raw tmux");
|
|
assert!(out.status.success(), "raw tmux failed: {out:?}");
|
|
|
|
fx.tmux
|
|
.new_session(maestro_id, cwd.path(), "sleep 30")
|
|
.expect("new_session");
|
|
|
|
wait_for(Duration::from_secs(3), || {
|
|
fx.tmux.list_sessions().unwrap() == vec![maestro_id]
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn send_keys_and_pipe_pane_capture_output() {
|
|
let fx = TmuxFixture::new();
|
|
let cwd = TempDir::new().unwrap();
|
|
let sink = TempDir::new().unwrap();
|
|
let sink_path = sink.path().join("out.log");
|
|
let id = Uuid::new_v4();
|
|
|
|
fx.tmux
|
|
.new_session(id, cwd.path(), "bash --noprofile --norc")
|
|
.expect("new_session");
|
|
|
|
wait_for(Duration::from_secs(3), || {
|
|
fx.tmux.list_sessions().unwrap().contains(&id)
|
|
});
|
|
|
|
let sink_command = format!("cat >> {}", shell_quote(&sink_path.display().to_string()));
|
|
fx.tmux.pipe_pane(id, &sink_command).expect("pipe_pane");
|
|
|
|
fx.tmux
|
|
.send_keys(id, b"echo MAESTRO_MARKER_BANG\n")
|
|
.expect("send_keys");
|
|
|
|
wait_for(Duration::from_secs(5), || {
|
|
std::fs::read_to_string(&sink_path)
|
|
.map(|s| s.contains("MAESTRO_MARKER_BANG"))
|
|
.unwrap_or(false)
|
|
});
|
|
|
|
fx.tmux.kill_session(id).expect("kill_session");
|
|
}
|
|
|
|
#[test]
|
|
fn resize_window_succeeds() {
|
|
let fx = TmuxFixture::new();
|
|
let cwd = TempDir::new().unwrap();
|
|
let id = Uuid::new_v4();
|
|
|
|
fx.tmux
|
|
.new_session(id, cwd.path(), "sleep 30")
|
|
.expect("new_session");
|
|
|
|
wait_for(Duration::from_secs(3), || {
|
|
fx.tmux.list_sessions().unwrap().contains(&id)
|
|
});
|
|
|
|
fx.tmux.resize_window(id, 120, 40).expect("resize_window");
|
|
|
|
fx.tmux.kill_session(id).expect("kill_session");
|
|
}
|
|
|
|
#[test]
|
|
fn list_sessions_returns_empty_when_no_server() {
|
|
let fx = TmuxFixture::new();
|
|
let sessions = fx.tmux.list_sessions().expect("list_sessions");
|
|
assert!(sessions.is_empty());
|
|
}
|
|
|
|
fn shell_quote(s: &str) -> String {
|
|
let escaped = s.replace('\'', r#"'\''"#);
|
|
format!("'{escaped}'")
|
|
}
|