use super::*;
#[test]
fn pane_command_limit_sits_below_max_canon() {
const {
assert!(MAX_PANE_COMMAND_BYTES < 1024);
assert!(MAX_PANE_COMMAND_BYTES >= 512);
}
}
#[test]
fn oversized_pane_command_is_refused() {
let line = "x".repeat(MAX_PANE_COMMAND_BYTES + 1);
let msg = refuse_oversized_pane_command(&line).expect("an over-length line must be refused");
assert!(msg.contains("MAX_CANON"), "{msg}");
assert!(
msg.contains(&(MAX_PANE_COMMAND_BYTES + 1).to_string()),
"{msg}"
);
}
#[test]
fn pane_command_at_the_limit_is_allowed() {
let line = "x".repeat(MAX_PANE_COMMAND_BYTES);
assert!(refuse_oversized_pane_command(&line).is_none());
}
#[test]
fn send_command_line_refuses_an_oversized_line() {
let line = "x".repeat(MAX_PANE_COMMAND_BYTES + 1);
let err = send_command_line(
Some("/nonexistent/definitely-not-tmux"),
&TmuxTarget::session("tm-sess"),
&line,
)
.expect_err("an over-length command must be refused");
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput, "{err}");
assert!(err.to_string().contains("#8233"), "{err}");
}
fn argv_logging_tmux(dir: &std::path::Path, exit: u8) -> (String, std::path::PathBuf) {
let log = dir.join("argv.log");
let script = format!(
"#!/bin/sh\necho \"$*\" >> '{}'\necho 'fake failure' >&2\nexit {exit}\n",
log.display()
);
(write_fake_tmux(dir, "fake-tmux-argv", &script), log)
}
#[serial_test::serial]
#[test]
fn launch_line_over_the_limit_is_refused_before_tmux_runs() {
let dir = tempfile::tempdir().unwrap();
let (bin, log) = argv_logging_tmux(dir.path(), 0);
let line = "x".repeat(MAX_PANE_COMMAND_BYTES + 1);
let err = send_launch_line(Some(&bin), "tm-sess", &line)
.expect_err("an over-length launch line must be refused");
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput, "{err}");
assert!(!log.exists(), "tmux must not run for a refused line");
}
#[serial_test::serial]
#[test]
fn launch_line_within_the_limit_reaches_tmux() {
let dir = tempfile::tempdir().unwrap();
let (bin, log) = argv_logging_tmux(dir.path(), 0);
send_launch_line(Some(&bin), "tm-sess", "echo short-line").expect("a short line is typed");
let argv = std::fs::read_to_string(&log).unwrap();
assert!(argv.contains("-l echo short-line"), "{argv}");
assert!(argv.contains("Enter"), "{argv}");
let failing = tempfile::tempdir().unwrap();
let (bin, _) = argv_logging_tmux(failing.path(), 1);
let err = send_launch_line(Some(&bin), "tm-sess", "echo").expect_err("exit 1 is a failure");
assert!(err.to_string().contains("fake failure"), "{err}");
}
#[test]
fn cli_launch_paths_type_through_the_guard() {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let mut offenders = Vec::new();
for entry in walkdir::WalkDir::new(root.join("src")) {
let entry = entry.unwrap();
let rel = entry.path().strip_prefix(root).unwrap().to_string_lossy();
if !rel.ends_with(".rs") || rel == "src/core/tmux.rs" || rel == "src/core/tmux_tests.rs" {
continue;
}
let src = std::fs::read_to_string(entry.path()).unwrap();
offenders.extend(
unguarded_send_lines(&src)
.iter()
.map(|n| format!("{rel}:{n}")),
);
}
assert!(
offenders.is_empty(),
"launch line typed through the unguarded core::tmux::send_line: {offenders:?}"
);
let spec_send = "cli_launch::send_spec_launch(";
for (file, needle, sites) in [
("src/bin/tm/commands/launch.rs", spec_send, 2),
("src/bin/tm/commands/session/start.rs", spec_send, 1),
("src/client/http_client/session_connect.rs", spec_send, 1),
("src/bin/tm/commands/fleet/launch.rs", spec_send, 1),
("src/runtime/cli_launch.rs", "tmux::send_launch_line(", 1),
] {
let src = std::fs::read_to_string(root.join(file)).unwrap();
assert_eq!(
src.matches(needle).count(),
sites,
"{file}: expected {sites} `{needle}` site(s)"
);
}
}
#[test]
fn unguarded_send_lines_flags_calls_and_imports_only() {
for (src, want) in [
("let s = tmux::send_line(None, &t, &cmd)?;", vec![1]),
(
"use trusty_mpm::core::tmux::{self, send_line};\nsend_line(None, &t, c);",
vec![1, 2],
),
(
"use crate::core::tmux::{\n TmuxTarget,\n send_line,\n};",
vec![1],
),
(
"driver.send_line(&t, x)?;\n .send_line(&t, x)\nfn send_line(&self) {}",
vec![],
),
(
"tmux::send_launch_line(None, s, c)?;\nx.send_line_to_pane(n, p, t);",
vec![],
),
(
"// tmux::send_line(None, &t, c)\nuse crate::core::tmux::TmuxTarget;",
vec![],
),
] {
assert_eq!(unguarded_send_lines(src), want, "{src}");
}
}
fn unguarded_send_lines(src: &str) -> Vec<usize> {
let is_ident = |c: char| c.is_alphanumeric() || c == '_';
let mut hits = Vec::new();
let mut open_use: Option<(usize, String)> = None;
for (i, line) in src.lines().enumerate() {
let code = line.trim_start();
if code.starts_with("//") {
continue;
}
for (at, _) in code.match_indices("send_line(") {
let before = &code[..at];
let other =
before.ends_with('.') || before.ends_with("fn ") || before.ends_with(is_ident);
if !other {
hits.push(i + 1);
}
}
let bare = code
.strip_prefix("pub(crate) ")
.or_else(|| code.strip_prefix("pub "));
if open_use.is_none() && bare.unwrap_or(code).starts_with("use ") {
open_use = Some((i + 1, String::new()));
}
let Some((start, stmt)) = open_use.as_mut() else {
continue;
};
stmt.push_str(code);
if code.contains(';') {
if stmt.contains("tmux") && stmt.split(|c| !is_ident(c)).any(|w| w == "send_line") {
hits.push(*start);
}
open_use = None;
}
}
hits
}
#[test]
fn display_message_argv_untargeted() {
assert_eq!(
display_message_argv(None, "#S"),
vec!["display-message", "-p", "#S"]
);
}
#[test]
fn display_message_argv_session_targeted() {
assert_eq!(
display_message_argv(Some(&TmuxTarget::session("tmpm-sess")), "#{pane_pid}"),
vec!["display-message", "-t", "=tmpm-sess:", "-p", "#{pane_pid}"]
);
}
#[test]
fn show_environment_argv_untargeted() {
assert_eq!(
show_environment_argv(None, "TM_MANAGED_SESSION_ID"),
vec!["show-environment", "TM_MANAGED_SESSION_ID"]
);
}
#[test]
fn show_environment_argv_session_targeted() {
assert_eq!(
show_environment_argv(Some("tmpm-sess"), "TM_MANAGED_SESSION_ID"),
vec![
"show-environment",
"-t",
"=tmpm-sess",
"TM_MANAGED_SESSION_ID"
]
);
}
#[serial_test::serial]
#[test]
fn run_tmux_argv_with_bin_does_not_panic_on_missing_binary() {
let result = run_tmux_argv_with_bin(
"definitely-not-a-real-tmux-binary-2414",
&[
"display-message".to_string(),
"-p".to_string(),
"#S".to_string(),
],
);
assert!(result.is_err());
}
#[test]
fn resolve_tmux_binary_does_not_panic() {
let _ = resolve_tmux_binary();
}
#[tokio::test]
async fn with_tmux_binary_scopes_the_override_to_its_future() {
let shim = std::path::PathBuf::from("/nonexistent/tmux-shim-6542");
let (inner, inner_bare, spawned) = with_tmux_binary(shim.clone(), async {
tokio::task::yield_now().await;
let spawned = tokio::spawn(async { resolve_tmux_binary() })
.await
.expect("spawned task");
(
resolve_tmux_binary(),
resolve_tmux_binary_or_bare(),
spawned,
)
})
.await;
assert_eq!(inner, Some(shim.clone()));
assert_eq!(inner_bare, shim.to_string_lossy());
assert_ne!(
spawned,
Some(shim.clone()),
"another task must not inherit the override"
);
assert_ne!(resolve_tmux_binary(), Some(shim), "the scope must end");
}
#[test]
fn resolve_tmux_binary_or_bare_never_empty() {
assert!(!resolve_tmux_binary_or_bare().is_empty());
}
#[serial_test::serial]
#[test]
fn run_tmux_with_bin_does_not_panic_on_missing_binary() {
let result = run_tmux_with_bin(
"definitely-not-a-real-tmux-binary-2398",
&TmuxCommand::ListSessions,
);
assert!(result.is_err());
}
#[serial_test::serial]
#[test]
fn create_managed_session_does_not_panic_on_missing_binary() {
let result = create_managed_session(
Some("definitely-not-a-real-tmux-binary-2398"),
"tmpm-test-2398-no-bin",
None,
);
assert!(result.is_err());
}
#[test]
fn managed_session_command_sequence_matches_shared_layer() {
let got = managed_session_command_sequence(
"tmpm-sess",
Some("/tmp/proj"),
100_000,
true,
false,
true,
);
let want = trusty_common::tmux::managed_session_commands(
"tmpm-sess",
Some("/tmp/proj"),
100_000,
true,
false,
true,
None,
);
assert_eq!(got, want);
}
#[test]
fn exclusive_command_sequence_omits_the_attach_flag() {
let cmds =
managed_session_command_sequence("tm-sess", Some("/tmp/p"), 100_000, true, false, false);
let TmuxCommand::NewSession { idempotent, .. } = cmds.last().expect("ends with new-session")
else {
panic!("the sequence must end with NewSession");
};
assert!(!idempotent, "the exclusive create must not pass -A");
let argv = trusty_common::tmux::tmux_argv(cmds.last().expect("ends with new-session"));
assert!(
!argv.contains(&"-A".to_string()),
"argv still carries -A: {argv:?}"
);
}
#[test]
fn managed_session_command_sequence_applies_options_before_new_session() {
let cmds = managed_session_command_sequence("tmpm-sess", None, 50_000, false, false, true);
assert_eq!(cmds.len(), 4);
assert!(matches!(cmds[0], TmuxCommand::SetGlobalOption { .. }));
assert!(matches!(cmds[1], TmuxCommand::SetGlobalOption { .. }));
assert!(matches!(cmds[2], TmuxCommand::SetWindowGlobalOption { .. }));
assert!(matches!(cmds[3], TmuxCommand::NewSession { .. }));
}
fn write_fake_tmux(dir: &std::path::Path, name: &str, body: &str) -> String {
use std::io::Write as _;
use std::os::unix::fs::OpenOptionsExt as _;
let path = dir.join(name);
{
let mut f = std::fs::OpenOptions::new()
.create_new(true)
.write(true)
.mode(0o755)
.open(&path)
.expect("create fake tmux script");
f.write_all(body.as_bytes())
.expect("write fake tmux script");
}
path.to_string_lossy().into_owned()
}
#[serial_test::serial]
#[test]
fn ensure_server_up_retries_then_succeeds() {
let dir = tempfile::tempdir().unwrap();
let counter = dir.path().join("attempts");
std::fs::write(&counter, "0").unwrap();
let script = format!(
"#!/bin/sh\nn=$(cat '{counter}')\nn=$((n + 1))\necho \"$n\" > '{counter}'\nif [ \"$n\" -lt 3 ]; then\n echo 'error connecting to socket (No such file or directory)' >&2\n exit 1\nfi\nexit 0\n",
counter = counter.display()
);
let bin = write_fake_tmux(dir.path(), "fake-tmux-retry", &script);
let result = ensure_server_up(&bin);
assert!(
result.is_ok(),
"must eventually succeed within {START_SERVER_MAX_ATTEMPTS} attempts: {result:?}"
);
assert_eq!(
std::fs::read_to_string(&counter).unwrap().trim(),
"3",
"must have retried exactly up to the succeeding attempt"
);
}
#[serial_test::serial]
#[test]
fn ensure_server_up_fails_loudly_after_exhausting_retries() {
let dir = tempfile::tempdir().unwrap();
let script =
"#!/bin/sh\necho 'error connecting to socket (No such file or directory)' >&2\nexit 1\n";
let bin = write_fake_tmux(dir.path(), "fake-tmux-always-fails", script);
let result = ensure_server_up(&bin);
assert!(
result.is_err(),
"an always-failing server probe must error loudly, never proceed as if it succeeded"
);
assert!(
result.unwrap_err().contains("error connecting to socket"),
"the Err must carry the underlying failure reason"
);
}
#[serial_test::serial]
#[test]
fn probe_history_limit_reads_back_configured_value() {
let dir = tempfile::tempdir().unwrap();
let script = "#!/bin/sh\necho 100000\nexit 0\n";
let bin = write_fake_tmux(dir.path(), "fake-tmux-show-options", script);
assert_eq!(probe_history_limit(&bin), Ok(100_000));
}
#[serial_test::serial]
#[test]
fn probe_history_limit_errors_on_unparsable_output() {
let dir = tempfile::tempdir().unwrap();
let script = "#!/bin/sh\necho 'not-a-number'\nexit 0\n";
let bin = write_fake_tmux(dir.path(), "fake-tmux-bad-output", script);
let result = probe_history_limit(&bin);
assert!(
result.is_err(),
"unparsable show-options output must error loudly, not silently pass verification"
);
}
#[serial_test::serial]
#[test]
fn probe_history_limit_errors_on_nonzero_exit() {
let dir = tempfile::tempdir().unwrap();
let script = "#!/bin/sh\necho 'no server running' >&2\nexit 1\n";
let bin = write_fake_tmux(dir.path(), "fake-tmux-show-options-fails", script);
assert!(probe_history_limit(&bin).is_err());
}
fn fake_show_options_case(history_limit: &str, alternate_screen: &str) -> String {
format!(
" show-options)\n case \"$4\" in\n history-limit) echo {history_limit} ;;\n alternate-screen) echo {alternate_screen} ;;\n esac\n ;;\n"
)
}
#[serial_test::serial]
#[test]
fn probe_alternate_screen_reads_back_on() {
let dir = tempfile::tempdir().unwrap();
let bin = write_fake_tmux(
dir.path(),
"fake-tmux-alt-on",
"#!/bin/sh\necho on\nexit 0\n",
);
assert_eq!(probe_alternate_screen(&bin), Ok(true));
}
#[serial_test::serial]
#[test]
fn probe_alternate_screen_reads_back_off() {
let dir = tempfile::tempdir().unwrap();
let bin = write_fake_tmux(
dir.path(),
"fake-tmux-alt-off",
"#!/bin/sh\necho off\nexit 0\n",
);
assert_eq!(probe_alternate_screen(&bin), Ok(false));
}
#[serial_test::serial]
#[test]
fn probe_alternate_screen_errors_on_unrecognised_value() {
let dir = tempfile::tempdir().unwrap();
let bin = write_fake_tmux(
dir.path(),
"fake-tmux-alt-garbage",
"#!/bin/sh\necho maybe\nexit 0\n",
);
assert!(probe_alternate_screen(&bin).is_err());
}
#[serial_test::serial]
#[test]
fn probe_alternate_screen_errors_on_nonzero_exit() {
let dir = tempfile::tempdir().unwrap();
let bin = write_fake_tmux(
dir.path(),
"fake-tmux-alt-fails",
"#!/bin/sh\necho 'no server running' >&2\nexit 1\n",
);
assert!(probe_alternate_screen(&bin).is_err());
}
#[serial_test::serial]
#[test]
fn probe_alternate_screen_queries_the_window_scope() {
let dir = tempfile::tempdir().unwrap();
let log = dir.path().join("argv.log");
let script = format!(
"#!/bin/sh\necho \"$@\" >> '{log}'\necho off\nexit 0\n",
log = log.display()
);
let bin = write_fake_tmux(dir.path(), "fake-tmux-alt-argv", &script);
assert_eq!(probe_alternate_screen(&bin), Ok(false));
assert_eq!(
std::fs::read_to_string(&log).unwrap().trim(),
"show-options -wg -v alternate-screen"
);
}
#[serial_test::serial]
#[test]
fn create_managed_session_confirms_server_before_applying_options() {
let dir = tempfile::tempdir().unwrap();
let log = dir.path().join("calls.log");
let opts = crate::core::trusty_tools_config::ResolvedTmuxOptions {
history_limit: 123_456,
mouse: true,
alternate_screen: false,
};
let script = format!(
"#!/bin/sh\necho \"$1\" >> '{log}'\ncase \"$1\" in\n{show_options}esac\nexit 0\n",
log = log.display(),
show_options = fake_show_options_case(
&opts.history_limit.to_string(),
if opts.alternate_screen { "on" } else { "off" },
)
);
let bin = write_fake_tmux(dir.path(), "fake-tmux-order", &script);
let outcome =
create_managed_session_with_options(Some(&bin), "tmpm-3386-order-test", None, opts)
.expect("fake tmux always exits 0");
assert!(
outcome.options_verified,
"a fake tmux that echoes back the configured history-limit must verify successfully"
);
let calls = std::fs::read_to_string(&log).unwrap();
let lines: Vec<&str> = calls.lines().collect();
assert_eq!(
lines.first(),
Some(&"start-server"),
"the server must be confirmed up BEFORE any set-option -g (#3386): {lines:?}"
);
let new_session_pos = lines
.iter()
.position(|l| *l == "new-session")
.expect("new-session must still run");
let set_option_positions: Vec<usize> = lines
.iter()
.enumerate()
.filter(|(_, l)| **l == "set-option")
.map(|(i, _)| i)
.collect();
assert!(
!set_option_positions.is_empty(),
"the scrollback/mouse options must still be applied: {lines:?}"
);
assert!(
set_option_positions
.iter()
.all(|&p| p > 0 && p < new_session_pos),
"set-option -g must run strictly AFTER start-server and BEFORE new-session \
(#3386 / pre-existing #2398 ordering guarantee): {lines:?}"
);
}
#[serial_test::serial]
#[test]
fn apply_and_verify_scrollback_options_succeeds_on_second_attempt() {
let dir = tempfile::tempdir().unwrap();
let counter = dir.path().join("probe-attempts");
std::fs::write(&counter, "0").unwrap();
let script = format!(
"#!/bin/sh\ncase \"$1\" in\n show-options)\n case \"$4\" in\n history-limit)\n n=$(cat '{counter}')\n n=$((n + 1))\n echo \"$n\" > '{counter}'\n if [ \"$n\" -lt 2 ]; then\n echo 2000\n else\n echo 100000\n fi\n ;;\n alternate-screen) echo on ;;\n esac\n ;;\nesac\nexit 0\n",
counter = counter.display()
);
let bin = write_fake_tmux(dir.path(), "fake-tmux-verify-retry", &script);
let options = trusty_common::tmux::scrollback_option_commands(100_000, true, true);
let verified = apply_and_verify_scrollback_options(&bin, &options, 100_000, true);
assert!(
verified,
"must succeed once the probe reports the expected value on a later attempt"
);
assert_eq!(
std::fs::read_to_string(&counter).unwrap().trim(),
"2",
"must have retried the WHOLE cycle (not just start-server) exactly once"
);
}
#[serial_test::serial]
#[test]
fn apply_and_verify_scrollback_options_returns_false_after_exhausting_retries() {
let dir = tempfile::tempdir().unwrap();
let counter = dir.path().join("probe-attempts");
std::fs::write(&counter, "0").unwrap();
let script = format!(
"#!/bin/sh\ncase \"$1\" in\n show-options)\n case \"$4\" in\n history-limit)\n n=$(cat '{counter}')\n n=$((n + 1))\n echo \"$n\" > '{counter}'\n echo 2000\n ;;\n alternate-screen) echo on ;;\n esac\n ;;\nesac\nexit 0\n",
counter = counter.display()
);
let bin = write_fake_tmux(dir.path(), "fake-tmux-verify-always-wrong", &script);
let options = trusty_common::tmux::scrollback_option_commands(100_000, true, true);
let verified = apply_and_verify_scrollback_options(&bin, &options, 100_000, true);
assert!(
!verified,
"must return false (never silently proceed as verified) once every \
apply-and-verify attempt is exhausted — this is the #3386 review's caller-visible \
degraded signal"
);
assert_eq!(
std::fs::read_to_string(&counter).unwrap().trim(),
APPLY_VERIFY_MAX_ATTEMPTS.to_string(),
"must have made exactly APPLY_VERIFY_MAX_ATTEMPTS probe attempts"
);
}
#[serial_test::serial]
#[test]
fn apply_and_verify_scrollback_options_fails_when_only_alternate_screen_is_wrong() {
let dir = tempfile::tempdir().unwrap();
let script = format!(
"#!/bin/sh\ncase \"$1\" in\n{show_options}esac\nexit 0\n",
show_options = fake_show_options_case("100000", "on")
);
let bin = write_fake_tmux(dir.path(), "fake-tmux-alt-mismatch", &script);
let options = trusty_common::tmux::scrollback_option_commands(100_000, true, false);
let verified = apply_and_verify_scrollback_options(&bin, &options, 100_000, false);
assert!(
!verified,
"a correct history-limit must not verify away a wrong alternate-screen — that is \
the fail-open #5151 exists to close"
);
}
#[serial_test::serial]
#[test]
fn apply_and_verify_scrollback_options_verifies_alternate_screen_off() {
let dir = tempfile::tempdir().unwrap();
let script = format!(
"#!/bin/sh\ncase \"$1\" in\n{show_options}esac\nexit 0\n",
show_options = fake_show_options_case("100000", "off")
);
let bin = write_fake_tmux(dir.path(), "fake-tmux-alt-match", &script);
let options = trusty_common::tmux::scrollback_option_commands(100_000, true, false);
assert!(apply_and_verify_scrollback_options(
&bin, &options, 100_000, false
));
}
#[serial_test::serial]
#[test]
fn apply_and_verify_probes_the_same_scope_it_sets() {
let dir = tempfile::tempdir().unwrap();
let log = dir.path().join("argv.log");
let script = format!(
"#!/bin/sh\necho \"$@\" >> '{log}'\ncase \"$1\" in\n{show_options}esac\nexit 0\n",
log = log.display(),
show_options = fake_show_options_case("100000", "off")
);
let bin = write_fake_tmux(dir.path(), "fake-tmux-scope-log", &script);
let options = trusty_common::tmux::scrollback_option_commands(100_000, true, false);
assert!(apply_and_verify_scrollback_options(
&bin, &options, 100_000, false
));
let calls = std::fs::read_to_string(&log).unwrap();
let scope_of = |prefix: &str| -> String {
let line = calls
.lines()
.find(|l| l.starts_with(prefix) && l.contains("alternate-screen"))
.unwrap_or_else(|| panic!("no {prefix} call for alternate-screen in: {calls}"));
line.split_whitespace()
.nth(1)
.expect("scope flag")
.to_string()
};
let set_scope = scope_of("set-option");
let show_scope = scope_of("show-options");
assert_eq!(set_scope, "-wg", "alternate-screen must be set at -wg");
assert_eq!(
show_scope, set_scope,
"the readback must query the scope the set wrote: {calls}"
);
}
#[test]
fn env_declares_utf8_follows_tmux_precedence() {
assert!(declares_utf8(Some("en_US.UTF-8"), None, None));
assert!(declares_utf8(Some("C.utf8"), None, None));
assert!(!declares_utf8(Some("C"), None, None));
assert!(!declares_utf8(Some("POSIX"), None, Some("en_US.UTF-8")));
assert!(declares_utf8(Some(""), None, Some("en_US.UTF-8")));
assert!(declares_utf8(None, Some("en_US.UTF-8"), Some("C")));
assert!(!declares_utf8(None, None, None));
}
#[test]
fn force_utf8_client_env_is_a_no_op_under_a_utf8_locale() {
let read = |key: &str| std::env::var(key).ok();
let already = declares_utf8(
read("LC_ALL").as_deref(),
read("LC_CTYPE").as_deref(),
read("LANG").as_deref(),
);
let mut cmd = std::process::Command::new("true");
force_utf8_client_env(&mut cmd);
let injected = cmd
.get_envs()
.any(|(k, v)| k == "LC_ALL" && v == Some(std::ffi::OsStr::new(UTF8_LOCALE)));
assert_eq!(
injected, !already,
"LC_ALL must be injected exactly when the environment does not already \
declare UTF-8 (#6529)"
);
}