Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

config terminal simulation for specific stdios only #6125

Merged
merged 1 commit into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions tests/by-util/test_nohup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand All @@ -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"
);
}
206 changes: 161 additions & 45 deletions tests/common/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,15 @@
}
}

#[cfg(unix)]
#[derive(Debug, Default)]
pub struct TerminalSimulation {
size: Option<libc::winsize>,
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.
Expand Down Expand Up @@ -1242,9 +1251,7 @@
stderr_to_stdout: bool,
timeout: Option<Duration>,
#[cfg(unix)]
terminal_simulation: bool,
#[cfg(unix)]
terminal_size: Option<libc::winsize>,
terminal_simulation: Option<TerminalSimulation>,
tmpd: Option<Rc<TempDir>>, // drop last
}

Expand Down Expand Up @@ -1425,22 +1432,32 @@

/// 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;

Check warning on line 1449 in tests/common/util.rs

View check run for this annotation

Codecov / codecov/patch

tests/common/util.rs#L1449

Added line #L1449 was not covered by tests
}
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
}

Expand Down Expand Up @@ -1628,35 +1645,48 @@
};

#[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)]
Expand Down Expand Up @@ -3609,10 +3639,10 @@
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()),
Expand All @@ -3629,12 +3659,93 @@
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()),
Expand All @@ -3651,17 +3762,22 @@
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()),
Expand Down
21 changes: 21 additions & 0 deletions tests/fixtures/nohup/is_a_tty.sh
Original file line number Diff line number Diff line change
@@ -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
21 changes: 0 additions & 21 deletions tests/fixtures/nohup/is_atty.sh

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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."
Expand Down
Loading