#![cfg(unix)]
mod common;
use std::error::Error;
use std::time::Duration;
use common::{
assert_success, stderr, stdout, terminate_child, CliHarness, FrozenTmuxBinary,
TmuxCompatHarness, TmuxCompatRun, TmuxCompatRunConfig, FROZEN_TMUX_ENV,
};
#[test]
fn capture_pane_prints_unattached_transcript() -> Result<(), Box<dyn Error>> {
let harness = CliHarness::new("capture-print")?;
let mut daemon = harness.start_hidden_daemon()?;
let marker = "cli_capture_print_marker";
assert_success(&harness.run(&["new-session", "-d", "-s", "alpha"])?);
assert_success(&harness.run(&[
"send-keys",
"-t",
"alpha:0.0",
&format!("printf '{marker}\\n'"),
"Enter",
])?);
let output = wait_for_capture(&harness, marker)?;
assert!(stdout(&output).contains(marker));
assert!(stderr(&output).is_empty());
terminate_child(daemon.child_mut())?;
Ok(())
}
#[test]
fn capture_pane_writes_buffer_without_printing() -> Result<(), Box<dyn Error>> {
let harness = CliHarness::new("capture-buffer")?;
let mut daemon = harness.start_hidden_daemon()?;
let marker = "cli_capture_buffer_marker";
assert_success(&harness.run(&["new-session", "-d", "-s", "alpha"])?);
assert_success(&harness.run(&[
"send-keys",
"-t",
"alpha:0.0",
&format!("printf '{marker}\\n'"),
"Enter",
])?);
let _ = wait_for_capture(&harness, marker)?;
assert_success(&harness.run(&["capture-pane", "-t", "alpha:0.0", "-b", "cap"])?);
let show = harness.run(&["show-buffer", "-b", "cap"])?;
assert_eq!(show.status.code(), Some(0));
assert!(stdout(&show).contains(marker));
assert!(stderr(&show).is_empty());
terminate_child(daemon.child_mut())?;
Ok(())
}
#[test]
fn capture_pane_print_does_not_reorder_buffers() -> Result<(), Box<dyn Error>> {
let harness = CliHarness::new("capture-print-buffer-order")?;
let mut daemon = harness.start_hidden_daemon()?;
let marker = "cli_capture_order_marker";
assert_success(&harness.run(&["new-session", "-d", "-s", "alpha"])?);
assert_success(&harness.run(&["set-buffer", "stable-head"])?);
assert_success(&harness.run(&[
"send-keys",
"-t",
"alpha:0.0",
&format!("printf '{marker}\\n'"),
"Enter",
])?);
let _ = wait_for_capture(&harness, marker)?;
let show = harness.run(&["show-buffer"])?;
assert_eq!(show.status.code(), Some(0));
assert_eq!(stdout(&show), "stable-head");
terminate_child(daemon.child_mut())?;
Ok(())
}
#[test]
fn capture_pane_matches_frozen_tmux_for_respawned_output_when_available(
) -> Result<(), Box<dyn Error>> {
let harness = TmuxCompatHarness::new("capture-pane-tmux-compat")?;
let tmux_binary = match FrozenTmuxBinary::discover() {
FrozenTmuxBinary::Available(path) => path,
FrozenTmuxBinary::Unavailable {
checked_path,
reason,
} => {
eprintln!(
"runtime skip: frozen tmux binary unavailable via {FROZEN_TMUX_ENV} or default '{}': {reason}",
checked_path.display()
);
return Ok(());
}
};
let config = TmuxCompatRunConfig::default();
let create = harness.run_pair_with(
&tmux_binary,
&["new-session", "-d", "-s", "alpha", "-x", "80", "-y", "24"],
config.clone(),
)?;
assert_exact_tmux_compat(&create);
let remain_on_exit = harness.run_pair_with(
&tmux_binary,
&["set-window-option", "-t", "alpha:0", "remain-on-exit", "on"],
config.clone(),
)?;
assert_exact_tmux_compat(&remain_on_exit);
let respawn = harness.run_pair_with(
&tmux_binary,
&[
"respawn-pane",
"-k",
"-t",
"alpha:0.0",
"printf 'capture-tmux-compat-marker\\n'; sleep 10",
],
config.clone(),
)?;
assert_exact_tmux_compat(&respawn);
assert_capture_tmux_compat_when(
&harness,
&tmux_binary,
&config,
&["capture-pane", "-p", "-t", "alpha:0.0"],
|run| {
run.tmux
.stdout_string()
.contains("capture-tmux-compat-marker")
},
"capture-pane compatibility never observed marker",
)
}
#[test]
fn capture_pane_matches_frozen_tmux_after_respawned_pane_exits_when_available(
) -> Result<(), Box<dyn Error>> {
let harness = TmuxCompatHarness::new("capture-pane-dead-pane-tmux-compat")?;
let tmux_binary = match FrozenTmuxBinary::discover() {
FrozenTmuxBinary::Available(path) => path,
FrozenTmuxBinary::Unavailable {
checked_path,
reason,
} => {
eprintln!(
"runtime skip: frozen tmux binary unavailable via {FROZEN_TMUX_ENV} or default '{}': {reason}",
checked_path.display()
);
return Ok(());
}
};
let config = TmuxCompatRunConfig::default();
let create = harness.run_pair_with(
&tmux_binary,
&["new-session", "-d", "-s", "alpha", "-x", "80", "-y", "24"],
config.clone(),
)?;
assert_exact_tmux_compat(&create);
let remain_on_exit = harness.run_pair_with(
&tmux_binary,
&["set-window-option", "-t", "alpha:0", "remain-on-exit", "on"],
config.clone(),
)?;
assert_exact_tmux_compat(&remain_on_exit);
let respawn = harness.run_pair_with(
&tmux_binary,
&[
"respawn-pane",
"-k",
"-t",
"alpha:0.0",
"printf 'one\\ntwo\\n'; sleep 0.2",
],
config.clone(),
)?;
assert_exact_tmux_compat(&respawn);
assert_capture_tmux_compat_when(
&harness,
&tmux_binary,
&config,
&["capture-pane", "-p", "-t", "alpha:0.0"],
|run| run.tmux.stdout_string().contains("Pane is dead"),
"capture-pane dead-pane compatibility never observed the dead pane marker",
)
}
#[test]
fn capture_pane_matches_frozen_tmux_for_escape_range_and_quiet_flags_when_available(
) -> Result<(), Box<dyn Error>> {
let harness = TmuxCompatHarness::new("capture-pane-flags-tmux-compat")?;
let tmux_binary = match FrozenTmuxBinary::discover() {
FrozenTmuxBinary::Available(path) => path,
FrozenTmuxBinary::Unavailable {
checked_path,
reason,
} => {
eprintln!(
"runtime skip: frozen tmux binary unavailable via {FROZEN_TMUX_ENV} or default '{}': {reason}",
checked_path.display()
);
return Ok(());
}
};
let config = TmuxCompatRunConfig::default();
let create = harness.run_pair_with(
&tmux_binary,
&["new-session", "-d", "-s", "alpha", "-x", "80", "-y", "24"],
config.clone(),
)?;
assert_exact_tmux_compat(&create);
let remain_on_exit = harness.run_pair_with(
&tmux_binary,
&["set-window-option", "-t", "alpha:0", "remain-on-exit", "on"],
config.clone(),
)?;
assert_exact_tmux_compat(&remain_on_exit);
let respawn = harness.run_pair_with(
&tmux_binary,
&[
"respawn-pane",
"-k",
"-t",
"alpha:0.0",
"printf '\\033[31mcapture-flags-marker\\033[0m \\nplain\\n'; sleep 10",
],
config.clone(),
)?;
assert_exact_tmux_compat(&respawn);
assert_capture_tmux_compat_when(
&harness,
&tmux_binary,
&config,
&["capture-pane", "-p", "-e", "-C", "-t", "alpha:0.0"],
|run| {
run.tmux
.stdout_string()
.contains("\\033[31mcapture-flags-marker\\033[39m")
},
"capture-pane -e -C compatibility never observed the escaped ANSI marker",
)?;
assert_capture_tmux_compat_when(
&harness,
&tmux_binary,
&config,
&[
"capture-pane",
"-p",
"-S",
"0",
"-E",
"0",
"-t",
"alpha:0.0",
],
|run| run.tmux.stdout_string().contains("capture-flags-marker"),
"capture-pane range compatibility never observed the marker line",
)?;
let alternate_quiet = harness.run_pair_with(
&tmux_binary,
&["capture-pane", "-p", "-a", "-q", "-t", "alpha:0.0"],
config,
)?;
assert_exact_tmux_compat(&alternate_quiet);
Ok(())
}
fn wait_for_capture(
harness: &CliHarness,
marker: &str,
) -> Result<std::process::Output, Box<dyn Error>> {
let mut last = None;
for _ in 0..100 {
let output = harness.run(&["capture-pane", "-p", "-t", "alpha:0.0"])?;
if output.status.code() == Some(0) && stdout(&output).contains(marker) {
return Ok(output);
}
last = Some(output);
std::thread::sleep(Duration::from_millis(20));
}
let last = last.expect("capture was attempted");
Err(format!(
"capture output never contained marker {marker}; status={:?} stdout={:?} stderr={:?}",
last.status.code(),
stdout(&last),
stderr(&last)
)
.into())
}
fn assert_exact_tmux_compat(run: &TmuxCompatRun) {
assert_eq!(run.tmux.status_code, run.rmux.status_code);
assert_eq!(run.tmux.timed_out, run.rmux.timed_out);
assert_eq!(run.tmux.stdout, run.rmux.stdout);
assert_eq!(run.tmux.stderr, run.rmux.stderr);
}
fn assert_capture_tmux_compat_when<F>(
harness: &TmuxCompatHarness,
tmux_binary: &std::path::Path,
config: &TmuxCompatRunConfig,
argv: &[&str],
ready: F,
context: &str,
) -> Result<(), Box<dyn Error>>
where
F: Fn(&TmuxCompatRun) -> bool,
{
let mut last = None;
for _ in 0..100 {
let capture = harness.run_pair_with(tmux_binary, argv, config.clone())?;
if capture.tmux.stdout == capture.rmux.stdout
&& capture.tmux.stderr == capture.rmux.stderr
&& capture.tmux.status_code == capture.rmux.status_code
&& ready(&capture)
{
assert_exact_tmux_compat(&capture);
return Ok(());
}
last = Some(capture);
std::thread::sleep(Duration::from_millis(20));
}
let last = last.expect("capture compatibility was attempted");
Err(format!(
"{context}; argv={argv:?}; tmux status={:?} stdout={:?} stderr={:?}; rmux status={:?} stdout={:?} stderr={:?}",
last.tmux.status_code,
last.tmux.stdout_string(),
last.tmux.stderr_string(),
last.rmux.status_code,
last.rmux.stdout_string(),
last.rmux.stderr_string()
)
.into())
}