mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
tests/util: Refactor UCommand and TestScenario.
Summary of changes in UCommand: * Extend UCommand by builder methods and simplify methods in TestScenario * Simplify code structures where possible. Add documentation. * Store bin_path as PathBuf and util_name as String in all structs * Remove UCommand::util and make bin_path, temp_dir private * Rename UCommand::with_limit -> UCommand::limit Summary of changes in TestScenario: * Rename some parameters in TestScenario methods to be more descriptive * Remove ucmd_keepenv, cmd_keepenv from TestScenario. Use UCommand::keep_env instead.
This commit is contained in:
parent
fdf0f96a01
commit
1c230fd779
12 changed files with 276 additions and 185 deletions
|
@ -103,7 +103,7 @@ fn test_closes_file_descriptors() {
|
|||
"alpha.txt",
|
||||
"alpha.txt",
|
||||
])
|
||||
.with_limit(Resource::NOFILE, 9, 9)
|
||||
.limit(Resource::NOFILE, 9, 9)
|
||||
.succeeds();
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ fn run_single_test(test: &TestCase, at: &AtPath, mut ucmd: UCommand) {
|
|||
let r = ucmd.run();
|
||||
if !r.succeeded() {
|
||||
println!("{}", r.stderr_str());
|
||||
panic!("{}: failed", ucmd);
|
||||
panic!("{ucmd}: failed");
|
||||
}
|
||||
|
||||
let perms = at.metadata(TEST_FILE).permissions().mode();
|
||||
|
|
|
@ -396,7 +396,7 @@ fn test_chown_only_user_id() {
|
|||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
||||
let result = scene.cmd_keepenv("id").arg("-u").run();
|
||||
let result = scene.cmd("id").keep_env().arg("-u").run();
|
||||
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
||||
return;
|
||||
}
|
||||
|
@ -430,7 +430,7 @@ fn test_chown_fail_id() {
|
|||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
||||
let result = scene.cmd_keepenv("id").arg("-u").run();
|
||||
let result = scene.cmd("id").keep_env().arg("-u").run();
|
||||
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
||||
return;
|
||||
}
|
||||
|
@ -487,7 +487,7 @@ fn test_chown_only_group_id() {
|
|||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
||||
let result = scene.cmd_keepenv("id").arg("-g").run();
|
||||
let result = scene.cmd("id").keep_env().arg("-g").run();
|
||||
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
||||
return;
|
||||
}
|
||||
|
@ -551,14 +551,14 @@ fn test_chown_owner_group_id() {
|
|||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
||||
let result = scene.cmd_keepenv("id").arg("-u").run();
|
||||
let result = scene.cmd("id").keep_env().arg("-u").run();
|
||||
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
||||
return;
|
||||
}
|
||||
let user_id = String::from(result.stdout_str().trim());
|
||||
assert!(!user_id.is_empty());
|
||||
|
||||
let result = scene.cmd_keepenv("id").arg("-g").run();
|
||||
let result = scene.cmd("id").keep_env().arg("-g").run();
|
||||
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
||||
return;
|
||||
}
|
||||
|
@ -612,14 +612,14 @@ fn test_chown_owner_group_mix() {
|
|||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
||||
let result = scene.cmd_keepenv("id").arg("-u").run();
|
||||
let result = scene.cmd("id").keep_env().arg("-u").run();
|
||||
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
||||
return;
|
||||
}
|
||||
let user_id = String::from(result.stdout_str().trim());
|
||||
assert!(!user_id.is_empty());
|
||||
|
||||
let result = scene.cmd_keepenv("id").arg("-gn").run();
|
||||
let result = scene.cmd("id").keep_env().arg("-gn").run();
|
||||
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1545,7 +1545,7 @@ fn test_closes_file_descriptors() {
|
|||
.arg("--reflink=auto")
|
||||
.arg("dir_with_10_files/")
|
||||
.arg("dir_with_10_files_new/")
|
||||
.with_limit(Resource::NOFILE, limit_fd, limit_fd)
|
||||
.limit(Resource::NOFILE, limit_fd, limit_fd)
|
||||
.succeeds();
|
||||
}
|
||||
|
||||
|
@ -1692,7 +1692,8 @@ fn test_cp_reflink_always_override() {
|
|||
.succeeds();
|
||||
|
||||
if !scene
|
||||
.cmd_keepenv("env")
|
||||
.cmd("env")
|
||||
.keep_env()
|
||||
.args(&["mkfs.btrfs", "--rootdir", ROOTDIR, DISK])
|
||||
.run()
|
||||
.succeeded()
|
||||
|
@ -1704,7 +1705,8 @@ fn test_cp_reflink_always_override() {
|
|||
scene.fixtures.mkdir(MOUNTPOINT);
|
||||
|
||||
let mount = scene
|
||||
.cmd_keepenv("sudo")
|
||||
.cmd("sudo")
|
||||
.keep_env()
|
||||
.args(&["-E", "--non-interactive", "mount", DISK, MOUNTPOINT])
|
||||
.run();
|
||||
|
||||
|
@ -1730,7 +1732,8 @@ fn test_cp_reflink_always_override() {
|
|||
.succeeds();
|
||||
|
||||
scene
|
||||
.cmd_keepenv("sudo")
|
||||
.cmd("sudo")
|
||||
.keep_env()
|
||||
.args(&["-E", "--non-interactive", "umount", MOUNTPOINT])
|
||||
.succeeds();
|
||||
}
|
||||
|
|
|
@ -156,7 +156,8 @@ fn test_unset_variable() {
|
|||
// This test depends on the HOME variable being pre-defined by the
|
||||
// default shell
|
||||
let out = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.arg("-u")
|
||||
.arg("HOME")
|
||||
.succeeds()
|
||||
|
|
|
@ -425,7 +425,8 @@ fn test_mktemp_tmpdir_one_arg() {
|
|||
let scene = TestScenario::new(util_name!());
|
||||
|
||||
let result = scene
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.arg("--tmpdir")
|
||||
.arg("apt-key-gpghome.XXXXXXXXXX")
|
||||
.succeeds();
|
||||
|
@ -438,7 +439,8 @@ fn test_mktemp_directory_tmpdir() {
|
|||
let scene = TestScenario::new(util_name!());
|
||||
|
||||
let result = scene
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.arg("--directory")
|
||||
.arg("--tmpdir")
|
||||
.arg("apt-key-gpghome.XXXXXXXXXX")
|
||||
|
|
|
@ -20,7 +20,8 @@ fn test_nproc_all_omp() {
|
|||
assert!(nproc > 0);
|
||||
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.env("OMP_NUM_THREADS", "60")
|
||||
.succeeds();
|
||||
|
||||
|
@ -28,7 +29,8 @@ fn test_nproc_all_omp() {
|
|||
assert_eq!(nproc_omp, 60);
|
||||
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.env("OMP_NUM_THREADS", "1") // Has no effect
|
||||
.arg("--all")
|
||||
.succeeds();
|
||||
|
@ -37,7 +39,8 @@ fn test_nproc_all_omp() {
|
|||
|
||||
// If the parsing fails, returns the number of CPU
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.env("OMP_NUM_THREADS", "incorrectnumber") // returns the number CPU
|
||||
.succeeds();
|
||||
let nproc_omp: u8 = result.stdout_str().trim().parse().unwrap();
|
||||
|
@ -51,7 +54,8 @@ fn test_nproc_ignore() {
|
|||
if nproc_total > 1 {
|
||||
// Ignore all CPU but one
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.arg("--ignore")
|
||||
.arg((nproc_total - 1).to_string())
|
||||
.succeeds();
|
||||
|
@ -59,7 +63,8 @@ fn test_nproc_ignore() {
|
|||
assert_eq!(nproc, 1);
|
||||
// Ignore all CPU but one with a string
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.arg("--ignore= 1")
|
||||
.succeeds();
|
||||
let nproc: u8 = result.stdout_str().trim().parse().unwrap();
|
||||
|
@ -70,7 +75,8 @@ fn test_nproc_ignore() {
|
|||
#[test]
|
||||
fn test_nproc_ignore_all_omp() {
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.env("OMP_NUM_THREADS", "42")
|
||||
.arg("--ignore=40")
|
||||
.succeeds();
|
||||
|
@ -81,7 +87,8 @@ fn test_nproc_ignore_all_omp() {
|
|||
#[test]
|
||||
fn test_nproc_omp_limit() {
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.env("OMP_NUM_THREADS", "42")
|
||||
.env("OMP_THREAD_LIMIT", "0")
|
||||
.succeeds();
|
||||
|
@ -89,7 +96,8 @@ fn test_nproc_omp_limit() {
|
|||
assert_eq!(nproc, 42);
|
||||
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.env("OMP_NUM_THREADS", "42")
|
||||
.env("OMP_THREAD_LIMIT", "2")
|
||||
.succeeds();
|
||||
|
@ -97,7 +105,8 @@ fn test_nproc_omp_limit() {
|
|||
assert_eq!(nproc, 2);
|
||||
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.env("OMP_NUM_THREADS", "42")
|
||||
.env("OMP_THREAD_LIMIT", "2bad")
|
||||
.succeeds();
|
||||
|
@ -109,14 +118,16 @@ fn test_nproc_omp_limit() {
|
|||
assert!(nproc_system > 0);
|
||||
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.env("OMP_THREAD_LIMIT", "1")
|
||||
.succeeds();
|
||||
let nproc: u8 = result.stdout_str().trim().parse().unwrap();
|
||||
assert_eq!(nproc, 1);
|
||||
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.env("OMP_NUM_THREADS", "0")
|
||||
.env("OMP_THREAD_LIMIT", "")
|
||||
.succeeds();
|
||||
|
@ -124,7 +135,8 @@ fn test_nproc_omp_limit() {
|
|||
assert_eq!(nproc, nproc_system);
|
||||
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.env("OMP_NUM_THREADS", "")
|
||||
.env("OMP_THREAD_LIMIT", "")
|
||||
.succeeds();
|
||||
|
@ -132,7 +144,8 @@ fn test_nproc_omp_limit() {
|
|||
assert_eq!(nproc, nproc_system);
|
||||
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.env("OMP_NUM_THREADS", "2,2,1")
|
||||
.env("OMP_THREAD_LIMIT", "")
|
||||
.succeeds();
|
||||
|
@ -140,7 +153,8 @@ fn test_nproc_omp_limit() {
|
|||
assert_eq!(2, nproc);
|
||||
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.env("OMP_NUM_THREADS", "2,ignored")
|
||||
.env("OMP_THREAD_LIMIT", "")
|
||||
.succeeds();
|
||||
|
@ -148,7 +162,8 @@ fn test_nproc_omp_limit() {
|
|||
assert_eq!(2, nproc);
|
||||
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.env("OMP_NUM_THREADS", "2,2,1")
|
||||
.env("OMP_THREAD_LIMIT", "0")
|
||||
.succeeds();
|
||||
|
@ -156,7 +171,8 @@ fn test_nproc_omp_limit() {
|
|||
assert_eq!(2, nproc);
|
||||
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.env("OMP_NUM_THREADS", "2,2,1")
|
||||
.env("OMP_THREAD_LIMIT", "1bad")
|
||||
.succeeds();
|
||||
|
@ -164,7 +180,8 @@ fn test_nproc_omp_limit() {
|
|||
assert_eq!(2, nproc);
|
||||
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.env("OMP_NUM_THREADS", "29,2,1")
|
||||
.env("OMP_THREAD_LIMIT", "1bad")
|
||||
.succeeds();
|
||||
|
|
|
@ -8,7 +8,8 @@ fn test_get_all() {
|
|||
assert_eq!(env::var(key), Ok("VALUE".to_string()));
|
||||
|
||||
TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.succeeds()
|
||||
.stdout_contains("HOME=")
|
||||
.stdout_contains("KEY=VALUE");
|
||||
|
@ -21,7 +22,8 @@ fn test_get_var() {
|
|||
assert_eq!(env::var(key), Ok("VALUE".to_string()));
|
||||
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.arg("KEY")
|
||||
.succeeds();
|
||||
|
||||
|
|
|
@ -31,7 +31,8 @@ fn test_buffer_sizes() {
|
|||
let buffer_sizes = ["0", "50K", "50k", "1M", "100M"];
|
||||
for buffer_size in &buffer_sizes {
|
||||
TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.arg("-n")
|
||||
.arg("-S")
|
||||
.arg(buffer_size)
|
||||
|
@ -44,7 +45,8 @@ fn test_buffer_sizes() {
|
|||
let buffer_sizes = ["1000G", "10T"];
|
||||
for buffer_size in &buffer_sizes {
|
||||
TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.arg("-n")
|
||||
.arg("-S")
|
||||
.arg(buffer_size)
|
||||
|
@ -918,7 +920,8 @@ fn test_compress_merge() {
|
|||
fn test_compress_fail() {
|
||||
#[cfg(not(windows))]
|
||||
TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.args(&[
|
||||
"ext_sort.txt",
|
||||
"-n",
|
||||
|
@ -934,7 +937,8 @@ fn test_compress_fail() {
|
|||
// So, don't check the output
|
||||
#[cfg(windows)]
|
||||
TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.args(&[
|
||||
"ext_sort.txt",
|
||||
"-n",
|
||||
|
@ -949,7 +953,8 @@ fn test_compress_fail() {
|
|||
#[test]
|
||||
fn test_merge_batches() {
|
||||
TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.timeout(Duration::from_secs(120))
|
||||
.args(&["ext_sort.txt", "-n", "-S", "150b"])
|
||||
.succeeds()
|
||||
|
@ -959,7 +964,8 @@ fn test_merge_batches() {
|
|||
#[test]
|
||||
fn test_merge_batch_size() {
|
||||
TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.arg("--batch-size=2")
|
||||
.arg("-m")
|
||||
.arg("--unique")
|
||||
|
@ -1067,7 +1073,8 @@ fn test_output_is_input() {
|
|||
at.touch("file");
|
||||
at.append("file", input);
|
||||
scene
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.args(&["-m", "-u", "-o", "file", "file", "file", "file"])
|
||||
.succeeds();
|
||||
assert_eq!(at.read("file"), input);
|
||||
|
|
|
@ -10,7 +10,8 @@ fn test_invalid_arg() {
|
|||
#[test]
|
||||
fn test_uptime() {
|
||||
TestScenario::new(util_name!())
|
||||
.ucmd_keepenv()
|
||||
.ucmd()
|
||||
.keep_env()
|
||||
.succeeds()
|
||||
.stdout_contains("load average:")
|
||||
.stdout_contains(" up ");
|
||||
|
|
|
@ -22,7 +22,8 @@ fn test_users_check_name() {
|
|||
// note: clippy::needless_borrow *false positive*
|
||||
#[allow(clippy::needless_borrow)]
|
||||
let expected = TestScenario::new(&util_name)
|
||||
.cmd_keepenv(util_name)
|
||||
.cmd(util_name)
|
||||
.keep_env()
|
||||
.env("LC_ALL", "C")
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// * For the full copyright and license information, please view the LICENSE
|
||||
// * file that was distributed with this source code.
|
||||
|
||||
//spell-checker: ignore (linux) rlimit prlimit coreutil ggroups uchild uncaptured scmd SHLVL
|
||||
//spell-checker: ignore (linux) rlimit prlimit coreutil ggroups uchild uncaptured scmd SHLVL canonicalized
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
|
@ -13,6 +13,7 @@ use rlimit::prlimit;
|
|||
use rstest::rstest;
|
||||
#[cfg(unix)]
|
||||
use std::borrow::Cow;
|
||||
use std::collections::VecDeque;
|
||||
#[cfg(not(windows))]
|
||||
use std::ffi::CString;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
@ -65,7 +66,7 @@ fn read_scenario_fixture<S: AsRef<OsStr>>(tmpd: &Option<Rc<TempDir>>, file_rel_p
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct CmdResult {
|
||||
/// bin_path provided by `TestScenario` or `UCommand`
|
||||
bin_path: String,
|
||||
bin_path: PathBuf,
|
||||
/// util_name provided by `TestScenario` or `UCommand`
|
||||
util_name: Option<String>,
|
||||
//tmpd is used for convenience functions for asserts against fixtures
|
||||
|
@ -79,21 +80,23 @@ pub struct CmdResult {
|
|||
}
|
||||
|
||||
impl CmdResult {
|
||||
pub fn new<T, U>(
|
||||
bin_path: String,
|
||||
util_name: Option<String>,
|
||||
pub fn new<S, T, U, V>(
|
||||
bin_path: S,
|
||||
util_name: Option<T>,
|
||||
tmpd: Option<Rc<TempDir>>,
|
||||
exit_status: Option<ExitStatus>,
|
||||
stdout: T,
|
||||
stderr: U,
|
||||
stdout: U,
|
||||
stderr: V,
|
||||
) -> Self
|
||||
where
|
||||
T: Into<Vec<u8>>,
|
||||
S: Into<PathBuf>,
|
||||
T: AsRef<str>,
|
||||
U: Into<Vec<u8>>,
|
||||
V: Into<Vec<u8>>,
|
||||
{
|
||||
Self {
|
||||
bin_path,
|
||||
util_name,
|
||||
bin_path: bin_path.into(),
|
||||
util_name: util_name.map(|s| s.as_ref().into()),
|
||||
tmpd,
|
||||
exit_status,
|
||||
stdout: stdout.into(),
|
||||
|
@ -635,7 +638,7 @@ impl CmdResult {
|
|||
self.stderr_only(format!(
|
||||
"{0}: {2}\nTry '{1} {0} --help' for more information.\n",
|
||||
self.util_name.as_ref().unwrap(), // This shouldn't be called using a normal command
|
||||
self.bin_path,
|
||||
self.bin_path.display(),
|
||||
msg.as_ref()
|
||||
))
|
||||
}
|
||||
|
@ -1094,18 +1097,21 @@ pub struct TestScenario {
|
|||
}
|
||||
|
||||
impl TestScenario {
|
||||
pub fn new(util_name: &str) -> Self {
|
||||
pub fn new<T>(util_name: T) -> Self
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
let tmpd = Rc::new(TempDir::new().unwrap());
|
||||
let ts = Self {
|
||||
bin_path: PathBuf::from(TESTS_BINARY),
|
||||
util_name: String::from(util_name),
|
||||
util_name: util_name.as_ref().into(),
|
||||
fixtures: AtPath::new(tmpd.as_ref().path()),
|
||||
tmpd,
|
||||
};
|
||||
let mut fixture_path_builder = env::current_dir().unwrap();
|
||||
fixture_path_builder.push(TESTS_DIR);
|
||||
fixture_path_builder.push(FIXTURES_DIR);
|
||||
fixture_path_builder.push(util_name);
|
||||
fixture_path_builder.push(util_name.as_ref());
|
||||
if let Ok(m) = fs::metadata(&fixture_path_builder) {
|
||||
if m.is_dir() {
|
||||
recursive_copy(&fixture_path_builder, &ts.fixtures.subdir).unwrap();
|
||||
|
@ -1117,68 +1123,57 @@ impl TestScenario {
|
|||
/// Returns builder for invoking the target uutils binary. Paths given are
|
||||
/// treated relative to the environment's unique temporary test directory.
|
||||
pub fn ucmd(&self) -> UCommand {
|
||||
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 {
|
||||
UCommand::new_from_tmp(bin, Some(util_name), self.tmpd.clone(), env_clear)
|
||||
UCommand::from_test_scenario(self)
|
||||
}
|
||||
|
||||
/// Returns builder for invoking any system command. Paths given are treated
|
||||
/// relative to the environment's unique temporary test directory.
|
||||
pub fn cmd<S: AsRef<OsStr>>(&self, bin: S) -> UCommand {
|
||||
UCommand::new_from_tmp::<S, S>(bin, None, self.tmpd.clone(), true)
|
||||
pub fn cmd<S: Into<PathBuf>>(&self, bin_path: S) -> UCommand {
|
||||
let mut command = UCommand::new();
|
||||
command.bin_path(bin_path);
|
||||
command.temp_dir(self.tmpd.clone());
|
||||
command
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
self.composite_cmd(&self.bin_path, bin, true)
|
||||
}
|
||||
|
||||
// different names are used rather than an argument
|
||||
// because the need to keep the environment is exceedingly rare.
|
||||
pub fn ucmd_keepenv(&self) -> UCommand {
|
||||
self.composite_cmd(&self.bin_path, &self.util_name, false)
|
||||
}
|
||||
|
||||
/// 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.)
|
||||
pub fn cmd_keepenv<S: AsRef<OsStr>>(&self, bin: S) -> UCommand {
|
||||
UCommand::new_from_tmp::<S, S>(bin, None, self.tmpd.clone(), false)
|
||||
pub fn ccmd<S: AsRef<str>>(&self, util_name: S) -> UCommand {
|
||||
UCommand::with_util(util_name, self.tmpd.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// A `UCommand` is a wrapper around an individual Command that provides several additional features
|
||||
/// A `UCommand` is a builder wrapping an individual Command that provides several additional features:
|
||||
/// 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.
|
||||
#[derive(Debug)]
|
||||
/// 3. it provides convenience construction methods to set the Command uutils utility and temporary directory.
|
||||
///
|
||||
/// Per default `UCommand` runs a command given as an argument in a shell, platform independently.
|
||||
/// It does so with safety in mind, so the working directory is set to an individual temporary
|
||||
/// directory and the environment variables are cleared per default.
|
||||
///
|
||||
/// The default behavior can be changed with builder methods:
|
||||
/// * [`UCommand::with_util`]: Run `coreutils UTIL_NAME` instead of the shell
|
||||
/// * [`UCommand::from_test_scenario`]: Run `coreutils UTIL_NAME` instead of the shell in the
|
||||
/// temporary directory of the [`TestScenario`]
|
||||
/// * [`UCommand::current_dir`]: Sets the working directory
|
||||
/// * [`UCommand::keep_env`]: Keep environment variables instead of clearing them
|
||||
/// * ...
|
||||
#[derive(Debug, Default)]
|
||||
pub struct UCommand {
|
||||
args: Vec<OsString>,
|
||||
args: VecDeque<OsString>,
|
||||
env_vars: Vec<(OsString, OsString)>,
|
||||
current_dir: Option<PathBuf>,
|
||||
env_clear: bool,
|
||||
bin_path: Option<PathBuf>,
|
||||
util_name: Option<OsString>,
|
||||
util_name: Option<String>,
|
||||
has_run: bool,
|
||||
ignore_stdin_write_error: bool,
|
||||
stdin: Option<Stdio>,
|
||||
stdout: Option<Stdio>,
|
||||
stderr: Option<Stdio>,
|
||||
bytes_into_stdin: Option<Vec<u8>>,
|
||||
// TODO: Why android?
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
limits: Vec<(rlimit::Resource, u64, u64)>,
|
||||
stderr_to_stdout: bool,
|
||||
|
@ -1187,48 +1182,79 @@ pub struct UCommand {
|
|||
}
|
||||
|
||||
impl UCommand {
|
||||
/// Create a new plain [`UCommand`].
|
||||
///
|
||||
/// Executes a command that must be given as argument (for example with [`UCommand::arg`] in a
|
||||
/// shell (`sh -c` on unix platforms or `cmd /C` on windows).
|
||||
///
|
||||
/// Per default the environment is cleared and the working directory is set to an individual
|
||||
/// temporary directory for safety purposes.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
tmpd: None,
|
||||
has_run: false,
|
||||
bin_path: None,
|
||||
current_dir: None,
|
||||
args: vec![],
|
||||
env_clear: true,
|
||||
env_vars: vec![],
|
||||
util_name: None,
|
||||
ignore_stdin_write_error: false,
|
||||
bytes_into_stdin: None,
|
||||
stdin: None,
|
||||
stdout: None,
|
||||
stderr: None,
|
||||
// TODO: Why android?
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
limits: vec![],
|
||||
stderr_to_stdout: false,
|
||||
timeout: Some(Duration::from_secs(30)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_from_tmp<T: AsRef<OsStr>, S: AsRef<OsStr>>(
|
||||
bin_path: T,
|
||||
util_name: Option<S>,
|
||||
tmpd: Rc<TempDir>,
|
||||
env_clear: bool,
|
||||
) -> Self {
|
||||
let mut ucmd: Self = Self::new();
|
||||
ucmd.bin_path = Some(PathBuf::from(bin_path.as_ref()));
|
||||
ucmd.util_name = util_name.map(|s| s.as_ref().to_os_string());
|
||||
ucmd.tmpd = Some(tmpd);
|
||||
ucmd.env_clear = env_clear;
|
||||
/// Create a [`UCommand`] for a specific uutils utility.
|
||||
///
|
||||
/// Sets the temporary directory to `tmpd` and the execution binary to the path where
|
||||
/// `coreutils` is found.
|
||||
pub fn with_util<T>(util_name: T, tmpd: Rc<TempDir>) -> Self
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
let mut ucmd = Self::new();
|
||||
ucmd.util_name = Some(util_name.as_ref().into());
|
||||
ucmd.bin_path(TESTS_BINARY).temp_dir(tmpd);
|
||||
ucmd
|
||||
}
|
||||
|
||||
/// Create a [`UCommand`] from a [`TestScenario`].
|
||||
///
|
||||
/// The temporary directory and uutils utility are inherited from the [`TestScenario`] and the
|
||||
/// execution binary is set to `coreutils`.
|
||||
pub fn from_test_scenario(scene: &TestScenario) -> Self {
|
||||
Self::with_util(&scene.util_name, scene.tmpd.clone())
|
||||
}
|
||||
|
||||
/// Set the execution binary.
|
||||
///
|
||||
/// Make sure the binary found at this path is executable. It's safest to provide the
|
||||
/// canonicalized path instead of just the name of the executable, since path resolution is not
|
||||
/// guaranteed to work on all platforms.
|
||||
fn bin_path<T>(&mut self, bin_path: T) -> &mut Self
|
||||
where
|
||||
T: Into<PathBuf>,
|
||||
{
|
||||
self.bin_path = Some(bin_path.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the temporary directory.
|
||||
///
|
||||
/// Per default an individual temporary directory is created for every [`UCommand`]. If not
|
||||
/// specified otherwise with [`UCommand::current_dir`] the working directory is set to this
|
||||
/// temporary directory.
|
||||
fn temp_dir(&mut self, temp_dir: Rc<TempDir>) -> &mut Self {
|
||||
self.tmpd = Some(temp_dir);
|
||||
self
|
||||
}
|
||||
|
||||
/// Keep the environment variables instead of clearing them before running the command.
|
||||
pub fn keep_env(&mut self) -> &mut Self {
|
||||
self.env_clear = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the working directory for this [`UCommand`]
|
||||
///
|
||||
/// Per default the working directory is set to the [`UCommands`] temporary directory.
|
||||
pub fn current_dir<T>(&mut self, current_dir: T) -> &mut Self
|
||||
where
|
||||
T: AsRef<Path>,
|
||||
T: Into<PathBuf>,
|
||||
{
|
||||
self.current_dir = Some(current_dir.as_ref().into());
|
||||
self.current_dir = Some(current_dir.into());
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -1255,7 +1281,7 @@ impl UCommand {
|
|||
/// Add a parameter to the invocation. Path arguments are treated relative
|
||||
/// to the test environment directory.
|
||||
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
|
||||
self.args.push(arg.as_ref().into());
|
||||
self.args.push_back(arg.as_ref().into());
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -1302,9 +1328,8 @@ impl UCommand {
|
|||
self
|
||||
}
|
||||
|
||||
// TODO: Why android?
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub fn with_limit(
|
||||
pub fn limit(
|
||||
&mut self,
|
||||
resource: rlimit::Resource,
|
||||
soft_limit: u64,
|
||||
|
@ -1326,25 +1351,46 @@ impl UCommand {
|
|||
self
|
||||
}
|
||||
|
||||
// TODO: make public?
|
||||
/// Build the `std::process::Command` and apply the defaults on fields which were not specified
|
||||
/// by the user.
|
||||
///
|
||||
/// These __defaults__ are:
|
||||
/// * `bin_path`: Depending on the platform and os, the native shell (unix -> `/bin/sh` etc.).
|
||||
/// This default also requires to set the first argument to `-c` on unix (`/C` on windows) if
|
||||
/// this argument wasn't specified explicitly by the user.
|
||||
/// * `util_name`: `None`. If neither `bin_path` nor `util_name` were given the arguments are
|
||||
/// run in a shell (See `bin_path` above).
|
||||
/// * `temp_dir`: If `current_dir` was not set, a new temporary directory will be created in
|
||||
/// which this command will be run and `current_dir` will be set to this `temp_dir`.
|
||||
/// * `current_dir`: The temporary directory given by `temp_dir`.
|
||||
/// * `timeout`: `30 seconds`
|
||||
/// * `env_clear`: `true`. (Almost) all environment variables will be cleared.
|
||||
/// * `stdin`: `Stdio::null()`
|
||||
/// * `ignore_stdin_write_error`: `false`
|
||||
/// * `stdout`, `stderr`: If not specified the output will be captured with [`CapturedOutput`]
|
||||
/// * `stderr_to_stdout`: `false`
|
||||
/// * `bytes_into_stdin`: `None`
|
||||
/// * `limits`: `None`.
|
||||
fn build(&mut self) -> (Command, Option<CapturedOutput>, Option<CapturedOutput>) {
|
||||
if self.bin_path.is_some() {
|
||||
if let Some(util_name) = &self.util_name {
|
||||
self.args.insert(0, OsString::from(util_name));
|
||||
self.args.push_front(util_name.into());
|
||||
}
|
||||
} else if let Some(util_name) = &self.util_name {
|
||||
self.bin_path = Some(PathBuf::from(TESTS_BINARY));
|
||||
self.args.insert(0, OsString::from(util_name));
|
||||
self.args.push_front(util_name.into());
|
||||
// neither `bin_path` nor `util_name` was set so we apply the default to run the arguments
|
||||
// in a platform specific shell
|
||||
} else if cfg!(unix) {
|
||||
let bin_path = if cfg!(target_os = "android") {
|
||||
PathBuf::from("/system/bin/sh")
|
||||
} else {
|
||||
PathBuf::from("/bin/sh")
|
||||
};
|
||||
#[cfg(target_os = "android")]
|
||||
let bin_path = PathBuf::from("/system/bin/sh");
|
||||
#[cfg(not(target_os = "android"))]
|
||||
let bin_path = PathBuf::from("/bin/sh");
|
||||
|
||||
self.bin_path = Some(bin_path);
|
||||
let c_arg = OsString::from("-c");
|
||||
if !self.args.contains(&c_arg) {
|
||||
self.args.insert(0, c_arg);
|
||||
self.args.push_front(c_arg);
|
||||
}
|
||||
} else {
|
||||
self.bin_path = Some(PathBuf::from("cmd"));
|
||||
|
@ -1355,21 +1401,26 @@ impl UCommand {
|
|||
.iter()
|
||||
.any(|s| s.eq_ignore_ascii_case(&c_arg) || s.eq_ignore_ascii_case(&k_arg))
|
||||
{
|
||||
self.args.insert(0, c_arg);
|
||||
self.args.push_front(c_arg);
|
||||
}
|
||||
};
|
||||
|
||||
// unwrap is safe here because we have set `self.bin_path` before
|
||||
let mut command = Command::new(self.bin_path.as_ref().unwrap());
|
||||
command.args(&self.args);
|
||||
|
||||
if self.tmpd.is_none() {
|
||||
self.tmpd = Some(Rc::new(tempfile::tempdir().unwrap()));
|
||||
}
|
||||
|
||||
// We use a temporary directory as working directory if not specified otherwise with
|
||||
// `current_dir()`. If neither `current_dir` nor a temporary directory is available, then we
|
||||
// create our own.
|
||||
if let Some(current_dir) = &self.current_dir {
|
||||
command.current_dir(current_dir);
|
||||
} else if let Some(temp_dir) = &self.tmpd {
|
||||
command.current_dir(temp_dir.path());
|
||||
} else {
|
||||
command.current_dir(self.tmpd.as_ref().unwrap().path());
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
self.current_dir = Some(temp_dir.path().into());
|
||||
command.current_dir(temp_dir.path());
|
||||
self.tmpd = Some(Rc::new(temp_dir));
|
||||
}
|
||||
|
||||
if self.env_clear {
|
||||
|
@ -1391,8 +1442,10 @@ impl UCommand {
|
|||
}
|
||||
}
|
||||
|
||||
for (key, value) in &self.env_vars {
|
||||
command.env(key, value);
|
||||
command.envs(self.env_vars.iter().cloned());
|
||||
|
||||
if self.timeout.is_none() {
|
||||
self.timeout = Some(Duration::from_secs(30));
|
||||
}
|
||||
|
||||
let mut captured_stdout = None;
|
||||
|
@ -1436,7 +1489,6 @@ impl UCommand {
|
|||
/// Spawns the command, feeds the stdin if any, and returns the
|
||||
/// child process immediately.
|
||||
pub fn run_no_wait(&mut self) -> UChild {
|
||||
// TODO: remove?
|
||||
assert!(!self.has_run, "{}", ALREADY_RUN);
|
||||
self.has_run = true;
|
||||
|
||||
|
@ -1509,7 +1561,7 @@ impl std::fmt::Display for UCommand {
|
|||
let mut comm_string: Vec<String> = vec![self
|
||||
.bin_path
|
||||
.as_ref()
|
||||
.map_or("".to_string(), |p| p.display().to_string())];
|
||||
.map_or(String::new(), |p| p.display().to_string())];
|
||||
comm_string.extend(self.args.iter().map(|s| s.to_string_lossy().to_string()));
|
||||
f.write_str(&comm_string.join(" "))
|
||||
}
|
||||
|
@ -1647,14 +1699,14 @@ impl<'a> UChildAssertion<'a> {
|
|||
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(),
|
||||
CmdResult::new(
|
||||
self.uchild.bin_path.clone(),
|
||||
self.uchild.util_name.clone(),
|
||||
self.uchild.tmpd.clone(),
|
||||
exit_status,
|
||||
stdout,
|
||||
stderr,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Make assertions of [`CmdResult`] with all output from start of the process until now.
|
||||
|
@ -1734,7 +1786,7 @@ impl<'a> UChildAssertion<'a> {
|
|||
/// Abstraction for a [`std::process::Child`] to handle the child process.
|
||||
pub struct UChild {
|
||||
raw: Child,
|
||||
bin_path: String,
|
||||
bin_path: PathBuf,
|
||||
util_name: Option<String>,
|
||||
captured_stdout: Option<CapturedOutput>,
|
||||
captured_stderr: Option<CapturedOutput>,
|
||||
|
@ -1754,11 +1806,8 @@ impl UChild {
|
|||
) -> Self {
|
||||
Self {
|
||||
raw: child,
|
||||
bin_path: ucommand.bin_path.as_ref().unwrap().display().to_string(),
|
||||
util_name: ucommand
|
||||
.util_name
|
||||
.clone()
|
||||
.map(|s| s.to_string_lossy().to_string()),
|
||||
bin_path: ucommand.bin_path.clone().unwrap(),
|
||||
util_name: ucommand.util_name.clone(),
|
||||
captured_stdout,
|
||||
captured_stderr,
|
||||
ignore_stdin_write_error: ucommand.ignore_stdin_write_error,
|
||||
|
@ -2388,11 +2437,13 @@ fn parse_coreutil_version(version_string: &str) -> f32 {
|
|||
///```
|
||||
#[cfg(unix)]
|
||||
pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result<CmdResult, String> {
|
||||
println!("{}", check_coreutil_version(&ts.util_name, VERSION_MIN)?);
|
||||
let util_name = &host_name_for(&ts.util_name);
|
||||
let util_name = ts.util_name.as_str();
|
||||
println!("{}", check_coreutil_version(util_name, VERSION_MIN)?);
|
||||
let util_name = host_name_for(util_name);
|
||||
|
||||
let result = ts
|
||||
.cmd_keepenv(util_name.as_ref())
|
||||
.cmd(util_name.as_ref())
|
||||
.keep_env()
|
||||
.env("LC_ALL", "C")
|
||||
.args(args)
|
||||
.run();
|
||||
|
@ -2464,7 +2515,8 @@ pub fn run_ucmd_as_root(
|
|||
// we can run sudo and we're root
|
||||
// run ucmd as root:
|
||||
Ok(ts
|
||||
.cmd_keepenv("sudo")
|
||||
.cmd("sudo")
|
||||
.keep_env()
|
||||
.env("LC_ALL", "C")
|
||||
.arg("-E")
|
||||
.arg("--non-interactive")
|
||||
|
@ -2492,30 +2544,8 @@ mod tests {
|
|||
// spell-checker:ignore (tests) asdfsadfa
|
||||
use super::*;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn run_cmd<T: AsRef<OsStr>>(cmd: T) -> CmdResult {
|
||||
let mut ucmd = UCommand::new_from_tmp::<&str, String>(
|
||||
"sh",
|
||||
None,
|
||||
Rc::new(tempfile::tempdir().unwrap()),
|
||||
true,
|
||||
);
|
||||
ucmd.arg("-c");
|
||||
ucmd.arg(cmd);
|
||||
ucmd.run()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn run_cmd<T: AsRef<OsStr>>(cmd: T) -> CmdResult {
|
||||
let mut ucmd = UCommand::new_from_tmp::<&str, String>(
|
||||
"cmd",
|
||||
None,
|
||||
Rc::new(tempfile::tempdir().unwrap()),
|
||||
true,
|
||||
);
|
||||
ucmd.arg("/C");
|
||||
ucmd.arg(cmd);
|
||||
ucmd.run()
|
||||
UCommand::new().arg(cmd).run()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -3257,7 +3287,7 @@ mod tests {
|
|||
#[cfg(feature = "echo")]
|
||||
#[test]
|
||||
fn test_ucommand_when_default() {
|
||||
let shell_cmd = format!("{} echo -n hello", TESTS_BINARY);
|
||||
let shell_cmd = format!("{TESTS_BINARY} echo -n hello");
|
||||
|
||||
let mut command = UCommand::new();
|
||||
command.arg(&shell_cmd).succeeds().stdout_is("hello");
|
||||
|
@ -3274,4 +3304,31 @@ mod tests {
|
|||
std::assert_eq!(command.args, &[expected_arg, OsString::from(&shell_cmd)]);
|
||||
assert!(command.tmpd.is_some());
|
||||
}
|
||||
|
||||
#[cfg(feature = "echo")]
|
||||
#[test]
|
||||
fn test_ucommand_with_util() {
|
||||
let tmpd = tempfile::tempdir().unwrap();
|
||||
let mut command = UCommand::with_util("echo", Rc::new(tmpd));
|
||||
|
||||
command
|
||||
.args(&["-n", "hello"])
|
||||
.succeeds()
|
||||
.stdout_only("hello");
|
||||
|
||||
std::assert_eq!(
|
||||
&PathBuf::from(TESTS_BINARY),
|
||||
command.bin_path.as_ref().unwrap()
|
||||
);
|
||||
std::assert_eq!("echo", &command.util_name.unwrap());
|
||||
std::assert_eq!(
|
||||
&[
|
||||
OsString::from("echo"),
|
||||
OsString::from("-n"),
|
||||
OsString::from("hello")
|
||||
],
|
||||
command.args.make_contiguous()
|
||||
);
|
||||
assert!(command.tmpd.is_some());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue