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-04-12 07:18:29 +00:00
//spell-checker: ignore (linux) rlimit prlimit coreutil ggroups
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 ;
2021-07-07 23:32:39 +00:00
#[ cfg(unix) ]
2021-07-07 12:30:38 +00:00
use std ::borrow ::Cow ;
2015-11-16 05:25:01 +00:00
use std ::env ;
2021-04-01 21:37:22 +00:00
#[ cfg(not(windows)) ]
use std ::ffi ::CString ;
use std ::ffi ::OsStr ;
2021-05-26 10:53:11 +00:00
use std ::fs ::{ self , hard_link , File , OpenOptions } ;
2022-09-08 17:17:40 +00:00
use std ::io ::{ 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 } ;
2018-03-12 08:20:58 +00:00
use std ::process ::{ Child , Command , Stdio } ;
2015-11-16 05:25:01 +00:00
use std ::rc ::Rc ;
2016-04-02 21:09:20 +00:00
use std ::thread ::sleep ;
use std ::time ::Duration ;
2021-01-19 07:15:53 +00:00
use tempfile ::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
/// Panics if not run
pub fn code ( & self ) -> i32 {
self . code . expect ( " Program must be run first " )
}
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
}
2021-03-31 09:25:23 +00:00
/// Returns the program's TempDir
/// 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
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
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
2021-02-23 09:21:01 +00:00
/// asserts that the command's exit code is the same as the given one
2022-01-30 13:59:31 +00:00
pub fn status_code ( & self , code : i32 ) -> & Self {
2021-05-05 20:59:40 +00:00
assert_eq! ( self . code , Some ( code ) ) ;
2021-03-31 09:25:23 +00:00
self
2021-02-23 09:21:01 +00:00
}
2016-07-17 09:20:33 +00:00
/// asserts that the command resulted in empty (zero-length) stderr stream output
/// generally, it's better to use stdout_only() instead,
/// 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
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
/// unless asserting there was neither stdout or stderr, stderr_only is usually a better choice
/// generally, it's better to use stderr_only() instead,
/// 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
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
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)
2016-07-17 09:20:33 +00:00
/// stdout_only is a better choice unless stderr may or will be non-empty
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.
2022-01-30 13:59:31 +00:00
pub fn stdout_is_any < T : AsRef < str > + std ::fmt ::Debug > ( & self , expected : & [ T ] ) -> & Self {
2021-06-11 20:02:10 +00:00
if ! expected . iter ( ) . any ( | msg | self . stdout_str ( ) = = msg . as_ref ( ) ) {
panic! (
" stdout was {} \n Expected any of {:#?} " ,
self . stdout_str ( ) ,
expected
2022-01-30 12:55:03 +00:00
) ;
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`.
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
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
2016-07-29 21:26:32 +00:00
/// like stdout_is(...), but expects the contents of the file at the provided relative path
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");
/// }
/// ```
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 )
}
2018-12-15 05:57:40 +00:00
/// like stdout_is_fixture(...), but replaces the data in fixture file based on values provided in template_vars
/// command output
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.
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
/// stderr_only is a better choice unless stdout may or will be non-empty
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
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
2021-04-10 20:19:53 +00:00
/// Like stdout_is_fixture, but for stderr
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
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
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 )
}
2016-07-29 21:26:32 +00:00
/// like stdout_only(...), but expects the contents of the file at the provided relative path
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
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
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
}
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."`
/// 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!
///
/// 2. the command resulted in empty (zero-length) stdout stream output
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 ( )
) )
}
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
}
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
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
}
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
}
2022-01-30 13:59:31 +00:00
pub fn stdout_matches ( & self , regex : & regex ::Regex ) -> & Self {
2021-04-22 20:37:44 +00:00
if ! regex . is_match ( self . stdout_str ( ) . trim ( ) ) {
2022-01-30 12:55:03 +00:00
panic! ( " Stdout does not match regex: \n {} " , self . stdout_str ( ) ) ;
2021-04-10 18:24:30 +00:00
}
self
}
2022-01-30 13:59:31 +00:00
pub fn stdout_does_not_match ( & self , regex : & regex ::Regex ) -> & Self {
2021-04-22 20:37:44 +00:00
if regex . is_match ( self . stdout_str ( ) . trim ( ) ) {
2022-01-30 12:55:03 +00:00
panic! ( " 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 )
. 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 )
. unwrap_or_else ( | e | panic! ( " Couldn't read {} : {} " , name , e ) ) ;
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 )
. 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 )
. unwrap_or_else ( | e | panic! ( " Couldn't write {} : {} " , name , e ) ) ;
}
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 ( ) )
2021-10-18 23:12:12 +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 )
2021-10-18 23:12:12 +00:00
. unwrap_or_else ( | e | panic! ( " Couldn't write(append) to {} : {} " , name , e ) ) ;
}
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 ( ) )
. unwrap_or_else ( | e | panic! ( " Couldn't write(truncate) {} : {} " , name , e ) ) ;
}
pub fn rename ( & self , source : & str , target : & str ) {
let source = self . plus ( source ) ;
let target = self . plus ( target ) ;
log_info ( " rename " , format! ( " {:?} {:?} " , source , target ) ) ;
std ::fs ::rename ( & source , & target )
. unwrap_or_else ( | e | panic! ( " Couldn't rename {:?} -> {:?} : {} " , source , target , e ) ) ;
}
pub fn remove ( & self , source : & str ) {
let source = self . plus ( source ) ;
log_info ( " remove " , format! ( " {:?} " , source ) ) ;
std ::fs ::remove_file ( & source )
. unwrap_or_else ( | e | panic! ( " Couldn't remove {:?} : {} " , source , e ) ) ;
}
pub fn copy ( & self , source : & str , target : & str ) {
let source = self . plus ( source ) ;
let target = self . plus ( target ) ;
log_info ( " copy " , format! ( " {:?} {:?} " , source , target ) ) ;
std ::fs ::copy ( & source , & target )
. unwrap_or_else ( | e | panic! ( " Couldn't copy {:?} -> {:?} : {} " , source , target , e ) ) ;
}
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 ( ) ) ,
2015-11-16 05:25:01 +00:00
Err ( _ ) = > " " . to_string ( ) ,
}
}
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
tmpd : Option < Rc < TempDir > > ,
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 ) > ,
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 ( ) ;
2021-11-09 05:16:06 +00:00
let util_name = util_name . as_ref ( ) . map ( | un | un . as_ref ( ) ) ;
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 ! [ ] ,
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
}
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
) ;
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
}
2016-07-29 21:26:32 +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 {
2022-01-30 10:09:50 +00:00
assert! ( self . bytes_into_stdin . is_some ( ) , " {} " , NO_STDIN_MEANINGLESS ) ;
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
}
2016-07-17 09:20:33 +00:00
/// Spawns the command, feeds the stdin if any, and returns the
/// child process immediately.
2016-04-02 14:41:59 +00:00
pub fn run_no_wait ( & mut self ) -> Child {
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 ) ;
2021-04-09 09:08:31 +00:00
let mut child = self
2020-01-28 05:14:11 +00:00
. raw
2021-05-29 12:32:35 +00:00
. stdin ( self . stdin . take ( ) . unwrap_or_else ( Stdio ::piped ) )
. stdout ( self . stdout . take ( ) . unwrap_or_else ( Stdio ::piped ) )
. stderr ( self . stderr . take ( ) . unwrap_or_else ( Stdio ::piped ) )
2016-04-02 14:23:26 +00:00
. spawn ( )
. unwrap ( ) ;
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 ( ) ;
}
2021-04-22 20:37:44 +00:00
if let Some ( ref input ) = self . bytes_into_stdin {
2022-09-08 17:17:40 +00:00
let child_stdin = child
2018-03-12 08:20:58 +00:00
. stdin
2016-04-02 14:23:26 +00:00
. take ( )
2022-09-08 17:17:40 +00:00
. unwrap_or_else ( | | panic! ( " Could not take child process stdin " ) ) ;
let mut writer = BufWriter ::new ( child_stdin ) ;
let result = writer . write_all ( input ) ;
2021-04-09 09:08:31 +00:00
if ! self . ignore_stdin_write_error {
2022-09-08 17:17:40 +00:00
if let Err ( e ) = result {
2022-01-30 12:55:03 +00:00
panic! ( " failed to write to stdin of child: {} " , e ) ;
2021-04-09 09:08:31 +00:00
}
}
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 {
let prog = self . run_no_wait ( ) . wait_with_output ( ) . unwrap ( ) ;
2016-04-02 14:23:26 +00:00
2015-11-16 05:25:01 +00:00
CmdResult {
2021-11-09 17:37:38 +00:00
bin_path : self . bin_path . clone ( ) ,
util_name : self . util_name . clone ( ) ,
2016-07-29 21:26:32 +00:00
tmpd : self . tmpd . clone ( ) ,
2021-02-23 09:21:01 +00:00
code : prog . status . code ( ) ,
2015-11-16 05:25:01 +00:00
success : prog . status . success ( ) ,
2021-05-26 20:15:28 +00:00
stdout : prog . stdout ,
2021-05-26 20:20:16 +00:00
stderr : prog . stderr ,
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.
/// It is recommended that, instead of this, you use a combination of pipe_in()
/// 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.
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.
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 ( ) ;
format! ( " {} / {} " , tmpdir_path . to_str ( ) . unwrap ( ) , file_rel_path )
}
2015-11-16 05:25:01 +00:00
}
2021-10-18 23:12:12 +00:00
/// Wrapper for `child.stdout.read_exact()`.
/// Careful, this blocks indefinitely if `size` bytes is never reached.
2016-04-02 21:09:20 +00:00
pub fn read_size ( child : & mut Child , size : usize ) -> String {
2022-01-25 02:18:59 +00:00
String ::from_utf8 ( read_size_bytes ( child , size ) ) . unwrap ( )
}
/// Read the specified number of bytes from the stdout of the child process.
///
/// Careful, this blocks indefinitely if `size` bytes is never reached.
pub fn read_size_bytes ( child : & mut Child , size : usize ) -> Vec < u8 > {
2016-04-02 21:09:20 +00:00
let mut output = Vec ::new ( ) ;
output . resize ( size , 0 ) ;
2016-06-17 14:45:35 +00:00
sleep ( Duration ::from_secs ( 1 ) ) ;
2018-03-12 08:20:58 +00:00
child
. stdout
. as_mut ( )
. unwrap ( )
2021-03-31 10:54:16 +00:00
. read_exact ( output . as_mut_slice ( ) )
2018-03-12 08:20:58 +00:00
. unwrap ( ) ;
2022-01-25 02:18:59 +00:00
output
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 | {
println! ( " {} : {} , using \" nobody \" instead " , UUTILS_WARNING , e ) ;
" 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 ) ;
log_info ( " run " , format! ( " {} --version " , util_name ) ) ;
let version_check = match Command ::new ( util_name . as_ref ( ) )
. env ( " LC_ALL " , " C " )
. arg ( " --version " )
. output ( )
{
Ok ( s ) = > s ,
2022-01-16 14:57:33 +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 (
| | Err ( format! ( " {} : unexpected output format for reference coreutil: ' {} --version' " , UUTILS_WARNING , util_name ) ) ,
| s | {
if s . contains ( & format! ( " (GNU coreutils) {} " , version_expected ) ) {
2022-01-16 14:57:33 +00:00
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 {
Ok ( format! ( " {} : version for the reference coreutil ' {} ' is higher than expected; expected: {} , found: {} " , UUTILS_INFO , util_name , version_expected , version_found ) )
} else {
Err ( format! ( " {} : version for the reference coreutil ' {} ' does not match; expected: {} , found: {} " , UUTILS_WARNING , util_name , version_expected , version_found ) ) }
} else {
Err ( format! ( " {} : no coreutils version string found for reference coreutils ' {} --version' " , UUTILS_WARNING , util_name ) )
}
} ,
)
}
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 ( ) ) ,
Err ( e ) = > Err ( format! ( " {} : {} " , UUTILS_WARNING , e ) ) ,
}
} else {
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 {
2021-11-09 17:37:38 +00:00
bin_path : " " . into ( ) ,
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 {
2021-11-09 17:37:38 +00:00
bin_path : " " . into ( ) ,
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 {
2021-11-09 17:37:38 +00:00
bin_path : " " . into ( ) ,
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 {
2021-11-09 17:37:38 +00:00
bin_path : " " . into ( ) ,
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 {
2021-11-09 17:37:38 +00:00
bin_path : " " . into ( ) ,
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 {
2021-11-09 17:37:38 +00:00
bin_path : " " . into ( ) ,
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 {
2021-11-09 17:37:38 +00:00
bin_path : " " . into ( ) ,
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 {
2021-11-09 17:37:38 +00:00
bin_path : " " . into ( ) ,
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 {
2021-11-09 17:37:38 +00:00
bin_path : " " . into ( ) ,
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 {
2021-11-09 17:37:38 +00:00
bin_path : " " . into ( ) ,
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 {
2021-11-09 17:37:38 +00:00
bin_path : " " . into ( ) ,
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 {
2021-11-09 17:37:38 +00:00
bin_path : " " . into ( ) ,
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 {
2021-11-09 17:37:38 +00:00
bin_path : " " . into ( ) ,
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 {
2021-11-09 17:37:38 +00:00
bin_path : " " . into ( ) ,
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 {
2021-11-09 17:37:38 +00:00
bin_path : " " . into ( ) ,
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 {
2021-11-09 17:37:38 +00:00
bin_path : " " . into ( ) ,
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 {
2021-11-09 17:37:38 +00:00
bin_path : " " . into ( ) ,
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 ( ) ;
}
}
2021-04-10 18:18:38 +00:00
}