@@ -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 \n stdout is not atty \n stderr is not atty \n "
3645+ "stdin is not a tty \n stdout is not a tty \n stderr 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 \n stdout is atty\r \n stderr is atty\r \n terminal size: 30 80\r \n "
3667+ "stdin is a tty\r \n terminal size: 30 80\r \n stdout is a tty\r \n stderr 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\n terminal size: 30 80\n stdout is not a tty\n stderr 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 \n stdout is a tty\r \n stderr 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\n stdout is not a tty\n stderr 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 \n stdout is atty \r \n stderr is atty \r \n terminal size: 10 40 \r \n "
3780+ "stdin is a tty \r \n terminal size: 10 40 \r \n stdout is a tty \r \n stderr is a tty \r \n "
36653781 ) ;
36663782 std:: assert_eq!(
36673783 String :: from_utf8_lossy( out. stderr( ) ) ,
0 commit comments