mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
config terminal simulation for specific stdios only
This commit is contained in:
parent
eacf53d010
commit
537941b676
5 changed files with 189 additions and 73 deletions
|
@ -45,7 +45,7 @@ fn test_nohup_with_pseudo_terminal_emulation_on_stdin_stdout_stderr_get_replaced
|
||||||
let result = ts
|
let result = ts
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.terminal_simulation(true)
|
.terminal_simulation(true)
|
||||||
.args(&["sh", "is_atty.sh"])
|
.args(&["sh", "is_a_tty.sh"])
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
|
||||||
assert_eq!(
|
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
|
// this proves that nohup was exchanging the stdio file descriptors
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
std::fs::read_to_string(ts.fixtures.plus_as_string("nohup.out")).unwrap(),
|
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"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1207,6 +1207,15 @@ impl TestScenario {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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:
|
/// 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
|
/// 1. it has convenience functions that are more ergonomic to use for piping in stdin, spawning the command
|
||||||
/// and asserting on the results.
|
/// and asserting on the results.
|
||||||
|
@ -1242,9 +1251,7 @@ pub struct UCommand {
|
||||||
stderr_to_stdout: bool,
|
stderr_to_stdout: bool,
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
terminal_simulation: bool,
|
terminal_simulation: Option<TerminalSimulation>,
|
||||||
#[cfg(unix)]
|
|
||||||
terminal_size: Option<libc::winsize>,
|
|
||||||
tmpd: Option<Rc<TempDir>>, // drop last
|
tmpd: Option<Rc<TempDir>>, // drop last
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1425,22 +1432,32 @@ impl UCommand {
|
||||||
|
|
||||||
/// Set if process should be run in a simulated terminal
|
/// 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])
|
/// (unix: pty, windows: ConPTY[not yet supported])
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn terminal_simulation(&mut self, enable: bool) -> &mut Self {
|
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
|
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`].
|
/// This is useful to test behavior that is only active if e.g. [`stdout.is_terminal()`] is [`true`].
|
||||||
/// And the size of the terminal matters additionally.
|
/// This function allows to set a specific size and to attach the terminal to only parts of the in/out.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn terminal_size(&mut self, win_size: libc::winsize) -> &mut Self {
|
pub fn terminal_sim_stdio(&mut self, config: TerminalSimulation) -> &mut Self {
|
||||||
self.terminal_simulation(true);
|
self.terminal_simulation = Some(config);
|
||||||
self.terminal_size = Some(win_size);
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1628,35 +1645,48 @@ impl UCommand {
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
if self.terminal_simulation {
|
if let Some(simulated_terminal) = &self.terminal_simulation {
|
||||||
let terminal_size = self.terminal_size.unwrap_or(libc::winsize {
|
let terminal_size = simulated_terminal.size.unwrap_or(libc::winsize {
|
||||||
ws_col: 80,
|
ws_col: 80,
|
||||||
ws_row: 30,
|
ws_row: 30,
|
||||||
ws_xpixel: 80 * 8,
|
ws_xpixel: 80 * 8,
|
||||||
ws_ypixel: 30 * 10,
|
ws_ypixel: 30 * 10,
|
||||||
});
|
});
|
||||||
|
|
||||||
let OpenptyResult {
|
if simulated_terminal.stdin {
|
||||||
slave: pi_slave,
|
let OpenptyResult {
|
||||||
master: pi_master,
|
slave: pi_slave,
|
||||||
} = nix::pty::openpty(&terminal_size, None).unwrap();
|
master: pi_master,
|
||||||
let OpenptyResult {
|
} = nix::pty::openpty(&terminal_size, None).unwrap();
|
||||||
slave: po_slave,
|
stdin_pty = Some(File::from(pi_master));
|
||||||
master: po_master,
|
command.stdin(pi_slave);
|
||||||
} = 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));
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
captured_stdout =
|
if simulated_terminal.stderr {
|
||||||
self.spawn_reader_thread(captured_stdout, po_master, "stdout_reader".to_string());
|
let OpenptyResult {
|
||||||
captured_stderr =
|
slave: pe_slave,
|
||||||
self.spawn_reader_thread(captured_stderr, pe_master, "stderr_reader".to_string());
|
master: pe_master,
|
||||||
|
} = nix::pty::openpty(&terminal_size, None).unwrap();
|
||||||
command.stdin(pi_slave).stdout(po_slave).stderr(pe_slave);
|
captured_stderr = self.spawn_reader_thread(
|
||||||
|
captured_stderr,
|
||||||
|
pe_master,
|
||||||
|
"stderr_reader".to_string(),
|
||||||
|
);
|
||||||
|
command.stderr(pe_slave);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -3609,10 +3639,10 @@ mod tests {
|
||||||
fn test_simulation_of_terminal_false() {
|
fn test_simulation_of_terminal_false() {
|
||||||
let scene = TestScenario::new("util");
|
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!(
|
std::assert_eq!(
|
||||||
String::from_utf8_lossy(out.stdout()),
|
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!(
|
std::assert_eq!(
|
||||||
String::from_utf8_lossy(out.stderr()),
|
String::from_utf8_lossy(out.stderr()),
|
||||||
|
@ -3629,12 +3659,93 @@ mod tests {
|
||||||
let out = scene
|
let out = scene
|
||||||
.ccmd("env")
|
.ccmd("env")
|
||||||
.arg("sh")
|
.arg("sh")
|
||||||
.arg("is_atty.sh")
|
.arg("is_a_tty.sh")
|
||||||
.terminal_simulation(true)
|
.terminal_simulation(true)
|
||||||
.succeeds();
|
.succeeds();
|
||||||
std::assert_eq!(
|
std::assert_eq!(
|
||||||
String::from_utf8_lossy(out.stdout()),
|
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!(
|
std::assert_eq!(
|
||||||
String::from_utf8_lossy(out.stderr()),
|
String::from_utf8_lossy(out.stderr()),
|
||||||
|
@ -3651,17 +3762,22 @@ mod tests {
|
||||||
let out = scene
|
let out = scene
|
||||||
.ccmd("env")
|
.ccmd("env")
|
||||||
.arg("sh")
|
.arg("sh")
|
||||||
.arg("is_atty.sh")
|
.arg("is_a_tty.sh")
|
||||||
.terminal_size(libc::winsize {
|
.terminal_sim_stdio(TerminalSimulation {
|
||||||
ws_col: 40,
|
size: Some(libc::winsize {
|
||||||
ws_row: 10,
|
ws_col: 40,
|
||||||
ws_xpixel: 40 * 8,
|
ws_row: 10,
|
||||||
ws_ypixel: 10 * 10,
|
ws_xpixel: 40 * 8,
|
||||||
|
ws_ypixel: 10 * 10,
|
||||||
|
}),
|
||||||
|
stdout: true,
|
||||||
|
stdin: true,
|
||||||
|
stderr: true,
|
||||||
})
|
})
|
||||||
.succeeds();
|
.succeeds();
|
||||||
std::assert_eq!(
|
std::assert_eq!(
|
||||||
String::from_utf8_lossy(out.stdout()),
|
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!(
|
std::assert_eq!(
|
||||||
String::from_utf8_lossy(out.stderr()),
|
String::from_utf8_lossy(out.stderr()),
|
||||||
|
|
21
tests/fixtures/nohup/is_a_tty.sh
vendored
Normal file
21
tests/fixtures/nohup/is_a_tty.sh
vendored
Normal file
|
@ -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
tests/fixtures/nohup/is_atty.sh
vendored
21
tests/fixtures/nohup/is_atty.sh
vendored
|
@ -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
|
|
|
@ -1,22 +1,22 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
if [ -t 0 ] ; then
|
if [ -t 0 ] ; then
|
||||||
echo "stdin is atty"
|
echo "stdin is a tty"
|
||||||
|
echo "terminal size: $(stty size)"
|
||||||
else
|
else
|
||||||
echo "stdin is not atty"
|
echo "stdin is not a tty"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -t 1 ] ; then
|
if [ -t 1 ] ; then
|
||||||
echo "stdout is atty"
|
echo "stdout is a tty"
|
||||||
else
|
else
|
||||||
echo "stdout is not atty"
|
echo "stdout is not a tty"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -t 2 ] ; then
|
if [ -t 2 ] ; then
|
||||||
echo "stderr is atty"
|
echo "stderr is a tty"
|
||||||
echo "terminal size: $(stty size)"
|
|
||||||
else
|
else
|
||||||
echo "stderr is not atty"
|
echo "stderr is not a tty"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
>&2 echo "This is an error message."
|
>&2 echo "This is an error message."
|
Loading…
Add table
Add a link
Reference in a new issue