2021-07-07 14:19:58 +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.
2022-11-23 05:43:02 +00:00
//spell-checker: ignore (linux) rlimit prlimit coreutil ggroups uchild uncaptured
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
2021-04-24 14:43:13 +00:00
use pretty_assertions ::assert_eq ;
2021-06-02 16:06:56 +00:00
#[ cfg(target_os = " linux " ) ]
2022-04-12 07:18:29 +00:00
use rlimit ::prlimit ;
2022-12-10 18:57:25 +00:00
use rstest ::rstest ;
2021-07-07 23:32:39 +00:00
#[ cfg(unix) ]
2021-07-07 12:30:38 +00:00
use std ::borrow ::Cow ;
2021-04-01 21:37:22 +00:00
#[ cfg(not(windows)) ]
use std ::ffi ::CString ;
use std ::ffi ::OsStr ;
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) ]
2022-09-23 21:42:35 +00:00
use std ::os ::unix ::fs ::{ symlink as symlink_dir , symlink as symlink_file , PermissionsExt } ;
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 } ;
2022-11-23 05:43:02 +00:00
use std ::process ::{ Child , Command , 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 } ;
use std ::{ env , hint , thread } ;
2022-11-23 05:43:02 +00:00
use tempfile ::{ Builder , TempDir } ;
2022-08-17 13:20:20 +00:00
use uucore ::Args ;
2015-11-16 05:25:01 +00:00
2015-12-24 03:59:04 +00:00
#[ cfg(windows) ]
2020-05-24 23:46:16 +00:00
static PROGNAME : & str = concat! ( env! ( " CARGO_PKG_NAME " ) , " .exe " ) ;
2015-12-24 03:59:04 +00:00
#[ cfg(not(windows)) ]
2020-05-24 23:46:16 +00:00
static PROGNAME : & str = env! ( " CARGO_PKG_NAME " ) ;
2016-05-22 19:09:04 +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 " ;
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 {
std ::env ::var ( " CI " )
2021-05-29 12:32:35 +00:00
. unwrap_or_else ( | _ | String ::from ( " false " ) )
2020-06-13 23:54:43 +00:00
. eq_ignore_ascii_case ( " true " )
}
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`
bin_path : String ,
/// 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)
2021-04-17 13:48:23 +00:00
code : Option < i32 > ,
2021-01-18 13:42:44 +00:00
/// zero-exit from running the Command?
/// see [`success`]
2021-04-22 20:37:44 +00:00
success : bool ,
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 {
2021-06-10 09:01:44 +00:00
pub fn new (
2021-11-09 17:37:38 +00:00
bin_path : String ,
util_name : Option < String > ,
2021-06-10 09:01:44 +00:00
tmpd : Option < Rc < TempDir > > ,
code : Option < i32 > ,
success : bool ,
stdout : & [ u8 ] ,
stderr : & [ u8 ] ,
2022-01-30 13:59:31 +00:00
) -> Self {
Self {
2021-11-09 17:37:38 +00:00
bin_path ,
util_name ,
2021-06-10 09:01:44 +00:00
tmpd ,
code ,
success ,
stdout : stdout . to_vec ( ) ,
stderr : stderr . to_vec ( ) ,
}
}
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
}
/// 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 {
2022-11-14 17:39:48 +00:00
self . code
. expect ( " Program must be run first or has not finished, yet " )
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 {
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 {
self . success
}
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! (
self . success ,
" Command was expected to succeed. \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-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! (
! self . success ,
" 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 {
2021-05-26 20:15:28 +00:00
assert_eq! ( self . stdout , 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 ( ) ;
for kv in vars . iter ( ) {
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
}
2016-07-17 09:20:33 +00:00
/// asserts that the command resulted in stderr stream output that equals the
/// passed in value, when both are trimmed of trailing whitespace
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 {
2018-03-12 08:20:58 +00:00
assert_eq! (
2021-05-26 20:15:28 +00:00
self . stderr_str ( ) . trim_end ( ) ,
2019-12-17 04:09:20 +00:00
String ::from ( msg . as_ref ( ) ) . trim_end ( )
2018-03-12 08:20:58 +00:00
) ;
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 {
2021-05-26 20:15:28 +00:00
assert_eq! ( self . stderr , 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
/// passed in value, when both are trimmed of trailing whitespace
/// 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 {
2016-02-17 05:03:52 +00:00
assert! ( ! self . success ) ;
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
/// the following format when both are trimmed of trailing whitespace
/// `"{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! (
" {0}: {2} \n Try '{1} {0} --help' for more information. " ,
self . util_name . as_ref ( ) . unwrap ( ) , // This shouldn't be called using a normal command
self . bin_path ,
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-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! (
regex . is_match ( self . stdout_str ( ) . trim ( ) ) ,
" 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 ]
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! (
! regex . is_match ( self . stdout_str ( ) . trim ( ) ) ,
" 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 {
" / "
}
}
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
2015-11-16 05:25:01 +00:00
pub fn plus ( & self , name : & str ) -> PathBuf {
let mut pathbuf = self . subdir . clone ( ) ;
pathbuf . push ( name ) ;
pathbuf
}
2016-03-29 01:13:40 +00:00
2015-11-16 05:25:01 +00:00
pub fn plus_as_string ( & self , name : & str ) -> String {
String ::from ( self . plus ( name ) . to_str ( ) . unwrap ( ) )
}
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 ( )
. write ( true )
. 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 ( )
. write ( true )
. 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
}
2015-11-16 05:25:01 +00:00
pub fn mkdir ( & self , dir : & str ) {
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
2015-11-16 05:25:01 +00:00
pub fn touch ( & self , file : & str ) {
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) ]
let original = original . replace ( '/' , & MAIN_SEPARATOR . to_string ( ) ) ;
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) ]
let original = original . replace ( '/' , & MAIN_SEPARATOR . to_string ( ) ) ;
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 ) ,
}
}
pub fn file_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_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 {
2022-01-30 13:59:31 +00:00
pub fn new ( util_name : & str ) -> Self {
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 {
2015-11-16 05:25:01 +00:00
bin_path : {
2021-05-30 05:10:54 +00:00
// Instead of hard coding the path relative to the current
2016-05-22 19:09:04 +00:00
// directory, use Cargo's OUT_DIR to find path to executable.
// This allows tests to be run using profiles other than debug.
2016-12-19 19:21:42 +00:00
let target_dir = path_concat! ( env! ( " OUT_DIR " ) , " .. " , " .. " , " .. " , PROGNAME ) ;
2016-11-25 19:14:46 +00:00
PathBuf ::from ( AtPath ::new ( Path ::new ( & target_dir ) ) . root_dir_resolved ( ) )
2015-11-16 05:25:01 +00:00
} ,
util_name : String ::from ( util_name ) ,
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 ) ;
fixture_path_builder . push ( util_name ) ;
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 {
2021-11-09 07:20:43 +00:00
self . composite_cmd ( & self . bin_path , & self . util_name , true )
}
/// Returns builder for invoking the target uutils binary. Paths given are
/// treated relative to the environment's unique temporary test directory.
pub fn composite_cmd < S : AsRef < OsStr > , T : AsRef < OsStr > > (
& self ,
bin : S ,
util_name : T ,
env_clear : bool ,
) -> UCommand {
2022-01-30 12:07:20 +00:00
UCommand ::new_from_tmp ( bin , & Some ( util_name ) , self . tmpd . clone ( ) , env_clear )
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.
2015-11-16 05:25:01 +00:00
pub fn cmd < S : AsRef < OsStr > > ( & self , bin : S ) -> UCommand {
2022-01-30 12:07:20 +00:00
UCommand ::new_from_tmp ::< S , S > ( bin , & None , self . tmpd . clone ( ) , true )
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.
pub fn ccmd < S : AsRef < OsStr > > ( & self , bin : S ) -> UCommand {
2021-11-09 07:20:43 +00:00
self . composite_cmd ( & self . bin_path , bin , true )
2021-04-01 21:16:47 +00:00
}
2015-11-16 05:25:01 +00:00
// different names are used rather than an argument
// because the need to keep the environment is exceedingly rare.
2016-07-29 21:26:32 +00:00
pub fn ucmd_keepenv ( & self ) -> UCommand {
2021-11-09 07:20:43 +00:00
self . composite_cmd ( & self . bin_path , & self . util_name , false )
2015-11-16 05:25:01 +00:00
}
2016-03-29 01:13:40 +00:00
2021-05-26 10:53:11 +00:00
/// Returns builder for invoking any system command. Paths given are treated
/// relative to the environment's unique temporary test directory.
/// Differs from the builder returned by `cmd` in that `cmd_keepenv` does not call
/// `Command::env_clear` (Clears the entire environment map for the child process.)
2015-11-16 05:25:01 +00:00
pub fn cmd_keepenv < S : AsRef < OsStr > > ( & self , bin : S ) -> UCommand {
2022-01-30 12:07:20 +00:00
UCommand ::new_from_tmp ::< S , S > ( bin , & None , self . tmpd . clone ( ) , false )
2015-11-16 05:25:01 +00:00
}
}
2016-11-25 19:14:46 +00:00
/// A `UCommand` is a wrapper around 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.
/// 3. it provides convenience construction arguments to set the Command working directory and/or clear its environment.
2018-11-21 16:49:12 +00:00
#[ derive(Debug) ]
2015-11-16 05:25:01 +00:00
pub struct UCommand {
pub raw : Command ,
comm_string : String ,
2021-11-09 05:16:06 +00:00
bin_path : String ,
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 > > ,
2022-02-09 18:08:28 +00:00
#[ cfg(any(target_os = " linux " , target_os = " android " )) ]
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 > ,
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 {
2021-11-09 05:16:06 +00:00
pub fn new < T : AsRef < OsStr > , S : AsRef < OsStr > , U : AsRef < OsStr > > (
2021-11-09 03:09:18 +00:00
bin_path : T ,
2022-01-30 12:07:20 +00:00
util_name : & Option < S > ,
2021-11-09 03:09:18 +00:00
curdir : U ,
env_clear : bool ,
2022-01-30 13:59:31 +00:00
) -> Self {
2021-11-09 03:09:18 +00:00
let bin_path = bin_path . as_ref ( ) ;
2022-12-14 14:42:07 +00:00
let util_name = util_name . as_ref ( ) . map ( std ::convert ::AsRef ::as_ref ) ;
2021-11-09 05:16:06 +00:00
2022-01-30 13:59:31 +00:00
let mut ucmd = Self {
2015-11-16 05:25:01 +00:00
tmpd : None ,
has_run : false ,
raw : {
2021-11-09 03:09:18 +00:00
let mut cmd = Command ::new ( bin_path ) ;
2015-11-16 05:25:01 +00:00
cmd . current_dir ( curdir . as_ref ( ) ) ;
if env_clear {
2022-04-16 04:56:14 +00:00
cmd . env_clear ( ) ;
2016-02-14 15:34:39 +00:00
if cfg! ( windows ) {
2021-05-30 05:10:54 +00:00
// spell-checker:ignore (dll) rsaenh
2016-02-14 15:34:39 +00:00
// %SYSTEMROOT% is required on Windows to initialize crypto provider
// ... and crypto provider is required for std::rand
2021-05-30 05:10:54 +00:00
// From `procmon`: RegQueryValue HKLM\SOFTWARE\Microsoft\Cryptography\Defaults\Provider\Microsoft Strong Cryptographic Provider\Image Path
2016-02-14 15:34:39 +00:00
// SUCCESS Type: REG_SZ, Length: 66, Data: %SystemRoot%\system32\rsaenh.dll"
2022-04-16 04:56:14 +00:00
if let Some ( systemroot ) = env ::var_os ( " SYSTEMROOT " ) {
cmd . env ( " SYSTEMROOT " , systemroot ) ;
2016-02-14 15:34:39 +00:00
}
} else {
2022-04-16 04:56:14 +00:00
// if someone is setting LD_PRELOAD, there's probably a good reason for it
if let Some ( ld_preload ) = env ::var_os ( " LD_PRELOAD " ) {
cmd . env ( " LD_PRELOAD " , ld_preload ) ;
}
2016-02-14 15:34:39 +00:00
}
2015-11-16 05:25:01 +00:00
}
cmd
} ,
2021-11-09 03:09:18 +00:00
comm_string : String ::from ( bin_path . to_str ( ) . unwrap ( ) ) ,
2021-11-09 05:16:06 +00:00
bin_path : bin_path . to_str ( ) . unwrap ( ) . to_string ( ) ,
util_name : util_name . map ( | un | un . to_str ( ) . unwrap ( ) . to_string ( ) ) ,
2021-04-09 09:08:31 +00:00
ignore_stdin_write_error : false ,
2021-04-22 20:37:44 +00:00
bytes_into_stdin : None ,
stdin : None ,
stdout : None ,
stderr : None ,
2022-02-09 18:08:28 +00:00
#[ cfg(any(target_os = " linux " , target_os = " android " )) ]
2021-06-02 16:06:56 +00:00
limits : vec ! [ ] ,
2022-11-14 17:39:48 +00:00
stderr_to_stdout : false ,
2022-12-10 18:57:25 +00:00
timeout : Some ( Duration ::from_secs ( 30 ) ) ,
2021-11-09 05:16:06 +00:00
} ;
if let Some ( un ) = util_name {
ucmd . arg ( un ) ;
2015-11-16 05:25:01 +00:00
}
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
2021-11-09 05:16:06 +00:00
pub fn new_from_tmp < T : AsRef < OsStr > , S : AsRef < OsStr > > (
2021-11-09 03:09:18 +00:00
bin_path : T ,
2022-01-30 12:07:20 +00:00
util_name : & Option < S > ,
2021-11-09 03:09:18 +00:00
tmpd : Rc < TempDir > ,
env_clear : bool ,
2022-01-30 13:59:31 +00:00
) -> Self {
2022-08-12 07:45:16 +00:00
let tmpd_path_buf = String ::from ( tmpd . as_ref ( ) . path ( ) . to_str ( ) . unwrap ( ) ) ;
2022-01-30 13:59:31 +00:00
let mut ucmd : Self = Self ::new ( bin_path , util_name , tmpd_path_buf , env_clear ) ;
2015-11-16 05:25:01 +00:00
ucmd . tmpd = Some ( tmpd ) ;
ucmd
}
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 {
2021-10-11 20:51:14 +00:00
assert! ( ! self . has_run , " {} " , ALREADY_RUN ) ;
2021-05-29 12:32:35 +00:00
self . comm_string . push ( ' ' ) ;
2021-04-25 21:28:42 +00:00
self . comm_string
. push_str ( arg . as_ref ( ) . to_str ( ) . unwrap_or_default ( ) ) ;
2015-11-16 05:25:01 +00:00
self . raw . arg ( arg . as_ref ( ) ) ;
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 {
2021-10-11 20:51:14 +00:00
assert! ( ! self . has_run , " {} " , MULTIPLE_STDIN_MEANINGLESS ) ;
2021-04-25 21:28:42 +00:00
let strings = args
. iter ( )
. map ( | s | s . as_ref ( ) . to_os_string ( ) )
2022-08-17 13:20:20 +00:00
. collect_ignore ( ) ;
2021-04-25 21:28:42 +00:00
for s in strings {
2021-05-29 12:32:35 +00:00
self . comm_string . push ( ' ' ) ;
2021-04-25 21:28:42 +00:00
self . comm_string . push_str ( & s ) ;
2015-11-16 05:25:01 +00:00
}
self . raw . args ( args . as_ref ( ) ) ;
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 > ,
{
2021-10-11 20:51:14 +00:00
assert! ( ! self . has_run , " {} " , ALREADY_RUN ) ;
2016-01-03 13:16:12 +00:00
self . raw . env ( key , val ) ;
2021-03-31 09:25:23 +00:00
self
2016-01-03 13:16:12 +00:00
}
2022-02-09 18:08:28 +00:00
#[ cfg(any(target_os = " linux " , target_os = " android " )) ]
2021-06-02 16:06:56 +00:00
pub fn with_limit (
& 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
}
2016-07-17 09:20:33 +00:00
/// Spawns the command, feeds the stdin if any, and returns the
/// child process immediately.
2022-11-18 00:25:43 +00:00
pub fn run_no_wait ( & mut self ) -> UChild {
2021-10-11 20:51:14 +00:00
assert! ( ! self . has_run , " {} " , ALREADY_RUN ) ;
2015-11-16 05:25:01 +00:00
self . has_run = true ;
log_info ( " run " , & self . comm_string ) ;
2022-11-23 05:43:02 +00:00
let mut captured_stdout = None ;
let mut captured_stderr = None ;
2022-11-14 17:39:48 +00:00
let command = if self . stderr_to_stdout {
2022-11-23 05:43:02 +00:00
let mut output = CapturedOutput ::default ( ) ;
2022-11-14 17:39:48 +00:00
let command = self
. raw
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
command
} 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
2022-11-14 17:39:48 +00:00
self . raw
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 )
. stderr ( stderr )
} ;
2022-11-23 05:43:02 +00:00
let child = command . spawn ( ) . unwrap ( ) ;
2016-04-02 14:23:26 +00:00
2021-06-02 16:06:56 +00:00
#[ cfg(target_os = " linux " ) ]
for & ( resource , soft_limit , hard_limit ) in & self . limits {
prlimit (
child . id ( ) as i32 ,
resource ,
Some ( ( soft_limit , hard_limit ) ) ,
None ,
)
. unwrap ( ) ;
}
2022-11-23 05:43:02 +00:00
let mut child = UChild ::from ( self , child , captured_stdout , captured_stderr ) ;
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
}
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
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 ,
}
}
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 ,
}
}
}
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 {
2022-11-23 05:43:02 +00:00
let ( code , success ) = match self . uchild . is_alive ( ) {
2022-11-14 17:39:48 +00:00
true = > ( None , true ) ,
false = > {
2022-11-23 05:43:02 +00:00
let status = self . uchild . raw . wait ( ) . unwrap ( ) ;
2022-11-14 17:39:48 +00:00
( status . code ( ) , status . success ( ) )
}
} ;
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 ) ,
) ,
} ;
CmdResult {
bin_path : self . uchild . bin_path . clone ( ) ,
util_name : self . uchild . util_name . clone ( ) ,
tmpd : self . uchild . tmpd . clone ( ) ,
code ,
success ,
stdout ,
stderr ,
}
}
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 ,
bin_path : String ,
util_name : Option < String > ,
captured_stdout : Option < CapturedOutput > ,
captured_stderr : Option < CapturedOutput > ,
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 > ,
) -> Self {
Self {
raw : child ,
2022-11-23 05:43:02 +00:00
bin_path : ucommand . bin_path . clone ( ) ,
util_name : ucommand . util_name . clone ( ) ,
2022-11-14 17:39:48 +00:00
captured_stdout ,
captured_stderr ,
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 ,
code : output . status . code ( ) ,
success : output . status . success ( ) ,
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 > {
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 ( ) ;
let handle = thread ::spawn ( move | | sender . send ( child . wait_with_output ( ) ) ) ;
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 ( ) {
output . stdout = stdout . output_bytes ( ) ;
}
if let Some ( stderr ) = self . captured_stderr . as_mut ( ) {
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.
///
/// 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! [ ] ,
}
}
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 ;
let content = content . into ( ) ;
let stdin = self
2022-11-23 05:43:02 +00:00
. raw
. stdin
. take ( )
2022-11-14 17:39:48 +00:00
. expect ( " Could not pipe into child process. Was it set to Stdio::null()? " ) ;
2022-11-23 05:43:02 +00:00
let join_handle = thread ::spawn ( move | | {
2022-11-14 17:39:48 +00:00
let mut writer = BufWriter ::new ( stdin ) ;
2022-11-23 05:43:02 +00:00
match writer . write_all ( & content ) . and_then ( | _ | writer . flush ( ) ) {
Err ( error ) if ! ignore_stdin_write_error = > Err ( io ::Error ::new (
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
) ) ,
Ok ( _ ) | Err ( _ ) = > Ok ( ( ) ) ,
2022-11-14 17:39:48 +00:00
}
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 < ( ) > {
2022-11-14 17:39:48 +00:00
let stdin = self . raw . stdin . as_mut ( ) . unwrap ( ) ;
2022-11-23 05:43:02 +00:00
match stdin . write_all ( & data . into ( ) ) . and_then ( | _ | stdin . flush ( ) ) {
Err ( error ) if ! self . ignore_stdin_write_error = > Err ( io ::Error ::new (
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
) ) ,
Ok ( _ ) | Err ( _ ) = > Ok ( ( ) ) ,
}
}
/// 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 ( ) ;
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 {
2021-11-10 21:59:41 +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 > {
2021-09-13 10:12:32 +00:00
println! ( " {} " , check_coreutil_version ( & ts . util_name , VERSION_MIN ) ? ) ;
2021-07-07 20:46:16 +00:00
let util_name = & host_name_for ( & ts . util_name ) ;
2021-07-07 12:30:38 +00:00
2021-07-07 20:46:16 +00:00
let result = ts
2021-07-07 12:30:38 +00:00
. cmd_keepenv ( util_name . as_ref ( ) )
. env ( " LC_ALL " , " C " )
. args ( args )
. run ( ) ;
let ( stdout , stderr ) : ( String , String ) = if cfg! ( target_os = " linux " ) {
(
result . stdout_str ( ) . to_string ( ) ,
result . stderr_str ( ) . to_string ( ) ,
)
} 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 ) ,
result . stderr_str ( ) . replace ( & from , to ) ,
)
} ;
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 ( ) ) ,
Some ( result . code ( ) ) ,
result . succeeded ( ) ,
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
2021-09-19 20:24:11 +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:
/// 'sudo -E --non-interactive whoami' first.
///
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 ] ,
) -> std ::result ::Result < CmdResult , String > {
2022-05-10 11:46:22 +00:00
if ! is_ci ( ) {
// check if we can run 'sudo'
log_info ( " run " , " sudo -E --non-interactive whoami " ) ;
match Command ::new ( " sudo " )
2021-09-19 20:24:11 +00:00
. 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 ( )
{
Ok ( output ) if String ::from_utf8_lossy ( & output . stdout ) . eq ( " root \n " ) = > {
// we can run sudo and we're root
// run ucmd as root:
Ok ( ts
. cmd_keepenv ( " sudo " )
. env ( " LC_ALL " , " C " )
. arg ( " -E " )
. arg ( " --non-interactive " )
. arg ( & ts . bin_path )
. arg ( & ts . util_name )
. args ( args )
. run ( ) )
}
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
}
} else {
2022-12-14 14:42:07 +00:00
Err ( format! ( " {UUTILS_INFO} : {} " , " cannot run inside CI " ) )
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 ::* ;
#[ test ]
fn test_code_is ( ) {
let res = CmdResult {
2022-11-30 09:37:59 +00:00
bin_path : String ::new ( ) ,
2021-11-09 17:37:38 +00:00
util_name : None ,
2021-04-10 18:18:38 +00:00
tmpd : None ,
code : Some ( 32 ) ,
success : false ,
stdout : " " . into ( ) ,
stderr : " " . into ( ) ,
} ;
res . code_is ( 32 ) ;
}
#[ test ]
#[ should_panic ]
fn test_code_is_fail ( ) {
let res = CmdResult {
2022-11-30 09:37:59 +00:00
bin_path : String ::new ( ) ,
2021-11-09 17:37:38 +00:00
util_name : None ,
2021-04-10 18:18:38 +00:00
tmpd : None ,
code : Some ( 32 ) ,
success : false ,
stdout : " " . into ( ) ,
stderr : " " . into ( ) ,
} ;
res . code_is ( 1 ) ;
}
#[ test ]
fn test_failure ( ) {
let res = CmdResult {
2022-11-30 09:37:59 +00:00
bin_path : String ::new ( ) ,
2021-11-09 17:37:38 +00:00
util_name : None ,
2021-04-10 18:18:38 +00:00
tmpd : None ,
code : None ,
success : false ,
stdout : " " . into ( ) ,
stderr : " " . into ( ) ,
} ;
res . failure ( ) ;
}
#[ test ]
#[ should_panic ]
fn test_failure_fail ( ) {
let res = CmdResult {
2022-11-30 09:37:59 +00:00
bin_path : String ::new ( ) ,
2021-11-09 17:37:38 +00:00
util_name : None ,
2021-04-10 18:18:38 +00:00
tmpd : None ,
code : None ,
success : true ,
stdout : " " . into ( ) ,
stderr : " " . into ( ) ,
} ;
res . failure ( ) ;
}
#[ test ]
fn test_success ( ) {
let res = CmdResult {
2022-11-30 09:37:59 +00:00
bin_path : String ::new ( ) ,
2021-11-09 17:37:38 +00:00
util_name : None ,
2021-04-10 18:18:38 +00:00
tmpd : None ,
code : None ,
success : true ,
stdout : " " . into ( ) ,
stderr : " " . into ( ) ,
} ;
res . success ( ) ;
}
#[ test ]
#[ should_panic ]
fn test_success_fail ( ) {
let res = CmdResult {
2022-11-30 09:37:59 +00:00
bin_path : String ::new ( ) ,
2021-11-09 17:37:38 +00:00
util_name : None ,
2021-04-10 18:18:38 +00:00
tmpd : None ,
code : None ,
success : false ,
stdout : " " . into ( ) ,
stderr : " " . into ( ) ,
} ;
res . success ( ) ;
}
#[ test ]
2021-05-30 05:10:54 +00:00
fn test_no_stderr_output ( ) {
2021-04-10 18:18:38 +00:00
let res = CmdResult {
2022-11-30 09:37:59 +00:00
bin_path : String ::new ( ) ,
2021-11-09 17:37:38 +00:00
util_name : None ,
2021-04-10 18:18:38 +00:00
tmpd : None ,
code : None ,
success : true ,
stdout : " " . into ( ) ,
stderr : " " . into ( ) ,
} ;
res . no_stderr ( ) ;
res . no_stdout ( ) ;
}
#[ test ]
#[ should_panic ]
fn test_no_stderr_fail ( ) {
let res = CmdResult {
2022-11-30 09:37:59 +00:00
bin_path : String ::new ( ) ,
2021-11-09 17:37:38 +00:00
util_name : None ,
2021-04-10 18:18:38 +00:00
tmpd : None ,
code : None ,
success : true ,
stdout : " " . into ( ) ,
stderr : " asdfsadfa " . into ( ) ,
} ;
res . no_stderr ( ) ;
}
#[ test ]
#[ should_panic ]
fn test_no_stdout_fail ( ) {
let res = CmdResult {
2022-11-30 09:37:59 +00:00
bin_path : String ::new ( ) ,
2021-11-09 17:37:38 +00:00
util_name : None ,
2021-04-10 18:18:38 +00:00
tmpd : None ,
code : None ,
success : true ,
stdout : " asdfsadfa " . into ( ) ,
stderr : " " . into ( ) ,
} ;
res . no_stdout ( ) ;
}
#[ test ]
fn test_std_does_not_contain ( ) {
let res = CmdResult {
2022-11-30 09:37:59 +00:00
bin_path : String ::new ( ) ,
2021-11-09 17:37:38 +00:00
util_name : None ,
2021-04-10 18:18:38 +00:00
tmpd : None ,
code : None ,
success : true ,
stdout : " This is a likely error message \n " . into ( ) ,
stderr : " This is a likely error message \n " . into ( ) ,
} ;
res . stdout_does_not_contain ( " unlikely " ) ;
res . stderr_does_not_contain ( " unlikely " ) ;
}
#[ test ]
#[ should_panic ]
fn test_stdout_does_not_contain_fail ( ) {
let res = CmdResult {
2022-11-30 09:37:59 +00:00
bin_path : String ::new ( ) ,
2021-11-09 17:37:38 +00:00
util_name : None ,
2021-04-10 18:18:38 +00:00
tmpd : None ,
code : None ,
success : true ,
stdout : " This is a likely error message \n " . into ( ) ,
stderr : " " . into ( ) ,
} ;
res . stdout_does_not_contain ( " likely " ) ;
}
#[ test ]
#[ should_panic ]
fn test_stderr_does_not_contain_fail ( ) {
let res = CmdResult {
2022-11-30 09:37:59 +00:00
bin_path : String ::new ( ) ,
2021-11-09 17:37:38 +00:00
util_name : None ,
2021-04-10 18:18:38 +00:00
tmpd : None ,
code : None ,
success : true ,
stdout : " " . into ( ) ,
stderr : " This is a likely error message \n " . into ( ) ,
} ;
res . stderr_does_not_contain ( " likely " ) ;
}
#[ test ]
fn test_stdout_matches ( ) {
let res = CmdResult {
2022-11-30 09:37:59 +00:00
bin_path : String ::new ( ) ,
2021-11-09 17:37:38 +00:00
util_name : None ,
2021-04-10 18:18:38 +00:00
tmpd : None ,
code : None ,
success : true ,
stdout : " This is a likely error message \n " . into ( ) ,
stderr : " This is a likely error message \n " . into ( ) ,
} ;
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 ( ) {
let res = CmdResult {
2022-11-30 09:37:59 +00:00
bin_path : String ::new ( ) ,
2021-11-09 17:37:38 +00:00
util_name : None ,
2021-04-10 18:18:38 +00:00
tmpd : None ,
code : None ,
success : true ,
stdout : " This is a likely error message \n " . into ( ) ,
stderr : " This is a likely error message \n " . into ( ) ,
} ;
let negative = regex ::Regex ::new ( " .*unlikely.* " ) . unwrap ( ) ;
res . stdout_matches ( & negative ) ;
}
#[ test ]
#[ should_panic ]
fn test_stdout_not_matches_fail ( ) {
let res = CmdResult {
2022-11-30 09:37:59 +00:00
bin_path : String ::new ( ) ,
2021-11-09 17:37:38 +00:00
util_name : None ,
2021-04-10 18:18:38 +00:00
tmpd : None ,
code : None ,
success : true ,
stdout : " This is a likely error message \n " . into ( ) ,
stderr : " This is a likely error message \n " . into ( ) ,
} ;
let positive = regex ::Regex ::new ( " .*likely.* " ) . unwrap ( ) ;
res . stdout_does_not_match ( & positive ) ;
}
2021-04-17 12:01:52 +00:00
#[ test ]
fn test_normalized_newlines_stdout_is ( ) {
let res = CmdResult {
2022-11-30 09:37:59 +00:00
bin_path : String ::new ( ) ,
2021-11-09 17:37:38 +00:00
util_name : None ,
2021-04-17 12:01:52 +00:00
tmpd : None ,
code : None ,
success : true ,
stdout : " A \r \n B \n C " . into ( ) ,
stderr : " " . into ( ) ,
} ;
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 " ) ;
}
#[ test ]
#[ should_panic ]
fn test_normalized_newlines_stdout_is_fail ( ) {
let res = CmdResult {
2022-11-30 09:37:59 +00:00
bin_path : String ::new ( ) ,
2021-11-09 17:37:38 +00:00
util_name : None ,
2021-04-17 12:01:52 +00:00
tmpd : None ,
code : None ,
success : true ,
stdout : " A \r \n B \n C " . into ( ) ,
stderr : " " . into ( ) ,
} ;
res . normalized_newlines_stdout_is ( " A \r \n B \n C \n " ) ;
}
2021-07-07 12:30:38 +00:00
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 ( ) {
2022-05-10 11:46:22 +00:00
if ! is_ci ( ) {
// 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-20 22:23:23 +00:00
} else {
2022-05-10 11:46:22 +00:00
println! ( " TEST SKIPPED (cannot run inside CI) " ) ;
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 ( ) {
if trials < = 0 {
panic! ( " Assertion failed: child process is still alive. " )
}
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 " ) ;
}
Err ( error ) = > panic! (
" Assertion failed: Expected error with timeout but was: {} " ,
error
) ,
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 " ) ;
}
Err ( error ) = > panic! (
" Assertion failed: Expected error with timeout but was: {} " ,
error
) ,
Ok ( _ ) = > panic! ( " Assertion failed: Expected timeout of `try_kill`. " ) ,
}
}
#[ 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 ( ) ;
}
2021-04-10 18:18:38 +00:00
}