From 537941b676f38ae7bc8618b2a3d754453cc65de2 Mon Sep 17 00:00:00 2001 From: Ulrich Hornung Date: Sun, 24 Mar 2024 23:06:00 +0100 Subject: [PATCH] config terminal simulation for specific stdios only --- tests/by-util/test_nohup.rs | 4 +- tests/common/util.rs | 206 ++++++++++++++---- tests/fixtures/nohup/is_a_tty.sh | 21 ++ tests/fixtures/nohup/is_atty.sh | 21 -- .../fixtures/util/{is_atty.sh => is_a_tty.sh} | 14 +- 5 files changed, 191 insertions(+), 75 deletions(-) create mode 100644 tests/fixtures/nohup/is_a_tty.sh delete mode 100644 tests/fixtures/nohup/is_atty.sh rename tests/fixtures/util/{is_atty.sh => is_a_tty.sh} (50%) diff --git a/tests/by-util/test_nohup.rs b/tests/by-util/test_nohup.rs index db0a22a5e42..db19114b663 100644 --- a/tests/by-util/test_nohup.rs +++ b/tests/by-util/test_nohup.rs @@ -45,7 +45,7 @@ fn test_nohup_with_pseudo_terminal_emulation_on_stdin_stdout_stderr_get_replaced let result = ts .ucmd() .terminal_simulation(true) - .args(&["sh", "is_atty.sh"]) + .args(&["sh", "is_a_tty.sh"]) .succeeds(); assert_eq!( @@ -58,6 +58,6 @@ fn test_nohup_with_pseudo_terminal_emulation_on_stdin_stdout_stderr_get_replaced // this proves that nohup was exchanging the stdio file descriptors assert_eq!( std::fs::read_to_string(ts.fixtures.plus_as_string("nohup.out")).unwrap(), - "stdin is not atty\nstdout is not atty\nstderr is not atty\n" + "stdin is not a tty\nstdout is not a tty\nstderr is not a tty\n" ); } diff --git a/tests/common/util.rs b/tests/common/util.rs index 19ef8317af2..d97a19fa804 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -1207,6 +1207,15 @@ impl TestScenario { } } +#[cfg(unix)] +#[derive(Debug, Default)] +pub struct TerminalSimulation { + size: Option, + stdin: bool, + stdout: bool, + stderr: bool, +} + /// A `UCommand` is a builder wrapping an individual Command that provides several additional features: /// 1. it has convenience functions that are more ergonomic to use for piping in stdin, spawning the command /// and asserting on the results. @@ -1242,9 +1251,7 @@ pub struct UCommand { stderr_to_stdout: bool, timeout: Option, #[cfg(unix)] - terminal_simulation: bool, - #[cfg(unix)] - terminal_size: Option, + terminal_simulation: Option, tmpd: Option>, // drop last } @@ -1425,22 +1432,32 @@ impl UCommand { /// Set if process should be run in a simulated terminal /// - /// This is useful to test behavior that is only active if [`stdout.is_terminal()`] is [`true`]. + /// This is useful to test behavior that is only active if e.g. [`stdout.is_terminal()`] is [`true`]. + /// This function uses default terminal size and attaches stdin, stdout and stderr to that terminal. + /// For more control over the terminal simulation, use `terminal_sim_stdio` /// (unix: pty, windows: ConPTY[not yet supported]) #[cfg(unix)] pub fn terminal_simulation(&mut self, enable: bool) -> &mut Self { - self.terminal_simulation = enable; + if enable { + self.terminal_simulation = Some(TerminalSimulation { + stdin: true, + stdout: true, + stderr: true, + ..Default::default() + }); + } else { + self.terminal_simulation = None; + } self } - /// Set if process should be run in a simulated terminal with specific size + /// Allows to simulate a terminal use-case with specific properties. /// - /// This is useful to test behavior that is only active if [`stdout.is_terminal()`] is [`true`]. - /// And the size of the terminal matters additionally. + /// This is useful to test behavior that is only active if e.g. [`stdout.is_terminal()`] is [`true`]. + /// This function allows to set a specific size and to attach the terminal to only parts of the in/out. #[cfg(unix)] - pub fn terminal_size(&mut self, win_size: libc::winsize) -> &mut Self { - self.terminal_simulation(true); - self.terminal_size = Some(win_size); + pub fn terminal_sim_stdio(&mut self, config: TerminalSimulation) -> &mut Self { + self.terminal_simulation = Some(config); self } @@ -1628,35 +1645,48 @@ impl UCommand { }; #[cfg(unix)] - if self.terminal_simulation { - let terminal_size = self.terminal_size.unwrap_or(libc::winsize { + if let Some(simulated_terminal) = &self.terminal_simulation { + let terminal_size = simulated_terminal.size.unwrap_or(libc::winsize { ws_col: 80, ws_row: 30, ws_xpixel: 80 * 8, ws_ypixel: 30 * 10, }); - let OpenptyResult { - slave: pi_slave, - master: pi_master, - } = nix::pty::openpty(&terminal_size, None).unwrap(); - let OpenptyResult { - slave: po_slave, - master: po_master, - } = nix::pty::openpty(&terminal_size, None).unwrap(); - let OpenptyResult { - slave: pe_slave, - master: pe_master, - } = nix::pty::openpty(&terminal_size, None).unwrap(); - - stdin_pty = Some(File::from(pi_master)); - - captured_stdout = - self.spawn_reader_thread(captured_stdout, po_master, "stdout_reader".to_string()); - captured_stderr = - self.spawn_reader_thread(captured_stderr, pe_master, "stderr_reader".to_string()); - - command.stdin(pi_slave).stdout(po_slave).stderr(pe_slave); + if simulated_terminal.stdin { + let OpenptyResult { + slave: pi_slave, + master: pi_master, + } = nix::pty::openpty(&terminal_size, None).unwrap(); + stdin_pty = Some(File::from(pi_master)); + command.stdin(pi_slave); + } + + if simulated_terminal.stdout { + let OpenptyResult { + slave: po_slave, + master: po_master, + } = nix::pty::openpty(&terminal_size, None).unwrap(); + captured_stdout = self.spawn_reader_thread( + captured_stdout, + po_master, + "stdout_reader".to_string(), + ); + command.stdout(po_slave); + } + + if simulated_terminal.stderr { + let OpenptyResult { + slave: pe_slave, + master: pe_master, + } = nix::pty::openpty(&terminal_size, None).unwrap(); + captured_stderr = self.spawn_reader_thread( + captured_stderr, + pe_master, + "stderr_reader".to_string(), + ); + command.stderr(pe_slave); + } } #[cfg(unix)] @@ -3609,10 +3639,10 @@ mod tests { fn test_simulation_of_terminal_false() { let scene = TestScenario::new("util"); - let out = scene.ccmd("env").arg("sh").arg("is_atty.sh").succeeds(); + let out = scene.ccmd("env").arg("sh").arg("is_a_tty.sh").succeeds(); std::assert_eq!( String::from_utf8_lossy(out.stdout()), - "stdin is not atty\nstdout is not atty\nstderr is not atty\n" + "stdin is not a tty\nstdout is not a tty\nstderr is not a tty\n" ); std::assert_eq!( String::from_utf8_lossy(out.stderr()), @@ -3629,12 +3659,93 @@ mod tests { let out = scene .ccmd("env") .arg("sh") - .arg("is_atty.sh") + .arg("is_a_tty.sh") .terminal_simulation(true) .succeeds(); std::assert_eq!( String::from_utf8_lossy(out.stdout()), - "stdin is atty\r\nstdout is atty\r\nstderr is atty\r\nterminal size: 30 80\r\n" + "stdin is a tty\r\nterminal size: 30 80\r\nstdout is a tty\r\nstderr is a tty\r\n" + ); + std::assert_eq!( + String::from_utf8_lossy(out.stderr()), + "This is an error message.\r\n" + ); + } + + #[cfg(unix)] + #[cfg(feature = "env")] + #[test] + fn test_simulation_of_terminal_for_stdin_only() { + let scene = TestScenario::new("util"); + + let out = scene + .ccmd("env") + .arg("sh") + .arg("is_a_tty.sh") + .terminal_sim_stdio(TerminalSimulation { + stdin: true, + stdout: false, + stderr: false, + ..Default::default() + }) + .succeeds(); + std::assert_eq!( + String::from_utf8_lossy(out.stdout()), + "stdin is a tty\nterminal size: 30 80\nstdout is not a tty\nstderr is not a tty\n" + ); + std::assert_eq!( + String::from_utf8_lossy(out.stderr()), + "This is an error message.\n" + ); + } + + #[cfg(unix)] + #[cfg(feature = "env")] + #[test] + fn test_simulation_of_terminal_for_stdout_only() { + let scene = TestScenario::new("util"); + + let out = scene + .ccmd("env") + .arg("sh") + .arg("is_a_tty.sh") + .terminal_sim_stdio(TerminalSimulation { + stdin: false, + stdout: true, + stderr: false, + ..Default::default() + }) + .succeeds(); + std::assert_eq!( + String::from_utf8_lossy(out.stdout()), + "stdin is not a tty\r\nstdout is a tty\r\nstderr is not a tty\r\n" + ); + std::assert_eq!( + String::from_utf8_lossy(out.stderr()), + "This is an error message.\n" + ); + } + + #[cfg(unix)] + #[cfg(feature = "env")] + #[test] + fn test_simulation_of_terminal_for_stderr_only() { + let scene = TestScenario::new("util"); + + let out = scene + .ccmd("env") + .arg("sh") + .arg("is_a_tty.sh") + .terminal_sim_stdio(TerminalSimulation { + stdin: false, + stdout: false, + stderr: true, + ..Default::default() + }) + .succeeds(); + std::assert_eq!( + String::from_utf8_lossy(out.stdout()), + "stdin is not a tty\nstdout is not a tty\nstderr is a tty\n" ); std::assert_eq!( String::from_utf8_lossy(out.stderr()), @@ -3651,17 +3762,22 @@ mod tests { let out = scene .ccmd("env") .arg("sh") - .arg("is_atty.sh") - .terminal_size(libc::winsize { - ws_col: 40, - ws_row: 10, - ws_xpixel: 40 * 8, - ws_ypixel: 10 * 10, + .arg("is_a_tty.sh") + .terminal_sim_stdio(TerminalSimulation { + size: Some(libc::winsize { + ws_col: 40, + ws_row: 10, + ws_xpixel: 40 * 8, + ws_ypixel: 10 * 10, + }), + stdout: true, + stdin: true, + stderr: true, }) .succeeds(); std::assert_eq!( String::from_utf8_lossy(out.stdout()), - "stdin is atty\r\nstdout is atty\r\nstderr is atty\r\nterminal size: 10 40\r\n" + "stdin is a tty\r\nterminal size: 10 40\r\nstdout is a tty\r\nstderr is a tty\r\n" ); std::assert_eq!( String::from_utf8_lossy(out.stderr()), diff --git a/tests/fixtures/nohup/is_a_tty.sh b/tests/fixtures/nohup/is_a_tty.sh new file mode 100644 index 00000000000..1eb0fb5229e --- /dev/null +++ b/tests/fixtures/nohup/is_a_tty.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +if [ -t 0 ] ; then + echo "stdin is a tty" +else + echo "stdin is not a tty" +fi + +if [ -t 1 ] ; then + echo "stdout is a tty" +else + echo "stdout is not a tty" +fi + +if [ -t 2 ] ; then + echo "stderr is a tty" +else + echo "stderr is not a tty" +fi + +true diff --git a/tests/fixtures/nohup/is_atty.sh b/tests/fixtures/nohup/is_atty.sh deleted file mode 100644 index 30c07ecee71..00000000000 --- a/tests/fixtures/nohup/is_atty.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -if [ -t 0 ] ; then - echo "stdin is atty" -else - echo "stdin is not atty" -fi - -if [ -t 1 ] ; then - echo "stdout is atty" -else - echo "stdout is not atty" -fi - -if [ -t 2 ] ; then - echo "stderr is atty" -else - echo "stderr is not atty" -fi - -true diff --git a/tests/fixtures/util/is_atty.sh b/tests/fixtures/util/is_a_tty.sh similarity index 50% rename from tests/fixtures/util/is_atty.sh rename to tests/fixtures/util/is_a_tty.sh index 30f8caf32e8..0a6d64494f0 100644 --- a/tests/fixtures/util/is_atty.sh +++ b/tests/fixtures/util/is_a_tty.sh @@ -1,22 +1,22 @@ #!/bin/bash if [ -t 0 ] ; then - echo "stdin is atty" + echo "stdin is a tty" + echo "terminal size: $(stty size)" else - echo "stdin is not atty" + echo "stdin is not a tty" fi if [ -t 1 ] ; then - echo "stdout is atty" + echo "stdout is a tty" else - echo "stdout is not atty" + echo "stdout is not a tty" fi if [ -t 2 ] ; then - echo "stderr is atty" - echo "terminal size: $(stty size)" + echo "stderr is a tty" else - echo "stderr is not atty" + echo "stderr is not a tty" fi >&2 echo "This is an error message."