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-daemon/tests/tmux.rs
T
naps62-yoloandClaude Opus 4.7 b1aa2e6eab
ci / rust (push) Successful in 2m59s
fix(maestro): chain default-terminal into new-session instead of pre-set
CI e2e tests failed with "tmux exited with code 1: server exited
unexpectedly" because the startup `start-server \; set-option -g
default-terminal` call left an empty server that tmux's default
`exit-empty on` killed before the first `new-session` could attach.

Move the `set-option -g default-terminal tmux-256color` into the same
tmux command as `new-session` (chained with `;`) so a single tmux
invocation creates the server, sets the option, and spawns the pane.
The pane reads `default-terminal` at creation time, so chaining before
`new-session` still applies to the very first session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 06:56:29 +00:00

168 lines
4.7 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 new_session_sets_256color_env_and_default_terminal() {
let fx = TmuxFixture::new();
let cwd = TempDir::new().unwrap();
let sink = TempDir::new().unwrap();
let sink_path = sink.path().join("env.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"printf 'TERM=%s\\nCOLORTERM=%s\\nCOLORS=%s\\n' \"$TERM\" \"$COLORTERM\" \"$(tput colors)\"\n",
)
.expect("send_keys");
wait_for(Duration::from_secs(5), || {
std::fs::read_to_string(&sink_path)
.map(|s| {
s.contains("TERM=tmux-256color")
&& s.contains("COLORTERM=truecolor")
&& s.contains("COLORS=256")
})
.unwrap_or(false)
});
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}'")
}