Skip to content

Commit 684e1f9

Browse files
committed
config terminal simulation for specific stdios only
1 parent 1bb5111 commit 684e1f9

File tree

2 files changed

+158
-42
lines changed

2 files changed

+158
-42
lines changed

tests/common/util.rs

Lines changed: 157 additions & 41 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)]
@@ -3634,7 +3664,88 @@ mod tests {
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 atty\r\nterminal size: 30 80\r\nstdout is atty\r\nstderr is atty\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_atty.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 atty\nterminal size: 30 80\nstdout is not atty\nstderr is not atty\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_atty.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 atty\r\nstdout is atty\r\nstderr is not atty\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_atty.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 atty\nstdout is not atty\nstderr is atty\n"
36383749
);
36393750
std::assert_eq!(
36403751
String::from_utf8_lossy(out.stderr()),
@@ -3652,16 +3763,21 @@ mod tests {
36523763
.ccmd("env")
36533764
.arg("sh")
36543765
.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,
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 atty\r\nterminal size: 10 40\r\nstdout is atty\r\nstderr is atty\r\n"
36653781
);
36663782
std::assert_eq!(
36673783
String::from_utf8_lossy(out.stderr()),

tests/fixtures/util/is_atty.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
if [ -t 0 ] ; then
44
echo "stdin is atty"
5+
echo "terminal size: $(stty size)"
56
else
67
echo "stdin is not atty"
78
fi
@@ -14,7 +15,6 @@ fi
1415

1516
if [ -t 2 ] ; then
1617
echo "stderr is atty"
17-
echo "terminal size: $(stty size)"
1818
else
1919
echo "stderr is not atty"
2020
fi

0 commit comments

Comments
 (0)