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