2023-08-24 10:07:48 +00:00
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
2021-07-07 14:19:58 +00:00
2024-03-05 14:56:20 +00:00
//spell-checker: ignore (linux) rlimit prlimit coreutil ggroups uchild uncaptured scmd SHLVL canonicalized openpty
//spell-checker: ignore (linux) winsize xpixel ypixel setrlimit FSIZE
2021-06-02 16:06:56 +00:00
2021-03-22 10:13:38 +00:00
#![ allow(dead_code) ]
2021-06-02 16:06:56 +00:00
2024-02-07 21:02:19 +00:00
#[ cfg(unix) ]
use nix ::pty ::OpenptyResult ;
2021-04-24 14:43:13 +00:00
use pretty_assertions ::assert_eq ;
2024-03-05 14:56:20 +00:00
#[ cfg(unix) ]
use rlimit ::setrlimit ;
2023-03-26 09:53:20 +00:00
#[ cfg(feature = " sleep " ) ]
2022-12-10 18:57:25 +00:00
use rstest ::rstest ;
2021-07-07 12:30:38 +00:00
use std ::borrow ::Cow ;
2023-01-25 02:40:39 +00:00
use std ::collections ::VecDeque ;
2021-04-01 21:37:22 +00:00
#[ cfg(not(windows)) ]
use std ::ffi ::CString ;
2023-01-22 16:46:25 +00:00
use std ::ffi ::{ OsStr , OsString } ;
2022-11-14 17:39:48 +00:00
use std ::fs ::{ self , hard_link , remove_file , File , OpenOptions } ;
use std ::io ::{ self , BufWriter , Read , Result , Write } ;
2015-11-16 05:25:01 +00:00
#[ cfg(unix) ]
2024-02-07 21:02:19 +00:00
use std ::os ::fd ::OwnedFd ;
#[ cfg(unix) ]
2022-09-23 21:42:35 +00:00
use std ::os ::unix ::fs ::{ symlink as symlink_dir , symlink as symlink_file , PermissionsExt } ;
2023-01-05 15:51:42 +00:00
#[ cfg(unix) ]
2024-03-05 14:56:20 +00:00
use std ::os ::unix ::process ::CommandExt ;
#[ cfg(unix) ]
2023-01-05 15:51:42 +00:00
use std ::os ::unix ::process ::ExitStatusExt ;
2015-11-16 05:25:01 +00:00
#[ cfg(windows) ]
2018-11-13 04:55:25 +00:00
use std ::os ::windows ::fs ::{ symlink_dir , symlink_file } ;
2022-07-10 14:49:25 +00:00
#[ cfg(windows) ]
use std ::path ::MAIN_SEPARATOR ;
2015-11-16 05:25:01 +00:00
use std ::path ::{ Path , PathBuf } ;
2023-01-04 20:30:14 +00:00
use std ::process ::{ Child , Command , ExitStatus , Output , Stdio } ;
2015-11-16 05:25:01 +00:00
use std ::rc ::Rc ;
2022-12-10 18:57:25 +00:00
use std ::sync ::mpsc ::{ self , RecvTimeoutError } ;
2022-11-14 17:39:48 +00:00
use std ::thread ::{ sleep , JoinHandle } ;
2022-12-10 18:57:25 +00:00
use std ::time ::{ Duration , Instant } ;
2024-02-07 21:02:19 +00:00
use std ::{ env , hint , mem , thread } ;
2022-11-23 05:43:02 +00:00
use tempfile ::{ Builder , TempDir } ;
2015-11-16 05:25:01 +00:00
2020-02-07 04:40:45 +00:00
static TESTS_DIR : & str = " tests " ;
static FIXTURES_DIR : & str = " fixtures " ;
2016-05-22 19:09:04 +00:00
2020-02-07 04:40:45 +00:00
static ALREADY_RUN : & str = " you have already run this UCommand, if you want to run \
2021-04-02 09:56:49 +00:00
another command in the same test , use TestScenario ::new instead of \
testing ( ) ; " ;
2020-02-07 04:40:45 +00:00
static MULTIPLE_STDIN_MEANINGLESS : & str = " Ucommand is designed around a typical use case of: provide args and input stream -> spawn process -> block until completion -> return output streams. For verifying that a particular section of the input stream is what causes a particular behavior, use the Command type directly. " ;
2016-03-26 19:27:54 +00:00
2021-04-09 09:08:31 +00:00
static NO_STDIN_MEANINGLESS : & str = " Setting this flag has no effect if there is no stdin " ;
2024-02-07 21:02:19 +00:00
static END_OF_TRANSMISSION_SEQUENCE : & [ u8 ] = & [ b '\n' , 0x04 ] ;
2021-04-09 09:08:31 +00:00
2023-01-22 16:46:25 +00:00
pub const TESTS_BINARY : & str = env! ( " CARGO_BIN_EXE_coreutils " ) ;
2023-03-24 02:52:02 +00:00
pub const PATH : & str = env! ( " PATH " ) ;
2023-01-22 16:46:25 +00:00
2023-03-23 17:15:05 +00:00
/// Default environment variables to run the commands with
const DEFAULT_ENV : [ ( & str , & str ) ; 2 ] = [ ( " LC_ALL " , " C " ) , ( " TZ " , " UTC " ) ] ;
2021-03-22 10:13:38 +00:00
/// Test if the program is running under CI
2020-06-13 23:54:43 +00:00
pub fn is_ci ( ) -> bool {
2024-01-01 16:45:20 +00:00
std ::env ::var ( " CI " ) . is_ok_and ( | s | s . eq_ignore_ascii_case ( " true " ) )
2020-06-13 23:54:43 +00:00
}
2021-03-31 09:25:23 +00:00
/// Read a test scenario fixture, returning its bytes
fn read_scenario_fixture < S : AsRef < OsStr > > ( tmpd : & Option < Rc < TempDir > > , file_rel_path : S ) -> Vec < u8 > {
2016-07-29 21:26:32 +00:00
let tmpdir_path = tmpd . as_ref ( ) . unwrap ( ) . as_ref ( ) . path ( ) ;
2021-03-31 09:25:23 +00:00
AtPath ::new ( tmpdir_path ) . read_bytes ( file_rel_path . as_ref ( ) . to_str ( ) . unwrap ( ) )
2016-07-29 21:26:32 +00:00
}
2016-05-22 19:09:04 +00:00
2016-07-17 09:20:33 +00:00
/// A command result is the outputs of a command (streams and status code)
/// within a struct which has convenience assertion functions about those outputs
2021-04-10 18:18:38 +00:00
#[ derive(Debug, Clone) ]
2015-11-16 05:25:01 +00:00
pub struct CmdResult {
2021-11-09 17:37:38 +00:00
/// bin_path provided by `TestScenario` or `UCommand`
2023-01-25 02:40:39 +00:00
bin_path : PathBuf ,
2021-11-09 17:37:38 +00:00
/// util_name provided by `TestScenario` or `UCommand`
util_name : Option < String > ,
2016-07-29 21:26:32 +00:00
//tmpd is used for convenience functions for asserts against fixtures
tmpd : Option < Rc < TempDir > > ,
2021-02-23 09:21:01 +00:00
/// exit status for command (if there is one)
2023-01-04 20:30:14 +00:00
exit_status : Option < ExitStatus > ,
2021-03-31 09:25:23 +00:00
/// captured standard output after running the Command
2021-05-26 20:15:28 +00:00
stdout : Vec < u8 > ,
2021-03-31 09:25:23 +00:00
/// captured standard error after running the Command
2021-05-26 20:15:28 +00:00
stderr : Vec < u8 > ,
2015-11-16 05:25:01 +00:00
}
2016-02-17 05:03:52 +00:00
impl CmdResult {
2023-01-25 02:40:39 +00:00
pub fn new < S , T , U , V > (
bin_path : S ,
util_name : Option < T > ,
2021-06-10 09:01:44 +00:00
tmpd : Option < Rc < TempDir > > ,
2023-01-04 20:30:14 +00:00
exit_status : Option < ExitStatus > ,
2023-01-25 02:40:39 +00:00
stdout : U ,
stderr : V ,
2022-11-30 06:35:52 +00:00
) -> Self
where
2023-01-25 02:40:39 +00:00
S : Into < PathBuf > ,
T : AsRef < str > ,
2022-11-30 06:35:52 +00:00
U : Into < Vec < u8 > > ,
2023-01-25 02:40:39 +00:00
V : Into < Vec < u8 > > ,
2022-11-30 06:35:52 +00:00
{
2022-01-30 13:59:31 +00:00
Self {
2023-01-25 02:40:39 +00:00
bin_path : bin_path . into ( ) ,
util_name : util_name . map ( | s | s . as_ref ( ) . into ( ) ) ,
2021-06-10 09:01:44 +00:00
tmpd ,
2023-01-04 20:30:14 +00:00
exit_status ,
2022-11-30 06:35:52 +00:00
stdout : stdout . into ( ) ,
stderr : stderr . into ( ) ,
2021-06-10 09:01:44 +00:00
}
}
2023-01-05 18:47:50 +00:00
/// Apply a function to `stdout` as bytes and return a new [`CmdResult`]
2022-11-30 06:35:52 +00:00
pub fn stdout_apply < ' a , F , R > ( & ' a self , function : F ) -> Self
where
F : Fn ( & ' a [ u8 ] ) -> R ,
R : Into < Vec < u8 > > ,
{
Self ::new (
self . bin_path . clone ( ) ,
self . util_name . clone ( ) ,
self . tmpd . clone ( ) ,
2023-01-04 20:30:14 +00:00
self . exit_status ,
2022-11-30 06:35:52 +00:00
function ( & self . stdout ) ,
self . stderr . as_slice ( ) ,
)
}
2023-01-05 18:47:50 +00:00
/// Apply a function to `stdout` as `&str` and return a new [`CmdResult`]
2022-11-30 06:35:52 +00:00
pub fn stdout_str_apply < ' a , F , R > ( & ' a self , function : F ) -> Self
where
F : Fn ( & ' a str ) -> R ,
R : Into < Vec < u8 > > ,
{
Self ::new (
self . bin_path . clone ( ) ,
self . util_name . clone ( ) ,
self . tmpd . clone ( ) ,
2023-01-04 20:30:14 +00:00
self . exit_status ,
2022-11-30 06:35:52 +00:00
function ( self . stdout_str ( ) ) ,
self . stderr . as_slice ( ) ,
)
}
2023-01-05 18:47:50 +00:00
/// Apply a function to `stderr` as bytes and return a new [`CmdResult`]
2022-11-30 06:35:52 +00:00
pub fn stderr_apply < ' a , F , R > ( & ' a self , function : F ) -> Self
where
F : Fn ( & ' a [ u8 ] ) -> R ,
R : Into < Vec < u8 > > ,
{
Self ::new (
self . bin_path . clone ( ) ,
self . util_name . clone ( ) ,
self . tmpd . clone ( ) ,
2023-01-04 20:30:14 +00:00
self . exit_status ,
2022-11-30 06:35:52 +00:00
self . stdout . as_slice ( ) ,
function ( & self . stderr ) ,
)
}
2023-01-05 18:47:50 +00:00
/// Apply a function to `stderr` as `&str` and return a new [`CmdResult`]
2022-11-30 06:35:52 +00:00
pub fn stderr_str_apply < ' a , F , R > ( & ' a self , function : F ) -> Self
where
F : Fn ( & ' a str ) -> R ,
R : Into < Vec < u8 > > ,
{
Self ::new (
self . bin_path . clone ( ) ,
self . util_name . clone ( ) ,
self . tmpd . clone ( ) ,
2023-01-04 20:30:14 +00:00
self . exit_status ,
2022-11-30 06:35:52 +00:00
self . stdout . as_slice ( ) ,
function ( self . stderr_str ( ) ) ,
)
}
2023-01-05 18:47:50 +00:00
/// Assert `stdout` as bytes with a predicate function returning a `bool`.
#[ track_caller ]
pub fn stdout_check < ' a , F > ( & ' a self , predicate : F ) -> & Self
where
F : Fn ( & ' a [ u8 ] ) -> bool ,
{
assert! (
predicate ( & self . stdout ) ,
" Predicate for stdout as `bytes` evaluated to false. \n stdout='{:?}' \n stderr='{:?}' \n " ,
& self . stdout ,
& self . stderr
) ;
self
}
/// Assert `stdout` as `&str` with a predicate function returning a `bool`.
#[ track_caller ]
pub fn stdout_str_check < ' a , F > ( & ' a self , predicate : F ) -> & Self
where
F : Fn ( & ' a str ) -> bool ,
{
assert! (
predicate ( self . stdout_str ( ) ) ,
" Predicate for stdout as `str` evaluated to false. \n stdout='{}' \n stderr='{}' \n " ,
self . stdout_str ( ) ,
self . stderr_str ( )
) ;
self
}
/// Assert `stderr` as bytes with a predicate function returning a `bool`.
#[ track_caller ]
pub fn stderr_check < ' a , F > ( & ' a self , predicate : F ) -> & Self
where
F : Fn ( & ' a [ u8 ] ) -> bool ,
{
assert! (
predicate ( & self . stderr ) ,
" Predicate for stderr as `bytes` evaluated to false. \n stdout='{:?}' \n stderr='{:?}' \n " ,
& self . stdout ,
& self . stderr
) ;
self
}
/// Assert `stderr` as `&str` with a predicate function returning a `bool`.
#[ track_caller ]
pub fn stderr_str_check < ' a , F > ( & ' a self , predicate : F ) -> & Self
where
F : Fn ( & ' a str ) -> bool ,
{
assert! (
predicate ( self . stderr_str ( ) ) ,
" Predicate for stderr as `str` evaluated to false. \n stdout='{}' \n stderr='{}' \n " ,
self . stdout_str ( ) ,
self . stderr_str ( )
) ;
self
}
2023-01-05 15:51:42 +00:00
/// Return the exit status of the child process, if any.
///
/// Returns None if the child process is still running or hasn't been started.
pub fn try_exit_status ( & self ) -> Option < ExitStatus > {
self . exit_status
}
/// Return the exit status of the child process.
///
/// # Panics
///
/// If the child process is still running or hasn't been started.
pub fn exit_status ( & self ) -> ExitStatus {
self . try_exit_status ( )
. expect ( " Program must be run first or has not finished, yet " )
}
/// Return the signal the child process received if any.
///
/// # Platform specific behavior
///
/// This method is only available on unix systems.
#[ cfg(unix) ]
pub fn signal ( & self ) -> Option < i32 > {
self . exit_status ( ) . signal ( )
}
/// Assert that the given signal `value` equals the signal the child process received.
///
/// See also [`std::os::unix::process::ExitStatusExt::signal`].
///
/// # Platform specific behavior
///
/// This assertion method is only available on unix systems.
#[ cfg(unix) ]
#[ track_caller ]
pub fn signal_is ( & self , value : i32 ) -> & Self {
let actual = self . signal ( ) . unwrap_or_else ( | | {
panic! (
" Expected process to be terminated by the '{}' signal, but exit status is: '{}' " ,
value ,
self . try_exit_status ( )
. map_or ( " Not available " . to_string ( ) , | e | e . to_string ( ) )
)
} ) ;
assert_eq! ( actual , value ) ;
self
}
/// Assert that the given signal `name` equals the signal the child process received.
///
/// Strings like `SIGINT`, `INT` or a number like `15` are all valid names. See also
/// [`std::os::unix::process::ExitStatusExt::signal`] and
/// [`uucore::signals::signal_by_name_or_value`]
///
/// # Platform specific behavior
///
/// This assertion method is only available on unix systems.
#[ cfg(unix) ]
#[ track_caller ]
pub fn signal_name_is ( & self , name : & str ) -> & Self {
use uucore ::signals ::signal_by_name_or_value ;
let expected : i32 = signal_by_name_or_value ( name )
2023-01-27 09:29:45 +00:00
. unwrap_or_else ( | | panic! ( " Invalid signal name or value: ' {name} ' " ) )
2023-01-05 15:51:42 +00:00
. try_into ( )
. unwrap ( ) ;
let actual = self . signal ( ) . unwrap_or_else ( | | {
panic! (
" Expected process to be terminated by the '{}' signal, but exit status is: '{}' " ,
name ,
self . try_exit_status ( )
. map_or ( " Not available " . to_string ( ) , | e | e . to_string ( ) )
)
} ) ;
assert_eq! ( actual , expected ) ;
self
}
2021-03-31 09:25:23 +00:00
/// Returns a reference to the program's standard output as a slice of bytes
pub fn stdout ( & self ) -> & [ u8 ] {
2021-05-26 20:15:28 +00:00
& self . stdout
2021-03-31 09:25:23 +00:00
}
/// Returns the program's standard output as a string slice
pub fn stdout_str ( & self ) -> & str {
2021-05-26 20:15:28 +00:00
std ::str ::from_utf8 ( & self . stdout ) . unwrap ( )
2021-03-31 09:25:23 +00:00
}
/// Returns the program's standard output as a string
/// consumes self
pub fn stdout_move_str ( self ) -> String {
2021-05-26 20:15:28 +00:00
String ::from_utf8 ( self . stdout ) . unwrap ( )
2021-03-31 09:25:23 +00:00
}
/// Returns the program's standard output as a vec of bytes
/// consumes self
pub fn stdout_move_bytes ( self ) -> Vec < u8 > {
2021-05-26 20:15:28 +00:00
self . stdout
2021-03-31 09:25:23 +00:00
}
/// Returns a reference to the program's standard error as a slice of bytes
pub fn stderr ( & self ) -> & [ u8 ] {
2021-05-26 20:15:28 +00:00
& self . stderr
2021-03-31 09:25:23 +00:00
}
/// Returns the program's standard error as a string slice
pub fn stderr_str ( & self ) -> & str {
2021-05-26 20:15:28 +00:00
std ::str ::from_utf8 ( & self . stderr ) . unwrap ( )
2021-03-31 09:25:23 +00:00
}
2024-03-14 18:38:28 +00:00
/// Returns the program's standard error as a string slice, automatically handling invalid utf8
pub fn stderr_str_lossy ( & self ) -> Cow < '_ , str > {
String ::from_utf8_lossy ( & self . stderr )
}
2021-03-31 09:25:23 +00:00
/// Returns the program's standard error as a string
/// consumes self
pub fn stderr_move_str ( self ) -> String {
2021-05-26 20:15:28 +00:00
String ::from_utf8 ( self . stderr ) . unwrap ( )
2021-03-31 09:25:23 +00:00
}
/// Returns the program's standard error as a vec of bytes
/// consumes self
pub fn stderr_move_bytes ( self ) -> Vec < u8 > {
2021-05-26 20:15:28 +00:00
self . stderr
2021-03-31 09:25:23 +00:00
}
/// Returns the program's exit code
2022-12-14 14:42:07 +00:00
/// Panics if not run or has not finished yet for example when run with `run_no_wait()`
2021-03-31 09:25:23 +00:00
pub fn code ( & self ) -> i32 {
2023-01-05 15:51:42 +00:00
self . exit_status ( ) . code ( ) . unwrap ( )
2021-03-31 09:25:23 +00:00
}
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn code_is ( & self , expected_code : i32 ) -> & Self {
2024-03-14 18:38:28 +00:00
let fails = self . code ( ) ! = expected_code ;
if fails {
eprintln! (
" stdout: \n {} \n stderr: \n {} " ,
self . stdout_str ( ) ,
self . stderr_str ( )
) ;
}
2021-04-10 18:24:30 +00:00
assert_eq! ( self . code ( ) , expected_code ) ;
self
}
2022-12-14 14:42:07 +00:00
/// Returns the program's `TempDir`
2021-03-31 09:25:23 +00:00
/// Panics if not present
pub fn tmpd ( & self ) -> Rc < TempDir > {
match & self . tmpd {
Some ( ptr ) = > ptr . clone ( ) ,
2021-04-02 09:56:49 +00:00
None = > panic! ( " Command not associated with a TempDir " ) ,
2021-03-31 09:25:23 +00:00
}
}
/// Returns whether the program succeeded
pub fn succeeded ( & self ) -> bool {
2023-01-04 20:30:14 +00:00
self . exit_status . map_or ( true , | e | e . success ( ) )
2021-03-31 09:25:23 +00:00
}
2016-07-17 09:20:33 +00:00
/// asserts that the command resulted in a success (zero) status code
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn success ( & self ) -> & Self {
2021-10-10 07:57:39 +00:00
assert! (
2023-01-04 20:30:14 +00:00
self . succeeded ( ) ,
2024-03-14 18:38:28 +00:00
" Command was expected to succeed. code: {} \n stdout = {} \n stderr = {} " ,
self . code ( ) ,
2021-10-10 07:57:39 +00:00
self . stdout_str ( ) ,
self . stderr_str ( )
) ;
2021-03-31 09:25:23 +00:00
self
2016-02-17 05:03:52 +00:00
}
2016-03-29 01:13:40 +00:00
2016-07-17 09:20:33 +00:00
/// asserts that the command resulted in a failure (non-zero) status code
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn failure ( & self ) -> & Self {
2021-10-10 07:57:39 +00:00
assert! (
2023-01-04 20:30:14 +00:00
! self . succeeded ( ) ,
2021-10-10 07:57:39 +00:00
" Command was expected to fail. \n stdout = {} \n stderr = {} " ,
self . stdout_str ( ) ,
self . stderr_str ( )
) ;
2021-03-31 09:25:23 +00:00
self
2016-02-17 05:03:52 +00:00
}
2016-08-06 03:18:34 +00:00
2016-07-17 09:20:33 +00:00
/// asserts that the command resulted in empty (zero-length) stderr stream output
2022-12-14 14:42:07 +00:00
/// generally, it's better to use `stdout_only()` instead,
2016-07-17 09:20:33 +00:00
/// but you might find yourself using this function if
2021-04-07 09:48:21 +00:00
/// 1. you can not know exactly what stdout will be or
/// 2. you know that stdout will also be empty
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn no_stderr ( & self ) -> & Self {
2021-10-10 07:57:39 +00:00
assert! (
self . stderr . is_empty ( ) ,
" Expected stderr to be empty, but it's: \n {} " ,
self . stderr_str ( )
) ;
2021-03-31 09:25:23 +00:00
self
2016-02-17 05:03:52 +00:00
}
2016-08-06 03:18:34 +00:00
2016-07-17 09:20:33 +00:00
/// asserts that the command resulted in empty (zero-length) stderr stream output
2022-12-14 14:42:07 +00:00
/// unless asserting there was neither stdout or stderr, `stderr_only` is usually a better choice
/// generally, it's better to use `stderr_only()` instead,
2016-07-17 09:20:33 +00:00
/// but you might find yourself using this function if
2021-04-07 09:48:21 +00:00
/// 1. you can not know exactly what stderr will be or
/// 2. you know that stderr will also be empty
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn no_stdout ( & self ) -> & Self {
2021-10-10 07:57:39 +00:00
assert! (
self . stdout . is_empty ( ) ,
" Expected stdout to be empty, but it's: \n {} " ,
2022-04-09 04:15:59 +00:00
self . stdout_str ( )
2021-10-10 07:57:39 +00:00
) ;
2021-03-31 09:25:23 +00:00
self
2016-02-17 05:03:52 +00:00
}
2016-03-29 01:13:40 +00:00
2022-12-03 04:46:48 +00:00
/// Assert that there is output to neither stderr nor stdout.
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-12-03 04:46:48 +00:00
pub fn no_output ( & self ) -> & Self {
self . no_stdout ( ) . no_stderr ( )
}
2016-07-17 09:20:33 +00:00
/// asserts that the command resulted in stdout stream output that equals the
2019-02-07 20:54:48 +00:00
/// passed in value, trailing whitespace are kept to force strict comparison (#1235)
2022-12-14 14:42:07 +00:00
/// `stdout_only()` is a better choice unless stderr may or will be non-empty
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn stdout_is < T : AsRef < str > > ( & self , msg : T ) -> & Self {
2021-05-26 20:15:28 +00:00
assert_eq! ( self . stdout_str ( ) , String ::from ( msg . as_ref ( ) ) ) ;
2021-03-31 09:25:23 +00:00
self
}
2021-06-11 20:02:10 +00:00
/// like `stdout_is`, but succeeds if any elements of `expected` matches stdout.
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn stdout_is_any < T : AsRef < str > + std ::fmt ::Debug > ( & self , expected : & [ T ] ) -> & Self {
2022-12-14 14:42:07 +00:00
assert! (
expected . iter ( ) . any ( | msg | self . stdout_str ( ) = = msg . as_ref ( ) ) ,
" stdout was {} \n Expected any of {:#?} " ,
self . stdout_str ( ) ,
expected
) ;
2021-06-11 20:02:10 +00:00
self
}
2021-04-17 12:01:52 +00:00
/// Like `stdout_is` but newlines are normalized to `\n`.
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn normalized_newlines_stdout_is < T : AsRef < str > > ( & self , msg : T ) -> & Self {
2021-04-17 12:01:52 +00:00
let msg = msg . as_ref ( ) . replace ( " \r \n " , " \n " ) ;
2021-05-26 20:15:28 +00:00
assert_eq! ( self . stdout_str ( ) . replace ( " \r \n " , " \n " ) , msg ) ;
2021-04-17 12:01:52 +00:00
self
}
2021-03-31 09:25:23 +00:00
/// asserts that the command resulted in stdout stream output,
/// whose bytes equal those of the passed in slice
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn stdout_is_bytes < T : AsRef < [ u8 ] > > ( & self , msg : T ) -> & Self {
2023-01-06 16:05:59 +00:00
assert_eq! ( self . stdout , msg . as_ref ( ) ,
" stdout as bytes wasn't equal to expected bytes. Result as strings: \n stdout ='{:?}' \n expected='{:?}' " ,
std ::str ::from_utf8 ( & self . stdout ) ,
std ::str ::from_utf8 ( msg . as_ref ( ) ) ,
) ;
2021-03-31 09:25:23 +00:00
self
2016-02-17 05:03:52 +00:00
}
2016-08-06 03:18:34 +00:00
2022-12-14 14:42:07 +00:00
/// like `stdout_is()`, but expects the contents of the file at the provided relative path
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn stdout_is_fixture < T : AsRef < OsStr > > ( & self , file_rel_path : T ) -> & Self {
2016-07-29 21:26:32 +00:00
let contents = read_scenario_fixture ( & self . tmpd , file_rel_path ) ;
2021-04-24 14:43:13 +00:00
self . stdout_is ( String ::from_utf8 ( contents ) . unwrap ( ) )
2016-07-29 21:26:32 +00:00
}
2022-02-15 02:16:37 +00:00
/// Assert that the bytes of stdout exactly match those of the given file.
///
/// Contrast this with [`CmdResult::stdout_is_fixture`], which
/// decodes the contents of the file as a UTF-8 [`String`] before
/// comparison with stdout.
///
/// # Examples
///
/// Use this method in a unit test like this:
///
/// ```rust,ignore
/// #[test]
/// fn test_something() {
/// new_ucmd!().succeeds().stdout_is_fixture_bytes("expected.bin");
/// }
/// ```
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-02-15 02:16:37 +00:00
pub fn stdout_is_fixture_bytes < T : AsRef < OsStr > > ( & self , file_rel_path : T ) -> & Self {
let contents = read_scenario_fixture ( & self . tmpd , file_rel_path ) ;
self . stdout_is_bytes ( contents )
}
2022-12-14 14:42:07 +00:00
/// like `stdout_is_fixture()`, but replaces the data in fixture file based on values provided in `template_vars`
2018-12-15 05:57:40 +00:00
/// command output
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2021-05-29 17:34:17 +00:00
pub fn stdout_is_templated_fixture < T : AsRef < OsStr > > (
& self ,
file_rel_path : T ,
2021-06-11 20:00:26 +00:00
template_vars : & [ ( & str , & str ) ] ,
2022-01-30 13:59:31 +00:00
) -> & Self {
2021-05-29 17:34:17 +00:00
let mut contents =
String ::from_utf8 ( read_scenario_fixture ( & self . tmpd , file_rel_path ) ) . unwrap ( ) ;
2018-12-15 05:57:40 +00:00
for kv in template_vars {
2018-12-16 16:06:42 +00:00
contents = contents . replace ( kv . 0 , kv . 1 ) ;
2018-12-15 05:57:40 +00:00
}
self . stdout_is ( contents )
}
2016-03-29 01:13:40 +00:00
2021-06-11 20:02:10 +00:00
/// like `stdout_is_templated_fixture`, but succeeds if any replacement by `template_vars` results in the actual stdout.
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2021-06-11 20:02:10 +00:00
pub fn stdout_is_templated_fixture_any < T : AsRef < OsStr > > (
& self ,
file_rel_path : T ,
template_vars : & [ Vec < ( String , String ) > ] ,
) {
let contents = String ::from_utf8 ( read_scenario_fixture ( & self . tmpd , file_rel_path ) ) . unwrap ( ) ;
let possible_values = template_vars . iter ( ) . map ( | vars | {
let mut contents = contents . clone ( ) ;
2023-08-21 06:11:38 +00:00
for kv in vars {
2021-06-11 20:02:10 +00:00
contents = contents . replace ( & kv . 0 , & kv . 1 ) ;
}
contents
} ) ;
2022-01-30 12:07:20 +00:00
self . stdout_is_any ( & possible_values . collect ::< Vec < _ > > ( ) ) ;
2021-06-11 20:02:10 +00:00
}
2023-01-05 20:09:15 +00:00
/// assert that the command resulted in stderr stream output that equals the
/// passed in value.
///
2022-12-14 14:42:07 +00:00
/// `stderr_only` is a better choice unless stdout may or will be non-empty
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn stderr_is < T : AsRef < str > > ( & self , msg : T ) -> & Self {
2023-01-05 20:09:15 +00:00
assert_eq! ( self . stderr_str ( ) , msg . as_ref ( ) ) ;
2021-03-31 09:25:23 +00:00
self
}
/// asserts that the command resulted in stderr stream output,
/// whose bytes equal those of the passed in slice
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn stderr_is_bytes < T : AsRef < [ u8 ] > > ( & self , msg : T ) -> & Self {
2023-01-06 16:05:59 +00:00
assert_eq! (
& self . stderr ,
msg . as_ref ( ) ,
" stderr as bytes wasn't equal to expected bytes. Result as strings: \n stderr ='{:?}' \n expected='{:?}' " ,
std ::str ::from_utf8 ( & self . stderr ) ,
std ::str ::from_utf8 ( msg . as_ref ( ) )
) ;
2021-03-31 09:25:23 +00:00
self
2016-02-17 05:03:52 +00:00
}
2016-03-29 01:13:40 +00:00
2022-12-14 14:42:07 +00:00
/// Like `stdout_is_fixture`, but for stderr
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn stderr_is_fixture < T : AsRef < OsStr > > ( & self , file_rel_path : T ) -> & Self {
2021-04-10 20:19:53 +00:00
let contents = read_scenario_fixture ( & self . tmpd , file_rel_path ) ;
2021-04-24 14:43:13 +00:00
self . stderr_is ( String ::from_utf8 ( contents ) . unwrap ( ) )
2021-04-10 20:19:53 +00:00
}
2016-07-17 09:20:33 +00:00
/// asserts that
2021-04-07 09:48:21 +00:00
/// 1. the command resulted in stdout stream output that equals the
/// passed in value
/// 2. the command resulted in empty (zero-length) stderr stream output
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn stdout_only < T : AsRef < str > > ( & self , msg : T ) -> & Self {
2016-08-08 06:54:13 +00:00
self . no_stderr ( ) . stdout_is ( msg )
2016-02-17 05:03:52 +00:00
}
2016-03-29 01:13:40 +00:00
2021-03-31 09:25:23 +00:00
/// asserts that
/// 1. the command resulted in a stdout stream whose bytes
/// equal those of the passed in value
/// 2. the command resulted in an empty stderr stream
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn stdout_only_bytes < T : AsRef < [ u8 ] > > ( & self , msg : T ) -> & Self {
2021-03-31 09:25:23 +00:00
self . no_stderr ( ) . stdout_is_bytes ( msg )
}
2022-12-14 14:42:07 +00:00
/// like `stdout_only()`, but expects the contents of the file at the provided relative path
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn stdout_only_fixture < T : AsRef < OsStr > > ( & self , file_rel_path : T ) -> & Self {
2016-07-29 21:26:32 +00:00
let contents = read_scenario_fixture ( & self . tmpd , file_rel_path ) ;
2021-03-31 09:25:23 +00:00
self . stdout_only_bytes ( contents )
2016-07-29 21:26:32 +00:00
}
2016-07-17 09:20:33 +00:00
/// asserts that
2021-04-07 09:48:21 +00:00
/// 1. the command resulted in stderr stream output that equals the
2023-02-16 14:33:33 +00:00
/// passed in value
2021-04-07 09:48:21 +00:00
/// 2. the command resulted in empty (zero-length) stdout stream output
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn stderr_only < T : AsRef < str > > ( & self , msg : T ) -> & Self {
2016-08-08 06:54:13 +00:00
self . no_stdout ( ) . stderr_is ( msg )
2016-02-17 05:03:52 +00:00
}
2016-03-29 01:13:40 +00:00
2021-03-31 09:25:23 +00:00
/// asserts that
/// 1. the command resulted in a stderr stream whose bytes equal the ones
/// of the passed value
/// 2. the command resulted in an empty stdout stream
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn stderr_only_bytes < T : AsRef < [ u8 ] > > ( & self , msg : T ) -> & Self {
2022-02-06 23:03:13 +00:00
self . no_stdout ( ) . stderr_is_bytes ( msg )
2021-03-31 09:25:23 +00:00
}
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn fails_silently ( & self ) -> & Self {
2023-01-04 20:30:14 +00:00
assert! ( ! self . succeeded ( ) ) ;
2021-03-31 09:25:23 +00:00
assert! ( self . stderr . is_empty ( ) ) ;
self
}
2021-11-09 17:43:55 +00:00
/// asserts that
/// 1. the command resulted in stderr stream output that equals the
2023-02-16 14:33:33 +00:00
/// the following format
2021-11-09 17:43:55 +00:00
/// `"{util_name}: {msg}\nTry '{bin_path} {util_name} --help' for more information."`
2022-12-14 14:42:07 +00:00
/// This the expected format when a `UUsageError` is returned or when `show_error!` is called
/// `msg` should be the same as the one provided to `UUsageError::new` or `show_error!`
2021-11-09 17:43:55 +00:00
///
/// 2. the command resulted in empty (zero-length) stdout stream output
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn usage_error < T : AsRef < str > > ( & self , msg : T ) -> & Self {
2021-11-09 17:43:55 +00:00
self . stderr_only ( format! (
2023-01-05 20:09:15 +00:00
" {0}: {2} \n Try '{1} {0} --help' for more information. \n " ,
2021-11-09 17:43:55 +00:00
self . util_name . as_ref ( ) . unwrap ( ) , // This shouldn't be called using a normal command
2023-01-25 02:40:39 +00:00
self . bin_path . display ( ) ,
2021-11-09 17:43:55 +00:00
msg . as_ref ( )
) )
}
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn stdout_contains < T : AsRef < str > > ( & self , cmp : T ) -> & Self {
2021-05-05 20:59:40 +00:00
assert! (
self . stdout_str ( ) . contains ( cmp . as_ref ( ) ) ,
" '{}' does not contain '{}' " ,
self . stdout_str ( ) ,
cmp . as_ref ( )
) ;
2021-03-31 09:25:23 +00:00
self
}
2023-06-20 18:28:05 +00:00
#[ track_caller ]
pub fn stdout_contains_line < T : AsRef < str > > ( & self , cmp : T ) -> & Self {
assert! (
self . stdout_str ( ) . lines ( ) . any ( | line | line = = cmp . as_ref ( ) ) ,
" '{}' does not contain line '{}' " ,
self . stdout_str ( ) ,
cmp . as_ref ( )
) ;
self
}
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn stderr_contains < T : AsRef < str > > ( & self , cmp : T ) -> & Self {
2021-05-05 20:59:40 +00:00
assert! (
self . stderr_str ( ) . contains ( cmp . as_ref ( ) ) ,
" '{}' does not contain '{}' " ,
self . stderr_str ( ) ,
cmp . as_ref ( )
) ;
2021-03-31 09:25:23 +00:00
self
2016-02-17 05:03:52 +00:00
}
2021-04-10 18:24:30 +00:00
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn stdout_does_not_contain < T : AsRef < str > > ( & self , cmp : T ) -> & Self {
2021-05-17 01:21:20 +00:00
assert! (
! self . stdout_str ( ) . contains ( cmp . as_ref ( ) ) ,
" '{}' contains '{}' but should not " ,
self . stdout_str ( ) ,
cmp . as_ref ( ) ,
) ;
2021-04-10 18:24:30 +00:00
self
}
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn stderr_does_not_contain < T : AsRef < str > > ( & self , cmp : T ) -> & Self {
2021-04-10 18:24:30 +00:00
assert! ( ! self . stderr_str ( ) . contains ( cmp . as_ref ( ) ) ) ;
self
}
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn stdout_matches ( & self , regex : & regex ::Regex ) -> & Self {
2022-12-14 14:42:07 +00:00
assert! (
2023-02-16 14:33:33 +00:00
regex . is_match ( self . stdout_str ( ) ) ,
2022-12-14 14:42:07 +00:00
" Stdout does not match regex: \n {} " ,
self . stdout_str ( )
) ;
2021-04-10 18:24:30 +00:00
self
}
2023-01-01 14:25:37 +00:00
#[ track_caller ]
dd: fix precision for display of total time spent
Improve the display of the total time spent transferring bytes so that
the number of seconds is displayed using the `%g` format specifier as
in `printf`. This matches the behavior of GNU `dd`.
Before this commit, the precision was always set to one digit after
the decimal point. For example,
$ dd count=100000 if=/dev/zero of=/dev/null
100000+0 records in
100000+0 records out
51200000 bytes (51 MB, 49 MiB) copied, 0.2 s, 268.1 MB/s
After this commit, the precision increases dynamically as the duration
decreases. For example,
$ dd count=100000 if=/dev/zero of=/dev/null
100000+0 records in
100000+0 records out
51200000 bytes (51 MB, 49 MiB) copied, 0.1019 s, 507 MB/s
$ dd count=1000 if=/dev/zero of=/dev/null
1000+0 records in
1000+0 records out
512000 bytes (512 kB, 500 KiB) copied, 0.002663 s, 256 MB/s
$ dd count=10 if=/dev/zero of=/dev/null
10+0 records in
10+0 records out
5120 bytes (5.1 kB, 5.0 KiB) copied, 0.000182 s, 5.1 MB/s
2023-03-12 03:37:12 +00:00
pub fn stderr_matches ( & self , regex : & regex ::Regex ) -> & Self {
assert! (
regex . is_match ( self . stderr_str ( ) ) ,
" Stderr does not match regex: \n {} " ,
self . stderr_str ( )
) ;
self
}
#[ track_caller ]
2022-01-30 13:59:31 +00:00
pub fn stdout_does_not_match ( & self , regex : & regex ::Regex ) -> & Self {
2022-12-14 14:42:07 +00:00
assert! (
2023-02-16 14:33:33 +00:00
! regex . is_match ( self . stdout_str ( ) ) ,
2022-12-14 14:42:07 +00:00
" Stdout matches regex: \n {} " ,
self . stdout_str ( )
) ;
2021-04-10 18:24:30 +00:00
self
}
2016-02-17 05:03:52 +00:00
}
2015-11-16 05:25:01 +00:00
pub fn log_info < T : AsRef < str > , U : AsRef < str > > ( msg : T , par : U ) {
println! ( " {} : {} " , msg . as_ref ( ) , par . as_ref ( ) ) ;
}
pub fn recursive_copy ( src : & Path , dest : & Path ) -> Result < ( ) > {
2019-04-28 09:00:19 +00:00
if fs ::metadata ( src ) ? . is_dir ( ) {
2019-12-08 04:02:50 +00:00
for entry in fs ::read_dir ( src ) ? {
2019-04-28 09:00:19 +00:00
let entry = entry ? ;
2015-11-16 05:25:01 +00:00
let mut new_dest = PathBuf ::from ( dest ) ;
new_dest . push ( entry . file_name ( ) ) ;
2019-04-28 09:00:19 +00:00
if fs ::metadata ( entry . path ( ) ) ? . is_dir ( ) {
fs ::create_dir ( & new_dest ) ? ;
recursive_copy ( & entry . path ( ) , & new_dest ) ? ;
2015-11-16 05:25:01 +00:00
} else {
2022-11-15 15:57:08 +00:00
fs ::copy ( entry . path ( ) , new_dest ) ? ;
2015-11-16 05:25:01 +00:00
}
}
}
Ok ( ( ) )
}
2016-03-29 01:06:31 +00:00
pub fn get_root_path ( ) -> & 'static str {
if cfg! ( windows ) {
" C: \\ "
} else {
" / "
}
}
2024-01-13 23:38:17 +00:00
/// Compares the extended attributes (xattrs) of two files or directories.
///
/// # Returns
///
/// `true` if both paths have the same set of extended attributes, `false` otherwise.
#[ cfg(all(unix, not(target_os = " macos " ))) ]
pub fn compare_xattrs < P : AsRef < std ::path ::Path > > ( path1 : P , path2 : P ) -> bool {
let get_sorted_xattrs = | path : P | {
xattr ::list ( path )
. map ( | attrs | {
let mut attrs = attrs . collect ::< Vec < _ > > ( ) ;
attrs . sort ( ) ;
attrs
} )
. unwrap_or_else ( | _ | Vec ::new ( ) )
} ;
get_sorted_xattrs ( path1 ) = = get_sorted_xattrs ( path2 )
}
2016-07-17 09:20:33 +00:00
/// Object-oriented path struct that represents and operates on
/// paths relative to the directory it was constructed for.
2016-08-23 12:57:13 +00:00
#[ derive(Clone) ]
2015-11-16 05:25:01 +00:00
pub struct AtPath {
pub subdir : PathBuf ,
}
2016-03-29 01:13:40 +00:00
2015-11-16 05:25:01 +00:00
impl AtPath {
2022-01-30 13:59:31 +00:00
pub fn new ( subdir : & Path ) -> Self {
Self {
2018-03-12 08:20:58 +00:00
subdir : PathBuf ::from ( subdir ) ,
}
2015-11-16 05:25:01 +00:00
}
2016-03-29 01:13:40 +00:00
2015-11-16 05:25:01 +00:00
pub fn as_string ( & self ) -> String {
self . subdir . to_str ( ) . unwrap ( ) . to_owned ( )
}
2016-03-29 01:13:40 +00:00
2023-02-13 09:00:03 +00:00
pub fn plus < P : AsRef < Path > > ( & self , name : P ) -> PathBuf {
2015-11-16 05:25:01 +00:00
let mut pathbuf = self . subdir . clone ( ) ;
pathbuf . push ( name ) ;
pathbuf
}
2016-03-29 01:13:40 +00:00
2023-02-13 09:00:03 +00:00
pub fn plus_as_string < P : AsRef < Path > > ( & self , name : P ) -> String {
self . plus ( name ) . display ( ) . to_string ( )
2015-11-16 05:25:01 +00:00
}
2016-03-29 01:13:40 +00:00
2015-11-16 05:25:01 +00:00
fn minus ( & self , name : & str ) -> PathBuf {
let prefixed = PathBuf ::from ( name ) ;
if prefixed . starts_with ( & self . subdir ) {
let mut unprefixed = PathBuf ::new ( ) ;
2018-03-12 08:20:58 +00:00
for component in prefixed . components ( ) . skip ( self . subdir . components ( ) . count ( ) ) {
2018-01-16 22:58:35 +00:00
unprefixed . push ( component . as_os_str ( ) . to_str ( ) . unwrap ( ) ) ;
2015-11-16 05:25:01 +00:00
}
unprefixed
} else {
prefixed
}
}
2016-03-29 01:13:40 +00:00
2015-11-16 05:25:01 +00:00
pub fn minus_as_string ( & self , name : & str ) -> String {
String ::from ( self . minus ( name ) . to_str ( ) . unwrap ( ) )
}
2016-03-29 01:13:40 +00:00
2021-04-10 08:41:59 +00:00
pub fn set_readonly ( & self , name : & str ) {
let metadata = fs ::metadata ( self . plus ( name ) ) . unwrap ( ) ;
let mut permissions = metadata . permissions ( ) ;
permissions . set_readonly ( true ) ;
fs ::set_permissions ( self . plus ( name ) , permissions ) . unwrap ( ) ;
}
2015-11-16 05:25:01 +00:00
pub fn open ( & self , name : & str ) -> File {
log_info ( " open " , self . plus_as_string ( name ) ) ;
File ::open ( self . plus ( name ) ) . unwrap ( )
}
2016-03-29 01:13:40 +00:00
2015-11-16 05:25:01 +00:00
pub fn read ( & self , name : & str ) -> String {
let mut f = self . open ( name ) ;
let mut contents = String ::new ( ) ;
2021-03-31 10:54:16 +00:00
f . read_to_string ( & mut contents )
2022-12-14 14:42:07 +00:00
. unwrap_or_else ( | e | panic! ( " Couldn't read {name} : {e} " ) ) ;
2015-11-16 05:25:01 +00:00
contents
}
2016-03-29 01:13:40 +00:00
2021-03-31 09:25:23 +00:00
pub fn read_bytes ( & self , name : & str ) -> Vec < u8 > {
let mut f = self . open ( name ) ;
let mut contents = Vec ::new ( ) ;
f . read_to_end ( & mut contents )
2022-12-14 14:42:07 +00:00
. unwrap_or_else ( | e | panic! ( " Couldn't read {name} : {e} " ) ) ;
2021-03-31 09:25:23 +00:00
contents
}
2015-11-16 05:25:01 +00:00
pub fn write ( & self , name : & str , contents : & str ) {
2021-10-18 23:12:12 +00:00
log_info ( " write(default) " , self . plus_as_string ( name ) ) ;
2021-03-31 10:54:16 +00:00
std ::fs ::write ( self . plus ( name ) , contents )
2022-12-14 14:42:07 +00:00
. unwrap_or_else ( | e | panic! ( " Couldn't write {name} : {e} " ) ) ;
2015-11-16 05:25:01 +00:00
}
2016-03-29 01:13:40 +00:00
2021-03-31 09:25:23 +00:00
pub fn write_bytes ( & self , name : & str , contents : & [ u8 ] ) {
2021-10-18 23:12:12 +00:00
log_info ( " write(default) " , self . plus_as_string ( name ) ) ;
2021-03-31 09:25:23 +00:00
std ::fs ::write ( self . plus ( name ) , contents )
2022-12-14 14:42:07 +00:00
. unwrap_or_else ( | e | panic! ( " Couldn't write {name} : {e} " ) ) ;
2021-03-31 09:25:23 +00:00
}
2016-04-02 21:09:20 +00:00
pub fn append ( & self , name : & str , contents : & str ) {
2021-10-18 23:12:12 +00:00
log_info ( " write(append) " , self . plus_as_string ( name ) ) ;
2018-03-12 08:20:58 +00:00
let mut f = OpenOptions ::new ( )
. append ( true )
2021-10-18 23:12:12 +00:00
. create ( true )
2018-03-12 08:20:58 +00:00
. open ( self . plus ( name ) )
. unwrap ( ) ;
2021-05-29 12:32:35 +00:00
f . write_all ( contents . as_bytes ( ) )
2022-12-14 14:42:07 +00:00
. unwrap_or_else ( | e | panic! ( " Couldn't write(append) {name} : {e} " ) ) ;
2016-04-02 21:09:20 +00:00
}
2021-03-31 09:25:23 +00:00
pub fn append_bytes ( & self , name : & str , contents : & [ u8 ] ) {
2021-10-18 23:12:12 +00:00
log_info ( " write(append) " , self . plus_as_string ( name ) ) ;
2021-03-31 09:25:23 +00:00
let mut f = OpenOptions ::new ( )
. append ( true )
2021-10-18 23:12:12 +00:00
. create ( true )
2021-03-31 09:25:23 +00:00
. open ( self . plus ( name ) )
. unwrap ( ) ;
f . write_all ( contents )
2022-12-14 14:42:07 +00:00
. unwrap_or_else ( | e | panic! ( " Couldn't write(append) to {name} : {e} " ) ) ;
2021-10-18 23:12:12 +00:00
}
pub fn truncate ( & self , name : & str , contents : & str ) {
log_info ( " write(truncate) " , self . plus_as_string ( name ) ) ;
let mut f = OpenOptions ::new ( )
. write ( true )
. truncate ( true )
. create ( true )
. open ( self . plus ( name ) )
. unwrap ( ) ;
f . write_all ( contents . as_bytes ( ) )
2022-12-14 14:42:07 +00:00
. unwrap_or_else ( | e | panic! ( " Couldn't write(truncate) {name} : {e} " ) ) ;
2021-10-18 23:12:12 +00:00
}
pub fn rename ( & self , source : & str , target : & str ) {
let source = self . plus ( source ) ;
let target = self . plus ( target ) ;
2022-12-14 14:42:07 +00:00
log_info ( " rename " , format! ( " {source:?} {target:?} " ) ) ;
2021-10-18 23:12:12 +00:00
std ::fs ::rename ( & source , & target )
2022-12-14 14:42:07 +00:00
. unwrap_or_else ( | e | panic! ( " Couldn't rename {source:?} -> {target:?} : {e} " ) ) ;
2021-10-18 23:12:12 +00:00
}
pub fn remove ( & self , source : & str ) {
let source = self . plus ( source ) ;
2022-12-14 14:42:07 +00:00
log_info ( " remove " , format! ( " {source:?} " ) ) ;
std ::fs ::remove_file ( & source ) . unwrap_or_else ( | e | panic! ( " Couldn't remove {source:?} : {e} " ) ) ;
2021-10-18 23:12:12 +00:00
}
pub fn copy ( & self , source : & str , target : & str ) {
let source = self . plus ( source ) ;
let target = self . plus ( target ) ;
2022-12-14 14:42:07 +00:00
log_info ( " copy " , format! ( " {source:?} {target:?} " ) ) ;
2021-10-18 23:12:12 +00:00
std ::fs ::copy ( & source , & target )
2022-12-14 14:42:07 +00:00
. unwrap_or_else ( | e | panic! ( " Couldn't copy {source:?} -> {target:?} : {e} " ) ) ;
2021-10-18 23:12:12 +00:00
}
pub fn rmdir ( & self , dir : & str ) {
log_info ( " rmdir " , self . plus_as_string ( dir ) ) ;
2022-11-15 15:57:08 +00:00
fs ::remove_dir ( self . plus ( dir ) ) . unwrap ( ) ;
2021-03-31 09:25:23 +00:00
}
2023-02-13 09:00:03 +00:00
pub fn mkdir < P : AsRef < Path > > ( & self , dir : P ) {
let dir = dir . as_ref ( ) ;
2015-11-16 05:25:01 +00:00
log_info ( " mkdir " , self . plus_as_string ( dir ) ) ;
2022-11-15 15:57:08 +00:00
fs ::create_dir ( self . plus ( dir ) ) . unwrap ( ) ;
2015-11-16 05:25:01 +00:00
}
2021-10-18 23:12:12 +00:00
2015-11-16 05:25:01 +00:00
pub fn mkdir_all ( & self , dir : & str ) {
log_info ( " mkdir_all " , self . plus_as_string ( dir ) ) ;
fs ::create_dir_all ( self . plus ( dir ) ) . unwrap ( ) ;
}
2016-03-29 01:13:40 +00:00
2015-11-16 05:25:01 +00:00
pub fn make_file ( & self , name : & str ) -> File {
2022-11-15 15:57:08 +00:00
match File ::create ( self . plus ( name ) ) {
2015-11-16 05:25:01 +00:00
Ok ( f ) = > f ,
Err ( e ) = > panic! ( " {} " , e ) ,
}
}
2016-03-29 01:13:40 +00:00
2023-02-13 09:00:03 +00:00
pub fn touch < P : AsRef < Path > > ( & self , file : P ) {
let file = file . as_ref ( ) ;
2015-11-16 05:25:01 +00:00
log_info ( " touch " , self . plus_as_string ( file ) ) ;
2022-11-15 15:57:08 +00:00
File ::create ( self . plus ( file ) ) . unwrap ( ) ;
2015-11-16 05:25:01 +00:00
}
2016-03-29 01:13:40 +00:00
2021-03-29 11:10:13 +00:00
#[ cfg(not(windows)) ]
pub fn mkfifo ( & self , fifo : & str ) {
let full_path = self . plus_as_string ( fifo ) ;
log_info ( " mkfifo " , & full_path ) ;
unsafe {
let fifo_name : CString = CString ::new ( full_path ) . expect ( " CString creation failed. " ) ;
libc ::mkfifo ( fifo_name . as_ptr ( ) , libc ::S_IWUSR | libc ::S_IRUSR ) ;
}
}
#[ cfg(not(windows)) ]
pub fn is_fifo ( & self , fifo : & str ) -> bool {
unsafe {
let name = CString ::new ( self . plus_as_string ( fifo ) ) . unwrap ( ) ;
let mut stat : libc ::stat = std ::mem ::zeroed ( ) ;
if libc ::stat ( name . as_ptr ( ) , & mut stat ) > = 0 {
2022-03-23 23:32:27 +00:00
libc ::S_IFIFO & stat . st_mode as libc ::mode_t ! = 0
2021-03-29 11:10:13 +00:00
} else {
false
}
}
}
2021-07-28 02:43:04 +00:00
pub fn hard_link ( & self , original : & str , link : & str ) {
2021-05-26 10:53:11 +00:00
log_info (
" hard_link " ,
2022-11-15 15:57:08 +00:00
format! (
2021-07-28 02:43:04 +00:00
" {},{} " ,
self . plus_as_string ( original ) ,
self . plus_as_string ( link )
) ,
2021-05-26 10:53:11 +00:00
) ;
2022-11-15 15:57:08 +00:00
hard_link ( self . plus ( original ) , self . plus ( link ) ) . unwrap ( ) ;
2021-05-26 10:53:11 +00:00
}
2021-07-28 02:43:04 +00:00
pub fn symlink_file ( & self , original : & str , link : & str ) {
2018-03-12 08:20:58 +00:00
log_info (
" symlink " ,
2022-11-15 15:57:08 +00:00
format! (
2021-07-28 02:43:04 +00:00
" {},{} " ,
self . plus_as_string ( original ) ,
self . plus_as_string ( link )
) ,
2018-03-12 08:20:58 +00:00
) ;
2022-11-15 15:57:08 +00:00
symlink_file ( self . plus ( original ) , self . plus ( link ) ) . unwrap ( ) ;
2015-11-16 05:25:01 +00:00
}
2016-03-29 01:13:40 +00:00
2022-03-03 22:06:15 +00:00
pub fn relative_symlink_file ( & self , original : & str , link : & str ) {
2022-07-10 14:49:25 +00:00
#[ cfg(windows) ]
2024-03-23 08:56:43 +00:00
let original = original . replace ( '/' , std ::path ::MAIN_SEPARATOR_STR ) ;
2022-06-25 20:33:22 +00:00
log_info (
" symlink " ,
2022-11-15 15:57:08 +00:00
format! ( " {} , {} " , & original , & self . plus_as_string ( link ) ) ,
2022-06-25 20:33:22 +00:00
) ;
2022-11-15 15:57:08 +00:00
symlink_file ( original , self . plus ( link ) ) . unwrap ( ) ;
2022-03-03 22:06:15 +00:00
}
2021-07-28 02:43:04 +00:00
pub fn symlink_dir ( & self , original : & str , link : & str ) {
2018-11-13 04:55:25 +00:00
log_info (
" symlink " ,
2022-11-15 15:57:08 +00:00
format! (
2021-07-28 02:43:04 +00:00
" {},{} " ,
self . plus_as_string ( original ) ,
self . plus_as_string ( link )
) ,
2018-11-13 04:55:25 +00:00
) ;
2022-11-15 15:57:08 +00:00
symlink_dir ( self . plus ( original ) , self . plus ( link ) ) . unwrap ( ) ;
2018-11-13 04:55:25 +00:00
}
2022-06-25 20:23:24 +00:00
pub fn relative_symlink_dir ( & self , original : & str , link : & str ) {
2022-07-10 14:49:25 +00:00
#[ cfg(windows) ]
2024-03-23 08:56:43 +00:00
let original = original . replace ( '/' , std ::path ::MAIN_SEPARATOR_STR ) ;
2022-06-25 20:33:22 +00:00
log_info (
" symlink " ,
2022-11-15 15:57:08 +00:00
format! ( " {} , {} " , & original , & self . plus_as_string ( link ) ) ,
2022-06-25 20:33:22 +00:00
) ;
2022-11-15 15:57:08 +00:00
symlink_dir ( original , self . plus ( link ) ) . unwrap ( ) ;
2022-06-25 20:23:24 +00:00
}
2015-11-16 05:25:01 +00:00
pub fn is_symlink ( & self , path : & str ) -> bool {
log_info ( " is_symlink " , self . plus_as_string ( path ) ) ;
2022-11-15 15:57:08 +00:00
match fs ::symlink_metadata ( self . plus ( path ) ) {
2015-11-16 05:25:01 +00:00
Ok ( m ) = > m . file_type ( ) . is_symlink ( ) ,
Err ( _ ) = > false ,
}
}
pub fn resolve_link ( & self , path : & str ) -> String {
log_info ( " resolve_link " , self . plus_as_string ( path ) ) ;
2022-11-15 15:57:08 +00:00
match fs ::read_link ( self . plus ( path ) ) {
2018-03-12 08:20:58 +00:00
Ok ( p ) = > self . minus_as_string ( p . to_str ( ) . unwrap ( ) ) ,
2022-11-30 09:37:59 +00:00
Err ( _ ) = > String ::new ( ) ,
2015-11-16 05:25:01 +00:00
}
}
2022-09-23 17:31:24 +00:00
pub fn read_symlink ( & self , path : & str ) -> String {
log_info ( " read_symlink " , self . plus_as_string ( path ) ) ;
2022-11-15 15:57:08 +00:00
fs ::read_link ( self . plus ( path ) )
2022-09-23 17:31:24 +00:00
. unwrap ( )
. to_str ( )
. unwrap ( )
. to_owned ( )
}
2016-08-27 23:12:58 +00:00
pub fn symlink_metadata ( & self , path : & str ) -> fs ::Metadata {
2022-11-15 15:57:08 +00:00
match fs ::symlink_metadata ( self . plus ( path ) ) {
2016-08-27 23:12:58 +00:00
Ok ( m ) = > m ,
Err ( e ) = > panic! ( " {} " , e ) ,
}
}
2015-11-16 05:25:01 +00:00
pub fn metadata ( & self , path : & str ) -> fs ::Metadata {
2022-11-15 15:57:08 +00:00
match fs ::metadata ( self . plus ( path ) ) {
2015-11-16 05:25:01 +00:00
Ok ( m ) = > m ,
Err ( e ) = > panic! ( " {} " , e ) ,
}
}
2023-02-13 09:00:03 +00:00
pub fn file_exists < P : AsRef < Path > > ( & self , path : P ) -> bool {
2022-11-15 15:57:08 +00:00
match fs ::metadata ( self . plus ( path ) ) {
2015-11-16 05:25:01 +00:00
Ok ( m ) = > m . is_file ( ) ,
Err ( _ ) = > false ,
}
}
2022-07-03 17:25:18 +00:00
/// Decide whether the named symbolic link exists in the test directory.
pub fn symlink_exists ( & self , path : & str ) -> bool {
2022-11-15 15:57:08 +00:00
match fs ::symlink_metadata ( self . plus ( path ) ) {
2022-07-03 17:25:18 +00:00
Ok ( m ) = > m . file_type ( ) . is_symlink ( ) ,
Err ( _ ) = > false ,
}
}
2015-11-16 05:25:01 +00:00
pub fn dir_exists ( & self , path : & str ) -> bool {
2022-11-15 15:57:08 +00:00
match fs ::metadata ( self . plus ( path ) ) {
2015-11-16 05:25:01 +00:00
Ok ( m ) = > m . is_dir ( ) ,
Err ( _ ) = > false ,
}
}
2016-01-10 10:47:57 +00:00
pub fn root_dir_resolved ( & self ) -> String {
log_info ( " current_directory_resolved " , " " ) ;
2020-01-28 05:14:11 +00:00
let s = self
. subdir
2018-03-12 08:20:58 +00:00
. canonicalize ( )
. unwrap ( )
. to_str ( )
. unwrap ( )
. to_owned ( ) ;
2016-03-27 07:05:55 +00:00
// Due to canonicalize()'s use of GetFinalPathNameByHandleW() on Windows, the resolved path
// starts with '\\?\' to extend the limit of a given path to 32,767 wide characters.
//
// To address this issue, we remove this prepended string if available.
//
// Source:
// http://stackoverflow.com/questions/31439011/getfinalpathnamebyhandle-without-prepended
let prefix = " \\ \\ ? \\ " ;
2021-10-24 01:41:51 +00:00
if let Some ( stripped ) = s . strip_prefix ( prefix ) {
String ::from ( stripped )
2016-03-27 07:05:55 +00:00
} else {
s
}
2016-01-10 10:47:57 +00:00
}
2022-09-23 21:42:35 +00:00
/// Set the permissions of the specified file.
///
/// # Panics
///
/// This function panics if there is an error loading the metadata
/// or setting the permissions of the file.
#[ cfg(not(windows)) ]
pub fn set_mode ( & self , filename : & str , mode : u32 ) {
let path = self . plus ( filename ) ;
let mut perms = std ::fs ::metadata ( & path ) . unwrap ( ) . permissions ( ) ;
perms . set_mode ( mode ) ;
std ::fs ::set_permissions ( & path , perms ) . unwrap ( ) ;
}
2015-11-16 05:25:01 +00:00
}
2016-07-17 09:20:33 +00:00
/// An environment for running a single uutils test case, serves three functions:
/// 1. centralizes logic for locating the uutils binary and calling the utility
2021-03-22 10:13:38 +00:00
/// 2. provides a unique temporary directory for the test case
2016-07-17 09:20:33 +00:00
/// 3. copies over fixtures for the utility to the temporary directory
2021-03-22 10:13:38 +00:00
///
/// Fixtures can be found under `tests/fixtures/$util_name/`
2016-07-29 21:26:32 +00:00
pub struct TestScenario {
2021-07-27 05:21:12 +00:00
pub bin_path : PathBuf ,
2021-07-07 20:46:16 +00:00
pub util_name : String ,
2015-11-16 05:25:01 +00:00
pub fixtures : AtPath ,
tmpd : Rc < TempDir > ,
}
2016-03-29 01:13:40 +00:00
2016-07-29 21:26:32 +00:00
impl TestScenario {
2023-01-25 02:40:39 +00:00
pub fn new < T > ( util_name : T ) -> Self
where
T : AsRef < str > ,
{
2020-06-09 09:30:19 +00:00
let tmpd = Rc ::new ( TempDir ::new ( ) . unwrap ( ) ) ;
2022-01-30 13:59:31 +00:00
let ts = Self {
2023-01-22 16:46:25 +00:00
bin_path : PathBuf ::from ( TESTS_BINARY ) ,
2023-01-25 02:40:39 +00:00
util_name : util_name . as_ref ( ) . into ( ) ,
2016-11-25 19:14:46 +00:00
fixtures : AtPath ::new ( tmpd . as_ref ( ) . path ( ) ) ,
2021-04-24 14:43:13 +00:00
tmpd ,
2015-11-16 05:25:01 +00:00
} ;
let mut fixture_path_builder = env ::current_dir ( ) . unwrap ( ) ;
2016-05-22 19:09:04 +00:00
fixture_path_builder . push ( TESTS_DIR ) ;
fixture_path_builder . push ( FIXTURES_DIR ) ;
2023-01-25 02:40:39 +00:00
fixture_path_builder . push ( util_name . as_ref ( ) ) ;
2016-11-25 19:14:46 +00:00
if let Ok ( m ) = fs ::metadata ( & fixture_path_builder ) {
if m . is_dir ( ) {
2015-11-16 05:25:01 +00:00
recursive_copy ( & fixture_path_builder , & ts . fixtures . subdir ) . unwrap ( ) ;
2016-11-25 19:14:46 +00:00
}
2015-11-16 05:25:01 +00:00
}
ts
}
2016-03-29 01:13:40 +00:00
2021-03-22 10:13:38 +00:00
/// Returns builder for invoking the target uutils binary. Paths given are
/// treated relative to the environment's unique temporary test directory.
2016-07-29 21:26:32 +00:00
pub fn ucmd ( & self ) -> UCommand {
2023-01-25 02:40:39 +00:00
UCommand ::from_test_scenario ( self )
2015-11-16 05:25:01 +00:00
}
2016-03-29 01:13:40 +00:00
2021-03-22 10:13:38 +00:00
/// Returns builder for invoking any system command. Paths given are treated
/// relative to the environment's unique temporary test directory.
2023-01-25 02:40:39 +00:00
pub fn cmd < S : Into < PathBuf > > ( & self , bin_path : S ) -> UCommand {
let mut command = UCommand ::new ( ) ;
command . bin_path ( bin_path ) ;
command . temp_dir ( self . tmpd . clone ( ) ) ;
command
2015-11-16 05:25:01 +00:00
}
2016-03-29 01:13:40 +00:00
2021-04-01 21:16:47 +00:00
/// Returns builder for invoking any uutils command. Paths given are treated
/// relative to the environment's unique temporary test directory.
2023-01-25 02:40:39 +00:00
pub fn ccmd < S : AsRef < str > > ( & self , util_name : S ) -> UCommand {
UCommand ::with_util ( util_name , self . tmpd . clone ( ) )
2015-11-16 05:25:01 +00:00
}
}
2023-01-25 02:40:39 +00:00
/// A `UCommand` is a builder wrapping an individual Command that provides several additional features:
2016-07-17 09:20:33 +00:00
/// 1. it has convenience functions that are more ergonomic to use for piping in stdin, spawning the command
/// and asserting on the results.
/// 2. it tracks arguments provided so that in test cases which may provide variations of an arg in loops
/// the test failure can display the exact call which preceded an assertion failure.
2023-01-25 02:40:39 +00:00
/// 3. it provides convenience construction methods to set the Command uutils utility and temporary directory.
///
/// Per default `UCommand` runs a command given as an argument in a shell, platform independently.
/// It does so with safety in mind, so the working directory is set to an individual temporary
/// directory and the environment variables are cleared per default.
///
/// The default behavior can be changed with builder methods:
/// * [`UCommand::with_util`]: Run `coreutils UTIL_NAME` instead of the shell
/// * [`UCommand::from_test_scenario`]: Run `coreutils UTIL_NAME` instead of the shell in the
/// temporary directory of the [`TestScenario`]
/// * [`UCommand::current_dir`]: Sets the working directory
/// * ...
#[ derive(Debug, Default) ]
2015-11-16 05:25:01 +00:00
pub struct UCommand {
2023-01-25 02:40:39 +00:00
args : VecDeque < OsString > ,
2023-01-22 16:46:25 +00:00
env_vars : Vec < ( OsString , OsString ) > ,
current_dir : Option < PathBuf > ,
bin_path : Option < PathBuf > ,
2021-11-09 05:16:06 +00:00
util_name : Option < String > ,
2015-11-16 05:25:01 +00:00
has_run : bool ,
2021-04-09 09:08:31 +00:00
ignore_stdin_write_error : bool ,
2021-04-22 20:37:44 +00:00
stdin : Option < Stdio > ,
stdout : Option < Stdio > ,
stderr : Option < Stdio > ,
bytes_into_stdin : Option < Vec < u8 > > ,
2024-03-05 14:56:20 +00:00
#[ cfg(unix) ]
2022-04-12 07:18:29 +00:00
limits : Vec < ( rlimit ::Resource , u64 , u64 ) > ,
2022-11-14 17:39:48 +00:00
stderr_to_stdout : bool ,
2022-12-10 18:57:25 +00:00
timeout : Option < Duration > ,
2024-02-25 17:54:10 +00:00
#[ cfg(unix) ]
2024-02-07 21:02:19 +00:00
terminal_simulation : bool ,
2024-02-25 17:54:10 +00:00
#[ cfg(unix) ]
terminal_size : Option < libc ::winsize > ,
2022-11-23 05:43:02 +00:00
tmpd : Option < Rc < TempDir > > , // drop last
2015-11-16 05:25:01 +00:00
}
2016-03-29 01:13:40 +00:00
2015-11-16 05:25:01 +00:00
impl UCommand {
2023-01-25 02:40:39 +00:00
/// Create a new plain [`UCommand`].
///
/// Executes a command that must be given as argument (for example with [`UCommand::arg`] in a
/// shell (`sh -c` on unix platforms or `cmd /C` on windows).
///
/// Per default the environment is cleared and the working directory is set to an individual
/// temporary directory for safety purposes.
2023-01-22 16:46:25 +00:00
pub fn new ( ) -> Self {
Self {
2023-01-25 02:40:39 +00:00
.. Default ::default ( )
2015-11-16 05:25:01 +00:00
}
}
2021-11-09 05:16:06 +00:00
2023-01-25 02:40:39 +00:00
/// Create a [`UCommand`] for a specific uutils utility.
///
/// Sets the temporary directory to `tmpd` and the execution binary to the path where
/// `coreutils` is found.
pub fn with_util < T > ( util_name : T , tmpd : Rc < TempDir > ) -> Self
where
T : AsRef < str > ,
{
let mut ucmd = Self ::new ( ) ;
ucmd . util_name = Some ( util_name . as_ref ( ) . into ( ) ) ;
ucmd . bin_path ( TESTS_BINARY ) . temp_dir ( tmpd ) ;
2021-11-09 05:16:06 +00:00
ucmd
2015-11-16 05:25:01 +00:00
}
2016-03-29 01:13:40 +00:00
2023-01-25 02:40:39 +00:00
/// Create a [`UCommand`] from a [`TestScenario`].
///
/// The temporary directory and uutils utility are inherited from the [`TestScenario`] and the
/// execution binary is set to `coreutils`.
pub fn from_test_scenario ( scene : & TestScenario ) -> Self {
Self ::with_util ( & scene . util_name , scene . tmpd . clone ( ) )
}
/// Set the execution binary.
///
/// Make sure the binary found at this path is executable. It's safest to provide the
/// canonicalized path instead of just the name of the executable, since path resolution is not
/// guaranteed to work on all platforms.
fn bin_path < T > ( & mut self , bin_path : T ) -> & mut Self
where
T : Into < PathBuf > ,
{
self . bin_path = Some ( bin_path . into ( ) ) ;
self
}
/// Set the temporary directory.
///
/// Per default an individual temporary directory is created for every [`UCommand`]. If not
/// specified otherwise with [`UCommand::current_dir`] the working directory is set to this
/// temporary directory.
fn temp_dir ( & mut self , temp_dir : Rc < TempDir > ) -> & mut Self {
self . tmpd = Some ( temp_dir ) ;
self
}
/// Set the working directory for this [`UCommand`]
///
/// Per default the working directory is set to the [`UCommands`] temporary directory.
2023-01-22 16:46:25 +00:00
pub fn current_dir < T > ( & mut self , current_dir : T ) -> & mut Self
where
2023-01-25 02:40:39 +00:00
T : Into < PathBuf > ,
2023-01-22 16:46:25 +00:00
{
2023-01-25 02:40:39 +00:00
self . current_dir = Some ( current_dir . into ( ) ) ;
2023-01-22 16:46:25 +00:00
self
2015-11-16 05:25:01 +00:00
}
2016-03-29 01:13:40 +00:00
2022-01-30 13:59:31 +00:00
pub fn set_stdin < T : Into < Stdio > > ( & mut self , stdin : T ) -> & mut Self {
2021-04-22 20:37:44 +00:00
self . stdin = Some ( stdin . into ( ) ) ;
self
}
2022-01-30 13:59:31 +00:00
pub fn set_stdout < T : Into < Stdio > > ( & mut self , stdout : T ) -> & mut Self {
2021-04-22 20:37:44 +00:00
self . stdout = Some ( stdout . into ( ) ) ;
self
}
2022-01-30 13:59:31 +00:00
pub fn set_stderr < T : Into < Stdio > > ( & mut self , stderr : T ) -> & mut Self {
2021-04-22 20:37:44 +00:00
self . stderr = Some ( stderr . into ( ) ) ;
self
}
2022-11-14 17:39:48 +00:00
pub fn stderr_to_stdout ( & mut self ) -> & mut Self {
self . stderr_to_stdout = true ;
self
}
2021-03-22 10:13:38 +00:00
/// Add a parameter to the invocation. Path arguments are treated relative
/// to the test environment directory.
2022-01-30 13:59:31 +00:00
pub fn arg < S : AsRef < OsStr > > ( & mut self , arg : S ) -> & mut Self {
2023-01-25 02:40:39 +00:00
self . args . push_back ( arg . as_ref ( ) . into ( ) ) ;
2021-03-31 09:25:23 +00:00
self
2015-11-16 05:25:01 +00:00
}
2021-03-22 10:13:38 +00:00
/// Add multiple parameters to the invocation. Path arguments are treated relative
/// to the test environment directory.
2022-01-30 13:59:31 +00:00
pub fn args < S : AsRef < OsStr > > ( & mut self , args : & [ S ] ) -> & mut Self {
2023-01-22 16:46:25 +00:00
self . args . extend ( args . iter ( ) . map ( | s | s . as_ref ( ) . into ( ) ) ) ;
2021-03-31 09:25:23 +00:00
self
2015-11-16 05:25:01 +00:00
}
2016-01-03 13:16:12 +00:00
2021-05-30 05:10:54 +00:00
/// provides standard input to feed in to the command when spawned
2022-01-30 13:59:31 +00:00
pub fn pipe_in < T : Into < Vec < u8 > > > ( & mut self , input : T ) -> & mut Self {
2021-10-11 20:51:14 +00:00
assert! (
2022-01-30 10:09:50 +00:00
self . bytes_into_stdin . is_none ( ) ,
2021-10-11 20:51:14 +00:00
" {} " ,
MULTIPLE_STDIN_MEANINGLESS
) ;
2022-12-03 09:59:01 +00:00
self . set_stdin ( Stdio ::piped ( ) ) ;
2021-04-22 20:37:44 +00:00
self . bytes_into_stdin = Some ( input . into ( ) ) ;
2021-03-31 09:25:23 +00:00
self
2016-07-17 09:20:33 +00:00
}
2022-12-14 14:42:07 +00:00
/// like `pipe_in()`, but uses the contents of the file at the provided relative path as the piped in data
2022-01-30 13:59:31 +00:00
pub fn pipe_in_fixture < S : AsRef < OsStr > > ( & mut self , file_rel_path : S ) -> & mut Self {
2016-07-29 21:26:32 +00:00
let contents = read_scenario_fixture ( & self . tmpd , file_rel_path ) ;
self . pipe_in ( contents )
}
2021-04-09 09:08:31 +00:00
/// Ignores error caused by feeding stdin to the command.
/// This is typically useful to test non-standard workflows
/// like feeding something to a command that does not read it
2022-01-30 13:59:31 +00:00
pub fn ignore_stdin_write_error ( & mut self ) -> & mut Self {
2021-04-09 09:08:31 +00:00
self . ignore_stdin_write_error = true ;
self
}
2022-01-30 13:59:31 +00:00
pub fn env < K , V > ( & mut self , key : K , val : V ) -> & mut Self
2018-03-12 08:20:58 +00:00
where
K : AsRef < OsStr > ,
V : AsRef < OsStr > ,
{
2023-01-22 16:46:25 +00:00
self . env_vars
. push ( ( key . as_ref ( ) . into ( ) , val . as_ref ( ) . into ( ) ) ) ;
2021-03-31 09:25:23 +00:00
self
2016-01-03 13:16:12 +00:00
}
2023-03-23 17:15:05 +00:00
pub fn envs < I , K , V > ( & mut self , iter : I ) -> & mut Self
where
I : IntoIterator < Item = ( K , V ) > ,
K : AsRef < OsStr > ,
V : AsRef < OsStr > ,
{
for ( k , v ) in iter {
self . env ( k , v ) ;
}
self
}
2024-03-05 14:56:20 +00:00
#[ cfg(unix) ]
2023-01-25 02:40:39 +00:00
pub fn limit (
2021-06-02 16:06:56 +00:00
& mut self ,
resource : rlimit ::Resource ,
2022-04-12 07:18:29 +00:00
soft_limit : u64 ,
hard_limit : u64 ,
2021-06-02 16:06:56 +00:00
) -> & mut Self {
self . limits . push ( ( resource , soft_limit , hard_limit ) ) ;
self
}
2022-12-10 18:57:25 +00:00
/// Set the timeout for [`UCommand::run`] and similar methods in [`UCommand`].
///
/// After the timeout elapsed these `run` methods (besides [`UCommand::run_no_wait`]) will
/// panic. When [`UCommand::run_no_wait`] is used, this timeout is applied to
/// [`UChild::wait_with_output`] including all other waiting methods in [`UChild`] implicitly
/// using `wait_with_output()` and additionally [`UChild::kill`]. The default timeout of `kill`
/// will be overwritten by this `timeout`.
pub fn timeout ( & mut self , timeout : Duration ) -> & mut Self {
self . timeout = Some ( timeout ) ;
self
}
2024-02-25 17:54:10 +00:00
/// Set if process should be run in a simulated terminal
///
2024-02-07 21:02:19 +00:00
/// This is useful to test behavior that is only active if [`stdout.is_terminal()`] is [`true`].
2024-02-25 17:54:10 +00:00
/// (unix: pty, windows: ConPTY[not yet supported])
2024-02-07 21:02:19 +00:00
#[ cfg(unix) ]
pub fn terminal_simulation ( & mut self , enable : bool ) -> & mut Self {
self . terminal_simulation = enable ;
self
}
2024-02-25 17:54:10 +00:00
/// Set if process should be run in a simulated terminal with specific size
///
/// This is useful to test behavior that is only active if [`stdout.is_terminal()`] is [`true`].
/// And the size of the terminal matters additionally.
#[ cfg(unix) ]
pub fn terminal_size ( & mut self , win_size : libc ::winsize ) -> & mut Self {
self . terminal_simulation ( true ) ;
self . terminal_size = Some ( win_size ) ;
self
}
2024-02-07 21:02:19 +00:00
#[ cfg(unix) ]
fn read_from_pty ( pty_fd : std ::os ::fd ::OwnedFd , out : File ) {
let read_file = std ::fs ::File ::from ( pty_fd ) ;
let mut reader = std ::io ::BufReader ::new ( read_file ) ;
let mut writer = std ::io ::BufWriter ::new ( out ) ;
let result = std ::io ::copy ( & mut reader , & mut writer ) ;
match result {
Ok ( _ ) = > { }
// Input/output error (os error 5) is returned due to pipe closes. Buffer gets content anyway.
Err ( e ) if e . raw_os_error ( ) . unwrap_or_default ( ) = = 5 = > { }
Err ( e ) = > {
eprintln! ( " Unexpected error: {:?} " , e ) ;
2024-02-25 17:54:10 +00:00
panic! ( " error forwarding output of pty " ) ;
2024-02-07 21:02:19 +00:00
}
}
}
#[ cfg(unix) ]
fn spawn_reader_thread (
& self ,
captured_output : Option < CapturedOutput > ,
pty_fd_master : OwnedFd ,
name : String ,
) -> Option < CapturedOutput > {
if let Some ( mut captured_output_i ) = captured_output {
let fd = captured_output_i . try_clone ( ) . unwrap ( ) ;
let handle = std ::thread ::Builder ::new ( )
. name ( name )
. spawn ( move | | {
Self ::read_from_pty ( pty_fd_master , fd ) ;
} )
. unwrap ( ) ;
captured_output_i . reader_thread_handle = Some ( handle ) ;
Some ( captured_output_i )
} else {
None
}
}
2023-01-25 02:40:39 +00:00
/// Build the `std::process::Command` and apply the defaults on fields which were not specified
/// by the user.
///
/// These __defaults__ are:
/// * `bin_path`: Depending on the platform and os, the native shell (unix -> `/bin/sh` etc.).
/// This default also requires to set the first argument to `-c` on unix (`/C` on windows) if
/// this argument wasn't specified explicitly by the user.
/// * `util_name`: `None`. If neither `bin_path` nor `util_name` were given the arguments are
/// run in a shell (See `bin_path` above).
/// * `temp_dir`: If `current_dir` was not set, a new temporary directory will be created in
/// which this command will be run and `current_dir` will be set to this `temp_dir`.
/// * `current_dir`: The temporary directory given by `temp_dir`.
/// * `timeout`: `30 seconds`
/// * `stdin`: `Stdio::null()`
/// * `ignore_stdin_write_error`: `false`
/// * `stdout`, `stderr`: If not specified the output will be captured with [`CapturedOutput`]
/// * `stderr_to_stdout`: `false`
/// * `bytes_into_stdin`: `None`
/// * `limits`: `None`.
2024-02-07 21:02:19 +00:00
fn build (
& mut self ,
) -> (
Command ,
Option < CapturedOutput > ,
Option < CapturedOutput > ,
Option < File > ,
) {
2023-01-22 16:46:25 +00:00
if self . bin_path . is_some ( ) {
if let Some ( util_name ) = & self . util_name {
2023-01-25 02:40:39 +00:00
self . args . push_front ( util_name . into ( ) ) ;
2023-01-22 16:46:25 +00:00
}
} else if let Some ( util_name ) = & self . util_name {
self . bin_path = Some ( PathBuf ::from ( TESTS_BINARY ) ) ;
2023-01-25 02:40:39 +00:00
self . args . push_front ( util_name . into ( ) ) ;
// neither `bin_path` nor `util_name` was set so we apply the default to run the arguments
// in a platform specific shell
2023-01-22 16:46:25 +00:00
} else if cfg! ( unix ) {
2023-01-25 02:40:39 +00:00
#[ cfg(target_os = " android " ) ]
let bin_path = PathBuf ::from ( " /system/bin/sh " ) ;
#[ cfg(not(target_os = " android " )) ]
let bin_path = PathBuf ::from ( " /bin/sh " ) ;
2023-01-22 16:46:25 +00:00
self . bin_path = Some ( bin_path ) ;
let c_arg = OsString ::from ( " -c " ) ;
if ! self . args . contains ( & c_arg ) {
2023-01-25 02:40:39 +00:00
self . args . push_front ( c_arg ) ;
2023-01-22 16:46:25 +00:00
}
} else {
self . bin_path = Some ( PathBuf ::from ( " cmd " ) ) ;
let c_arg = OsString ::from ( " /C " ) ;
let k_arg = OsString ::from ( " /K " ) ;
if ! self
. args
. iter ( )
. any ( | s | s . eq_ignore_ascii_case ( & c_arg ) | | s . eq_ignore_ascii_case ( & k_arg ) )
{
2023-01-25 02:40:39 +00:00
self . args . push_front ( c_arg ) ;
2023-01-22 16:46:25 +00:00
}
} ;
2023-01-25 02:40:39 +00:00
// unwrap is safe here because we have set `self.bin_path` before
2023-01-22 16:46:25 +00:00
let mut command = Command ::new ( self . bin_path . as_ref ( ) . unwrap ( ) ) ;
command . args ( & self . args ) ;
2023-01-25 02:40:39 +00:00
// We use a temporary directory as working directory if not specified otherwise with
// `current_dir()`. If neither `current_dir` nor a temporary directory is available, then we
// create our own.
2023-01-22 16:46:25 +00:00
if let Some ( current_dir ) = & self . current_dir {
command . current_dir ( current_dir ) ;
2023-01-25 02:40:39 +00:00
} else if let Some ( temp_dir ) = & self . tmpd {
command . current_dir ( temp_dir . path ( ) ) ;
2023-01-22 16:46:25 +00:00
} else {
2023-01-25 02:40:39 +00:00
let temp_dir = tempfile ::tempdir ( ) . unwrap ( ) ;
self . current_dir = Some ( temp_dir . path ( ) . into ( ) ) ;
command . current_dir ( temp_dir . path ( ) ) ;
self . tmpd = Some ( Rc ::new ( temp_dir ) ) ;
2023-01-22 16:46:25 +00:00
}
2023-03-24 02:52:02 +00:00
command . env_clear ( ) ;
if cfg! ( windows ) {
// spell-checker:ignore (dll) rsaenh
// %SYSTEMROOT% is required on Windows to initialize crypto provider
// ... and crypto provider is required for std::rand
// From `procmon`: RegQueryValue HKLM\SOFTWARE\Microsoft\Cryptography\Defaults\Provider\Microsoft Strong Cryptographic Provider\Image Path
// SUCCESS Type: REG_SZ, Length: 66, Data: %SystemRoot%\system32\rsaenh.dll"
if let Some ( systemroot ) = env ::var_os ( " SYSTEMROOT " ) {
command . env ( " SYSTEMROOT " , systemroot ) ;
}
} else {
// if someone is setting LD_PRELOAD, there's probably a good reason for it
if let Some ( ld_preload ) = env ::var_os ( " LD_PRELOAD " ) {
command . env ( " LD_PRELOAD " , ld_preload ) ;
2023-01-22 16:46:25 +00:00
}
}
2023-03-23 17:15:05 +00:00
command
. envs ( DEFAULT_ENV )
. envs ( self . env_vars . iter ( ) . cloned ( ) ) ;
2023-01-25 02:40:39 +00:00
if self . timeout . is_none ( ) {
self . timeout = Some ( Duration ::from_secs ( 30 ) ) ;
2023-01-22 16:46:25 +00:00
}
2022-11-23 05:43:02 +00:00
let mut captured_stdout = None ;
let mut captured_stderr = None ;
2024-02-07 21:02:19 +00:00
#[ cfg(unix) ]
let mut stdin_pty : Option < File > = None ;
#[ cfg(not(unix)) ]
let stdin_pty : Option < File > = None ;
2023-01-22 16:46:25 +00:00
if self . stderr_to_stdout {
2022-11-23 05:43:02 +00:00
let mut output = CapturedOutput ::default ( ) ;
2023-01-22 16:46:25 +00:00
command
2022-12-03 09:59:01 +00:00
. stdin ( self . stdin . take ( ) . unwrap_or_else ( Stdio ::null ) )
2022-11-23 05:43:02 +00:00
. stdout ( Stdio ::from ( output . try_clone ( ) . unwrap ( ) ) )
. stderr ( Stdio ::from ( output . try_clone ( ) . unwrap ( ) ) ) ;
captured_stdout = Some ( output ) ;
2022-11-14 17:39:48 +00:00
} else {
let stdout = if self . stdout . is_some ( ) {
self . stdout . take ( ) . unwrap ( )
} else {
2022-11-23 05:43:02 +00:00
let mut stdout = CapturedOutput ::default ( ) ;
let stdio = Stdio ::from ( stdout . try_clone ( ) . unwrap ( ) ) ;
captured_stdout = Some ( stdout ) ;
2022-11-14 17:39:48 +00:00
stdio
} ;
2022-11-23 05:43:02 +00:00
2022-11-14 17:39:48 +00:00
let stderr = if self . stderr . is_some ( ) {
self . stderr . take ( ) . unwrap ( )
} else {
2022-11-23 05:43:02 +00:00
let mut stderr = CapturedOutput ::default ( ) ;
let stdio = Stdio ::from ( stderr . try_clone ( ) . unwrap ( ) ) ;
captured_stderr = Some ( stderr ) ;
2022-11-14 17:39:48 +00:00
stdio
} ;
2022-11-23 05:43:02 +00:00
2023-01-22 16:46:25 +00:00
command
2022-12-03 09:59:01 +00:00
. stdin ( self . stdin . take ( ) . unwrap_or_else ( Stdio ::null ) )
2022-11-14 17:39:48 +00:00
. stdout ( stdout )
2023-01-22 16:46:25 +00:00
. stderr ( stderr ) ;
2022-11-14 17:39:48 +00:00
} ;
2024-02-07 21:02:19 +00:00
#[ cfg(unix) ]
if self . terminal_simulation {
2024-02-25 17:54:10 +00:00
let terminal_size = self . terminal_size . unwrap_or ( libc ::winsize {
2024-02-07 21:02:19 +00:00
ws_col : 80 ,
ws_row : 30 ,
2024-02-25 17:54:10 +00:00
ws_xpixel : 80 * 8 ,
ws_ypixel : 30 * 10 ,
} ) ;
2024-02-07 21:02:19 +00:00
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 ) ;
}
2024-03-05 14:56:20 +00:00
#[ cfg(unix) ]
if ! self . limits . is_empty ( ) {
// just to be safe: move a copy of the limits list into the closure.
// this way the closure is fully self-contained.
let limits_copy = self . limits . clone ( ) ;
let closure = move | | -> Result < ( ) > {
for & ( resource , soft_limit , hard_limit ) in & limits_copy {
setrlimit ( resource , soft_limit , hard_limit ) ? ;
}
Ok ( ( ) )
} ;
// SAFETY: the closure is self-contained and doesn't do any memory
// writes that would need to be propagated back to the parent process.
// also, the closure doesn't access stdin, stdout and stderr.
unsafe {
command . pre_exec ( closure ) ;
}
}
2024-02-07 21:02:19 +00:00
( command , captured_stdout , captured_stderr , stdin_pty )
2023-01-22 16:46:25 +00:00
}
/// Spawns the command, feeds the stdin if any, and returns the
/// child process immediately.
pub fn run_no_wait ( & mut self ) -> UChild {
assert! ( ! self . has_run , " {} " , ALREADY_RUN ) ;
self . has_run = true ;
2024-02-07 21:02:19 +00:00
let ( mut command , captured_stdout , captured_stderr , stdin_pty ) = self . build ( ) ;
2023-01-22 16:46:25 +00:00
log_info ( " run " , self . to_string ( ) ) ;
2022-11-23 05:43:02 +00:00
let child = command . spawn ( ) . unwrap ( ) ;
2016-04-02 14:23:26 +00:00
2024-02-07 21:02:19 +00:00
let mut child = UChild ::from ( self , child , captured_stdout , captured_stderr , stdin_pty ) ;
2022-11-23 05:43:02 +00:00
if let Some ( input ) = self . bytes_into_stdin . take ( ) {
child . pipe_in ( input ) ;
2016-04-02 14:23:26 +00:00
}
2021-04-09 09:08:31 +00:00
child
2016-04-02 14:41:59 +00:00
}
/// Spawns the command, feeds the stdin if any, waits for the result
2016-07-17 09:20:33 +00:00
/// and returns a command result.
/// It is recommended that you instead use succeeds() or fails()
2016-04-02 14:41:59 +00:00
pub fn run ( & mut self ) -> CmdResult {
2022-11-23 05:43:02 +00:00
self . run_no_wait ( ) . wait ( ) . unwrap ( )
2015-11-16 05:25:01 +00:00
}
2016-03-29 01:13:40 +00:00
2016-07-17 09:20:33 +00:00
/// Spawns the command, feeding the passed in stdin, waits for the result
/// and returns a command result.
2022-12-14 14:42:07 +00:00
/// It is recommended that, instead of this, you use a combination of `pipe_in()`
2016-07-17 09:20:33 +00:00
/// with succeeds() or fails()
2016-02-17 05:03:52 +00:00
pub fn run_piped_stdin < T : Into < Vec < u8 > > > ( & mut self , input : T ) -> CmdResult {
self . pipe_in ( input ) . run ( )
}
2016-03-29 01:13:40 +00:00
2016-07-17 09:20:33 +00:00
/// Spawns the command, feeds the stdin if any, waits for the result,
/// asserts success, and returns a command result.
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2016-02-17 05:03:52 +00:00
pub fn succeeds ( & mut self ) -> CmdResult {
let cmd_result = self . run ( ) ;
cmd_result . success ( ) ;
cmd_result
}
2016-03-29 01:13:40 +00:00
2016-07-17 09:20:33 +00:00
/// Spawns the command, feeds the stdin if any, waits for the result,
2019-04-28 09:12:37 +00:00
/// asserts failure, and returns a command result.
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2016-02-17 05:03:52 +00:00
pub fn fails ( & mut self ) -> CmdResult {
let cmd_result = self . run ( ) ;
cmd_result . failure ( ) ;
cmd_result
2015-11-16 05:25:01 +00:00
}
2018-12-15 05:57:40 +00:00
2021-05-29 17:34:17 +00:00
pub fn get_full_fixture_path ( & self , file_rel_path : & str ) -> String {
2018-12-15 05:57:40 +00:00
let tmpdir_path = self . tmpd . as_ref ( ) . unwrap ( ) . path ( ) ;
2022-12-14 14:42:07 +00:00
format! ( " {} / {file_rel_path} " , tmpdir_path . to_str ( ) . unwrap ( ) )
2018-12-15 05:57:40 +00:00
}
2015-11-16 05:25:01 +00:00
}
2023-01-22 16:46:25 +00:00
impl std ::fmt ::Display for UCommand {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
let mut comm_string : Vec < String > = vec! [ self
. bin_path
. as_ref ( )
2023-01-25 02:40:39 +00:00
. map_or ( String ::new ( ) , | p | p . display ( ) . to_string ( ) ) ] ;
2023-01-22 16:46:25 +00:00
comm_string . extend ( self . args . iter ( ) . map ( | s | s . to_string_lossy ( ) . to_string ( ) ) ) ;
f . write_str ( & comm_string . join ( " " ) )
}
}
2022-11-14 17:39:48 +00:00
/// Stored the captured output in a temporary file. The file is deleted as soon as
/// [`CapturedOutput`] is dropped.
#[ derive(Debug) ]
2022-11-23 05:43:02 +00:00
struct CapturedOutput {
2022-11-14 17:39:48 +00:00
current_file : File ,
2022-11-23 05:43:02 +00:00
output : tempfile ::NamedTempFile , // drop last
2024-02-07 21:02:19 +00:00
reader_thread_handle : Option < thread ::JoinHandle < ( ) > > ,
2022-01-25 02:18:59 +00:00
}
2022-11-14 17:39:48 +00:00
impl CapturedOutput {
2022-12-14 14:42:07 +00:00
/// Creates a new instance of `CapturedOutput`
2022-11-14 17:39:48 +00:00
fn new ( output : tempfile ::NamedTempFile ) -> Self {
Self {
current_file : output . reopen ( ) . unwrap ( ) ,
output ,
2024-02-07 21:02:19 +00:00
reader_thread_handle : None ,
2022-11-14 17:39:48 +00:00
}
}
2022-11-23 05:43:02 +00:00
/// Try to clone the file pointer.
fn try_clone ( & mut self ) -> io ::Result < File > {
self . output . as_file ( ) . try_clone ( )
2022-11-14 17:39:48 +00:00
}
2022-11-23 05:43:02 +00:00
/// Return the captured output as [`String`].
///
/// Subsequent calls to any of the other output methods will operate on the subsequent output.
fn output ( & mut self ) -> String {
String ::from_utf8 ( self . output_bytes ( ) ) . unwrap ( )
2022-11-14 17:39:48 +00:00
}
2022-11-23 05:43:02 +00:00
/// Return the exact amount of bytes as `String`.
///
/// Subsequent calls to any of the other output methods will operate on the subsequent output.
///
/// # Important
///
/// This method blocks indefinitely if the amount of bytes given by `size` cannot be read
fn output_exact ( & mut self , size : usize ) -> String {
2022-11-14 17:39:48 +00:00
String ::from_utf8 ( self . output_exact_bytes ( size ) ) . unwrap ( )
}
2022-11-23 05:43:02 +00:00
/// Return the captured output as bytes.
///
/// Subsequent calls to any of the other output methods will operate on the subsequent output.
fn output_bytes ( & mut self ) -> Vec < u8 > {
2022-11-14 17:39:48 +00:00
let mut buffer = Vec ::< u8 > ::new ( ) ;
self . current_file . read_to_end ( & mut buffer ) . unwrap ( ) ;
buffer
}
2022-11-23 05:43:02 +00:00
/// Return all captured output, so far.
///
/// Subsequent calls to any of the other output methods will operate on the subsequent output.
fn output_all_bytes ( & mut self ) -> Vec < u8 > {
2022-11-14 17:39:48 +00:00
let mut buffer = Vec ::< u8 > ::new ( ) ;
2022-11-23 05:43:02 +00:00
let mut file = self . output . reopen ( ) . unwrap ( ) ;
file . read_to_end ( & mut buffer ) . unwrap ( ) ;
self . current_file = file ;
2022-11-14 17:39:48 +00:00
buffer
}
2022-11-23 05:43:02 +00:00
/// Return the exact amount of bytes.
///
/// Subsequent calls to any of the other output methods will operate on the subsequent output.
///
/// # Important
///
/// This method blocks indefinitely if the amount of bytes given by `size` cannot be read
fn output_exact_bytes ( & mut self , size : usize ) -> Vec < u8 > {
2022-11-14 17:39:48 +00:00
let mut buffer = vec! [ 0 ; size ] ;
2022-11-23 05:43:02 +00:00
self . current_file . read_exact ( & mut buffer ) . unwrap ( ) ;
2022-11-14 17:39:48 +00:00
buffer
}
}
2022-11-23 05:43:02 +00:00
impl Default for CapturedOutput {
fn default ( ) -> Self {
let mut retries = 10 ;
let file = loop {
let file = Builder ::new ( ) . rand_bytes ( 10 ) . suffix ( " .out " ) . tempfile ( ) ;
if file . is_ok ( ) | | retries < = 0 {
break file . unwrap ( ) ;
}
sleep ( Duration ::from_millis ( 100 ) ) ;
retries - = 1 ;
} ;
Self {
current_file : file . reopen ( ) . unwrap ( ) ,
output : file ,
2024-02-07 21:02:19 +00:00
reader_thread_handle : None ,
2022-11-23 05:43:02 +00:00
}
}
}
2022-11-14 17:39:48 +00:00
impl Drop for CapturedOutput {
fn drop ( & mut self ) {
let _ = remove_file ( self . output . path ( ) ) ;
}
}
#[ derive(Debug, Copy, Clone) ]
pub enum AssertionMode {
All ,
Current ,
Exact ( usize , usize ) ,
}
pub struct UChildAssertion < ' a > {
uchild : & ' a mut UChild ,
}
impl < ' a > UChildAssertion < ' a > {
pub fn new ( uchild : & ' a mut UChild ) -> Self {
Self { uchild }
}
fn with_output ( & mut self , mode : AssertionMode ) -> CmdResult {
2023-01-04 20:30:14 +00:00
let exit_status = if self . uchild . is_alive ( ) {
None
} else {
Some ( self . uchild . raw . wait ( ) . unwrap ( ) )
2022-11-14 17:39:48 +00:00
} ;
let ( stdout , stderr ) = match mode {
AssertionMode ::All = > (
self . uchild . stdout_all_bytes ( ) ,
self . uchild . stderr_all_bytes ( ) ,
) ,
AssertionMode ::Current = > ( self . uchild . stdout_bytes ( ) , self . uchild . stderr_bytes ( ) ) ,
AssertionMode ::Exact ( expected_stdout_size , expected_stderr_size ) = > (
self . uchild . stdout_exact_bytes ( expected_stdout_size ) ,
self . uchild . stderr_exact_bytes ( expected_stderr_size ) ,
) ,
} ;
2023-01-25 02:40:39 +00:00
CmdResult ::new (
self . uchild . bin_path . clone ( ) ,
self . uchild . util_name . clone ( ) ,
self . uchild . tmpd . clone ( ) ,
2023-01-04 20:30:14 +00:00
exit_status ,
2022-11-14 17:39:48 +00:00
stdout ,
stderr ,
2023-01-25 02:40:39 +00:00
)
2022-11-14 17:39:48 +00:00
}
2022-11-23 05:43:02 +00:00
// Make assertions of [`CmdResult`] with all output from start of the process until now.
//
// This method runs [`UChild::stdout_all_bytes`] and [`UChild::stderr_all_bytes`] under the
// hood. See there for side effects
2022-11-14 17:39:48 +00:00
pub fn with_all_output ( & mut self ) -> CmdResult {
self . with_output ( AssertionMode ::All )
}
2022-11-23 05:43:02 +00:00
// Make assertions of [`CmdResult`] with the current output.
//
// This method runs [`UChild::stdout_bytes`] and [`UChild::stderr_bytes`] under the hood. See
// there for side effects
2022-11-14 17:39:48 +00:00
pub fn with_current_output ( & mut self ) -> CmdResult {
self . with_output ( AssertionMode ::Current )
}
2022-11-23 05:43:02 +00:00
// Make assertions of [`CmdResult`] with the exact output.
//
// This method runs [`UChild::stdout_exact_bytes`] and [`UChild::stderr_exact_bytes`] under the
// hood. See there for side effects
2022-11-14 17:39:48 +00:00
pub fn with_exact_output (
& mut self ,
expected_stdout_size : usize ,
expected_stderr_size : usize ,
) -> CmdResult {
self . with_output ( AssertionMode ::Exact (
expected_stdout_size ,
expected_stderr_size ,
) )
}
2022-11-23 05:43:02 +00:00
// Assert that the child process is alive
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-11-14 17:39:48 +00:00
pub fn is_alive ( & mut self ) -> & mut Self {
match self
. uchild
2022-11-23 05:43:02 +00:00
. raw
2022-11-14 17:39:48 +00:00
. try_wait ( )
{
Ok ( Some ( status ) ) = > panic! (
" Assertion failed. Expected '{}' to be running but exited with status={}. \n stdout: {} \n stderr: {} " ,
uucore ::util_name ( ) ,
status ,
self . uchild . stdout_all ( ) ,
self . uchild . stderr_all ( )
) ,
Ok ( None ) = > { }
2022-12-14 14:42:07 +00:00
Err ( error ) = > panic! ( " Assertion failed with error ' {error:?} ' " ) ,
2022-11-14 17:39:48 +00:00
}
self
}
2022-11-23 05:43:02 +00:00
// Assert that the child process has exited
2023-01-01 14:25:37 +00:00
#[ track_caller ]
2022-11-14 17:39:48 +00:00
pub fn is_not_alive ( & mut self ) -> & mut Self {
match self
. uchild
2022-11-23 05:43:02 +00:00
. raw
2022-11-14 17:39:48 +00:00
. try_wait ( )
{
Ok ( None ) = > panic! (
" Assertion failed. Expected '{}' to be not running but was alive. \n stdout: {} \n stderr: {} " ,
uucore ::util_name ( ) ,
self . uchild . stdout_all ( ) ,
self . uchild . stderr_all ( ) ) ,
Ok ( _ ) = > { } ,
2022-12-14 14:42:07 +00:00
Err ( error ) = > panic! ( " Assertion failed with error ' {error:?} ' " ) ,
2022-11-14 17:39:48 +00:00
}
self
}
}
2022-11-23 05:43:02 +00:00
/// Abstraction for a [`std::process::Child`] to handle the child process.
2022-11-14 17:39:48 +00:00
pub struct UChild {
raw : Child ,
2023-01-25 02:40:39 +00:00
bin_path : PathBuf ,
2022-11-14 17:39:48 +00:00
util_name : Option < String > ,
captured_stdout : Option < CapturedOutput > ,
captured_stderr : Option < CapturedOutput > ,
2024-02-07 21:02:19 +00:00
stdin_pty : Option < File > ,
2022-11-14 17:39:48 +00:00
ignore_stdin_write_error : bool ,
2022-11-23 05:43:02 +00:00
stderr_to_stdout : bool ,
join_handle : Option < JoinHandle < io ::Result < ( ) > > > ,
2022-12-10 18:57:25 +00:00
timeout : Option < Duration > ,
2022-11-23 05:43:02 +00:00
tmpd : Option < Rc < TempDir > > , // drop last
2022-11-14 17:39:48 +00:00
}
impl UChild {
2022-11-23 05:43:02 +00:00
fn from (
ucommand : & UCommand ,
2022-11-14 17:39:48 +00:00
child : Child ,
captured_stdout : Option < CapturedOutput > ,
captured_stderr : Option < CapturedOutput > ,
2024-02-07 21:02:19 +00:00
stdin_pty : Option < File > ,
2022-11-14 17:39:48 +00:00
) -> Self {
Self {
raw : child ,
2023-01-25 02:40:39 +00:00
bin_path : ucommand . bin_path . clone ( ) . unwrap ( ) ,
2022-11-23 05:43:02 +00:00
util_name : ucommand . util_name . clone ( ) ,
2022-11-14 17:39:48 +00:00
captured_stdout ,
captured_stderr ,
2024-02-07 21:02:19 +00:00
stdin_pty ,
2022-11-23 05:43:02 +00:00
ignore_stdin_write_error : ucommand . ignore_stdin_write_error ,
stderr_to_stdout : ucommand . stderr_to_stdout ,
join_handle : None ,
2022-12-10 18:57:25 +00:00
timeout : ucommand . timeout ,
2022-11-23 05:43:02 +00:00
tmpd : ucommand . tmpd . clone ( ) ,
2022-11-14 17:39:48 +00:00
}
}
2022-11-23 05:43:02 +00:00
/// Convenience method for `sleep(Duration::from_millis(millis))`
2022-11-14 17:39:48 +00:00
pub fn delay ( & mut self , millis : u64 ) -> & mut Self {
sleep ( Duration ::from_millis ( millis ) ) ;
self
}
2022-11-23 05:43:02 +00:00
/// Return the pid of the child process, similar to [`Child::id`].
2022-11-14 17:39:48 +00:00
pub fn id ( & self ) -> u32 {
self . raw . id ( )
}
2022-11-23 05:43:02 +00:00
/// Return true if the child process is still alive and false otherwise.
pub fn is_alive ( & mut self ) -> bool {
self . raw . try_wait ( ) . unwrap ( ) . is_none ( )
}
/// Return true if the child process is exited and false otherwise.
#[ allow(clippy::wrong_self_convention) ]
pub fn is_not_alive ( & mut self ) -> bool {
! self . is_alive ( )
}
/// Return a [`UChildAssertion`]
pub fn make_assertion ( & mut self ) -> UChildAssertion {
UChildAssertion ::new ( self )
}
/// Convenience function for calling [`UChild::delay`] and then [`UChild::make_assertion`]
pub fn make_assertion_with_delay ( & mut self , millis : u64 ) -> UChildAssertion {
self . delay ( millis ) . make_assertion ( )
}
2022-12-10 18:57:25 +00:00
/// Try to kill the child process and wait for it's termination.
2022-11-14 17:39:48 +00:00
///
2022-12-10 18:57:25 +00:00
/// This method blocks until the child process is killed, but returns an error if `self.timeout`
/// or the default of 60s was reached. If no such error happened, the process resources are
/// released, so there is usually no need to call `wait` or alike on unix systems although it's
/// still possible to do so.
///
/// # Platform specific behavior
///
/// On unix systems the child process resources will be released like a call to [`Child::wait`]
/// or alike would do.
///
/// # Error
///
/// If [`Child::kill`] returned an error or if the child process could not be terminated within
/// `self.timeout` or the default of 60s.
2022-11-23 05:43:02 +00:00
pub fn try_kill ( & mut self ) -> io ::Result < ( ) > {
2022-12-10 18:57:25 +00:00
let start = Instant ::now ( ) ;
2022-11-23 05:43:02 +00:00
self . raw . kill ( ) ? ;
2022-12-10 18:57:25 +00:00
let timeout = self . timeout . unwrap_or ( Duration ::from_secs ( 60 ) ) ;
// As a side effect, we're cleaning up the killed child process with the implicit call to
// `Child::try_wait` in `self.is_alive`, which reaps the process id on unix systems. We
// always fail with error on timeout if `self.timeout` is set to zero.
while self . is_alive ( ) | | timeout = = Duration ::ZERO {
if start . elapsed ( ) < timeout {
self . delay ( 10 ) ;
} else {
return Err ( io ::Error ::new (
io ::ErrorKind ::Other ,
format! ( " kill: Timeout of ' {} s' reached " , timeout . as_secs_f64 ( ) ) ,
) ) ;
2022-11-14 17:39:48 +00:00
}
2022-12-10 18:57:25 +00:00
hint ::spin_loop ( ) ;
2022-11-14 17:39:48 +00:00
}
2022-12-10 18:57:25 +00:00
Ok ( ( ) )
2022-11-23 05:43:02 +00:00
}
/// Terminate the child process unconditionally and wait for the termination.
///
2022-12-10 18:57:25 +00:00
/// Ignores any errors happening during [`Child::kill`] (i.e. child process already exited) but
/// still panics on timeout.
2022-11-23 05:43:02 +00:00
///
/// # Panics
2022-12-10 18:57:25 +00:00
/// If the child process could not be terminated within `self.timeout` or the default of 60s.
2022-11-23 05:43:02 +00:00
pub fn kill ( & mut self ) -> & mut Self {
self . try_kill ( )
. or_else ( | error | {
// We still throw the error on timeout in the `try_kill` function
if error . kind ( ) = = io ::ErrorKind ::Other {
Err ( error )
} else {
Ok ( ( ) )
}
} )
. unwrap ( ) ;
self
2022-11-14 17:39:48 +00:00
}
2022-11-23 05:43:02 +00:00
/// Wait for the child process to terminate and return a [`CmdResult`].
///
2022-12-10 18:57:25 +00:00
/// See [`UChild::wait_with_output`] for details on timeouts etc. This method can also be run if
/// the child process was killed with [`UChild::kill`].
2022-11-14 17:39:48 +00:00
///
/// # Errors
2022-12-10 18:57:25 +00:00
///
/// Returns the error from the call to [`UChild::wait_with_output`] if any
2022-11-14 17:39:48 +00:00
pub fn wait ( self ) -> io ::Result < CmdResult > {
let ( bin_path , util_name , tmpd ) = (
self . bin_path . clone ( ) ,
self . util_name . clone ( ) ,
self . tmpd . clone ( ) ,
) ;
2022-11-23 05:43:02 +00:00
#[ allow(deprecated) ]
2022-11-14 17:39:48 +00:00
let output = self . wait_with_output ( ) ? ;
Ok ( CmdResult {
bin_path ,
util_name ,
tmpd ,
2023-01-04 20:30:14 +00:00
exit_status : Some ( output . status ) ,
2022-11-14 17:39:48 +00:00
stdout : output . stdout ,
stderr : output . stderr ,
} )
}
2022-11-23 05:43:02 +00:00
/// Wait for the child process to terminate and return an instance of [`Output`].
///
2022-12-10 18:57:25 +00:00
/// If `self.timeout` is reached while waiting, a [`io::ErrorKind::Other`] representing a
/// timeout error is returned. If no errors happened, we join with the thread created by
/// [`UChild::pipe_in`] if any.
///
/// # Error
///
/// If `self.timeout` is reached while waiting or [`Child::wait_with_output`] returned an
/// error.
2022-11-23 05:43:02 +00:00
#[ deprecated = " Please use wait() -> io::Result<CmdResult> instead. " ]
2022-11-14 17:39:48 +00:00
pub fn wait_with_output ( mut self ) -> io ::Result < Output > {
2024-02-07 21:02:19 +00:00
// some apps do not stop execution until their stdin gets closed.
// to prevent a endless waiting here, we close the stdin.
self . join ( ) ; // ensure that all pending async input is piped in
self . close_stdin ( ) ;
2022-12-10 18:57:25 +00:00
let output = if let Some ( timeout ) = self . timeout {
let child = self . raw ;
let ( sender , receiver ) = mpsc ::channel ( ) ;
2024-02-07 21:02:19 +00:00
let handle = thread ::Builder ::new ( )
. name ( " wait_with_output " . to_string ( ) )
. spawn ( move | | sender . send ( child . wait_with_output ( ) ) )
. unwrap ( ) ;
2022-12-10 18:57:25 +00:00
match receiver . recv_timeout ( timeout ) {
Ok ( result ) = > {
// unwraps are safe here because we got a result from the sender and there was no panic
// causing a disconnect.
handle . join ( ) . unwrap ( ) . unwrap ( ) ;
result
}
Err ( RecvTimeoutError ::Timeout ) = > Err ( io ::Error ::new (
io ::ErrorKind ::Other ,
format! ( " wait: Timeout of ' {} s' reached " , timeout . as_secs_f64 ( ) ) ,
) ) ,
Err ( RecvTimeoutError ::Disconnected ) = > {
2022-12-16 00:18:27 +00:00
handle . join ( ) . expect ( " Panic caused disconnect " ) . unwrap ( ) ;
panic! ( " Error receiving from waiting thread because of unexpected disconnect " ) ;
2022-12-10 18:57:25 +00:00
}
}
} else {
self . raw . wait_with_output ( )
} ;
let mut output = output ? ;
2022-11-14 17:39:48 +00:00
2022-11-23 05:43:02 +00:00
if let Some ( join_handle ) = self . join_handle . take ( ) {
join_handle
. join ( )
. expect ( " Error joining with the piping stdin thread " )
. unwrap ( ) ;
} ;
if let Some ( stdout ) = self . captured_stdout . as_mut ( ) {
2024-02-25 17:54:10 +00:00
if let Some ( handle ) = stdout . reader_thread_handle . take ( ) {
handle . join ( ) . unwrap ( ) ;
}
2022-11-23 05:43:02 +00:00
output . stdout = stdout . output_bytes ( ) ;
}
if let Some ( stderr ) = self . captured_stderr . as_mut ( ) {
2024-02-25 17:54:10 +00:00
if let Some ( handle ) = stderr . reader_thread_handle . take ( ) {
handle . join ( ) . unwrap ( ) ;
}
2022-11-23 05:43:02 +00:00
output . stderr = stderr . output_bytes ( ) ;
}
2022-11-14 17:39:48 +00:00
Ok ( output )
}
2022-11-23 05:43:02 +00:00
/// Read, consume and return the output as [`String`] from [`Child`]'s stdout.
///
2023-04-20 19:42:46 +00:00
/// See also [`UChild::stdout_bytes`] for side effects.
2022-11-14 17:39:48 +00:00
pub fn stdout ( & mut self ) -> String {
String ::from_utf8 ( self . stdout_bytes ( ) ) . unwrap ( )
}
2022-11-23 05:43:02 +00:00
/// Read and return all child's output in stdout as String.
///
/// Note, that a subsequent call of any of these functions
///
/// * [`UChild::stdout`]
/// * [`UChild::stdout_bytes`]
/// * [`UChild::stdout_exact_bytes`]
///
/// will operate on the subsequent output of the child process.
2022-11-14 17:39:48 +00:00
pub fn stdout_all ( & mut self ) -> String {
String ::from_utf8 ( self . stdout_all_bytes ( ) ) . unwrap ( )
}
2022-11-23 05:43:02 +00:00
/// Read, consume and return the output as bytes from [`Child`]'s stdout.
///
/// Each subsequent call to any of the functions below will operate on the subsequent output of
/// the child process:
2022-11-14 17:39:48 +00:00
///
/// * [`UChild::stdout`]
/// * [`UChild::stdout_exact_bytes`]
/// * and the call to itself [`UChild::stdout_bytes`]
pub fn stdout_bytes ( & mut self ) -> Vec < u8 > {
match self . captured_stdout . as_mut ( ) {
2022-11-23 05:43:02 +00:00
Some ( output ) = > output . output_bytes ( ) ,
2022-11-14 17:39:48 +00:00
None if self . raw . stdout . is_some ( ) = > {
let mut buffer : Vec < u8 > = vec! [ ] ;
let stdout = self . raw . stdout . as_mut ( ) . unwrap ( ) ;
stdout . read_to_end ( & mut buffer ) . unwrap ( ) ;
buffer
}
None = > vec! [ ] ,
}
}
2022-11-23 05:43:02 +00:00
/// Read and return all output from start of the child process until now.
///
/// Each subsequent call of any of the methods below will operate on the subsequent output of
/// the child process. This method will panic if the output wasn't captured (for example if
/// [`UCommand::set_stdout`] was used).
///
2022-11-14 17:39:48 +00:00
/// * [`UChild::stdout`]
/// * [`UChild::stdout_bytes`]
/// * [`UChild::stdout_exact_bytes`]
pub fn stdout_all_bytes ( & mut self ) -> Vec < u8 > {
match self . captured_stdout . as_mut ( ) {
Some ( output ) = > output . output_all_bytes ( ) ,
2022-11-23 05:43:02 +00:00
None = > {
panic! ( " Usage error: This method cannot be used if the output wasn't captured. " )
}
2022-11-14 17:39:48 +00:00
}
}
2022-11-23 05:43:02 +00:00
/// Read, consume and return the exact amount of bytes from `stdout`.
///
/// This method may block indefinitely if the `size` amount of bytes exceeds the amount of bytes
/// that can be read. See also [`UChild::stdout_bytes`] for side effects.
2022-11-14 17:39:48 +00:00
pub fn stdout_exact_bytes ( & mut self , size : usize ) -> Vec < u8 > {
match self . captured_stdout . as_mut ( ) {
Some ( output ) = > output . output_exact_bytes ( size ) ,
None if self . raw . stdout . is_some ( ) = > {
let mut buffer = vec! [ 0 ; size ] ;
let stdout = self . raw . stdout . as_mut ( ) . unwrap ( ) ;
stdout . read_exact ( & mut buffer ) . unwrap ( ) ;
buffer
}
None = > vec! [ ] ,
}
}
2022-11-23 05:43:02 +00:00
/// Read, consume and return the child's stderr as String.
///
/// See also [`UChild::stdout_bytes`] for side effects. If stderr is redirected to stdout with
/// [`UCommand::stderr_to_stdout`] then always an empty string will be returned.
2022-11-14 17:39:48 +00:00
pub fn stderr ( & mut self ) -> String {
String ::from_utf8 ( self . stderr_bytes ( ) ) . unwrap ( )
}
2022-11-23 05:43:02 +00:00
/// Read and return all child's output in stderr as String.
///
/// Note, that a subsequent call of any of these functions
///
/// * [`UChild::stderr`]
/// * [`UChild::stderr_bytes`]
/// * [`UChild::stderr_exact_bytes`]
///
/// will operate on the subsequent output of the child process. If stderr is redirected to
/// stdout with [`UCommand::stderr_to_stdout`] then always an empty string will be returned.
2022-11-14 17:39:48 +00:00
pub fn stderr_all ( & mut self ) -> String {
String ::from_utf8 ( self . stderr_all_bytes ( ) ) . unwrap ( )
}
2022-11-23 05:43:02 +00:00
/// Read, consume and return the currently available bytes from child's stderr.
///
/// If stderr is redirected to stdout with [`UCommand::stderr_to_stdout`] then always zero bytes
/// are returned. See also [`UChild::stdout_bytes`] for side effects.
2022-11-14 17:39:48 +00:00
pub fn stderr_bytes ( & mut self ) -> Vec < u8 > {
match self . captured_stderr . as_mut ( ) {
2022-11-23 05:43:02 +00:00
Some ( output ) = > output . output_bytes ( ) ,
2022-11-14 17:39:48 +00:00
None if self . raw . stderr . is_some ( ) = > {
let mut buffer : Vec < u8 > = vec! [ ] ;
let stderr = self . raw . stderr . as_mut ( ) . unwrap ( ) ;
stderr . read_to_end ( & mut buffer ) . unwrap ( ) ;
buffer
}
None = > vec! [ ] ,
}
}
2022-11-23 05:43:02 +00:00
/// Read and return all output from start of the child process until now.
///
/// Each subsequent call of any of the methods below will operate on the subsequent output of
/// the child process. This method will panic if the output wasn't captured (for example if
/// [`UCommand::set_stderr`] was used). If [`UCommand::stderr_to_stdout`] was used always zero
/// bytes are returned.
///
/// * [`UChild::stderr`]
/// * [`UChild::stderr_bytes`]
/// * [`UChild::stderr_exact_bytes`]
2022-11-14 17:39:48 +00:00
pub fn stderr_all_bytes ( & mut self ) -> Vec < u8 > {
match self . captured_stderr . as_mut ( ) {
Some ( output ) = > output . output_all_bytes ( ) ,
2022-11-23 05:43:02 +00:00
None if self . stderr_to_stdout = > vec! [ ] ,
None = > {
panic! ( " Usage error: This method cannot be used if the output wasn't captured. " )
}
2022-11-14 17:39:48 +00:00
}
}
2022-11-23 05:43:02 +00:00
/// Read, consume and return the exact amount of bytes from stderr.
///
/// If stderr is redirect to stdout with [`UCommand::stderr_to_stdout`] then always zero bytes
/// are returned.
2022-11-14 17:39:48 +00:00
///
/// # Important
/// This method blocks indefinitely if the `size` amount of bytes cannot be read.
pub fn stderr_exact_bytes ( & mut self , size : usize ) -> Vec < u8 > {
match self . captured_stderr . as_mut ( ) {
Some ( output ) = > output . output_exact_bytes ( size ) ,
None if self . raw . stderr . is_some ( ) = > {
let stderr = self . raw . stderr . as_mut ( ) . unwrap ( ) ;
let mut buffer = vec! [ 0 ; size ] ;
stderr . read_exact ( & mut buffer ) . unwrap ( ) ;
buffer
}
None = > vec! [ ] ,
}
}
2024-02-07 21:02:19 +00:00
fn access_stdin_as_writer < ' a > ( & ' a mut self ) -> Box < dyn Write + Send + ' a > {
if let Some ( stdin_fd ) = & self . stdin_pty {
Box ::new ( BufWriter ::new ( stdin_fd . try_clone ( ) . unwrap ( ) ) )
} else {
let stdin : & mut std ::process ::ChildStdin = self . raw . stdin . as_mut ( ) . unwrap ( ) ;
Box ::new ( BufWriter ::new ( stdin ) )
}
}
fn take_stdin_as_writer ( & mut self ) -> Box < dyn Write + Send > {
if let Some ( stdin_fd ) = mem ::take ( & mut self . stdin_pty ) {
Box ::new ( BufWriter ::new ( stdin_fd ) )
} else {
let stdin = self
. raw
. stdin
. take ( )
. expect ( " Could not pipe into child process. Was it set to Stdio::null()? " ) ;
Box ::new ( BufWriter ::new ( stdin ) )
}
}
2022-11-23 05:43:02 +00:00
/// Pipe data into [`Child`] stdin in a separate thread to avoid deadlocks.
2022-11-14 17:39:48 +00:00
///
2022-11-23 05:43:02 +00:00
/// In contrast to [`UChild::write_in`], this method is designed to simulate a pipe on the
/// command line and can be used only once or else panics. Note, that [`UCommand::set_stdin`]
/// must be used together with [`Stdio::piped`] or else this method doesn't work as expected.
/// `Stdio::piped` is the current default when using [`UCommand::run_no_wait`]) without calling
/// `set_stdin`. This method stores a [`JoinHandle`] of the thread in which the writing to the
/// child processes' stdin is running. The associated thread is joined with the main process in
/// the methods below when exiting the child process.
2022-11-14 17:39:48 +00:00
///
2022-11-23 05:43:02 +00:00
/// * [`UChild::wait`]
/// * [`UChild::wait_with_output`]
/// * [`UChild::pipe_in_and_wait`]
/// * [`UChild::pipe_in_and_wait_with_output`]
2022-11-14 17:39:48 +00:00
///
2022-11-23 05:43:02 +00:00
/// Usually, there's no need to join manually but if needed, the [`UChild::join`] method can be
/// used .
2022-11-14 17:39:48 +00:00
///
/// [`JoinHandle`]: std::thread::JoinHandle
2022-11-23 05:43:02 +00:00
pub fn pipe_in < T : Into < Vec < u8 > > > ( & mut self , content : T ) -> & mut Self {
2022-11-14 17:39:48 +00:00
let ignore_stdin_write_error = self . ignore_stdin_write_error ;
2024-02-07 21:02:19 +00:00
let mut content : Vec < u8 > = content . into ( ) ;
if self . stdin_pty . is_some ( ) {
content . append ( & mut END_OF_TRANSMISSION_SEQUENCE . to_vec ( ) ) ;
}
let mut writer = self . take_stdin_as_writer ( ) ;
let join_handle = std ::thread ::Builder ::new ( )
. name ( " pipe_in " . to_string ( ) )
. spawn (
move | | match writer . write_all ( & content ) . and_then ( | ( ) | writer . flush ( ) ) {
Err ( error ) if ! ignore_stdin_write_error = > Err ( io ::Error ::new (
io ::ErrorKind ::Other ,
format! ( " failed to write to stdin of child: {error} " ) ,
) ) ,
Ok ( ( ) ) | Err ( _ ) = > Ok ( ( ) ) ,
} ,
)
. unwrap ( ) ;
2022-11-23 05:43:02 +00:00
self . join_handle = Some ( join_handle ) ;
self
2022-11-14 17:39:48 +00:00
}
2022-11-23 05:43:02 +00:00
/// Call join on the thread created by [`UChild::pipe_in`] and if the thread is still running.
2022-11-14 17:39:48 +00:00
///
2022-11-23 05:43:02 +00:00
/// This method can be called multiple times but is a noop if already joined.
pub fn join ( & mut self ) -> & mut Self {
if let Some ( join_handle ) = self . join_handle . take ( ) {
join_handle
. join ( )
. expect ( " Error joining with the piping stdin thread " )
. unwrap ( ) ;
}
self
}
/// Convenience method for [`UChild::pipe_in`] and then [`UChild::wait`]
2022-11-14 17:39:48 +00:00
pub fn pipe_in_and_wait < T : Into < Vec < u8 > > > ( mut self , content : T ) -> CmdResult {
2022-11-23 05:43:02 +00:00
self . pipe_in ( content ) ;
self . wait ( ) . unwrap ( )
2022-11-14 17:39:48 +00:00
}
2022-11-23 05:43:02 +00:00
/// Convenience method for [`UChild::pipe_in`] and then [`UChild::wait_with_output`]
#[ deprecated = " Please use pipe_in_and_wait() -> CmdResult instead. " ]
2022-11-14 17:39:48 +00:00
pub fn pipe_in_and_wait_with_output < T : Into < Vec < u8 > > > ( mut self , content : T ) -> Output {
2022-11-23 05:43:02 +00:00
self . pipe_in ( content ) ;
#[ allow(deprecated) ]
self . wait_with_output ( ) . unwrap ( )
2022-11-14 17:39:48 +00:00
}
2022-11-23 05:43:02 +00:00
/// Write some bytes to the child process stdin.
///
/// This function is meant for small data and faking user input like typing a `yes` or `no`.
/// This function blocks until all data is written but can be used multiple times in contrast to
/// [`UChild::pipe_in`].
///
/// # Errors
/// If [`ChildStdin::write_all`] or [`ChildStdin::flush`] returned an error
pub fn try_write_in < T : Into < Vec < u8 > > > ( & mut self , data : T ) -> io ::Result < ( ) > {
2024-02-07 21:02:19 +00:00
let ignore_stdin_write_error = self . ignore_stdin_write_error ;
let mut writer = self . access_stdin_as_writer ( ) ;
2022-11-23 05:43:02 +00:00
2024-02-07 21:02:19 +00:00
match writer . write_all ( & data . into ( ) ) . and_then ( | ( ) | writer . flush ( ) ) {
Err ( error ) if ! ignore_stdin_write_error = > Err ( io ::Error ::new (
2022-11-23 05:43:02 +00:00
io ::ErrorKind ::Other ,
2022-12-14 14:42:07 +00:00
format! ( " failed to write to stdin of child: {error} " ) ,
2022-11-23 05:43:02 +00:00
) ) ,
2023-08-21 06:11:38 +00:00
Ok ( ( ) ) | Err ( _ ) = > Ok ( ( ) ) ,
2022-11-23 05:43:02 +00:00
}
}
/// Convenience function for [`UChild::try_write_in`] and a following `unwrap`.
pub fn write_in < T : Into < Vec < u8 > > > ( & mut self , data : T ) -> & mut Self {
self . try_write_in ( data ) . unwrap ( ) ;
self
2022-11-14 17:39:48 +00:00
}
2022-11-23 05:43:02 +00:00
/// Close the child process stdout.
///
/// Note this will have no effect if the output was captured with [`CapturedOutput`] which is the
/// default if [`UCommand::set_stdout`] wasn't called.
2022-11-14 17:39:48 +00:00
pub fn close_stdout ( & mut self ) -> & mut Self {
2022-11-23 05:43:02 +00:00
self . raw . stdout . take ( ) ;
2022-11-14 17:39:48 +00:00
self
}
2022-11-23 05:43:02 +00:00
/// Close the child process stderr.
///
/// Note this will have no effect if the output was captured with [`CapturedOutput`] which is the
/// default if [`UCommand::set_stderr`] wasn't called.
2022-11-14 17:39:48 +00:00
pub fn close_stderr ( & mut self ) -> & mut Self {
2022-11-23 05:43:02 +00:00
self . raw . stderr . take ( ) ;
2022-11-14 17:39:48 +00:00
self
}
2022-11-23 05:43:02 +00:00
/// Close the child process stdin.
///
/// Note, this does not have any effect if using the [`UChild::pipe_in`] method.
2022-11-14 17:39:48 +00:00
pub fn close_stdin ( & mut self ) -> & mut Self {
2022-11-23 05:43:02 +00:00
self . raw . stdin . take ( ) ;
2024-02-07 21:02:19 +00:00
if self . stdin_pty . is_some ( ) {
// a pty can not be closed. We need to send a EOT:
let _ = self . try_write_in ( END_OF_TRANSMISSION_SEQUENCE ) ;
self . stdin_pty . take ( ) ;
}
2022-11-14 17:39:48 +00:00
self
}
2016-04-02 21:09:20 +00:00
}
2021-04-10 20:19:53 +00:00
pub fn vec_of_size ( n : usize ) -> Vec < u8 > {
2021-05-29 12:32:35 +00:00
let result = vec! [ b 'a' ; n ] ;
2021-04-10 20:19:53 +00:00
assert_eq! ( result . len ( ) , n ) ;
result
}
2021-04-10 18:18:38 +00:00
2021-07-07 12:30:38 +00:00
pub fn whoami ( ) -> String {
// Apparently some CI environments have configuration issues, e.g. with 'whoami' and 'id'.
//
// From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)"
// whoami: cannot find name for user ID 1001
// id --name: cannot find name for user ID 1001
// id --name: cannot find name for group ID 116
//
// However, when running "id" from within "/bin/bash" it looks fine:
// id: "uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),101(systemd-journal)"
// whoami: "runner"
// Use environment variable to get current user instead of
// invoking `whoami` and fall back to user "nobody" on error.
2021-09-14 10:25:17 +00:00
std ::env ::var ( " USER " )
. or_else ( | _ | std ::env ::var ( " USERNAME " ) )
. unwrap_or_else ( | e | {
2022-12-14 14:42:07 +00:00
println! ( " {UUTILS_WARNING} : {e} , using \" nobody \" instead " ) ;
2021-09-14 10:25:17 +00:00
" nobody " . to_string ( )
} )
2021-07-07 12:30:38 +00:00
}
/// Add prefix 'g' for `util_name` if not on linux
#[ cfg(unix) ]
2021-07-11 09:22:33 +00:00
pub fn host_name_for ( util_name : & str ) -> Cow < str > {
2021-07-07 12:30:38 +00:00
// In some environments, e.g. macOS/freebsd, the GNU coreutils are prefixed with "g"
// to not interfere with the BSD counterparts already in `$PATH`.
#[ cfg(not(target_os = " linux " )) ]
2021-09-13 10:12:32 +00:00
{
// make call to `host_name_for` idempotent
if util_name . starts_with ( 'g' ) & & util_name ! = " groups " {
2021-11-10 21:59:41 +00:00
util_name . into ( )
2021-09-13 10:12:32 +00:00
} else {
2023-01-27 09:29:45 +00:00
format! ( " g {util_name} " ) . into ( )
2021-09-13 10:12:32 +00:00
}
}
2021-07-07 12:30:38 +00:00
#[ cfg(target_os = " linux " ) ]
2021-11-10 21:59:41 +00:00
util_name . into ( )
2021-07-07 12:30:38 +00:00
}
// GNU coreutils version 8.32 is the reference version since it is the latest version and the
// GNU test suite in "coreutils/.github/workflows/GnuTests.yml" runs against it.
// However, here 8.30 was chosen because right now there's no ubuntu image for the github actions
// CICD available with a higher version than 8.30.
// GNU coreutils versions from the CICD images for comparison:
// ubuntu-2004: 8.30 (latest)
// ubuntu-1804: 8.28
// macos-latest: 8.32
const VERSION_MIN : & str = " 8.30 " ; // minimum Version for the reference `coreutil` in `$PATH`
const UUTILS_WARNING : & str = " uutils-tests-warning " ;
const UUTILS_INFO : & str = " uutils-tests-info " ;
/// Run `util_name --version` and return Ok if the version is >= `version_expected`.
/// Returns an error if
/// * `util_name` cannot run
/// * the version cannot be parsed
/// * the version is too low
///
/// This is used by `expected_result` to check if the coreutils version is >= `VERSION_MIN`.
/// It makes sense to use this manually in a test if a feature
/// is tested that was introduced after `VERSION_MIN`
///
/// Example:
///
/// ```no_run
/// use crate::common::util::*;
/// const VERSION_MIN_MULTIPLE_USERS: &str = "8.31";
///
/// #[test]
/// fn test_xyz() {
/// unwrap_or_return!(check_coreutil_version(
/// util_name!(),
/// VERSION_MIN_MULTIPLE_USERS
/// ));
/// // proceed with the test...
/// }
/// ```
#[ cfg(unix) ]
pub fn check_coreutil_version (
util_name : & str ,
version_expected : & str ,
) -> std ::result ::Result < String , String > {
// example:
// $ id --version | head -n 1
// id (GNU coreutils) 8.32.162-4eda
let util_name = & host_name_for ( util_name ) ;
2022-12-14 14:42:07 +00:00
log_info ( " run " , format! ( " {util_name} --version " ) ) ;
2021-07-07 12:30:38 +00:00
let version_check = match Command ::new ( util_name . as_ref ( ) )
. env ( " LC_ALL " , " C " )
. arg ( " --version " )
. output ( )
{
Ok ( s ) = > s ,
2022-12-14 14:42:07 +00:00
Err ( e ) = > return Err ( format! ( " {UUTILS_WARNING} : ' {util_name} ' {e} " ) ) ,
2021-07-07 12:30:38 +00:00
} ;
std ::str ::from_utf8 ( & version_check . stdout ) . unwrap ( )
. split ( '\n' )
. collect ::< Vec < _ > > ( )
2022-08-12 07:45:16 +00:00
. first ( )
2021-07-07 12:30:38 +00:00
. map_or_else (
2022-12-14 14:42:07 +00:00
| | Err ( format! ( " {UUTILS_WARNING} : unexpected output format for reference coreutil: ' {util_name} --version' " ) ) ,
2021-07-07 12:30:38 +00:00
| s | {
2022-12-14 14:42:07 +00:00
if s . contains ( & format! ( " (GNU coreutils) {version_expected} " ) ) {
Ok ( format! ( " {UUTILS_INFO} : {s} " ) )
2021-07-07 12:30:38 +00:00
} else if s . contains ( " (GNU coreutils) " ) {
2021-10-01 21:25:56 +00:00
let version_found = parse_coreutil_version ( s ) ;
2021-07-07 12:30:38 +00:00
let version_expected = version_expected . parse ::< f32 > ( ) . unwrap_or_default ( ) ;
if version_found > version_expected {
2022-12-14 14:42:07 +00:00
Ok ( format! ( " {UUTILS_INFO} : version for the reference coreutil ' {util_name} ' is higher than expected; expected: {version_expected} , found: {version_found} " ) )
2021-07-07 12:30:38 +00:00
} else {
2022-12-14 14:42:07 +00:00
Err ( format! ( " {UUTILS_WARNING} : version for the reference coreutil ' {util_name} ' does not match; expected: {version_expected} , found: {version_found} " ) ) }
2021-07-07 12:30:38 +00:00
} else {
2022-12-14 14:42:07 +00:00
Err ( format! ( " {UUTILS_WARNING} : no coreutils version string found for reference coreutils ' {util_name} --version' " ) )
2021-07-07 12:30:38 +00:00
}
} ,
)
}
2021-10-01 21:25:56 +00:00
// simple heuristic to parse the coreutils SemVer string, e.g. "id (GNU coreutils) 8.32.263-0475"
fn parse_coreutil_version ( version_string : & str ) -> f32 {
version_string
. split_whitespace ( )
. last ( )
. unwrap ( )
. split ( '.' )
. take ( 2 )
. collect ::< Vec < _ > > ( )
. join ( " . " )
. parse ::< f32 > ( )
. unwrap_or_default ( )
}
2021-07-07 12:30:38 +00:00
/// This runs the GNU coreutils `util_name` binary in `$PATH` in order to
/// dynamically gather reference values on the system.
/// If the `util_name` in `$PATH` doesn't include a coreutils version string,
/// or the version is too low, this returns an error and the test should be skipped.
///
/// Example:
///
/// ```no_run
/// use crate::common::util::*;
/// #[test]
/// fn test_xyz() {
2021-07-07 20:46:16 +00:00
/// let ts = TestScenario::new(util_name!());
/// let result = ts.ucmd().run();
/// let exp_result = unwrap_or_return!(expected_result(&ts, &[]));
2021-07-07 12:30:38 +00:00
/// result
/// .stdout_is(exp_result.stdout_str())
/// .stderr_is(exp_result.stderr_str())
/// .code_is(exp_result.code());
/// }
///```
#[ cfg(unix) ]
2021-07-07 20:46:16 +00:00
pub fn expected_result ( ts : & TestScenario , args : & [ & str ] ) -> std ::result ::Result < CmdResult , String > {
2023-01-25 02:40:39 +00:00
let util_name = ts . util_name . as_str ( ) ;
println! ( " {} " , check_coreutil_version ( util_name , VERSION_MIN ) ? ) ;
let util_name = host_name_for ( util_name ) ;
2021-07-07 12:30:38 +00:00
2021-07-07 20:46:16 +00:00
let result = ts
2023-01-25 02:40:39 +00:00
. cmd ( util_name . as_ref ( ) )
2023-03-24 02:52:02 +00:00
. env ( " PATH " , PATH )
2023-03-23 17:15:05 +00:00
. envs ( DEFAULT_ENV )
2021-07-07 12:30:38 +00:00
. args ( args )
. run ( ) ;
let ( stdout , stderr ) : ( String , String ) = if cfg! ( target_os = " linux " ) {
(
result . stdout_str ( ) . to_string ( ) ,
2024-03-14 18:38:28 +00:00
result . stderr_str_lossy ( ) . to_string ( ) ,
2021-07-07 12:30:38 +00:00
)
} else {
// `host_name_for` added prefix, strip 'g' prefix from results:
let from = util_name . to_string ( ) + " : " ;
let to = & from [ 1 .. ] ;
(
result . stdout_str ( ) . replace ( & from , to ) ,
2024-03-14 18:38:28 +00:00
result . stderr_str_lossy ( ) . replace ( & from , to ) ,
2021-07-07 12:30:38 +00:00
)
} ;
Ok ( CmdResult ::new (
2021-11-09 17:37:38 +00:00
ts . bin_path . as_os_str ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ,
Some ( ts . util_name . clone ( ) ) ,
2021-07-07 12:30:38 +00:00
Some ( result . tmpd ( ) ) ,
2023-01-04 20:30:14 +00:00
result . exit_status ,
2021-07-07 12:30:38 +00:00
stdout . as_bytes ( ) ,
stderr . as_bytes ( ) ,
) )
}
2021-09-19 20:24:11 +00:00
/// This is a convenience wrapper to run a ucmd with root permissions.
2022-01-30 09:05:05 +00:00
/// It can be used to test programs when being root is needed
2023-08-21 06:11:38 +00:00
/// This runs `sudo -E --non-interactive target/debug/coreutils util_name args`
2021-09-20 22:23:23 +00:00
/// This is primarily designed to run in an environment where whoami is in $path
2021-09-19 20:24:11 +00:00
/// and where non-interactive sudo is possible.
/// To check if i) non-interactive sudo is possible and ii) if sudo works, this runs:
2023-08-21 06:11:38 +00:00
/// `sudo -E --non-interactive whoami` first.
2021-09-19 20:24:11 +00:00
///
2022-05-10 11:46:22 +00:00
/// This return an `Err()` if run inside CICD because there's no 'sudo'.
///
2021-09-19 20:24:11 +00:00
/// Example:
///
/// ```no_run
/// use crate::common::util::*;
/// #[test]
/// fn test_xyz() {
/// let ts = TestScenario::new("whoami");
/// let expected = "root\n".to_string();
/// if let Ok(result) = run_ucmd_as_root(&ts, &[]) {
/// result.stdout_is(expected);
/// } else {
2022-05-10 11:46:22 +00:00
/// println!("TEST SKIPPED");
2021-09-19 20:24:11 +00:00
/// }
/// }
///```
#[ cfg(unix) ]
pub fn run_ucmd_as_root (
ts : & TestScenario ,
args : & [ & str ] ,
2023-07-22 09:25:39 +00:00
) -> std ::result ::Result < CmdResult , String > {
run_ucmd_as_root_with_stdin_stdout ( ts , args , None , None )
}
#[ cfg(unix) ]
pub fn run_ucmd_as_root_with_stdin_stdout (
ts : & TestScenario ,
args : & [ & str ] ,
stdin : Option < & str > ,
stdout : Option < & str > ,
2021-09-19 20:24:11 +00:00
) -> std ::result ::Result < CmdResult , String > {
2023-03-22 16:13:50 +00:00
if is_ci ( ) {
Err ( format! ( " {UUTILS_INFO} : {} " , " cannot run inside CI " ) )
} else {
2022-05-10 11:46:22 +00:00
// check if we can run 'sudo'
log_info ( " run " , " sudo -E --non-interactive whoami " ) ;
match Command ::new ( " sudo " )
2023-03-23 17:15:05 +00:00
. envs ( DEFAULT_ENV )
2022-10-12 20:04:21 +00:00
. args ( [ " -E " , " --non-interactive " , " whoami " ] )
2022-05-10 11:46:22 +00:00
. output ( )
{
Ok ( output ) if String ::from_utf8_lossy ( & output . stdout ) . eq ( " root \n " ) = > {
// we can run sudo and we're root
// run ucmd as root:
2023-07-22 09:25:39 +00:00
let mut cmd = ts . cmd ( " sudo " ) ;
cmd . env ( " PATH " , PATH )
2023-03-23 17:15:05 +00:00
. envs ( DEFAULT_ENV )
2022-05-10 11:46:22 +00:00
. arg ( " -E " )
. arg ( " --non-interactive " )
. arg ( & ts . bin_path )
. arg ( & ts . util_name )
2023-07-22 09:25:39 +00:00
. args ( args ) ;
if let Some ( stdin ) = stdin {
cmd . set_stdin ( File ::open ( stdin ) . unwrap ( ) ) ;
}
if let Some ( stdout ) = stdout {
cmd . set_stdout ( File ::open ( stdout ) . unwrap ( ) ) ;
}
Ok ( cmd . run ( ) )
2022-05-10 11:46:22 +00:00
}
Ok ( output )
if String ::from_utf8_lossy ( & output . stderr ) . eq ( " sudo: a password is required \n " ) = >
{
Err ( " Cannot run non-interactive sudo " . to_string ( ) )
}
Ok ( _output ) = > Err ( " \" sudo whoami \" didn't return \" root \" " . to_string ( ) ) ,
2022-12-14 14:42:07 +00:00
Err ( e ) = > Err ( format! ( " {UUTILS_WARNING} : {e} " ) ) ,
2022-05-10 11:46:22 +00:00
}
2021-09-19 20:24:11 +00:00
}
}
2021-04-10 18:18:38 +00:00
/// Sanity checks for test utils
#[ cfg(test) ]
mod tests {
2021-05-30 05:10:54 +00:00
// spell-checker:ignore (tests) asdfsadfa
2021-04-10 18:18:38 +00:00
use super ::* ;
2023-01-05 18:47:50 +00:00
pub fn run_cmd < T : AsRef < OsStr > > ( cmd : T ) -> CmdResult {
2023-01-25 02:40:39 +00:00
UCommand ::new ( ) . arg ( cmd ) . run ( )
2021-04-10 18:18:38 +00:00
}
#[ test ]
2023-01-04 20:30:14 +00:00
fn test_command_result_when_no_output_with_exit_32 ( ) {
let result = run_cmd ( " exit 32 " ) ;
if cfg! ( windows ) {
std ::assert! ( result . bin_path . ends_with ( " cmd " ) ) ;
} else {
std ::assert! ( result . bin_path . ends_with ( " sh " ) ) ;
}
std ::assert! ( result . util_name . is_none ( ) ) ;
std ::assert! ( result . tmpd . is_some ( ) ) ;
assert! ( result . exit_status . is_some ( ) ) ;
std ::assert_eq! ( result . code ( ) , 32 ) ;
result . code_is ( 32 ) ;
assert! ( ! result . succeeded ( ) ) ;
result . failure ( ) ;
result . fails_silently ( ) ;
assert! ( result . stderr . is_empty ( ) ) ;
assert! ( result . stdout . is_empty ( ) ) ;
result . no_output ( ) ;
result . no_stderr ( ) ;
result . no_stdout ( ) ;
2021-04-10 18:18:38 +00:00
}
#[ test ]
#[ should_panic ]
2023-01-04 20:30:14 +00:00
fn test_command_result_when_exit_32_then_success_panic ( ) {
run_cmd ( " exit 32 " ) . success ( ) ;
2021-04-10 18:18:38 +00:00
}
#[ test ]
2023-01-04 20:30:14 +00:00
fn test_command_result_when_no_output_with_exit_0 ( ) {
let result = run_cmd ( " exit 0 " ) ;
assert! ( result . exit_status . is_some ( ) ) ;
std ::assert_eq! ( result . code ( ) , 0 ) ;
result . code_is ( 0 ) ;
assert! ( result . succeeded ( ) ) ;
result . success ( ) ;
assert! ( result . stderr . is_empty ( ) ) ;
assert! ( result . stdout . is_empty ( ) ) ;
result . no_output ( ) ;
result . no_stderr ( ) ;
result . no_stdout ( ) ;
2021-04-10 18:18:38 +00:00
}
#[ test ]
#[ should_panic ]
2023-01-04 20:30:14 +00:00
fn test_command_result_when_exit_0_then_failure_panics ( ) {
run_cmd ( " exit 0 " ) . failure ( ) ;
2021-04-10 18:18:38 +00:00
}
#[ test ]
2023-01-04 20:30:14 +00:00
#[ should_panic ]
fn test_command_result_when_exit_0_then_silent_failure_panics ( ) {
run_cmd ( " exit 0 " ) . fails_silently ( ) ;
2021-04-10 18:18:38 +00:00
}
#[ test ]
2023-01-04 20:30:14 +00:00
fn test_command_result_when_stdout_with_exit_0 ( ) {
#[ cfg(windows) ]
let ( result , vector , string ) = (
run_cmd ( " echo hello& exit 0 " ) ,
vec! [ b 'h' , b 'e' , b 'l' , b 'l' , b 'o' , b '\r' , b '\n' ] ,
" hello \r \n " ,
) ;
#[ cfg(not(windows)) ]
let ( result , vector , string ) = (
run_cmd ( " echo hello; exit 0 " ) ,
vec! [ b 'h' , b 'e' , b 'l' , b 'l' , b 'o' , b '\n' ] ,
" hello \n " ,
) ;
2021-04-10 18:18:38 +00:00
2023-01-04 20:30:14 +00:00
assert! ( result . exit_status . is_some ( ) ) ;
std ::assert_eq! ( result . code ( ) , 0 ) ;
result . code_is ( 0 ) ;
assert! ( result . succeeded ( ) ) ;
result . success ( ) ;
assert! ( result . stderr . is_empty ( ) ) ;
std ::assert_eq! ( result . stdout , vector ) ;
result . no_stderr ( ) ;
result . stdout_is ( string ) ;
result . stdout_is_bytes ( & vector ) ;
result . stdout_only ( string ) ;
result . stdout_only_bytes ( & vector ) ;
2021-04-10 18:18:38 +00:00
}
#[ test ]
2023-01-04 20:30:14 +00:00
fn test_command_result_when_stderr_with_exit_0 ( ) {
#[ cfg(windows) ]
let ( result , vector , string ) = (
run_cmd ( " echo hello>&2& exit 0 " ) ,
vec! [ b 'h' , b 'e' , b 'l' , b 'l' , b 'o' , b '\r' , b '\n' ] ,
" hello \r \n " ,
) ;
#[ cfg(not(windows)) ]
let ( result , vector , string ) = (
run_cmd ( " echo hello >&2; exit 0 " ) ,
vec! [ b 'h' , b 'e' , b 'l' , b 'l' , b 'o' , b '\n' ] ,
" hello \n " ,
) ;
2021-04-10 18:18:38 +00:00
2023-01-04 20:30:14 +00:00
assert! ( result . exit_status . is_some ( ) ) ;
std ::assert_eq! ( result . code ( ) , 0 ) ;
result . code_is ( 0 ) ;
assert! ( result . succeeded ( ) ) ;
result . success ( ) ;
assert! ( result . stdout . is_empty ( ) ) ;
result . no_stdout ( ) ;
std ::assert_eq! ( result . stderr , vector ) ;
result . stderr_is ( string ) ;
result . stderr_is_bytes ( & vector ) ;
result . stderr_only ( string ) ;
result . stderr_only_bytes ( & vector ) ;
2021-04-10 18:18:38 +00:00
}
#[ test ]
fn test_std_does_not_contain ( ) {
2023-01-04 20:30:14 +00:00
#[ cfg(windows) ]
let res = run_cmd (
" (echo This is a likely error message& echo This is a likely error message>&2) & exit 0 " ,
) ;
#[ cfg(not(windows)) ]
let res = run_cmd (
" echo This is a likely error message; echo This is a likely error message >&2; exit 0 " ,
) ;
2021-04-10 18:18:38 +00:00
res . stdout_does_not_contain ( " unlikely " ) ;
res . stderr_does_not_contain ( " unlikely " ) ;
}
#[ test ]
#[ should_panic ]
fn test_stdout_does_not_contain_fail ( ) {
2023-01-04 20:30:14 +00:00
#[ cfg(windows) ]
let res = run_cmd ( " echo This is a likely error message& exit 0 " ) ;
#[ cfg(not(windows)) ]
let res = run_cmd ( " echo This is a likely error message; exit 0 " ) ;
2021-04-10 18:18:38 +00:00
res . stdout_does_not_contain ( " likely " ) ;
}
#[ test ]
#[ should_panic ]
fn test_stderr_does_not_contain_fail ( ) {
2023-01-04 20:30:14 +00:00
#[ cfg(windows) ]
let res = run_cmd ( " echo This is a likely error message>&2 & exit 0 " ) ;
#[ cfg(not(windows)) ]
let res = run_cmd ( " echo This is a likely error message >&2; exit 0 " ) ;
2021-04-10 18:18:38 +00:00
res . stderr_does_not_contain ( " likely " ) ;
}
#[ test ]
fn test_stdout_matches ( ) {
2023-01-04 20:30:14 +00:00
#[ cfg(windows) ]
let res = run_cmd (
" (echo This is a likely error message& echo This is a likely error message>&2 ) & exit 0 " ,
) ;
#[ cfg(not(windows)) ]
let res = run_cmd (
" echo This is a likely error message; echo This is a likely error message >&2; exit 0 " ,
) ;
2021-04-10 18:18:38 +00:00
let positive = regex ::Regex ::new ( " .*likely.* " ) . unwrap ( ) ;
let negative = regex ::Regex ::new ( " .*unlikely.* " ) . unwrap ( ) ;
res . stdout_matches ( & positive ) ;
res . stdout_does_not_match ( & negative ) ;
}
#[ test ]
#[ should_panic ]
fn test_stdout_matches_fail ( ) {
2023-01-04 20:30:14 +00:00
#[ cfg(windows) ]
let res = run_cmd (
" (echo This is a likely error message& echo This is a likely error message>&2) & exit 0 " ,
) ;
#[ cfg(not(windows)) ]
let res = run_cmd (
" echo This is a likely error message; echo This is a likely error message >&2; exit 0 " ,
) ;
2021-04-10 18:18:38 +00:00
2023-01-04 20:30:14 +00:00
let negative = regex ::Regex ::new ( " .*unlikely.* " ) . unwrap ( ) ;
2021-04-10 18:18:38 +00:00
res . stdout_matches ( & negative ) ;
}
#[ test ]
#[ should_panic ]
fn test_stdout_not_matches_fail ( ) {
2023-01-04 20:30:14 +00:00
#[ cfg(windows) ]
let res = run_cmd (
" (echo This is a likely error message& echo This is a likely error message>&2) & exit 0 " ,
) ;
#[ cfg(not(windows)) ]
let res = run_cmd (
" echo This is a likely error message; echo This is a likely error message >&2; exit 0 " ,
) ;
2021-04-10 18:18:38 +00:00
2023-01-04 20:30:14 +00:00
let positive = regex ::Regex ::new ( " .*likely.* " ) . unwrap ( ) ;
2021-04-10 18:18:38 +00:00
res . stdout_does_not_match ( & positive ) ;
}
2021-04-17 12:01:52 +00:00
2023-01-04 20:30:14 +00:00
#[ cfg(feature = " echo " ) ]
2021-04-17 12:01:52 +00:00
#[ test ]
fn test_normalized_newlines_stdout_is ( ) {
2023-01-04 20:30:14 +00:00
let ts = TestScenario ::new ( " echo " ) ;
let res = ts . ucmd ( ) . args ( & [ " -ne " , " A \r \n B \n C " ] ) . run ( ) ;
2021-04-17 12:01:52 +00:00
res . normalized_newlines_stdout_is ( " A \r \n B \n C " ) ;
res . normalized_newlines_stdout_is ( " A \n B \n C " ) ;
res . normalized_newlines_stdout_is ( " A \n B \r \n C " ) ;
}
2023-01-04 20:30:14 +00:00
#[ cfg(feature = " echo " ) ]
2021-04-17 12:01:52 +00:00
#[ test ]
#[ should_panic ]
fn test_normalized_newlines_stdout_is_fail ( ) {
2023-01-04 20:30:14 +00:00
let ts = TestScenario ::new ( " echo " ) ;
let res = ts . ucmd ( ) . args ( & [ " -ne " , " A \r \n B \n C " ] ) . run ( ) ;
2021-04-17 12:01:52 +00:00
res . normalized_newlines_stdout_is ( " A \r \n B \n C \n " ) ;
}
2021-07-07 12:30:38 +00:00
2023-01-05 18:47:50 +00:00
#[ cfg(feature = " echo " ) ]
#[ test ]
fn test_cmd_result_stdout_check_and_stdout_str_check ( ) {
let result = TestScenario ::new ( " echo " ) . ucmd ( ) . arg ( " Hello world " ) . run ( ) ;
result . stdout_str_check ( | stdout | stdout . ends_with ( " world \n " ) ) ;
result . stdout_check ( | stdout | stdout . get ( 0 .. 2 ) . unwrap ( ) . eq ( & [ b 'H' , b 'e' ] ) ) ;
result . no_stderr ( ) ;
}
#[ cfg(feature = " echo " ) ]
#[ test ]
fn test_cmd_result_stderr_check_and_stderr_str_check ( ) {
let ts = TestScenario ::new ( " echo " ) ;
let result = run_cmd ( format! (
" {} {} Hello world >&2 " ,
ts . bin_path . display ( ) ,
ts . util_name
) ) ;
result . stderr_str_check ( | stderr | stderr . ends_with ( " world \n " ) ) ;
result . stderr_check ( | stderr | stderr . get ( 0 .. 2 ) . unwrap ( ) . eq ( & [ b 'H' , b 'e' ] ) ) ;
result . no_stdout ( ) ;
}
#[ cfg(feature = " echo " ) ]
#[ test ]
#[ should_panic ]
fn test_cmd_result_stdout_str_check_when_false_then_panics ( ) {
let result = TestScenario ::new ( " echo " ) . ucmd ( ) . arg ( " Hello world " ) . run ( ) ;
result . stdout_str_check ( str ::is_empty ) ;
}
#[ cfg(feature = " echo " ) ]
#[ test ]
#[ should_panic ]
fn test_cmd_result_stdout_check_when_false_then_panics ( ) {
let result = TestScenario ::new ( " echo " ) . ucmd ( ) . arg ( " Hello world " ) . run ( ) ;
2023-03-18 13:35:53 +00:00
result . stdout_check ( < [ u8 ] > ::is_empty ) ;
2023-01-05 18:47:50 +00:00
}
#[ cfg(feature = " echo " ) ]
#[ test ]
#[ should_panic ]
fn test_cmd_result_stderr_str_check_when_false_then_panics ( ) {
let result = TestScenario ::new ( " echo " ) . ucmd ( ) . arg ( " Hello world " ) . run ( ) ;
result . stderr_str_check ( | s | ! s . is_empty ( ) ) ;
}
#[ cfg(feature = " echo " ) ]
#[ test ]
#[ should_panic ]
fn test_cmd_result_stderr_check_when_false_then_panics ( ) {
let result = TestScenario ::new ( " echo " ) . ucmd ( ) . arg ( " Hello world " ) . run ( ) ;
result . stderr_check ( | s | ! s . is_empty ( ) ) ;
}
#[ cfg(feature = " echo " ) ]
#[ test ]
#[ should_panic ]
fn test_cmd_result_stdout_check_when_predicate_panics_then_panic ( ) {
let result = TestScenario ::new ( " echo " ) . ucmd ( ) . run ( ) ;
result . stdout_str_check ( | _ | panic! ( " Just testing " ) ) ;
}
2023-01-05 15:51:42 +00:00
#[ cfg(feature = " echo " ) ]
#[ cfg(unix) ]
#[ test ]
fn test_cmd_result_signal_when_normal_exit_then_no_signal ( ) {
let result = TestScenario ::new ( " echo " ) . ucmd ( ) . run ( ) ;
assert! ( result . signal ( ) . is_none ( ) ) ;
}
#[ cfg(feature = " sleep " ) ]
#[ cfg(unix) ]
#[ test ]
#[ should_panic = " Program must be run first or has not finished " ]
fn test_cmd_result_signal_when_still_running_then_panic ( ) {
let mut child = TestScenario ::new ( " sleep " ) . ucmd ( ) . arg ( " 60 " ) . run_no_wait ( ) ;
child
. make_assertion ( )
. is_alive ( )
. with_current_output ( )
. signal ( ) ;
}
#[ cfg(feature = " sleep " ) ]
#[ cfg(unix) ]
#[ test ]
fn test_cmd_result_signal_when_kill_then_signal ( ) {
let mut child = TestScenario ::new ( " sleep " ) . ucmd ( ) . arg ( " 60 " ) . run_no_wait ( ) ;
child . kill ( ) ;
child
. make_assertion ( )
. is_not_alive ( )
. with_current_output ( )
. signal_is ( 9 )
. signal_name_is ( " SIGKILL " )
. signal_name_is ( " KILL " )
. signal_name_is ( " 9 " )
. signal ( )
. expect ( " Signal was none " ) ;
let result = child . wait ( ) . unwrap ( ) ;
result
. signal_is ( 9 )
. signal_name_is ( " SIGKILL " )
. signal_name_is ( " KILL " )
. signal_name_is ( " 9 " )
. signal ( )
. expect ( " Signal was none " ) ;
}
#[ cfg(feature = " sleep " ) ]
#[ cfg(unix) ]
#[ rstest ]
#[ case::signal_full_name_lower_case( " sigkill " ) ]
#[ case::signal_short_name_lower_case( " kill " ) ]
#[ case::signal_only_part_of_name( " IGKILL " ) ] // spell-checker: disable-line
#[ case::signal_just_sig( " SIG " ) ]
#[ case::signal_value_too_high( " 100 " ) ]
#[ case::signal_value_negative( " -1 " ) ]
#[ should_panic = " Invalid signal name or value " ]
fn test_cmd_result_signal_when_invalid_signal_name_then_panic ( #[ case ] signal_name : & str ) {
let mut child = TestScenario ::new ( " sleep " ) . ucmd ( ) . arg ( " 60 " ) . run_no_wait ( ) ;
child . kill ( ) ;
let result = child . wait ( ) . unwrap ( ) ;
result . signal_name_is ( signal_name ) ;
}
2021-10-01 21:25:56 +00:00
#[ test ]
#[ cfg(unix) ]
fn test_parse_coreutil_version ( ) {
use std ::assert_eq ;
assert_eq! (
parse_coreutil_version ( " id (GNU coreutils) 9.0.123-0123 " ) . to_string ( ) ,
" 9 "
) ;
assert_eq! (
parse_coreutil_version ( " id (GNU coreutils) 8.32.263-0475 " ) . to_string ( ) ,
" 8.32 "
) ;
assert_eq! (
parse_coreutil_version ( " id (GNU coreutils) 8.25.123-0123 " ) . to_string ( ) ,
" 8.25 "
) ;
assert_eq! (
parse_coreutil_version ( " id (GNU coreutils) 9.0 " ) . to_string ( ) ,
" 9 "
) ;
assert_eq! (
parse_coreutil_version ( " id (GNU coreutils) 8.32 " ) . to_string ( ) ,
" 8.32 "
) ;
assert_eq! (
parse_coreutil_version ( " id (GNU coreutils) 8.25 " ) . to_string ( ) ,
" 8.25 "
) ;
}
2021-07-07 12:30:38 +00:00
#[ test ]
#[ cfg(unix) ]
fn test_check_coreutil_version ( ) {
match check_coreutil_version ( " id " , VERSION_MIN ) {
2021-07-07 23:32:39 +00:00
Ok ( s ) = > assert! ( s . starts_with ( " uutils-tests- " ) ) ,
Err ( s ) = > assert! ( s . starts_with ( " uutils-tests-warning " ) ) ,
2021-07-07 12:30:38 +00:00
} ;
2021-07-07 23:32:39 +00:00
#[ cfg(target_os = " linux " ) ]
2021-07-07 12:30:38 +00:00
std ::assert_eq! (
check_coreutil_version ( " no test name " , VERSION_MIN ) ,
Err ( " uutils-tests-warning: 'no test name' \
No such file or directory ( os error 2 ) "
. to_string ( ) )
) ;
}
#[ test ]
#[ cfg(unix) ]
fn test_expected_result ( ) {
2021-07-07 20:46:16 +00:00
let ts = TestScenario ::new ( " id " ) ;
2021-07-07 23:32:39 +00:00
// assert!(expected_result(&ts, &[]).is_ok());
match expected_result ( & ts , & [ ] ) {
Ok ( r ) = > assert! ( r . succeeded ( ) ) ,
Err ( s ) = > assert! ( s . starts_with ( " uutils-tests-warning " ) ) ,
}
2021-07-07 20:46:16 +00:00
let ts = TestScenario ::new ( " no test name " ) ;
assert! ( expected_result ( & ts , & [ ] ) . is_err ( ) ) ;
2021-07-07 12:30:38 +00:00
}
2021-09-13 10:12:32 +00:00
#[ test ]
#[ cfg(unix) ]
fn test_host_name_for ( ) {
#[ cfg(target_os = " linux " ) ]
{
std ::assert_eq! ( host_name_for ( " id " ) , " id " ) ;
std ::assert_eq! ( host_name_for ( " groups " ) , " groups " ) ;
std ::assert_eq! ( host_name_for ( " who " ) , " who " ) ;
}
#[ cfg(not(target_os = " linux " )) ]
{
// spell-checker:ignore (strings) ggroups gwho
std ::assert_eq! ( host_name_for ( " id " ) , " gid " ) ;
std ::assert_eq! ( host_name_for ( " groups " ) , " ggroups " ) ;
std ::assert_eq! ( host_name_for ( " who " ) , " gwho " ) ;
std ::assert_eq! ( host_name_for ( " gid " ) , " gid " ) ;
std ::assert_eq! ( host_name_for ( " ggroups " ) , " ggroups " ) ;
std ::assert_eq! ( host_name_for ( " gwho " ) , " gwho " ) ;
}
}
2021-09-19 20:24:11 +00:00
#[ test ]
#[ cfg(unix) ]
2021-09-19 22:20:06 +00:00
#[ cfg(feature = " whoami " ) ]
2021-09-19 20:24:11 +00:00
fn test_run_ucmd_as_root ( ) {
2023-03-22 16:13:50 +00:00
if is_ci ( ) {
println! ( " TEST SKIPPED (cannot run inside CI) " ) ;
} else {
2022-05-10 11:46:22 +00:00
// Skip test if we can't guarantee non-interactive `sudo`, or if we're not "root"
if let Ok ( output ) = Command ::new ( " sudo " )
. env ( " LC_ALL " , " C " )
2022-10-12 20:04:21 +00:00
. args ( [ " -E " , " --non-interactive " , " whoami " ] )
2022-05-10 11:46:22 +00:00
. output ( )
{
if output . status . success ( ) & & String ::from_utf8_lossy ( & output . stdout ) . eq ( " root \n " ) {
let ts = TestScenario ::new ( " whoami " ) ;
std ::assert_eq! (
run_ucmd_as_root ( & ts , & [ ] ) . unwrap ( ) . stdout_str ( ) . trim ( ) ,
" root "
) ;
} else {
println! ( " TEST SKIPPED (we're not root) " ) ;
}
} else {
println! ( " TEST SKIPPED (cannot run sudo) " ) ;
}
2021-09-19 20:24:11 +00:00
}
}
2022-09-08 17:13:49 +00:00
// This error was first detected when running tail so tail is used here but
// should fail with any command that takes piped input.
// See also https://github.com/uutils/coreutils/issues/3895
2022-10-01 10:26:54 +00:00
#[ cfg(feature = " tail " ) ]
2022-09-08 17:13:49 +00:00
#[ test ]
2022-10-04 08:14:43 +00:00
#[ cfg_attr(not(feature = " expensive_tests " ), ignore) ]
2022-09-08 17:13:49 +00:00
fn test_when_piped_input_then_no_broken_pipe ( ) {
let ts = TestScenario ::new ( " tail " ) ;
for i in 0 .. 10000 {
dbg! ( i ) ;
let test_string = " a \n b \n " ;
ts . ucmd ( )
. args ( & [ " -n " , " 0 " ] )
. pipe_in ( test_string )
. succeeds ( )
. no_stdout ( )
. no_stderr ( ) ;
}
}
2022-11-23 05:43:02 +00:00
#[ cfg(feature = " echo " ) ]
#[ test ]
fn test_uchild_when_run_with_a_non_blocking_util ( ) {
let ts = TestScenario ::new ( " echo " ) ;
ts . ucmd ( )
. arg ( " hello world " )
. run ( )
. success ( )
. stdout_only ( " hello world \n " ) ;
}
// Test basically that most of the methods of UChild are working
#[ cfg(feature = " echo " ) ]
#[ test ]
fn test_uchild_when_run_no_wait_with_a_non_blocking_util ( ) {
let ts = TestScenario ::new ( " echo " ) ;
let mut child = ts . ucmd ( ) . arg ( " hello world " ) . run_no_wait ( ) ;
2022-12-08 19:51:11 +00:00
// check `child.is_alive()` and `child.delay()` is working
let mut trials = 10 ;
while child . is_alive ( ) {
2023-03-18 13:35:53 +00:00
assert! (
trials > 0 ,
" Assertion failed: child process is still alive. "
) ;
2022-12-08 19:51:11 +00:00
child . delay ( 500 ) ;
trials - = 1 ;
}
2022-11-23 05:43:02 +00:00
assert! ( ! child . is_alive ( ) ) ;
// check `child.is_not_alive()` is working
assert! ( child . is_not_alive ( ) ) ;
// check the current output is correct
std ::assert_eq! ( child . stdout ( ) , " hello world \n " ) ;
assert! ( child . stderr ( ) . is_empty ( ) ) ;
// check the current output of echo is empty. We already called `child.stdout()` and `echo`
// exited so there's no additional output after the first call of `child.stdout()`
assert! ( child . stdout ( ) . is_empty ( ) ) ;
assert! ( child . stderr ( ) . is_empty ( ) ) ;
// check that we're still able to access all output of the child process, even after exit
// and call to `child.stdout()`
std ::assert_eq! ( child . stdout_all ( ) , " hello world \n " ) ;
assert! ( child . stderr_all ( ) . is_empty ( ) ) ;
// we should be able to call kill without panics, even if the process already exited
child . make_assertion ( ) . is_not_alive ( ) ;
child . kill ( ) ;
// we should be able to call wait without panics and apply some assertions
child . wait ( ) . unwrap ( ) . code_is ( 0 ) . no_stdout ( ) . no_stderr ( ) ;
}
#[ cfg(feature = " cat " ) ]
#[ test ]
fn test_uchild_when_pipe_in ( ) {
let ts = TestScenario ::new ( " cat " ) ;
2022-12-03 09:59:01 +00:00
let mut child = ts . ucmd ( ) . set_stdin ( Stdio ::piped ( ) ) . run_no_wait ( ) ;
2022-11-23 05:43:02 +00:00
child . pipe_in ( " content " ) ;
child . wait ( ) . unwrap ( ) . stdout_only ( " content " ) . success ( ) ;
ts . ucmd ( ) . pipe_in ( " content " ) . run ( ) . stdout_is ( " content " ) ;
}
#[ cfg(feature = " rm " ) ]
#[ test ]
fn test_uchild_when_run_no_wait_with_a_blocking_command ( ) {
let ts = TestScenario ::new ( " rm " ) ;
let at = & ts . fixtures ;
at . mkdir ( " a " ) ;
at . touch ( " a/empty " ) ;
#[ cfg(target_vendor = " apple " ) ]
2022-12-08 19:51:11 +00:00
let delay : u64 = 2000 ;
2022-11-23 05:43:02 +00:00
#[ cfg(not(target_vendor = " apple " )) ]
2022-12-08 19:51:11 +00:00
let delay : u64 = 1000 ;
2022-11-23 05:43:02 +00:00
let yes = if cfg! ( windows ) { " y \r \n " } else { " y \n " } ;
let mut child = ts
. ucmd ( )
2022-12-03 09:59:01 +00:00
. set_stdin ( Stdio ::piped ( ) )
2022-11-23 05:43:02 +00:00
. stderr_to_stdout ( )
. args ( & [ " -riv " , " a " ] )
. run_no_wait ( ) ;
child
. make_assertion_with_delay ( delay )
. is_alive ( )
. with_current_output ( )
. stdout_is ( " rm: descend into directory 'a'? " ) ;
#[ cfg(windows) ]
let expected = " rm: descend into directory 'a'? \
rm : remove regular empty file ' a \ \ empty ' ? " ;
#[ cfg(unix) ]
let expected = " rm: descend into directory 'a'? \
rm : remove regular empty file ' a / empty ' ? " ;
child . write_in ( yes ) ;
child
. make_assertion_with_delay ( delay )
. is_alive ( )
. with_all_output ( )
. stdout_is ( expected ) ;
#[ cfg(windows) ]
let expected = " removed 'a \\ empty' \n rm: remove directory 'a'? " ;
#[ cfg(unix) ]
let expected = " removed 'a/empty' \n rm: remove directory 'a'? " ;
child
. write_in ( yes )
. make_assertion_with_delay ( delay )
. is_alive ( )
. with_exact_output ( 44 , 0 )
. stdout_only ( expected ) ;
2022-12-08 19:51:11 +00:00
let expected = " removed directory 'a' \n " ;
2022-11-23 05:43:02 +00:00
child . write_in ( yes ) ;
2022-12-08 19:51:11 +00:00
child . wait ( ) . unwrap ( ) . stdout_only ( expected ) . success ( ) ;
2022-11-23 05:43:02 +00:00
}
#[ cfg(feature = " tail " ) ]
#[ test ]
fn test_uchild_when_run_with_stderr_to_stdout ( ) {
let ts = TestScenario ::new ( " tail " ) ;
let at = & ts . fixtures ;
at . write ( " data " , " file data \n " ) ;
let expected_stdout = " ==> data <== \n \
file data \ n \
tail : cannot open ' missing ' for reading : No such file or directory \ n " ;
ts . ucmd ( )
. args ( & [ " data " , " missing " ] )
. stderr_to_stdout ( )
. fails ( )
. stdout_only ( expected_stdout ) ;
}
#[ cfg(feature = " cat " ) ]
#[ cfg(unix) ]
#[ test ]
fn test_uchild_when_no_capture_reading_from_infinite_source ( ) {
use regex ::Regex ;
let ts = TestScenario ::new ( " cat " ) ;
let expected_stdout = b " \0 " . repeat ( 12345 ) ;
let mut child = ts
. ucmd ( )
. set_stdin ( Stdio ::from ( File ::open ( " /dev/zero " ) . unwrap ( ) ) )
. set_stdout ( Stdio ::piped ( ) )
. run_no_wait ( ) ;
child
. make_assertion ( )
. with_exact_output ( 12345 , 0 )
. stdout_only_bytes ( expected_stdout ) ;
child
. kill ( )
. make_assertion ( )
. with_current_output ( )
. stdout_matches ( & Regex ::new ( " [ \0 ].* " ) . unwrap ( ) )
. no_stderr ( ) ;
}
2022-12-10 18:57:25 +00:00
#[ cfg(feature = " sleep " ) ]
#[ test ]
fn test_uchild_when_wait_and_timeout_is_reached_then_timeout_error ( ) {
let ts = TestScenario ::new ( " sleep " ) ;
let child = ts
. ucmd ( )
. timeout ( Duration ::from_secs ( 1 ) )
. arg ( " 10.0 " )
. run_no_wait ( ) ;
match child . wait ( ) {
Err ( error ) if error . kind ( ) = = io ::ErrorKind ::Other = > {
std ::assert_eq! ( error . to_string ( ) , " wait: Timeout of '1s' reached " ) ;
}
2023-01-27 09:29:45 +00:00
Err ( error ) = > panic! ( " Assertion failed: Expected error with timeout but was: {error} " ) ,
2022-12-10 18:57:25 +00:00
Ok ( _ ) = > panic! ( " Assertion failed: Expected timeout of `wait`. " ) ,
}
}
#[ cfg(feature = " sleep " ) ]
#[ rstest ]
#[ timeout(Duration::from_secs(5)) ]
fn test_uchild_when_kill_and_timeout_higher_than_kill_time_then_no_panic ( ) {
let ts = TestScenario ::new ( " sleep " ) ;
let mut child = ts
. ucmd ( )
. timeout ( Duration ::from_secs ( 60 ) )
. arg ( " 20.0 " )
. run_no_wait ( ) ;
child . kill ( ) . make_assertion ( ) . is_not_alive ( ) ;
}
#[ cfg(feature = " sleep " ) ]
#[ test ]
fn test_uchild_when_try_kill_and_timeout_is_reached_then_error ( ) {
let ts = TestScenario ::new ( " sleep " ) ;
let mut child = ts . ucmd ( ) . timeout ( Duration ::ZERO ) . arg ( " 10.0 " ) . run_no_wait ( ) ;
match child . try_kill ( ) {
Err ( error ) if error . kind ( ) = = io ::ErrorKind ::Other = > {
std ::assert_eq! ( error . to_string ( ) , " kill: Timeout of '0s' reached " ) ;
}
2023-01-27 09:29:45 +00:00
Err ( error ) = > panic! ( " Assertion failed: Expected error with timeout but was: {error} " ) ,
2023-08-21 06:11:38 +00:00
Ok ( ( ) ) = > panic! ( " Assertion failed: Expected timeout of `try_kill`. " ) ,
2022-12-10 18:57:25 +00:00
}
}
#[ cfg(feature = " sleep " ) ]
#[ test ]
#[ should_panic = " kill: Timeout of '0s' reached " ]
fn test_uchild_when_kill_with_timeout_and_timeout_is_reached_then_panic ( ) {
let ts = TestScenario ::new ( " sleep " ) ;
let mut child = ts . ucmd ( ) . timeout ( Duration ::ZERO ) . arg ( " 10.0 " ) . run_no_wait ( ) ;
child . kill ( ) ;
panic! ( " Assertion failed: Expected timeout of `kill`. " ) ;
}
#[ cfg(feature = " sleep " ) ]
#[ test ]
#[ should_panic(expected = " wait: Timeout of '1.1s' reached " ) ]
fn test_ucommand_when_run_with_timeout_and_timeout_is_reached_then_panic ( ) {
let ts = TestScenario ::new ( " sleep " ) ;
ts . ucmd ( )
. timeout ( Duration ::from_millis ( 1100 ) )
. arg ( " 10.0 " )
. run ( ) ;
panic! ( " Assertion failed: Expected timeout of `run`. " )
}
#[ cfg(feature = " sleep " ) ]
#[ rstest ]
#[ timeout(Duration::from_secs(10)) ]
fn test_ucommand_when_run_with_timeout_higher_then_execution_time_then_no_panic ( ) {
let ts = TestScenario ::new ( " sleep " ) ;
ts . ucmd ( ) . timeout ( Duration ::from_secs ( 60 ) ) . arg ( " 1.0 " ) . run ( ) ;
}
2023-01-22 16:46:25 +00:00
#[ cfg(feature = " echo " ) ]
#[ test ]
fn test_ucommand_when_default ( ) {
2023-01-25 02:40:39 +00:00
let shell_cmd = format! ( " {TESTS_BINARY} echo -n hello " ) ;
2023-01-22 16:46:25 +00:00
let mut command = UCommand ::new ( ) ;
command . arg ( & shell_cmd ) . succeeds ( ) . stdout_is ( " hello " ) ;
#[ cfg(target_os = " android " ) ]
let ( expected_bin , expected_arg ) = ( PathBuf ::from ( " /system/bin/sh " ) , OsString ::from ( " -c " ) ) ;
#[ cfg(all(unix, not(target_os = " android " ))) ]
let ( expected_bin , expected_arg ) = ( PathBuf ::from ( " /bin/sh " ) , OsString ::from ( " -c " ) ) ;
#[ cfg(windows) ]
let ( expected_bin , expected_arg ) = ( PathBuf ::from ( " cmd " ) , OsString ::from ( " /C " ) ) ;
std ::assert_eq! ( & expected_bin , command . bin_path . as_ref ( ) . unwrap ( ) ) ;
assert! ( command . util_name . is_none ( ) ) ;
std ::assert_eq! ( command . args , & [ expected_arg , OsString ::from ( & shell_cmd ) ] ) ;
assert! ( command . tmpd . is_some ( ) ) ;
}
2023-01-25 02:40:39 +00:00
#[ cfg(feature = " echo " ) ]
#[ test ]
fn test_ucommand_with_util ( ) {
let tmpd = tempfile ::tempdir ( ) . unwrap ( ) ;
let mut command = UCommand ::with_util ( " echo " , Rc ::new ( tmpd ) ) ;
command
. args ( & [ " -n " , " hello " ] )
. succeeds ( )
. stdout_only ( " hello " ) ;
std ::assert_eq! (
& PathBuf ::from ( TESTS_BINARY ) ,
command . bin_path . as_ref ( ) . unwrap ( )
) ;
std ::assert_eq! ( " echo " , & command . util_name . unwrap ( ) ) ;
std ::assert_eq! (
& [
OsString ::from ( " echo " ) ,
OsString ::from ( " -n " ) ,
OsString ::from ( " hello " )
] ,
command . args . make_contiguous ( )
) ;
assert! ( command . tmpd . is_some ( ) ) ;
}
2024-01-13 23:38:17 +00:00
#[ cfg(all(unix, not(target_os = " macos " ))) ]
#[ test ]
fn test_compare_xattrs ( ) {
use tempfile ::tempdir ;
let temp_dir = tempdir ( ) . unwrap ( ) ;
let file_path1 = temp_dir . path ( ) . join ( " test_file1.txt " ) ;
let file_path2 = temp_dir . path ( ) . join ( " test_file2.txt " ) ;
File ::create ( & file_path1 ) . unwrap ( ) ;
File ::create ( & file_path2 ) . unwrap ( ) ;
let test_attr = " user.test_attr " ;
let test_value = b " test value " ;
xattr ::set ( & file_path1 , test_attr , test_value ) . unwrap ( ) ;
assert! ( ! compare_xattrs ( & file_path1 , & file_path2 ) ) ;
xattr ::set ( & file_path2 , test_attr , test_value ) . unwrap ( ) ;
assert! ( compare_xattrs ( & file_path1 , & file_path2 ) ) ;
}
2024-02-07 21:02:19 +00:00
#[ cfg(unix) ]
2024-03-03 14:02:19 +00:00
#[ cfg(feature = " env " ) ]
2024-02-07 21:02:19 +00:00
#[ test ]
fn test_simulation_of_terminal_false ( ) {
let scene = TestScenario ::new ( " util " ) ;
let out = scene . ccmd ( " env " ) . arg ( " sh " ) . arg ( " is_atty.sh " ) . succeeds ( ) ;
std ::assert_eq! (
String ::from_utf8_lossy ( out . stdout ( ) ) ,
" stdin is not atty \n stdout is not atty \n stderr is not atty \n "
) ;
std ::assert_eq! (
String ::from_utf8_lossy ( out . stderr ( ) ) ,
" This is an error message. \n "
) ;
}
#[ cfg(unix) ]
2024-03-03 14:02:19 +00:00
#[ cfg(feature = " env " ) ]
2024-02-07 21:02:19 +00:00
#[ test ]
fn test_simulation_of_terminal_true ( ) {
let scene = TestScenario ::new ( " util " ) ;
let out = scene
. ccmd ( " env " )
. arg ( " sh " )
. arg ( " is_atty.sh " )
. terminal_simulation ( true )
. succeeds ( ) ;
std ::assert_eq! (
String ::from_utf8_lossy ( out . stdout ( ) ) ,
2024-02-25 17:54:10 +00:00
" stdin is atty \r \n stdout is atty \r \n stderr is atty \r \n terminal size: 30 80 \r \n "
) ;
std ::assert_eq! (
String ::from_utf8_lossy ( out . stderr ( ) ) ,
" This is an error message. \r \n "
) ;
}
#[ cfg(unix) ]
2024-03-03 14:02:19 +00:00
#[ cfg(feature = " env " ) ]
2024-02-25 17:54:10 +00:00
#[ test ]
fn test_simulation_of_terminal_size_information ( ) {
let scene = TestScenario ::new ( " util " ) ;
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 ,
} )
. succeeds ( ) ;
std ::assert_eq! (
String ::from_utf8_lossy ( out . stdout ( ) ) ,
" stdin is atty \r \n stdout is atty \r \n stderr is atty \r \n terminal size: 10 40 \r \n "
2024-02-07 21:02:19 +00:00
) ;
std ::assert_eq! (
String ::from_utf8_lossy ( out . stderr ( ) ) ,
" This is an error message. \r \n "
) ;
}
#[ cfg(unix) ]
2024-03-03 14:02:19 +00:00
#[ cfg(feature = " env " ) ]
2024-02-07 21:02:19 +00:00
#[ test ]
fn test_simulation_of_terminal_pty_sends_eot_automatically ( ) {
let scene = TestScenario ::new ( " util " ) ;
let mut cmd = scene . ccmd ( " env " ) ;
cmd . timeout ( std ::time ::Duration ::from_secs ( 10 ) ) ;
cmd . args ( & [ " cat " , " - " ] ) ;
cmd . terminal_simulation ( true ) ;
let child = cmd . run_no_wait ( ) ;
let out = child . wait ( ) . unwrap ( ) ; // cat would block if there is no eot
std ::assert_eq! ( String ::from_utf8_lossy ( out . stderr ( ) ) , " " ) ;
std ::assert_eq! ( String ::from_utf8_lossy ( out . stdout ( ) ) , " \r \n " ) ;
}
#[ cfg(unix) ]
2024-03-03 14:02:19 +00:00
#[ cfg(feature = " env " ) ]
2024-02-07 21:02:19 +00:00
#[ test ]
fn test_simulation_of_terminal_pty_pipes_into_data_and_sends_eot_automatically ( ) {
let scene = TestScenario ::new ( " util " ) ;
let message = " Hello stdin forwarding! " ;
let mut cmd = scene . ccmd ( " env " ) ;
cmd . args ( & [ " cat " , " - " ] ) ;
cmd . terminal_simulation ( true ) ;
cmd . pipe_in ( message ) ;
let child = cmd . run_no_wait ( ) ;
let out = child . wait ( ) . unwrap ( ) ;
std ::assert_eq! (
String ::from_utf8_lossy ( out . stdout ( ) ) ,
format! ( " {} \r \n " , message )
) ;
std ::assert_eq! ( String ::from_utf8_lossy ( out . stderr ( ) ) , " " ) ;
}
#[ cfg(unix) ]
2024-03-03 14:02:19 +00:00
#[ cfg(feature = " env " ) ]
2024-02-07 21:02:19 +00:00
#[ test ]
fn test_simulation_of_terminal_pty_write_in_data_and_sends_eot_automatically ( ) {
let scene = TestScenario ::new ( " util " ) ;
let mut cmd = scene . ccmd ( " env " ) ;
cmd . args ( & [ " cat " , " - " ] ) ;
cmd . terminal_simulation ( true ) ;
let mut child = cmd . run_no_wait ( ) ;
child . write_in ( " Hello stdin forwarding via write_in! " ) ;
let out = child . wait ( ) . unwrap ( ) ;
std ::assert_eq! (
String ::from_utf8_lossy ( out . stdout ( ) ) ,
" Hello stdin forwarding via write_in! \r \n "
) ;
std ::assert_eq! ( String ::from_utf8_lossy ( out . stderr ( ) ) , " " ) ;
}
2024-03-05 14:56:20 +00:00
#[ cfg(unix) ]
#[ test ]
fn test_application_of_process_resource_limits_unlimited_file_size ( ) {
let ts = TestScenario ::new ( " util " ) ;
ts . cmd ( " sh " )
. args ( & [ " -c " , " ulimit -Sf; ulimit -Hf " ] )
. succeeds ( )
. no_stderr ( )
. stdout_is ( " unlimited \n unlimited \n " ) ;
}
#[ cfg(unix) ]
#[ test ]
fn test_application_of_process_resource_limits_limited_file_size ( ) {
let unit_size_bytes = if cfg! ( target_os = " macos " ) { 1024 } else { 512 } ;
let ts = TestScenario ::new ( " util " ) ;
ts . cmd ( " sh " )
. args ( & [ " -c " , " ulimit -Sf; ulimit -Hf " ] )
. limit (
rlimit ::Resource ::FSIZE ,
8 * unit_size_bytes ,
16 * unit_size_bytes ,
)
. succeeds ( )
. no_stderr ( )
. stdout_is ( " 8 \n 16 \n " ) ;
}
2021-04-10 18:18:38 +00:00
}