Skip to content

Commit 22f3b37

Browse files
committed
config terminal simulation for specific stdios only
1 parent 1c13a2d commit 22f3b37

File tree

5 files changed

+191
-75
lines changed

5 files changed

+191
-75
lines changed

tests/by-util/test_nohup.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ fn test_nohup_with_pseudo_terminal_emulation_on_stdin_stdout_stderr_get_replaced
4545
let result = ts
4646
.ucmd()
4747
.terminal_simulation(true)
48-
.args(&["sh", "is_atty.sh"])
48+
.args(&["sh", "is_a_tty.sh"])
4949
.succeeds();
5050

5151
assert_eq!(
@@ -58,6 +58,6 @@ fn test_nohup_with_pseudo_terminal_emulation_on_stdin_stdout_stderr_get_replaced
5858
// this proves that nohup was exchanging the stdio file descriptors
5959
assert_eq!(
6060
std::fs::read_to_string(ts.fixtures.plus_as_string("nohup.out")).unwrap(),
61-
"stdin is not atty\nstdout is not atty\nstderr is not atty\n"
61+
"stdin is not a tty\nstdout is not a tty\nstderr is not a tty\n"
6262
);
6363
}

tests/common/util.rs

Lines changed: 161 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,15 @@ impl TestScenario {
12071207
}
12081208
}
12091209

1210+
#[cfg(unix)]
1211+
#[derive(Debug, Default)]
1212+
pub struct TerminalSimulation {
1213+
size: Option<libc::winsize>,
1214+
stdin: bool,
1215+
stdout: bool,
1216+
stderr: bool,
1217+
}
1218+
12101219
/// A `UCommand` is a builder wrapping an individual Command that provides several additional features:
12111220
/// 1. it has convenience functions that are more ergonomic to use for piping in stdin, spawning the command
12121221
/// and asserting on the results.
@@ -1242,9 +1251,7 @@ pub struct UCommand {
12421251
stderr_to_stdout: bool,
12431252
timeout: Option<Duration>,
12441253
#[cfg(unix)]
1245-
terminal_simulation: bool,
1246-
#[cfg(unix)]
1247-
terminal_size: Option<libc::winsize>,
1254+
terminal_simulation: Option<TerminalSimulation>,
12481255
tmpd: Option<Rc<TempDir>>, // drop last
12491256
}
12501257

@@ -1425,22 +1432,32 @@ impl UCommand {
14251432

14261433
/// Set if process should be run in a simulated terminal
14271434
///
1428-
/// This is useful to test behavior that is only active if [`stdout.is_terminal()`] is [`true`].
1435+
/// This is useful to test behavior that is only active if e.g. [`stdout.is_terminal()`] is [`true`].
1436+
/// This function uses default terminal size and attaches stdin, stdout and stderr to that terminal.
1437+
/// For more control over the terminal simulation, use `terminal_sim_stdio`
14291438
/// (unix: pty, windows: ConPTY[not yet supported])
14301439
#[cfg(unix)]
14311440
pub fn terminal_simulation(&mut self, enable: bool) -> &mut Self {
1432-
self.terminal_simulation = enable;
1441+
if enable {
1442+
self.terminal_simulation = Some(TerminalSimulation {
1443+
stdin: true,
1444+
stdout: true,
1445+
stderr: true,
1446+
..Default::default()
1447+
});
1448+
} else {
1449+
self.terminal_simulation = None;
1450+
}
14331451
self
14341452
}
14351453

1436-
/// Set if process should be run in a simulated terminal with specific size
1454+
/// Allows to simulate a terminal use-case with specific properties.
14371455
///
1438-
/// This is useful to test behavior that is only active if [`stdout.is_terminal()`] is [`true`].
1439-
/// And the size of the terminal matters additionally.
1456+
/// This is useful to test behavior that is only active if e.g. [`stdout.is_terminal()`] is [`true`].
1457+
/// This function allows to set a specific size and to attach the terminal to only parts of the in/out.
14401458
#[cfg(unix)]
1441-
pub fn terminal_size(&mut self, win_size: libc::winsize) -> &mut Self {
1442-
self.terminal_simulation(true);
1443-
self.terminal_size = Some(win_size);
1459+
pub fn terminal_sim_stdio(&mut self, config: TerminalSimulation) -> &mut Self {
1460+
self.terminal_simulation = Some(config);
14441461
self
14451462
}
14461463

@@ -1628,35 +1645,48 @@ impl UCommand {
16281645
};
16291646

16301647
#[cfg(unix)]
1631-
if self.terminal_simulation {
1632-
let terminal_size = self.terminal_size.unwrap_or(libc::winsize {
1648+
if let Some(simulated_terminal) = &self.terminal_simulation {
1649+
let terminal_size = simulated_terminal.size.unwrap_or(libc::winsize {
16331650
ws_col: 80,
16341651
ws_row: 30,
16351652
ws_xpixel: 80 * 8,
16361653
ws_ypixel: 30 * 10,
16371654
});
16381655

1639-
let OpenptyResult {
1640-
slave: pi_slave,
1641-
master: pi_master,
1642-
} = nix::pty::openpty(&terminal_size, None).unwrap();
1643-
let OpenptyResult {
1644-
slave: po_slave,
1645-
master: po_master,
1646-
} = nix::pty::openpty(&terminal_size, None).unwrap();
1647-
let OpenptyResult {
1648-
slave: pe_slave,
1649-
master: pe_master,
1650-
} = nix::pty::openpty(&terminal_size, None).unwrap();
1651-
1652-
stdin_pty = Some(File::from(pi_master));
1653-
1654-
captured_stdout =
1655-
self.spawn_reader_thread(captured_stdout, po_master, "stdout_reader".to_string());
1656-
captured_stderr =
1657-
self.spawn_reader_thread(captured_stderr, pe_master, "stderr_reader".to_string());
1658-
1659-
command.stdin(pi_slave).stdout(po_slave).stderr(pe_slave);
1656+
if simulated_terminal.stdin {
1657+
let OpenptyResult {
1658+
slave: pi_slave,
1659+
master: pi_master,
1660+
} = nix::pty::openpty(&terminal_size, None).unwrap();
1661+
stdin_pty = Some(File::from(pi_master));
1662+
command.stdin(pi_slave);
1663+
}
1664+
1665+
if simulated_terminal.stdout {
1666+
let OpenptyResult {
1667+
slave: po_slave,
1668+
master: po_master,
1669+
} = nix::pty::openpty(&terminal_size, None).unwrap();
1670+
captured_stdout = self.spawn_reader_thread(
1671+
captured_stdout,
1672+
po_master,
1673+
"stdout_reader".to_string(),
1674+
);
1675+
command.stdout(po_slave);
1676+
}
1677+
1678+
if simulated_terminal.stderr {
1679+
let OpenptyResult {
1680+
slave: pe_slave,
1681+
master: pe_master,
1682+
} = nix::pty::openpty(&terminal_size, None).unwrap();
1683+
captured_stderr = self.spawn_reader_thread(
1684+
captured_stderr,
1685+
pe_master,
1686+
"stderr_reader".to_string(),
1687+
);
1688+
command.stderr(pe_slave);
1689+
}
16601690
}
16611691

16621692
#[cfg(unix)]
@@ -3609,10 +3639,10 @@ mod tests {
36093639
fn test_simulation_of_terminal_false() {
36103640
let scene = TestScenario::new("util");
36113641

3612-
let out = scene.ccmd("env").arg("sh").arg("is_atty.sh").succeeds();
3642+
let out = scene.ccmd("env").arg("sh").arg("is_a_tty.sh").succeeds();
36133643
std::assert_eq!(
36143644
String::from_utf8_lossy(out.stdout()),
3615-
"stdin is not atty\nstdout is not atty\nstderr is not atty\n"
3645+
"stdin is not a tty\nstdout is not a tty\nstderr is not a tty\n"
36163646
);
36173647
std::assert_eq!(
36183648
String::from_utf8_lossy(out.stderr()),
@@ -3629,12 +3659,93 @@ mod tests {
36293659
let out = scene
36303660
.ccmd("env")
36313661
.arg("sh")
3632-
.arg("is_atty.sh")
3662+
.arg("is_a_tty.sh")
36333663
.terminal_simulation(true)
36343664
.succeeds();
36353665
std::assert_eq!(
36363666
String::from_utf8_lossy(out.stdout()),
3637-
"stdin is atty\r\nstdout is atty\r\nstderr is atty\r\nterminal size: 30 80\r\n"
3667+
"stdin is a tty\r\nterminal size: 30 80\r\nstdout is a tty\r\nstderr is a tty\r\n"
3668+
);
3669+
std::assert_eq!(
3670+
String::from_utf8_lossy(out.stderr()),
3671+
"This is an error message.\r\n"
3672+
);
3673+
}
3674+
3675+
#[cfg(unix)]
3676+
#[cfg(feature = "env")]
3677+
#[test]
3678+
fn test_simulation_of_terminal_for_stdin_only() {
3679+
let scene = TestScenario::new("util");
3680+
3681+
let out = scene
3682+
.ccmd("env")
3683+
.arg("sh")
3684+
.arg("is_a_tty.sh")
3685+
.terminal_sim_stdio(TerminalSimulation {
3686+
stdin: true,
3687+
stdout: false,
3688+
stderr: false,
3689+
..Default::default()
3690+
})
3691+
.succeeds();
3692+
std::assert_eq!(
3693+
String::from_utf8_lossy(out.stdout()),
3694+
"stdin is a tty\nterminal size: 30 80\nstdout is not a tty\nstderr is not a tty\n"
3695+
);
3696+
std::assert_eq!(
3697+
String::from_utf8_lossy(out.stderr()),
3698+
"This is an error message.\n"
3699+
);
3700+
}
3701+
3702+
#[cfg(unix)]
3703+
#[cfg(feature = "env")]
3704+
#[test]
3705+
fn test_simulation_of_terminal_for_stdout_only() {
3706+
let scene = TestScenario::new("util");
3707+
3708+
let out = scene
3709+
.ccmd("env")
3710+
.arg("sh")
3711+
.arg("is_a_tty.sh")
3712+
.terminal_sim_stdio(TerminalSimulation {
3713+
stdin: false,
3714+
stdout: true,
3715+
stderr: false,
3716+
..Default::default()
3717+
})
3718+
.succeeds();
3719+
std::assert_eq!(
3720+
String::from_utf8_lossy(out.stdout()),
3721+
"stdin is not a tty\r\nstdout is a tty\r\nstderr is not a tty\r\n"
3722+
);
3723+
std::assert_eq!(
3724+
String::from_utf8_lossy(out.stderr()),
3725+
"This is an error message.\n"
3726+
);
3727+
}
3728+
3729+
#[cfg(unix)]
3730+
#[cfg(feature = "env")]
3731+
#[test]
3732+
fn test_simulation_of_terminal_for_stderr_only() {
3733+
let scene = TestScenario::new("util");
3734+
3735+
let out = scene
3736+
.ccmd("env")
3737+
.arg("sh")
3738+
.arg("is_a_tty.sh")
3739+
.terminal_sim_stdio(TerminalSimulation {
3740+
stdin: false,
3741+
stdout: false,
3742+
stderr: true,
3743+
..Default::default()
3744+
})
3745+
.succeeds();
3746+
std::assert_eq!(
3747+
String::from_utf8_lossy(out.stdout()),
3748+
"stdin is not a tty\nstdout is not a tty\nstderr is a tty\n"
36383749
);
36393750
std::assert_eq!(
36403751
String::from_utf8_lossy(out.stderr()),
@@ -3651,17 +3762,22 @@ mod tests {
36513762
let out = scene
36523763
.ccmd("env")
36533764
.arg("sh")
3654-
.arg("is_atty.sh")
3655-
.terminal_size(libc::winsize {
3656-
ws_col: 40,
3657-
ws_row: 10,
3658-
ws_xpixel: 40 * 8,
3659-
ws_ypixel: 10 * 10,
3765+
.arg("is_a_tty.sh")
3766+
.terminal_sim_stdio(TerminalSimulation {
3767+
size: Some(libc::winsize {
3768+
ws_col: 40,
3769+
ws_row: 10,
3770+
ws_xpixel: 40 * 8,
3771+
ws_ypixel: 10 * 10,
3772+
}),
3773+
stdout: true,
3774+
stdin: true,
3775+
stderr: true,
36603776
})
36613777
.succeeds();
36623778
std::assert_eq!(
36633779
String::from_utf8_lossy(out.stdout()),
3664-
"stdin is atty\r\nstdout is atty\r\nstderr is atty\r\nterminal size: 10 40\r\n"
3780+
"stdin is a tty\r\nterminal size: 10 40\r\nstdout is a tty\r\nstderr is a tty\r\n"
36653781
);
36663782
std::assert_eq!(
36673783
String::from_utf8_lossy(out.stderr()),

tests/fixtures/nohup/is_a_tty.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/bash
2+
3+
if [ -t 0 ] ; then
4+
echo "stdin is a tty"
5+
else
6+
echo "stdin is not a tty"
7+
fi
8+
9+
if [ -t 1 ] ; then
10+
echo "stdout is a tty"
11+
else
12+
echo "stdout is not a tty"
13+
fi
14+
15+
if [ -t 2 ] ; then
16+
echo "stderr is a tty"
17+
else
18+
echo "stderr is not a tty"
19+
fi
20+
21+
true

tests/fixtures/nohup/is_atty.sh

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
#!/bin/bash
22

33
if [ -t 0 ] ; then
4-
echo "stdin is atty"
4+
echo "stdin is a tty"
5+
echo "terminal size: $(stty size)"
56
else
6-
echo "stdin is not atty"
7+
echo "stdin is not a tty"
78
fi
89

910
if [ -t 1 ] ; then
10-
echo "stdout is atty"
11+
echo "stdout is a tty"
1112
else
12-
echo "stdout is not atty"
13+
echo "stdout is not a tty"
1314
fi
1415

1516
if [ -t 2 ] ; then
16-
echo "stderr is atty"
17-
echo "terminal size: $(stty size)"
17+
echo "stderr is a tty"
1818
else
19-
echo "stderr is not atty"
19+
echo "stderr is not a tty"
2020
fi
2121

2222
>&2 echo "This is an error message."

0 commit comments

Comments
 (0)