1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-09-16 19:56:17 +00:00

Merge branch 'master' of github.com:uutils/coreutils into hbina-tr-reimplement-expansion

Signed-off-by: Hanif Bin Ariffin <hanif.ariffin.4326@gmail.com>
This commit is contained in:
Hanif Bin Ariffin 2021-10-24 11:40:42 +08:00
commit 2dad536785
356 changed files with 11234 additions and 5621 deletions

View file

@ -85,11 +85,15 @@ fn test_wrap() {
#[test]
fn test_wrap_no_arg() {
for wrap_param in &["-w", "--wrap"] {
let expected_stderr = "error: The argument '--wrap <wrap>\' requires a value but none was \
supplied\n\nUSAGE:\n base32 [OPTION]... [FILE]\n\nFor more \
information try --help"
.to_string();
new_ucmd!()
let ts = TestScenario::new(util_name!());
let expected_stderr = &format!(
"error: The argument '--wrap <wrap>\' requires a value but none was \
supplied\n\nUSAGE:\n {1} {0} [OPTION]... [FILE]\n\nFor more \
information try --help",
ts.util_name,
ts.bin_path.to_string_lossy()
);
ts.ucmd()
.arg(wrap_param)
.fails()
.stderr_only(expected_stderr);
@ -109,12 +113,18 @@ fn test_wrap_bad_arg() {
#[test]
fn test_base32_extra_operand() {
let ts = TestScenario::new(util_name!());
// Expect a failure when multiple files are specified.
new_ucmd!()
ts.ucmd()
.arg("a.txt")
.arg("b.txt")
.fails()
.stderr_only("base32: extra operand 'b.txt'\nTry 'base32 --help' for more information.");
.stderr_only(format!(
"{0}: extra operand 'b.txt'\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
}
#[test]

View file

@ -95,12 +95,18 @@ fn test_wrap_bad_arg() {
#[test]
fn test_base64_extra_operand() {
let ts = TestScenario::new(util_name!());
// Expect a failure when multiple files are specified.
new_ucmd!()
ts.ucmd()
.arg("a.txt")
.arg("b.txt")
.fails()
.stderr_only("base64: extra operand 'b.txt'\nTry 'base64 --help' for more information.");
.stderr_only(format!(
"{0}: extra operand 'b.txt'\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
}
#[test]

View file

@ -114,9 +114,12 @@ fn test_no_args() {
#[test]
fn test_no_args_output() {
new_ucmd!()
.fails()
.stderr_is("basename: missing operand\nTry 'basename --help' for more information.");
let ts = TestScenario::new(util_name!());
ts.ucmd().fails().stderr_is(&format!(
"{0}: missing operand\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
}
#[test]
@ -126,10 +129,12 @@ fn test_too_many_args() {
#[test]
fn test_too_many_args_output() {
new_ucmd!()
.args(&["a", "b", "c"])
.fails()
.stderr_is("basename: extra operand 'c'\nTry 'basename --help' for more information.");
let ts = TestScenario::new(util_name!());
ts.ucmd().args(&["a", "b", "c"]).fails().stderr_is(format!(
"{0}: extra operand 'c'\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
}
#[cfg(any(unix, target_os = "redox"))]
@ -147,3 +152,32 @@ fn invalid_utf8_args_unix() {
let os_str = OsStr::from_bytes(&source[..]);
test_invalid_utf8_args(os_str);
}
#[test]
fn test_root() {
let expected = if cfg!(windows) { "\\\n" } else { "/\n" };
new_ucmd!().arg("/").succeeds().stdout_is(expected);
}
#[test]
fn test_double_slash() {
// TODO The GNU tests seem to suggest that some systems treat "//"
// as the same directory as "/" directory but not all systems. We
// should extend this test to account for that possibility.
let expected = if cfg!(windows) { "\\\n" } else { "/\n" };
new_ucmd!().arg("//").succeeds().stdout_is(expected);
new_ucmd!()
.args(&["//", "/"])
.succeeds()
.stdout_is(expected);
new_ucmd!()
.args(&["//", "//"])
.succeeds()
.stdout_is(expected);
}
#[test]
fn test_triple_slash() {
let expected = if cfg!(windows) { "\\\n" } else { "/\n" };
new_ucmd!().arg("///").succeeds().stdout_is(expected);
}

View file

@ -1,8 +1,13 @@
// spell-checker:ignore NOFILE
use crate::common::util::*;
use std::fs::OpenOptions;
#[cfg(unix)]
use std::io::Read;
#[cfg(target_os = "linux")]
use rlimit::Resource;
#[test]
fn test_output_simple() {
new_ucmd!()
@ -87,6 +92,23 @@ fn test_fifo_symlink() {
thread.join().unwrap();
}
#[test]
#[cfg(target_os = "linux")]
fn test_closes_file_descriptors() {
// Each file creates a pipe, which has two file descriptors.
// If they are not closed then five is certainly too many.
new_ucmd!()
.args(&[
"alpha.txt",
"alpha.txt",
"alpha.txt",
"alpha.txt",
"alpha.txt",
])
.with_limit(Resource::NOFILE, 9, 9)
.succeeds();
}
#[test]
#[cfg(unix)]
fn test_piped_to_regular_file() {

View file

@ -43,7 +43,7 @@ fn test_invalid_group() {
.arg("__nosuchgroup__")
.arg("/")
.fails()
.stderr_is("chgrp: invalid group: __nosuchgroup__");
.stderr_is("chgrp: invalid group: '__nosuchgroup__'");
}
#[test]
@ -230,7 +230,7 @@ fn test_big_h() {
}
#[test]
#[cfg(target_os = "linux")]
#[cfg(not(target_vendor = "apple"))]
fn basic_succeeds() {
let (at, mut ucmd) = at_and_ucmd!();
let one_group = nix::unistd::getgroups().unwrap();
@ -251,3 +251,105 @@ fn test_no_change() {
at.touch("file");
ucmd.arg("").arg(at.plus("file")).succeeds();
}
#[test]
#[cfg(not(target_vendor = "apple"))]
fn test_permission_denied() {
use std::os::unix::prelude::PermissionsExt;
if let Some(group) = nix::unistd::getgroups().unwrap().first() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("dir");
at.touch("dir/file");
std::fs::set_permissions(at.plus("dir"), PermissionsExt::from_mode(0o0000)).unwrap();
ucmd.arg("-R")
.arg(group.as_raw().to_string())
.arg("dir")
.fails()
.stderr_only("chgrp: cannot access 'dir': Permission denied");
}
}
#[test]
#[cfg(not(target_vendor = "apple"))]
fn test_subdir_permission_denied() {
use std::os::unix::prelude::PermissionsExt;
if let Some(group) = nix::unistd::getgroups().unwrap().first() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("dir");
at.mkdir("dir/subdir");
at.touch("dir/subdir/file");
std::fs::set_permissions(at.plus("dir/subdir"), PermissionsExt::from_mode(0o0000)).unwrap();
ucmd.arg("-R")
.arg(group.as_raw().to_string())
.arg("dir")
.fails()
.stderr_only("chgrp: cannot access 'dir/subdir': Permission denied");
}
}
#[test]
#[cfg(not(target_vendor = "apple"))]
fn test_traverse_symlinks() {
use std::os::unix::prelude::MetadataExt;
let groups = nix::unistd::getgroups().unwrap();
if groups.len() < 2 {
return;
}
let (first_group, second_group) = (groups[0], groups[1]);
for &(args, traverse_first, traverse_second) in &[
(&[][..] as &[&str], false, false),
(&["-H"][..], true, false),
(&["-P"][..], false, false),
(&["-L"][..], true, true),
] {
let scenario = TestScenario::new("chgrp");
let (at, mut ucmd) = (scenario.fixtures.clone(), scenario.ucmd());
at.mkdir("dir");
at.mkdir("dir2");
at.touch("dir2/file");
at.mkdir("dir3");
at.touch("dir3/file");
at.symlink_dir("dir2", "dir/dir2_ln");
at.symlink_dir("dir3", "dir3_ln");
scenario
.ccmd("chgrp")
.arg(first_group.to_string())
.arg("dir2/file")
.arg("dir3/file")
.succeeds();
assert!(at.plus("dir2/file").metadata().unwrap().gid() == first_group.as_raw());
assert!(at.plus("dir3/file").metadata().unwrap().gid() == first_group.as_raw());
ucmd.arg("-R")
.args(args)
.arg(second_group.to_string())
.arg("dir")
.arg("dir3_ln")
.succeeds()
.no_stderr();
assert_eq!(
at.plus("dir2/file").metadata().unwrap().gid(),
if traverse_second {
second_group.as_raw()
} else {
first_group.as_raw()
}
);
assert_eq!(
at.plus("dir3/file").metadata().unwrap().gid(),
if traverse_first {
second_group.as_raw()
} else {
first_group.as_raw()
}
);
}
}

View file

@ -1,10 +1,10 @@
use crate::common::util::*;
use std::fs::{metadata, set_permissions, OpenOptions};
use std::fs::{metadata, set_permissions, OpenOptions, Permissions};
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
use std::sync::Mutex;
extern crate libc;
use self::chmod::strip_minus_from_mode;
use uucore::mode::strip_minus_from_mode;
extern crate chmod;
use self::libc::umask;
@ -201,11 +201,6 @@ fn test_chmod_ugoa() {
before: 0o100000,
after: 0o100755,
},
TestCase {
args: vec!["-w", TEST_FILE],
before: 0o100777,
after: 0o100577,
},
TestCase {
args: vec!["-x", TEST_FILE],
before: 0o100777,
@ -213,6 +208,21 @@ fn test_chmod_ugoa() {
},
];
run_tests(tests);
// check that we print an error if umask prevents us from removing a permission
let (at, mut ucmd) = at_and_ucmd!();
at.touch("file");
set_permissions(at.plus("file"), Permissions::from_mode(0o777)).unwrap();
ucmd.args(&["-w", "file"])
.fails()
.code_is(1)
// spell-checker:disable-next-line
.stderr_is("chmod: file: new permissions are r-xrwxrwx, not r-xr-xr-x");
assert_eq!(
metadata(at.plus("file")).unwrap().permissions().mode(),
0o100577
);
unsafe {
umask(last);
}
@ -330,8 +340,8 @@ fn test_chmod_recursive() {
.arg("a")
.arg("z")
.succeeds()
.stderr_contains(&"to 333 (-wx-wx-wx)")
.stderr_contains(&"to 222 (-w--w--w-)");
.stdout_contains(&"to 0333 (-wx-wx-wx)")
.stdout_contains(&"to 0222 (-w--w--w-)");
assert_eq!(at.metadata("z/y").permissions().mode(), 0o100222);
assert_eq!(at.metadata("a/a").permissions().mode(), 0o100222);
@ -350,13 +360,24 @@ fn test_chmod_recursive() {
fn test_chmod_non_existing_file() {
new_ucmd!()
.arg("-R")
.arg("--verbose")
.arg("-r,a+w")
.arg("does-not-exist")
.fails()
.stderr_contains(&"cannot access 'does-not-exist': No such file or directory");
}
#[test]
fn test_chmod_non_existing_file_silent() {
new_ucmd!()
.arg("-R")
.arg("--quiet")
.arg("-r,a+w")
.arg("does-not-exist")
.fails()
.no_stderr()
.code_is(1);
}
#[test]
fn test_chmod_preserve_root() {
new_ucmd!()
@ -490,3 +511,49 @@ fn test_chmod_strip_minus_from_mode() {
assert_eq!(test.1, args.join(" "));
}
}
#[test]
fn test_chmod_keep_setgid() {
for &(from, arg, to) in &[
(0o7777, "777", 0o46777),
(0o7777, "=777", 0o40777),
(0o7777, "0777", 0o46777),
(0o7777, "=0777", 0o40777),
(0o7777, "00777", 0o40777),
(0o2444, "a+wx", 0o42777),
(0o2444, "a=wx", 0o42333),
(0o1444, "g+s", 0o43444),
(0o4444, "u-s", 0o40444),
(0o7444, "a-s", 0o41444),
] {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("dir");
set_permissions(at.plus("dir"), Permissions::from_mode(from)).unwrap();
let r = ucmd.arg(arg).arg("dir").succeeds();
println!("{}", r.stderr_str());
assert_eq!(at.metadata("dir").permissions().mode(), to);
}
}
#[test]
fn test_no_operands() {
new_ucmd!()
.arg("777")
.fails()
.code_is(1)
.stderr_is("chmod: missing operand");
}
#[test]
fn test_mode_after_dash_dash() {
let (at, ucmd) = at_and_ucmd!();
run_single_test(
&TestCase {
args: vec!["--", "-r", TEST_FILE],
before: 0o100777,
after: 0o100333,
},
at,
ucmd,
);
}

View file

@ -1,4 +1,4 @@
// spell-checker:ignore (words) agroupthatdoesntexist auserthatdoesntexist groupname notexisting passgrp
// spell-checker:ignore (words) agroupthatdoesntexist auserthatdoesntexist cuuser groupname notexisting passgrp
use crate::common::util::*;
#[cfg(target_os = "linux")]
@ -139,6 +139,14 @@ fn test_chown_only_owner_colon() {
.succeeds()
.stderr_contains(&"retained as");
scene
.ucmd()
.arg(format!("{}.", user_name))
.arg("--verbose")
.arg(file1)
.succeeds()
.stderr_contains(&"retained as");
scene
.ucmd()
.arg("root:")
@ -180,6 +188,14 @@ fn test_chown_only_colon() {
.arg(file1)
.fails()
.stderr_contains(&"invalid group: '::'");
scene
.ucmd()
.arg("..")
.arg("--verbose")
.arg(file1)
.fails()
.stderr_contains(&"invalid group: '..'");
}
#[test]
@ -195,6 +211,8 @@ fn test_chown_failed_stdout() {
}
#[test]
// FixME: Fails on freebsd because of chown: invalid group: 'root:root'
#[cfg(not(target_os = "freebsd"))]
fn test_chown_owner_group() {
// test chown username:group file.txt
@ -230,6 +248,22 @@ fn test_chown_owner_group() {
}
result.stderr_contains(&"retained as");
scene
.ucmd()
.arg("root:root:root")
.arg("--verbose")
.arg(file1)
.fails()
.stderr_contains(&"invalid group");
scene
.ucmd()
.arg("root.root.root")
.arg("--verbose")
.arg(file1)
.fails()
.stderr_contains(&"invalid group");
// TODO: on macos group name is not recognized correctly: "chown: invalid group: 'root:root'
#[cfg(any(windows, all(unix, not(target_os = "macos"))))]
scene
@ -242,8 +276,72 @@ fn test_chown_owner_group() {
}
#[test]
// TODO: on macos group name is not recognized correctly: "chown: invalid group: ':groupname'
#[cfg(any(windows, all(unix, not(target_os = "macos"))))]
// FixME: Fails on freebsd because of chown: invalid group: 'root:root'
#[cfg(not(target_os = "freebsd"))]
fn test_chown_various_input() {
// test chown username:group file.txt
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let result = scene.cmd("whoami").run();
if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") {
return;
}
let user_name = String::from(result.stdout_str().trim());
assert!(!user_name.is_empty());
let file1 = "test_chown_file1";
at.touch(file1);
let result = scene.cmd("id").arg("-gn").run();
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
return;
}
let group_name = String::from(result.stdout_str().trim());
assert!(!group_name.is_empty());
let result = scene
.ucmd()
.arg(format!("{}:{}", user_name, group_name))
.arg("--verbose")
.arg(file1)
.run();
if skipping_test_is_okay(&result, "chown: invalid group:") {
return;
}
result.stderr_contains(&"retained as");
// check that username.groupname is understood
let result = scene
.ucmd()
.arg(format!("{}.{}", user_name, group_name))
.arg("--verbose")
.arg(file1)
.run();
if skipping_test_is_okay(&result, "chown: invalid group:") {
return;
}
result.stderr_contains(&"retained as");
// Fails as user.name doesn't exist in the CI
// but it is valid
scene
.ucmd()
.arg(format!("{}:{}", "user.name", "groupname"))
.arg("--verbose")
.arg(file1)
.fails()
.stderr_contains(&"chown: invalid user: 'user.name:groupname'");
}
#[test]
// FixME: on macos & freebsd group name is not recognized correctly: "chown: invalid group: ':groupname'
#[cfg(any(
windows,
all(unix, not(any(target_os = "macos", target_os = "freebsd")))
))]
fn test_chown_only_group() {
// test chown :group file.txt
@ -321,6 +419,8 @@ fn test_chown_only_user_id() {
}
#[test]
// FixME: stderr = chown: ownership of 'test_chown_file1' retained as cuuser:wheel
#[cfg(not(target_os = "freebsd"))]
fn test_chown_only_group_id() {
// test chown :1111 file.txt
@ -398,6 +498,19 @@ fn test_chown_owner_group_id() {
}
result.stderr_contains(&"retained as");
let result = scene
.ucmd()
.arg(format!("{}.{}", user_id, group_id))
.arg("--verbose")
.arg(file1)
.run();
if skipping_test_is_okay(&result, "invalid user") {
// From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)"
// stderr: "chown: invalid user: '1001.116'
return;
}
result.stderr_contains(&"retained as");
scene
.ucmd()
.arg("0:0")
@ -408,6 +521,8 @@ fn test_chown_owner_group_id() {
}
#[test]
// FixME: Fails on freebsd because of chown: invalid group: '0:root'
#[cfg(not(target_os = "freebsd"))]
fn test_chown_owner_group_mix() {
// test chown 1111:group file.txt

View file

@ -15,6 +15,7 @@ fn test_missing_operand() {
#[test]
fn test_enter_chroot_fails() {
// NOTE: since #2689 this test also ensures that we don't regress #2687
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("jail");
@ -23,7 +24,7 @@ fn test_enter_chroot_fails() {
assert!(result
.stderr_str()
.starts_with("chroot: cannot chroot to jail: Operation not permitted (os error 1)"));
.starts_with("chroot: cannot chroot to 'jail': Operation not permitted (os error 1)"));
}
#[test]
@ -34,7 +35,7 @@ fn test_no_such_directory() {
ucmd.arg("a")
.fails()
.stderr_is("chroot: cannot change root directory to `a`: no such directory");
.stderr_is("chroot: cannot change root directory to 'a': no such directory");
}
#[test]
@ -89,3 +90,26 @@ fn test_preference_of_userspec() {
println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr_str());
}
#[test]
fn test_default_shell() {
// NOTE: This test intends to trigger code which can only be reached with root permissions.
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
let dir = "CHROOT_DIR";
at.mkdir(dir);
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
let _expected = format!(
"chroot: failed to run command '{}': No such file or directory",
shell
);
// TODO: [2021-09; jhscheer] uncomment if/when #2692 gets merged
// if let Ok(result) = run_ucmd_as_root(&ts, &[dir]) {
// result.stderr_contains(expected);
// } else {
// print!("TEST SKIPPED");
// }
}

View file

@ -68,7 +68,7 @@ fn test_invalid_file() {
.arg(folder_name)
.fails()
.no_stdout()
.stderr_contains("cksum: 'asdf' No such file or directory");
.stderr_contains("cksum: asdf: No such file or directory");
// Then check when the file is of an invalid type
at.mkdir(folder_name);
@ -76,7 +76,7 @@ fn test_invalid_file() {
.arg(folder_name)
.fails()
.no_stdout()
.stderr_contains("cksum: 'asdf' Is a directory");
.stderr_contains("cksum: asdf: Is a directory");
}
// Make sure crc is correct for files larger than 32 bytes

View file

@ -7,7 +7,9 @@ use std::fs::set_permissions;
#[cfg(not(windows))]
use std::os::unix::fs;
#[cfg(target_os = "linux")]
#[cfg(unix)]
use std::os::unix::fs::symlink as symlink_file;
#[cfg(all(unix, not(target_os = "freebsd")))]
use std::os::unix::fs::PermissionsExt;
#[cfg(windows)]
use std::os::windows::fs::symlink_file;
@ -561,14 +563,17 @@ fn test_cp_backup_off() {
#[test]
fn test_cp_backup_no_clobber_conflicting_options() {
let (_, mut ucmd) = at_and_ucmd!();
ucmd.arg("--backup")
let ts = TestScenario::new(util_name!());
ts.ucmd()
.arg("--backup")
.arg("--no-clobber")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.fails()
.stderr_is("cp: options --backup and --no-clobber are mutually exclusive\nTry 'cp --help' for more information.");
.fails().stderr_is(&format!(
"{0}: options --backup and --no-clobber are mutually exclusive\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
}
#[test]
@ -1302,3 +1307,64 @@ fn test_copy_symlink_force() {
.succeeds();
assert_eq!(at.resolve_link("copy"), "file");
}
#[test]
#[cfg(all(unix, not(target_os = "freebsd")))]
fn test_no_preserve_mode() {
use std::os::unix::prelude::MetadataExt;
use uucore::mode::get_umask;
const PERMS_ALL: u32 = 0o7777;
let (at, mut ucmd) = at_and_ucmd!();
at.touch("file");
set_permissions(at.plus("file"), PermissionsExt::from_mode(PERMS_ALL)).unwrap();
ucmd.arg("file")
.arg("dest")
.succeeds()
.no_stderr()
.no_stdout();
let umask = get_umask();
// remove sticky bit, setuid and setgid bit; apply umask
let expected_perms = PERMS_ALL & !0o7000 & !umask;
assert_eq!(
at.plus("dest").metadata().unwrap().mode() & 0o7777,
expected_perms
);
}
#[test]
#[cfg(all(unix, not(target_os = "freebsd")))]
fn test_preserve_mode() {
use std::os::unix::prelude::MetadataExt;
const PERMS_ALL: u32 = 0o7777;
let (at, mut ucmd) = at_and_ucmd!();
at.touch("file");
set_permissions(at.plus("file"), PermissionsExt::from_mode(PERMS_ALL)).unwrap();
ucmd.arg("file")
.arg("dest")
.arg("-p")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(
at.plus("dest").metadata().unwrap().mode() & 0o7777,
PERMS_ALL
);
}
#[test]
fn test_canonicalize_symlink() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("dir");
at.touch("dir/file");
symlink_file("../dir/file", at.plus("dir/file-ln")).unwrap();
ucmd.arg("dir/file-ln")
.arg(".")
.succeeds()
.no_stderr()
.no_stdout();
}

View file

@ -28,9 +28,7 @@ macro_rules! fixture_path {
macro_rules! assert_fixture_exists {
($fname:expr) => {{
let fpath = fixture_path!($fname);
if !fpath.exists() {
panic!("Fixture missing: {:?}", fpath);
}
assert!(fpath.exists(), "Fixture missing: {:?}", fpath);
}};
}
@ -38,9 +36,7 @@ macro_rules! assert_fixture_exists {
macro_rules! assert_fixture_not_exists {
($fname:expr) => {{
let fpath = PathBuf::from(format!("./fixtures/dd/{}", $fname));
if fpath.exists() {
panic!("Fixture present: {:?}", fpath);
}
assert!(!fpath.exists(), "Fixture present: {:?}", fpath);
}};
}

View file

@ -1,3 +1,4 @@
// spell-checker:ignore checkfile
macro_rules! get_hash(
($str:expr) => (
$str.split(' ').collect::<Vec<&str>>()[0]
@ -12,6 +13,7 @@ macro_rules! test_digest {
static DIGEST_ARG: &'static str = concat!("--", stringify!($t));
static BITS_ARG: &'static str = concat!("--bits=", stringify!($size));
static EXPECTED_FILE: &'static str = concat!(stringify!($id), ".expected");
static CHECK_FILE: &'static str = concat!(stringify!($id), ".checkfile");
#[test]
fn test_single_file() {
@ -26,6 +28,40 @@ macro_rules! test_digest {
assert_eq!(ts.fixtures.read(EXPECTED_FILE),
get_hash!(ts.ucmd().arg(DIGEST_ARG).arg(BITS_ARG).pipe_in_fixture("input.txt").succeeds().no_stderr().stdout_str()));
}
#[test]
fn test_check() {
let ts = TestScenario::new("hashsum");
ts.ucmd()
.args(&[DIGEST_ARG, BITS_ARG, "--check", CHECK_FILE])
.succeeds()
.no_stderr()
.stdout_is("input.txt: OK\n");
}
#[cfg(windows)]
#[test]
fn test_text_mode() {
// TODO Replace this with hard-coded files that store the
// expected output of text mode on an input file that has
// "\r\n" line endings.
let result = new_ucmd!()
.args(&[DIGEST_ARG, BITS_ARG, "-b"])
.pipe_in("a\nb\nc\n")
.succeeds();
let expected = result.no_stderr().stdout();
// Replace the "*-\n" at the end of the output with " -\n".
// The asterisk indicates that the digest was computed in
// binary mode.
let n = expected.len();
let expected = [&expected[..n - 3], &[b' ', b'-', b'\n']].concat();
new_ucmd!()
.args(&[DIGEST_ARG, BITS_ARG, "-t"])
.pipe_in("a\r\nb\r\nc\r\n")
.succeeds()
.no_stderr()
.stdout_is(std::str::from_utf8(&expected).unwrap());
}
}
)*)
}

0
tests/by-util/test_head.rs Executable file → Normal file
View file

View file

@ -10,8 +10,8 @@ fn test_hostname() {
assert!(ls_default_res.stdout().len() >= ls_domain_res.stdout().len());
}
// FixME: fails for "MacOS"
#[cfg(not(target_vendor = "apple"))]
// FixME: fails for "MacOS" and "freebsd" "failed to lookup address information: Name does not resolve"
#[cfg(not(any(target_os = "macos", target_os = "freebsd")))]
#[test]
fn test_hostname_ip() {
let result = new_ucmd!().arg("-i").succeeds();

View file

@ -4,7 +4,7 @@ use crate::common::util::*;
use filetime::FileTime;
use rust_users::*;
use std::os::unix::fs::PermissionsExt;
#[cfg(not(windows))]
#[cfg(not(any(windows, target_os = "freebsd")))]
use std::process::Command;
#[cfg(target_os = "linux")]
use std::thread::sleep;
@ -551,7 +551,9 @@ fn test_install_copy_then_compare_file_with_extra_mode() {
}
const STRIP_TARGET_FILE: &str = "helloworld_installed";
#[cfg(not(any(windows, target_os = "freebsd")))]
const SYMBOL_DUMP_PROGRAM: &str = "objdump";
#[cfg(not(any(windows, target_os = "freebsd")))]
const STRIP_SOURCE_FILE_SYMBOL: &str = "main";
fn strip_source_file() -> &'static str {
@ -563,7 +565,8 @@ fn strip_source_file() -> &'static str {
}
#[test]
#[cfg(not(windows))]
// FixME: Freebsd fails on 'No such file or directory'
#[cfg(not(any(windows, target_os = "freebsd")))]
fn test_install_and_strip() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
@ -586,7 +589,8 @@ fn test_install_and_strip() {
}
#[test]
#[cfg(not(windows))]
// FixME: Freebsd fails on 'No such file or directory'
#[cfg(not(any(windows, target_os = "freebsd")))]
fn test_install_and_strip_with_program() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;

View file

@ -227,6 +227,19 @@ fn autoformat() {
.pipe_in("1 x y z\n2 p")
.succeeds()
.stdout_only("1 x y z a\n2 p b\n");
new_ucmd!()
.arg("-")
.arg("fields_2.txt")
.arg("-a")
.arg("1")
.arg("-o")
.arg("auto")
.arg("-e")
.arg(".")
.pipe_in("1 x y z\n2 p\n99 a b\n")
.succeeds()
.stdout_only("1 x y z a\n2 p . . b\n99 a b . .\n");
}
#[test]

View file

@ -56,6 +56,12 @@ fn test_kill_list_all_signals() {
.stdout_contains("HUP");
}
#[test]
fn test_kill_list_final_new_line() {
let re = Regex::new("\\n$").unwrap();
assert!(re.is_match(new_ucmd!().arg("-l").succeeds().stdout_str()));
}
#[test]
fn test_kill_list_all_signals_as_table() {
// Check for a few signals. Do not try to be comprehensive.
@ -104,6 +110,26 @@ fn test_kill_with_signal_number_old_form() {
assert_eq!(target.wait_for_signal(), Some(9));
}
#[test]
fn test_kill_with_signal_name_old_form() {
let mut target = Target::new();
new_ucmd!()
.arg("-KILL")
.arg(format!("{}", target.pid()))
.succeeds();
assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL));
}
#[test]
fn test_kill_with_signal_prefixed_name_old_form() {
let mut target = Target::new();
new_ucmd!()
.arg("-SIGKILL")
.arg(format!("{}", target.pid()))
.succeeds();
assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL));
}
#[test]
fn test_kill_with_signal_number_new_form() {
let mut target = Target::new();
@ -125,3 +151,14 @@ fn test_kill_with_signal_name_new_form() {
.succeeds();
assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL));
}
#[test]
fn test_kill_with_signal_prefixed_name_new_form() {
let mut target = Target::new();
new_ucmd!()
.arg("-s")
.arg("SIGKILL")
.arg(format!("{}", target.pid()))
.succeeds();
assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL));
}

View file

@ -1,4 +1,4 @@
// spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup somefile somegroup somehiddenbackup somehiddenfile
// spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile
#[cfg(unix)]
extern crate unix_socket;
@ -333,6 +333,261 @@ fn test_ls_long() {
}
}
#[cfg(not(windows))]
#[test]
fn test_ls_long_format() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir(&at.plus_as_string("test-long-dir"));
at.touch(&at.plus_as_string("test-long-dir/test-long-file"));
at.mkdir(&at.plus_as_string("test-long-dir/test-long-dir"));
for arg in &["-l", "--long", "--format=long", "--format=verbose"] {
// Assuming sane username do not have spaces within them.
// A line of the output should be:
// One of the characters -bcCdDlMnpPsStTx?
// rwx, with - for missing permissions, thrice.
// Zero or one "." for indicating a file with security context
// A number, preceded by column whitespace, and followed by a single space.
// A username, currently [^ ], followed by column whitespace, twice (or thrice for Hurd).
// A number, followed by a single space.
// A month, followed by a single space.
// A day, preceded by column whitespace, and followed by a single space.
// Either a year or a time, currently [0-9:]+, preceded by column whitespace,
// and followed by a single space.
// Whatever comes after is irrelevant to this specific test.
scene.ucmd().arg(arg).arg("test-long-dir").succeeds().stdout_matches(&Regex::new(
r"\n[-bcCdDlMnpPsStTx?]([r-][w-][xt-]){3}\.? +\d+ [^ ]+ +[^ ]+( +[^ ]+)? +\d+ [A-Z][a-z]{2} {0,2}\d{0,2} {0,2}[0-9:]+ "
).unwrap());
}
// This checks for the line with the .. entry. The uname and group should be digits.
scene.ucmd().arg("-lan").arg("test-long-dir").succeeds().stdout_matches(&Regex::new(
r"\nd([r-][w-][xt-]){3}\.? +\d+ \d+ +\d+( +\d+)? +\d+ [A-Z][a-z]{2} {0,2}\d{0,2} {0,2}[0-9:]+ \.\."
).unwrap());
}
/// This test tests `ls -laR --color`.
/// This test is mainly about coloring, but, the recursion, symlink `->` processing,
/// and `.` and `..` being present in `-a` all need to work for the test to pass.
/// This test does not really test anything provided by `-l` but the file names and symlinks.
#[cfg(all(feature = "ln", feature = "mkdir", feature = "touch"))]
#[test]
#[cfg(all(feature = "ln", feature = "mkdir", feature = "touch"))]
fn test_ls_long_symlink_color() {
// If you break this test after breaking mkdir, touch, or ln, do not be alarmed!
// This test is made for ls, but it attempts to run those utils in the process.
// Having Some([2, 0]) in a color basically means that "it has the same color as whatever
// is in the 2nd expected output, the 0th color", where the 0th color is the name color, and
// the 1st color is the target color, in a fixed-size array of size 2.
// Basically these are references to be used for indexing the `colors` vector defined below.
type ColorReference = Option<[usize; 2]>;
// The string between \x1b[ and m
type Color = String;
// The string between the color start and the color end is the file name itself.
type Name = String;
let scene = TestScenario::new(util_name!());
// .
// ├── dir1
// │ ├── file1
// │ ├── dir2
// │ │ └── dir3
// │ ├── ln-dir-invalid -> dir1/dir2
// │ ├── ln-up2 -> ../..
// │ └── ln-root -> /
// ├── ln-file1 -> dir1/file1
// ├── ln-file-invalid -> dir1/invalid-target
// └── ln-dir3 -> ./dir1/dir2/dir3
prepare_folder_structure(&scene);
// We memoize the colors so we can refer to them later.
// Each entry will be the colors of the link name and link target of a specific output.
let mut colors: Vec<[Color; 2]> = vec![];
// The contents of each tuple are the expected colors and names for the link and target.
// We will loop over the ls output and compare to those.
// None values mean that we do not know what color to expect yet, as LS_COLOR might
// be set differently, and as different implementations of ls may use different codes,
// for example, our ls uses `[1;36m` while the GNU ls uses `[01;36m`.
//
// These have been sorting according to default ls sort, and this affects the order of
// discovery of colors, so be very careful when changing directory/file names being created.
let expected_output: [(ColorReference, &str, ColorReference, &str); 6] = [
// We don't know what colors are what the first time we meet a link.
(None, "ln-dir3", None, "./dir1/dir2/dir3"),
// We have acquired [0, 0], which should be the link color,
// and [0, 1], which should be the dir color, and we can compare to them from now on.
(None, "ln-file-invalid", Some([1, 1]), "dir1/invalid-target"),
// We acquired [1, 1], the non-existent color.
(Some([0, 0]), "ln-file1", None, "dir1/file1"),
(Some([1, 1]), "ln-dir-invalid", Some([1, 1]), "dir1/dir2"),
(Some([0, 0]), "ln-root", Some([0, 1]), "/"),
(Some([0, 0]), "ln-up2", Some([0, 1]), "../.."),
];
// We are only interested in lines or the ls output that are symlinks. These start with "lrwx".
let result = scene.ucmd().arg("-laR").arg("--color").arg(".").succeeds();
let mut result_lines = result
.stdout_str()
.lines()
.filter(|line| line.starts_with("lrwx"))
.enumerate();
// For each enumerated line, we assert that the output of ls matches the expected output.
//
// The unwraps within get_index_name_target will panic if a line starting lrwx does
// not have `colored_name -> target` within it.
while let Some((i, name, target)) = get_index_name_target(&mut result_lines) {
// The unwraps within capture_colored_string will panic if the name/target's color
// format is invalid.
let (matched_name_color, matched_name) = capture_colored_string(&name);
let (matched_target_color, matched_target) = capture_colored_string(&target);
colors.push([matched_name_color, matched_target_color]);
// We borrow them again after having moved them. This unwrap will never panic.
let [matched_name_color, matched_target_color] = colors.last().unwrap();
// We look up the Colors that are expected in `colors` using the ColorReferences
// stored in `expected_output`.
let expected_name_color = expected_output[i]
.0
.map(|color_reference| colors[color_reference[0]][color_reference[1]].as_str());
let expected_target_color = expected_output[i]
.2
.map(|color_reference| colors[color_reference[0]][color_reference[1]].as_str());
// This is the important part. The asserts inside assert_names_and_colors_are_equal
// will panic if the colors or names do not match the expected colors or names.
// Keep in mind an expected color `Option<&str>` of None can mean either that we
// don't expect any color here, as in `expected_output[2], or don't know what specific
// color to expect yet, as in expected_output[0:1].
assert_names_and_colors_are_equal(
matched_name_color,
expected_name_color,
&matched_name,
expected_output[i].1,
matched_target_color,
expected_target_color,
&matched_target,
expected_output[i].3,
);
}
// End of test, only definitions of the helper functions used above follows...
fn get_index_name_target<'a, I>(lines: &mut I) -> Option<(usize, Name, Name)>
where
I: Iterator<Item = (usize, &'a str)>,
{
match lines.next() {
Some((c, s)) => {
// `name` is whatever comes between \x1b (inclusive) and the arrow.
let name = String::from("\x1b")
+ s.split(" -> ")
.next()
.unwrap()
.split(" \x1b")
.last()
.unwrap();
// `target` is whatever comes after the arrow.
let target = s.split(" -> ").last().unwrap().to_string();
Some((c, name, target))
}
None => None,
}
}
#[allow(clippy::too_many_arguments)]
fn assert_names_and_colors_are_equal(
name_color: &str,
expected_name_color: Option<&str>,
name: &str,
expected_name: &str,
target_color: &str,
expected_target_color: Option<&str>,
target: &str,
expected_target: &str,
) {
// Names are always compared.
assert_eq!(&name, &expected_name);
assert_eq!(&target, &expected_target);
// Colors are only compared when we have inferred what color we are looking for.
if expected_name_color.is_some() {
assert_eq!(&name_color, &expected_name_color.unwrap());
}
if expected_target_color.is_some() {
assert_eq!(&target_color, &expected_target_color.unwrap());
}
}
fn capture_colored_string(input: &str) -> (Color, Name) {
let colored_name = Regex::new(r"\x1b\[([0-9;]+)m(.+)\x1b\[0m").unwrap();
match colored_name.captures(input) {
Some(captures) => (
captures.get(1).unwrap().as_str().to_string(),
captures.get(2).unwrap().as_str().to_string(),
),
None => ("".to_string(), input.to_string()),
}
}
fn prepare_folder_structure(scene: &TestScenario) {
// There is no way to change directory in the CI, so this is the best we can do.
// Also, keep in mind that windows might require privilege to symlink directories.
//
// We use scene.ccmd instead of scene.fixtures because we care about relative symlinks.
// So we're going to try out the built mkdir, touch, and ln here, and we expect them to succeed.
scene.ccmd("mkdir").arg("dir1").succeeds();
scene.ccmd("mkdir").arg("dir1/dir2").succeeds();
scene.ccmd("mkdir").arg("dir1/dir2/dir3").succeeds();
scene.ccmd("touch").arg("dir1/file1").succeeds();
scene
.ccmd("ln")
.arg("-s")
.arg("dir1/dir2")
.arg("dir1/ln-dir-invalid")
.succeeds();
scene
.ccmd("ln")
.arg("-s")
.arg("./dir1/dir2/dir3")
.arg("ln-dir3")
.succeeds();
scene
.ccmd("ln")
.arg("-s")
.arg("../..")
.arg("dir1/ln-up2")
.succeeds();
scene
.ccmd("ln")
.arg("-s")
.arg("/")
.arg("dir1/ln-root")
.succeeds();
scene
.ccmd("ln")
.arg("-s")
.arg("dir1/file1")
.arg("ln-file1")
.succeeds();
scene
.ccmd("ln")
.arg("-s")
.arg("dir1/invalid-target")
.arg("ln-file-invalid")
.succeeds();
}
}
#[test]
fn test_ls_long_total_size() {
let scene = TestScenario::new(util_name!());
@ -383,55 +638,57 @@ fn test_ls_long_formats() {
let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-long-formats"));
// Zero or one "." for indicating a file with security context
// Regex for three names, so all of author, group and owner
let re_three = Regex::new(r"[xrw-]{9} \d ([-0-9_a-z]+ ){3}0").unwrap();
let re_three = Regex::new(r"[xrw-]{9}\.? \d ([-0-9_a-z]+ ){3}0").unwrap();
#[cfg(unix)]
let re_three_num = Regex::new(r"[xrw-]{9} \d (\d+ ){3}0").unwrap();
let re_three_num = Regex::new(r"[xrw-]{9}\.? \d (\d+ ){3}0").unwrap();
// Regex for two names, either:
// - group and owner
// - author and owner
// - author and group
let re_two = Regex::new(r"[xrw-]{9} \d ([-0-9_a-z]+ ){2}0").unwrap();
let re_two = Regex::new(r"[xrw-]{9}\.? \d ([-0-9_a-z]+ ){2}0").unwrap();
#[cfg(unix)]
let re_two_num = Regex::new(r"[xrw-]{9} \d (\d+ ){2}0").unwrap();
let re_two_num = Regex::new(r"[xrw-]{9}\.? \d (\d+ ){2}0").unwrap();
// Regex for one name: author, group or owner
let re_one = Regex::new(r"[xrw-]{9} \d [-0-9_a-z]+ 0").unwrap();
let re_one = Regex::new(r"[xrw-]{9}\.? \d [-0-9_a-z]+ 0").unwrap();
#[cfg(unix)]
let re_one_num = Regex::new(r"[xrw-]{9} \d \d+ 0").unwrap();
let re_one_num = Regex::new(r"[xrw-]{9}\.? \d \d+ 0").unwrap();
// Regex for no names
let re_zero = Regex::new(r"[xrw-]{9} \d 0").unwrap();
let re_zero = Regex::new(r"[xrw-]{9}\.? \d 0").unwrap();
let result = scene
scene
.ucmd()
.arg("-l")
.arg("--author")
.arg("test-long-formats")
.succeeds();
assert!(re_three.is_match(result.stdout_str()));
.succeeds()
.stdout_matches(&re_three);
let result = scene
scene
.ucmd()
.arg("-l1")
.arg("--author")
.arg("test-long-formats")
.succeeds();
assert!(re_three.is_match(result.stdout_str()));
.succeeds()
.stdout_matches(&re_three);
#[cfg(unix)]
{
let result = scene
scene
.ucmd()
.arg("-n")
.arg("--author")
.arg("test-long-formats")
.succeeds();
assert!(re_three_num.is_match(result.stdout_str()));
.succeeds()
.stdout_matches(&re_three_num);
}
for arg in &[
@ -441,22 +698,22 @@ fn test_ls_long_formats() {
"-lG --author", // only author and owner
"-l --no-group --author", // only author and owner
] {
let result = scene
scene
.ucmd()
.args(&arg.split(' ').collect::<Vec<_>>())
.arg("test-long-formats")
.succeeds();
assert!(re_two.is_match(result.stdout_str()));
.succeeds()
.stdout_matches(&re_two);
#[cfg(unix)]
{
let result = scene
scene
.ucmd()
.arg("-n")
.args(&arg.split(' ').collect::<Vec<_>>())
.arg("test-long-formats")
.succeeds();
assert!(re_two_num.is_match(result.stdout_str()));
.succeeds()
.stdout_matches(&re_two_num);
}
}
@ -470,22 +727,22 @@ fn test_ls_long_formats() {
"-l --no-group", // only owner
"-gG --author", // only author
] {
let result = scene
scene
.ucmd()
.args(&arg.split(' ').collect::<Vec<_>>())
.arg("test-long-formats")
.succeeds();
assert!(re_one.is_match(result.stdout_str()));
.succeeds()
.stdout_matches(&re_one);
#[cfg(unix)]
{
let result = scene
scene
.ucmd()
.arg("-n")
.args(&arg.split(' ').collect::<Vec<_>>())
.arg("test-long-formats")
.succeeds();
assert!(re_one_num.is_match(result.stdout_str()));
.succeeds()
.stdout_matches(&re_one_num);
}
}
@ -502,22 +759,22 @@ fn test_ls_long_formats() {
"-og1",
"-og1l",
] {
let result = scene
scene
.ucmd()
.args(&arg.split(' ').collect::<Vec<_>>())
.arg("test-long-formats")
.succeeds();
assert!(re_zero.is_match(result.stdout_str()));
.succeeds()
.stdout_matches(&re_zero);
#[cfg(unix)]
{
let result = scene
scene
.ucmd()
.arg("-n")
.args(&arg.split(' ').collect::<Vec<_>>())
.arg("test-long-formats")
.succeeds();
assert!(re_zero.is_match(result.stdout_str()));
.succeeds()
.stdout_matches(&re_zero);
}
}
}
@ -995,7 +1252,7 @@ fn test_ls_inode() {
at.touch(file);
let re_short = Regex::new(r" *(\d+) test_inode").unwrap();
let re_long = Regex::new(r" *(\d+) [xrw-]{10} \d .+ test_inode").unwrap();
let re_long = Regex::new(r" *(\d+) [xrw-]{10}\.? \d .+ test_inode").unwrap();
let result = scene.ucmd().arg("test_inode").arg("-i").succeeds();
assert!(re_short.is_match(result.stdout_str()));
@ -1361,6 +1618,7 @@ fn test_ls_quoting_style() {
// Default is shell-escape
scene
.ucmd()
.arg("--hide-control-chars")
.arg("one\ntwo")
.succeeds()
.stdout_only("'one'$'\\n''two'\n");
@ -1382,23 +1640,8 @@ fn test_ls_quoting_style() {
] {
scene
.ucmd()
.arg(arg)
.arg("one\ntwo")
.succeeds()
.stdout_only(format!("{}\n", correct));
}
for (arg, correct) in &[
("--quoting-style=literal", "one?two"),
("-N", "one?two"),
("--literal", "one?two"),
("--quoting-style=shell", "one?two"),
("--quoting-style=shell-always", "'one?two'"),
] {
scene
.ucmd()
.arg(arg)
.arg("--hide-control-chars")
.arg(arg)
.arg("one\ntwo")
.succeeds()
.stdout_only(format!("{}\n", correct));
@ -1408,7 +1651,7 @@ fn test_ls_quoting_style() {
("--quoting-style=literal", "one\ntwo"),
("-N", "one\ntwo"),
("--literal", "one\ntwo"),
("--quoting-style=shell", "one\ntwo"),
("--quoting-style=shell", "one\ntwo"), // FIXME: GNU ls quotes this case
("--quoting-style=shell-always", "'one\ntwo'"),
] {
scene
@ -1435,6 +1678,7 @@ fn test_ls_quoting_style() {
] {
scene
.ucmd()
.arg("--hide-control-chars")
.arg(arg)
.arg("one\\two")
.succeeds()
@ -1450,6 +1694,7 @@ fn test_ls_quoting_style() {
] {
scene
.ucmd()
.arg("--hide-control-chars")
.arg(arg)
.arg("one\n&two")
.succeeds()
@ -1480,6 +1725,7 @@ fn test_ls_quoting_style() {
] {
scene
.ucmd()
.arg("--hide-control-chars")
.arg(arg)
.arg("one two")
.succeeds()
@ -1503,6 +1749,7 @@ fn test_ls_quoting_style() {
] {
scene
.ucmd()
.arg("--hide-control-chars")
.arg(arg)
.arg("one")
.succeeds()
@ -2029,3 +2276,68 @@ fn test_ls_dangling_symlinks() {
.succeeds() // this should fail, though at the moment, ls lacks a way to propagate errors encountered during display
.stdout_contains(if cfg!(windows) { "dangle" } else { "? dangle" });
}
#[test]
#[cfg(feature = "feat_selinux")]
fn test_ls_context1() {
use selinux::{self, KernelSupport};
if selinux::kernel_support() == KernelSupport::Unsupported {
println!("test skipped: Kernel has no support for SElinux context",);
return;
}
let file = "test_ls_context_file";
let expected = format!("unconfined_u:object_r:user_tmp_t:s0 {}\n", file);
let (at, mut ucmd) = at_and_ucmd!();
at.touch(file);
ucmd.args(&["-Z", file]).succeeds().stdout_is(expected);
}
#[test]
#[cfg(feature = "feat_selinux")]
fn test_ls_context2() {
use selinux::{self, KernelSupport};
if selinux::kernel_support() == KernelSupport::Unsupported {
println!("test skipped: Kernel has no support for SElinux context",);
return;
}
let ts = TestScenario::new(util_name!());
for c_flag in &["-Z", "--context"] {
ts.ucmd()
.args(&[c_flag, &"/"])
.succeeds()
.stdout_only(unwrap_or_return!(expected_result(&ts, &[c_flag, &"/"])).stdout_str());
}
}
#[test]
#[cfg(feature = "feat_selinux")]
fn test_ls_context_format() {
use selinux::{self, KernelSupport};
if selinux::kernel_support() == KernelSupport::Unsupported {
println!("test skipped: Kernel has no support for SElinux context",);
return;
}
let ts = TestScenario::new(util_name!());
// NOTE:
// --format=long/verbose matches the output of GNU's ls for --context
// except for the size count which may differ to the size count reported by GNU's ls.
for word in &[
"across",
"commas",
"horizontal",
// "long",
"single-column",
// "verbose",
"vertical",
] {
let format = format!("--format={}", word);
ts.ucmd()
.args(&[&"-Z", &format.as_str(), &"/"])
.succeeds()
.stdout_only(
unwrap_or_return!(expected_result(&ts, &[&"-Z", &format.as_str(), &"/"]))
.stdout_str(),
);
}
}

View file

@ -1,4 +1,6 @@
use crate::common::util::*;
#[cfg(not(windows))]
use std::os::unix::fs::PermissionsExt;
static TEST_DIR1: &str = "mkdir_test1";
static TEST_DIR2: &str = "mkdir_test2";
@ -65,3 +67,36 @@ fn test_mkdir_dup_file() {
// mkdir should fail for a file even if -p is specified.
scene.ucmd().arg("-p").arg(TEST_FILE7).fails();
}
#[test]
#[cfg(not(windows))]
fn test_symbolic_mode() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("-m").arg("a=rwx").arg(TEST_DIR1).succeeds();
let perms = at.metadata(TEST_DIR1).permissions().mode();
assert_eq!(perms, 0o40777)
}
#[test]
#[cfg(not(windows))]
fn test_symbolic_alteration() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("-m").arg("-w").arg(TEST_DIR1).succeeds();
let perms = at.metadata(TEST_DIR1).permissions().mode();
assert_eq!(perms, 0o40555)
}
#[test]
#[cfg(not(windows))]
fn test_multi_symbolic() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("-m")
.arg("u=rwx,g=rx,o=")
.arg(TEST_DIR1)
.succeeds();
let perms = at.metadata(TEST_DIR1).permissions().mode();
assert_eq!(perms, 0o40750)
}

View file

@ -15,11 +15,15 @@ fn test_more_dir_arg() {
// Maybe we could capture the error, i.e. "Device not found" in that case
// but I am leaving this for later
if atty::is(atty::Stream::Stdout) {
let result = new_ucmd!().arg(".").run();
let ts = TestScenario::new(util_name!());
let result = ts.ucmd().arg(".").run();
result.failure();
const EXPECTED_ERROR_MESSAGE: &str =
"more: '.' is a directory.\nTry 'more --help' for more information.";
assert_eq!(result.stderr_str().trim(), EXPECTED_ERROR_MESSAGE);
let expected_error_message = &format!(
"{0}: '.' is a directory.\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
);
assert_eq!(result.stderr_str().trim(), expected_error_message);
} else {
}
}

View file

@ -522,14 +522,17 @@ fn test_mv_backup_off() {
#[test]
fn test_mv_backup_no_clobber_conflicting_options() {
let (_, mut ucmd) = at_and_ucmd!();
let ts = TestScenario::new(util_name!());
ucmd.arg("--backup")
ts.ucmd().arg("--backup")
.arg("--no-clobber")
.arg("file1")
.arg("file2")
.fails()
.stderr_is("mv: options --backup and --no-clobber are mutually exclusive\nTry 'mv --help' for more information.");
.stderr_is(&format!("{0}: options --backup and --no-clobber are mutually exclusive\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
}
#[test]

View file

@ -22,10 +22,15 @@ fn test_negative_adjustment() {
#[test]
fn test_adjustment_with_no_command_should_error() {
new_ucmd!()
let ts = TestScenario::new(util_name!());
ts.ucmd()
.args(&["-n", "19"])
.run()
.stderr_is("nice: A command must be given with an adjustment.\nTry \"nice --help\" for more information.\n");
.stderr_is(&format!("{0}: A command must be given with an adjustment.\nTry '{1} {0} --help' for more information.\n",
ts.util_name,
ts.bin_path.to_string_lossy()
));
}
#[test]

View file

@ -1,3 +1,4 @@
// spell-checker:ignore abcdefghijklmnopqrstuvwxyz
// * This file is part of the uutils coreutils package.
// *
// * For the full copyright and license information, please view the LICENSE
@ -35,9 +36,10 @@ fn test_file() {
{
let mut f = File::create(&file).unwrap();
// spell-checker:disable-next-line
if f.write_all(b"abcdefghijklmnopqrstuvwxyz\n").is_err() {
panic!("Test setup failed - could not write file");
}
assert!(
!f.write_all(b"abcdefghijklmnopqrstuvwxyz\n").is_err(),
"Test setup failed - could not write file"
);
}
new_ucmd!()
@ -75,9 +77,10 @@ fn test_2files() {
// spell-checker:disable-next-line
for &(path, data) in &[(&file1, "abcdefghijklmnop"), (&file2, "qrstuvwxyz\n")] {
let mut f = File::create(&path).unwrap();
if f.write_all(data.as_bytes()).is_err() {
panic!("Test setup failed - could not write file");
}
assert!(
!f.write_all(data.as_bytes()).is_err(),
"Test setup failed - could not write file"
);
}
new_ucmd!()
@ -126,9 +129,10 @@ fn test_from_mixed() {
let (data1, data2, data3) = ("abcdefg", "hijklmnop", "qrstuvwxyz\n");
for &(path, data) in &[(&file1, data1), (&file3, data3)] {
let mut f = File::create(&path).unwrap();
if f.write_all(data.as_bytes()).is_err() {
panic!("Test setup failed - could not write file");
}
assert!(
!f.write_all(data.as_bytes()).is_err(),
"Test setup failed - could not write file"
);
}
new_ucmd!()

View file

@ -1,9 +1,13 @@
// spell-checker:ignore (words) symdir somefakedir
use std::path::PathBuf;
use crate::common::util::*;
#[test]
fn test_default() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.run().stdout_is(at.root_dir_resolved() + "\n");
ucmd.succeeds().stdout_is(at.root_dir_resolved() + "\n");
}
#[test]
@ -11,3 +15,118 @@ fn test_failed() {
let (_at, mut ucmd) = at_and_ucmd!();
ucmd.arg("will-fail").fails();
}
#[cfg(unix)]
#[test]
fn test_deleted_dir() {
use std::process::Command;
let ts = TestScenario::new(util_name!());
let at = ts.fixtures.clone();
let output = Command::new("sh")
.arg("-c")
.arg(format!(
"cd '{}'; mkdir foo; cd foo; rmdir ../foo; exec {} {}",
at.root_dir_resolved(),
ts.bin_path.to_str().unwrap(),
ts.util_name,
))
.output()
.unwrap();
assert!(!output.status.success());
assert!(output.stdout.is_empty());
assert_eq!(
output.stderr,
b"pwd: failed to get current directory: No such file or directory\n"
);
}
struct Env {
ucmd: UCommand,
#[cfg(not(windows))]
root: String,
subdir: String,
symdir: String,
}
fn symlinked_env() -> Env {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("subdir");
// Note: on Windows this requires admin permissions
at.symlink_dir("subdir", "symdir");
let root = PathBuf::from(at.root_dir_resolved());
ucmd.raw.current_dir(root.join("symdir"));
#[cfg(not(windows))]
ucmd.env("PWD", root.join("symdir"));
Env {
ucmd,
#[cfg(not(windows))]
root: root.to_string_lossy().into_owned(),
subdir: root.join("subdir").to_string_lossy().into_owned(),
symdir: root.join("symdir").to_string_lossy().into_owned(),
}
}
#[test]
fn test_symlinked_logical() {
let mut env = symlinked_env();
env.ucmd.arg("-L").succeeds().stdout_is(env.symdir + "\n");
}
#[test]
fn test_symlinked_physical() {
let mut env = symlinked_env();
env.ucmd.arg("-P").succeeds().stdout_is(env.subdir + "\n");
}
#[test]
fn test_symlinked_default() {
let mut env = symlinked_env();
env.ucmd.succeeds().stdout_is(env.subdir + "\n");
}
#[cfg(not(windows))]
pub mod untrustworthy_pwd_var {
use std::path::Path;
use super::*;
#[test]
fn test_nonexistent_logical() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("-L")
.env("PWD", "/somefakedir")
.succeeds()
.stdout_is(at.root_dir_resolved() + "\n");
}
#[test]
fn test_wrong_logical() {
let mut env = symlinked_env();
env.ucmd
.arg("-L")
.env("PWD", env.root)
.succeeds()
.stdout_is(env.subdir + "\n");
}
#[test]
fn test_redundant_logical() {
let mut env = symlinked_env();
env.ucmd
.arg("-L")
.env("PWD", Path::new(&env.symdir).join("."))
.succeeds()
.stdout_is(env.subdir + "\n");
}
#[test]
fn test_relative_logical() {
let mut env = symlinked_env();
env.ucmd
.arg("-L")
.env("PWD", ".")
.succeeds()
.stdout_is(env.subdir + "\n");
}
}

View file

@ -2,6 +2,17 @@ use crate::common::util::*;
static GIBBERISH: &str = "supercalifragilisticexpialidocious";
#[test]
fn test_resolve() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch("foo");
at.symlink_file("foo", "bar");
scene.ucmd().arg("bar").succeeds().stdout_contains("foo\n");
}
#[test]
fn test_canonicalize() {
let (at, mut ucmd) = at_and_ucmd!();

View file

@ -1,5 +1,9 @@
use crate::common::util::*;
use std::path::Path;
static GIBBERISH: &str = "supercalifragilisticexpialidocious";
#[test]
fn test_realpath_current_directory() {
let (at, mut ucmd) = at_and_ucmd!();
@ -106,3 +110,95 @@ fn test_realpath_file_and_links_strip_zero() {
.succeeds()
.stdout_contains("bar\u{0}");
}
#[test]
fn test_realpath_physical_mode() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir("dir1");
at.mkdir_all("dir2/bar");
at.symlink_dir("dir2/bar", "dir1/foo");
scene
.ucmd()
.arg("dir1/foo/..")
.succeeds()
.stdout_contains("dir2\n");
}
#[test]
fn test_realpath_logical_mode() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir("dir1");
at.mkdir("dir2");
at.symlink_dir("dir2", "dir1/foo");
scene
.ucmd()
.arg("-L")
.arg("dir1/foo/..")
.succeeds()
.stdout_contains("dir1\n");
}
#[test]
fn test_realpath_dangling() {
let (at, mut ucmd) = at_and_ucmd!();
at.symlink_file("nonexistent-file", "link");
ucmd.arg("link")
.succeeds()
.stdout_only(at.plus_as_string("nonexistent-file\n"));
}
#[test]
fn test_realpath_loop() {
let (at, mut ucmd) = at_and_ucmd!();
at.symlink_file("2", "1");
at.symlink_file("3", "2");
at.symlink_file("1", "3");
ucmd.arg("1")
.succeeds()
.stdout_only(at.plus_as_string("2\n"));
}
#[test]
fn test_realpath_default_allows_final_non_existent() {
let p = Path::new("").join(GIBBERISH);
let (at, mut ucmd) = at_and_ucmd!();
let expect = path_concat!(at.root_dir_resolved(), p.to_str().unwrap()) + "\n";
ucmd.arg(p.as_os_str()).succeeds().stdout_only(expect);
}
#[test]
fn test_realpath_default_forbids_non_final_non_existent() {
let p = Path::new("").join(GIBBERISH).join(GIBBERISH);
new_ucmd!().arg(p.to_str().unwrap()).fails();
}
#[test]
fn test_realpath_existing() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("-e")
.arg(".")
.succeeds()
.stdout_only(at.plus_as_string(&format!("{}\n", at.root_dir_resolved())));
}
#[test]
fn test_realpath_existing_error() {
new_ucmd!().arg("-e").arg(GIBBERISH).fails();
}
#[test]
fn test_realpath_missing() {
let p = Path::new("").join(GIBBERISH).join(GIBBERISH);
let (at, mut ucmd) = at_and_ucmd!();
let expect = path_concat!(at.root_dir_resolved(), p.to_str().unwrap()) + "\n";
ucmd.arg("-m")
.arg(p.as_os_str())
.succeeds()
.stdout_only(expect);
}

View file

@ -255,10 +255,12 @@ fn test_rm_force_no_operand() {
#[test]
fn test_rm_no_operand() {
let mut ucmd = new_ucmd!();
ucmd.fails()
.stderr_is("rm: missing an argument\nrm: for help, try 'rm --help'\n");
let ts = TestScenario::new(util_name!());
ts.ucmd().fails().stderr_is(&format!(
"{0}: missing an argument\n{0}: for help, try '{1} {0} --help'\n",
ts.util_name,
ts.bin_path.to_string_lossy()
));
}
#[test]

View file

@ -1,126 +1,238 @@
use crate::common::util::*;
const DIR: &str = "dir";
const DIR_FILE: &str = "dir/file";
const NESTED_DIR: &str = "dir/ect/ory";
const NESTED_DIR_FILE: &str = "dir/ect/ory/file";
#[cfg(windows)]
const NOT_FOUND: &str = "The system cannot find the file specified.";
#[cfg(not(windows))]
const NOT_FOUND: &str = "No such file or directory";
#[cfg(windows)]
const NOT_EMPTY: &str = "The directory is not empty.";
#[cfg(not(windows))]
const NOT_EMPTY: &str = "Directory not empty";
#[cfg(windows)]
const NOT_A_DIRECTORY: &str = "The directory name is invalid.";
#[cfg(not(windows))]
const NOT_A_DIRECTORY: &str = "Not a directory";
#[test]
fn test_rmdir_empty_directory_no_parents() {
let (at, mut ucmd) = at_and_ucmd!();
let dir = "test_rmdir_empty_no_parents";
at.mkdir(dir);
assert!(at.dir_exists(dir));
at.mkdir(DIR);
ucmd.arg(dir).succeeds().no_stderr();
ucmd.arg(DIR).succeeds().no_stderr();
assert!(!at.dir_exists(dir));
assert!(!at.dir_exists(DIR));
}
#[test]
fn test_rmdir_empty_directory_with_parents() {
let (at, mut ucmd) = at_and_ucmd!();
let dir = "test_rmdir_empty/with/parents";
at.mkdir_all(dir);
assert!(at.dir_exists(dir));
at.mkdir_all(NESTED_DIR);
ucmd.arg("-p").arg(dir).succeeds().no_stderr();
ucmd.arg("-p").arg(NESTED_DIR).succeeds().no_stderr();
assert!(!at.dir_exists(dir));
assert!(!at.dir_exists(NESTED_DIR));
assert!(!at.dir_exists(DIR));
}
#[test]
fn test_rmdir_nonempty_directory_no_parents() {
let (at, mut ucmd) = at_and_ucmd!();
let dir = "test_rmdir_nonempty_no_parents";
let file = "test_rmdir_nonempty_no_parents/foo";
at.mkdir(dir);
assert!(at.dir_exists(dir));
at.mkdir(DIR);
at.touch(DIR_FILE);
at.touch(file);
assert!(at.file_exists(file));
ucmd.arg(DIR)
.fails()
.stderr_is(format!("rmdir: failed to remove 'dir': {}", NOT_EMPTY));
ucmd.arg(dir).fails().stderr_is(
"rmdir: failed to remove 'test_rmdir_nonempty_no_parents': Directory not \
empty\n",
);
assert!(at.dir_exists(dir));
assert!(at.dir_exists(DIR));
}
#[test]
fn test_rmdir_nonempty_directory_with_parents() {
let (at, mut ucmd) = at_and_ucmd!();
let dir = "test_rmdir_nonempty/with/parents";
let file = "test_rmdir_nonempty/with/parents/foo";
at.mkdir_all(dir);
assert!(at.dir_exists(dir));
at.mkdir_all(NESTED_DIR);
at.touch(NESTED_DIR_FILE);
at.touch(file);
assert!(at.file_exists(file));
ucmd.arg("-p").arg(NESTED_DIR).fails().stderr_is(format!(
"rmdir: failed to remove 'dir/ect/ory': {}",
NOT_EMPTY
));
ucmd.arg("-p").arg(dir).fails().stderr_is(
"rmdir: failed to remove 'test_rmdir_nonempty/with/parents': Directory not \
empty\nrmdir: failed to remove 'test_rmdir_nonempty/with': Directory not \
empty\nrmdir: failed to remove 'test_rmdir_nonempty': Directory not \
empty\n",
);
assert!(at.dir_exists(dir));
assert!(at.dir_exists(NESTED_DIR));
}
#[test]
fn test_rmdir_ignore_nonempty_directory_no_parents() {
let (at, mut ucmd) = at_and_ucmd!();
let dir = "test_rmdir_ignore_nonempty_no_parents";
let file = "test_rmdir_ignore_nonempty_no_parents/foo";
at.mkdir(dir);
assert!(at.dir_exists(dir));
at.touch(file);
assert!(at.file_exists(file));
at.mkdir(DIR);
at.touch(DIR_FILE);
ucmd.arg("--ignore-fail-on-non-empty")
.arg(dir)
.arg(DIR)
.succeeds()
.no_stderr();
assert!(at.dir_exists(dir));
assert!(at.dir_exists(DIR));
}
#[test]
fn test_rmdir_ignore_nonempty_directory_with_parents() {
let (at, mut ucmd) = at_and_ucmd!();
let dir = "test_rmdir_ignore_nonempty/with/parents";
let file = "test_rmdir_ignore_nonempty/with/parents/foo";
at.mkdir_all(dir);
assert!(at.dir_exists(dir));
at.touch(file);
assert!(at.file_exists(file));
at.mkdir_all(NESTED_DIR);
at.touch(NESTED_DIR_FILE);
ucmd.arg("--ignore-fail-on-non-empty")
.arg("-p")
.arg(dir)
.arg(NESTED_DIR)
.succeeds()
.no_stderr();
assert!(at.dir_exists(dir));
assert!(at.dir_exists(NESTED_DIR));
}
#[test]
fn test_rmdir_remove_symlink_match_gnu_error() {
fn test_rmdir_not_a_directory() {
let (at, mut ucmd) = at_and_ucmd!();
let file = "file";
let fl = "fl";
at.touch(file);
assert!(at.file_exists(file));
at.symlink_file(file, fl);
assert!(at.file_exists(fl));
at.touch("file");
ucmd.arg("fl/")
ucmd.arg("--ignore-fail-on-non-empty")
.arg("file")
.fails()
.stderr_is("rmdir: failed to remove 'fl/': Not a directory");
.no_stdout()
.stderr_is(format!(
"rmdir: failed to remove 'file': {}",
NOT_A_DIRECTORY
));
}
#[test]
fn test_verbose_single() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir(DIR);
ucmd.arg("-v")
.arg(DIR)
.succeeds()
.no_stderr()
.stdout_is("rmdir: removing directory, 'dir'\n");
}
#[test]
fn test_verbose_multi() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir(DIR);
ucmd.arg("-v")
.arg("does_not_exist")
.arg(DIR)
.fails()
.stdout_is(
"rmdir: removing directory, 'does_not_exist'\n\
rmdir: removing directory, 'dir'\n",
)
.stderr_is(format!(
"rmdir: failed to remove 'does_not_exist': {}",
NOT_FOUND
));
}
#[test]
fn test_verbose_nested_failure() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir_all(NESTED_DIR);
at.touch("dir/ect/file");
ucmd.arg("-pv")
.arg(NESTED_DIR)
.fails()
.stdout_is(
"rmdir: removing directory, 'dir/ect/ory'\n\
rmdir: removing directory, 'dir/ect'\n",
)
.stderr_is(format!("rmdir: failed to remove 'dir/ect': {}", NOT_EMPTY));
}
#[cfg(unix)]
#[test]
fn test_rmdir_ignore_nonempty_no_permissions() {
use std::fs;
let (at, mut ucmd) = at_and_ucmd!();
// We make the *parent* dir read-only to prevent deleting the dir in it.
at.mkdir_all("dir/ect/ory");
at.touch("dir/ect/ory/file");
let dir_ect = at.plus("dir/ect");
let mut perms = fs::metadata(&dir_ect).unwrap().permissions();
perms.set_readonly(true);
fs::set_permissions(&dir_ect, perms.clone()).unwrap();
// rmdir should now get a permissions error that it interprets as
// a non-empty error.
ucmd.arg("--ignore-fail-on-non-empty")
.arg("dir/ect/ory")
.succeeds()
.no_stderr();
assert!(at.dir_exists("dir/ect/ory"));
// Politely restore permissions for cleanup
perms.set_readonly(false);
fs::set_permissions(&dir_ect, perms).unwrap();
}
#[test]
fn test_rmdir_remove_symlink_file() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("file");
at.symlink_file("file", "fl");
ucmd.arg("fl/").fails().stderr_is(format!(
"rmdir: failed to remove 'fl/': {}",
NOT_A_DIRECTORY
));
}
// This behavior is known to happen on Linux but not all Unixes
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_rmdir_remove_symlink_dir() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("dir");
at.symlink_dir("dir", "dl");
ucmd.arg("dl/")
.fails()
.stderr_is("rmdir: failed to remove 'dl/': Symbolic link not followed");
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_rmdir_remove_symlink_dangling() {
let (at, mut ucmd) = at_and_ucmd!();
at.symlink_dir("dir", "dl");
ucmd.arg("dl/")
.fails()
.stderr_is("rmdir: failed to remove 'dl/': Symbolic link not followed");
}

View file

@ -0,0 +1,151 @@
// spell-checker:ignore (jargon) xattributes
#![cfg(feature = "feat_selinux")]
use crate::common::util::*;
// TODO: Check the implementation of `--compute` somehow.
#[test]
fn version() {
new_ucmd!().arg("--version").succeeds();
new_ucmd!().arg("-V").succeeds();
}
#[test]
fn help() {
new_ucmd!().arg("--help").succeeds();
new_ucmd!().arg("-h").succeeds();
}
#[test]
fn print() {
new_ucmd!().succeeds();
for &flag in &["-c", "--compute"] {
new_ucmd!().arg(flag).succeeds();
}
for &flag in &[
"-t", "--type", "-u", "--user", "-r", "--role", "-l", "--range",
] {
new_ucmd!().args(&[flag, "example"]).succeeds();
new_ucmd!().args(&[flag, "example1,example2"]).succeeds();
}
}
#[test]
fn invalid() {
new_ucmd!().arg("invalid").fails().code_is(1);
let args = &[
"unconfined_u:unconfined_r:unconfined_t:s0",
"inexistent-file",
];
new_ucmd!().args(args).fails().code_is(127);
let args = &["invalid", "/bin/true"];
new_ucmd!().args(args).fails().code_is(1);
let args = &["--compute", "inexistent-file"];
new_ucmd!().args(args).fails().code_is(1);
let args = &["--compute", "--compute"];
new_ucmd!().args(args).fails().code_is(1);
// clap has an issue that makes this test fail: https://github.com/clap-rs/clap/issues/1543
// TODO: Enable this code once the issue is fixed in the clap version we're using.
//new_ucmd!().arg("--compute=example").fails().code_is(1);
for &flag in &[
"-t", "--type", "-u", "--user", "-r", "--role", "-l", "--range",
] {
new_ucmd!().arg(flag).fails().code_is(1);
let args = &[flag, "example", flag, "example"];
new_ucmd!().args(args).fails().code_is(1);
}
}
#[test]
fn plain_context() {
let ctx = "unconfined_u:unconfined_r:unconfined_t:s0-s0";
new_ucmd!().args(&[ctx, "/bin/true"]).succeeds();
new_ucmd!().args(&[ctx, "/bin/false"]).fails().code_is(1);
let output = new_ucmd!().args(&[ctx, "sestatus", "-v"]).succeeds();
let r = get_sestatus_context(output.stdout());
assert_eq!(r, "unconfined_u:unconfined_r:unconfined_t:s0");
let ctx = "system_u:unconfined_r:unconfined_t:s0-s0";
new_ucmd!().args(&[ctx, "/bin/true"]).succeeds();
let ctx = "system_u:system_r:unconfined_t:s0";
let output = new_ucmd!().args(&[ctx, "sestatus", "-v"]).succeeds();
assert_eq!(get_sestatus_context(output.stdout()), ctx);
}
#[test]
fn custom_context() {
let t_ud = "unconfined_t";
let u_ud = "unconfined_u";
let r_ud = "unconfined_r";
new_ucmd!().args(&["--compute", "/bin/true"]).succeeds();
let args = &["--compute", "/bin/false"];
new_ucmd!().args(args).fails().code_is(1);
let args = &["--type", t_ud, "/bin/true"];
new_ucmd!().args(args).succeeds();
let args = &["--compute", "--type", t_ud, "/bin/true"];
new_ucmd!().args(args).succeeds();
let args = &["--user=system_u", "/bin/true"];
new_ucmd!().args(args).succeeds();
let args = &["--compute", "--user=system_u", "/bin/true"];
new_ucmd!().args(args).succeeds();
let args = &["--role=system_r", "/bin/true"];
new_ucmd!().args(args).succeeds();
let args = &["--compute", "--role=system_r", "/bin/true"];
new_ucmd!().args(args).succeeds();
new_ucmd!().args(&["--range=s0", "/bin/true"]).succeeds();
let args = &["--compute", "--range=s0", "/bin/true"];
new_ucmd!().args(args).succeeds();
for &(ctx, u, r) in &[
("unconfined_u:unconfined_r:unconfined_t:s0", u_ud, r_ud),
("system_u:unconfined_r:unconfined_t:s0", "system_u", r_ud),
("unconfined_u:system_r:unconfined_t:s0", u_ud, "system_r"),
("system_u:system_r:unconfined_t:s0", "system_u", "system_r"),
] {
let args = &["-t", t_ud, "-u", u, "-r", r, "-l", "s0", "sestatus", "-v"];
let output = new_ucmd!().args(args).succeeds();
assert_eq!(get_sestatus_context(output.stdout()), ctx);
}
}
fn get_sestatus_context(output: &[u8]) -> &str {
let re = regex::bytes::Regex::new(r#"Current context:\s*(\S+)\s*"#)
.expect("Invalid regular expression");
output
.split(|&b| b == b'\n')
.find(|&b| b.starts_with(b"Current context:"))
.and_then(|line| {
re.captures_iter(line)
.next()
.and_then(|c| c.get(1))
.as_ref()
.map(regex::bytes::Match::as_bytes)
})
.and_then(|bytes| std::str::from_utf8(bytes).ok())
.expect("Output of sestatus is unexpected")
}

View file

@ -1,17 +1,139 @@
use crate::common::util::*;
use std::io::Read;
#[test]
fn test_hex_rejects_sign_after_identifier() {
new_ucmd!()
.args(&["0x-123ABC"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '0x-123ABC'")
.stderr_contains("for more information.");
new_ucmd!()
.args(&["0x+123ABC"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '0x+123ABC'")
.stderr_contains("for more information.");
new_ucmd!()
.args(&["-0x-123ABC"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '-0x-123ABC'")
.stderr_contains("for more information.");
new_ucmd!()
.args(&["-0x+123ABC"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '-0x+123ABC'")
.stderr_contains("for more information.");
}
#[test]
fn test_hex_lowercase_uppercase() {
new_ucmd!()
.args(&["0xa", "0xA"])
.succeeds()
.stdout_is("10\n");
new_ucmd!()
.args(&["0Xa", "0XA"])
.succeeds()
.stdout_is("10\n");
}
#[test]
fn test_hex_big_number() {
new_ucmd!()
.args(&[
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"0x100000000000000000000000000000000",
])
.succeeds()
.stdout_is(
"340282366920938463463374607431768211455\n340282366920938463463374607431768211456\n",
);
}
#[test]
fn test_hex_identifier_in_wrong_place() {
new_ucmd!()
.args(&["1234ABCD0x"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '1234ABCD0x'")
.stderr_contains("for more information.");
}
#[test]
fn test_rejects_nan() {
new_ucmd!().args(&["NaN"]).fails().stderr_only(
"seq: invalid 'not-a-number' argument: 'NaN'\nTry 'seq --help' for more information.",
);
let ts = TestScenario::new(util_name!());
ts.ucmd().args(&["NaN"]).fails().stderr_only(format!(
"{0}: invalid 'not-a-number' argument: 'NaN'\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
}
#[test]
fn test_rejects_non_floats() {
new_ucmd!().args(&["foo"]).fails().stderr_only(
"seq: invalid floating point argument: 'foo'\nTry 'seq --help' for more information.",
);
let ts = TestScenario::new(util_name!());
ts.ucmd().args(&["foo"]).fails().stderr_only(&format!(
"{0}: invalid floating point argument: 'foo'\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
}
#[test]
fn test_invalid_float() {
new_ucmd!()
.args(&["1e2.3"])
.fails()
.no_stdout()
.stderr_contains("invalid floating point argument: '1e2.3'")
.stderr_contains("for more information.");
new_ucmd!()
.args(&["1e2.3", "2"])
.fails()
.no_stdout()
.stderr_contains("invalid floating point argument: '1e2.3'")
.stderr_contains("for more information.");
new_ucmd!()
.args(&["1", "1e2.3"])
.fails()
.no_stdout()
.stderr_contains("invalid floating point argument: '1e2.3'")
.stderr_contains("for more information.");
new_ucmd!()
.args(&["1e2.3", "2", "3"])
.fails()
.no_stdout()
.stderr_contains("invalid floating point argument: '1e2.3'")
.stderr_contains("for more information.");
new_ucmd!()
.args(&["1", "1e2.3", "3"])
.fails()
.no_stdout()
.stderr_contains("invalid floating point argument: '1e2.3'")
.stderr_contains("for more information.");
new_ucmd!()
.args(&["1", "2", "1e2.3"])
.fails()
.no_stdout()
.stderr_contains("invalid floating point argument: '1e2.3'")
.stderr_contains("for more information.");
}
#[test]
fn test_width_invalid_float() {
new_ucmd!()
.args(&["-w", "1e2.3"])
.fails()
.no_stdout()
.stderr_contains("invalid floating point argument: '1e2.3'")
.stderr_contains("for more information.");
}
// ---- Tests for the big integer based path ----
@ -132,3 +254,293 @@ fn test_seq_wrong_arg_floats() {
fn test_zero_step_floats() {
new_ucmd!().args(&["10.0", "0", "32"]).fails();
}
#[test]
fn test_preserve_negative_zero_start() {
new_ucmd!()
.args(&["-0", "1"])
.succeeds()
.stdout_is("-0\n1\n")
.no_stderr();
new_ucmd!()
.args(&["-0", "1", "2"])
.succeeds()
.stdout_is("-0\n1\n2\n")
.no_stderr();
new_ucmd!()
.args(&["-0", "1", "2.0"])
.succeeds()
.stdout_is("-0\n1\n2\n")
.no_stderr();
}
#[test]
fn test_drop_negative_zero_end() {
new_ucmd!()
.args(&["1", "-1", "-0"])
.succeeds()
.stdout_is("1\n0\n")
.no_stderr();
}
#[test]
fn test_width_scientific_notation() {
new_ucmd!()
.args(&["-w", "999", "1e3"])
.succeeds()
.stdout_is("0999\n1000\n")
.no_stderr();
}
#[test]
fn test_width_negative_zero() {
new_ucmd!()
.args(&["-w", "-0", "1"])
.succeeds()
.stdout_is("-0\n01\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0", "1", "2"])
.succeeds()
.stdout_is("-0\n01\n02\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0", "1", "2.0"])
.succeeds()
.stdout_is("-0\n01\n02\n")
.no_stderr();
}
#[test]
fn test_width_negative_zero_decimal_notation() {
new_ucmd!()
.args(&["-w", "-0.0", "1"])
.succeeds()
.stdout_is("-0.0\n01.0\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0.0", "1.0"])
.succeeds()
.stdout_is("-0.0\n01.0\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0.0", "1", "2"])
.succeeds()
.stdout_is("-0.0\n01.0\n02.0\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0.0", "1", "2.0"])
.succeeds()
.stdout_is("-0.0\n01.0\n02.0\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0.0", "1.0", "2"])
.succeeds()
.stdout_is("-0.0\n01.0\n02.0\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0.0", "1.0", "2.0"])
.succeeds()
.stdout_is("-0.0\n01.0\n02.0\n")
.no_stderr();
}
#[test]
fn test_width_negative_zero_scientific_notation() {
new_ucmd!()
.args(&["-w", "-0e0", "1"])
.succeeds()
.stdout_is("-0\n01\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0e0", "1", "2"])
.succeeds()
.stdout_is("-0\n01\n02\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0e0", "1", "2.0"])
.succeeds()
.stdout_is("-0\n01\n02\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0e+1", "1"])
.succeeds()
.stdout_is("-00\n001\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0e+1", "1", "2"])
.succeeds()
.stdout_is("-00\n001\n002\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0e+1", "1", "2.0"])
.succeeds()
.stdout_is("-00\n001\n002\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0.000e0", "1"])
.succeeds()
.stdout_is("-0.000\n01.000\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0.000e0", "1", "2"])
.succeeds()
.stdout_is("-0.000\n01.000\n02.000\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0.000e0", "1", "2.0"])
.succeeds()
.stdout_is("-0.000\n01.000\n02.000\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0.000e-2", "1"])
.succeeds()
.stdout_is("-0.00000\n01.00000\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0.000e-2", "1", "2"])
.succeeds()
.stdout_is("-0.00000\n01.00000\n02.00000\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0.000e-2", "1", "2.0"])
.succeeds()
.stdout_is("-0.00000\n01.00000\n02.00000\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0.000e5", "1"])
.succeeds()
.stdout_is("-000000\n0000001\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0.000e5", "1", "2"])
.succeeds()
.stdout_is("-000000\n0000001\n0000002\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0.000e5", "1", "2.0"])
.succeeds()
.stdout_is("-000000\n0000001\n0000002\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0.000e5", "1"])
.succeeds()
.stdout_is("-000000\n0000001\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0.000e5", "1", "2"])
.succeeds()
.stdout_is("-000000\n0000001\n0000002\n")
.no_stderr();
new_ucmd!()
.args(&["-w", "-0.000e5", "1", "2.0"])
.succeeds()
.stdout_is("-000000\n0000001\n0000002\n")
.no_stderr();
}
#[test]
fn test_width_decimal_scientific_notation_increment() {
new_ucmd!()
.args(&["-w", ".1", "1e-2", ".11"])
.succeeds()
.stdout_is("0.10\n0.11\n")
.no_stderr();
new_ucmd!()
.args(&["-w", ".0", "1.500e-1", ".2"])
.succeeds()
.stdout_is("0.0000\n0.1500\n")
.no_stderr();
}
/// Test that trailing zeros in the start argument contribute to precision.
#[test]
fn test_width_decimal_scientific_notation_trailing_zeros_start() {
new_ucmd!()
.args(&["-w", ".1000", "1e-2", ".11"])
.succeeds()
.stdout_is("0.1000\n0.1100\n")
.no_stderr();
}
/// Test that trailing zeros in the increment argument contribute to precision.
#[test]
fn test_width_decimal_scientific_notation_trailing_zeros_increment() {
new_ucmd!()
.args(&["-w", "1e-1", "0.0100", ".11"])
.succeeds()
.stdout_is("0.1000\n0.1100\n")
.no_stderr();
}
/// Test that trailing zeros in the end argument do not contribute to width.
#[test]
fn test_width_decimal_scientific_notation_trailing_zeros_end() {
new_ucmd!()
.args(&["-w", "1e-1", "1e-2", ".1100"])
.succeeds()
.stdout_is("0.10\n0.11\n")
.no_stderr();
}
#[test]
fn test_width_floats() {
new_ucmd!()
.args(&["-w", "9.0", "10.0"])
.succeeds()
.stdout_is("09.0\n10.0\n")
.no_stderr();
}
// TODO This is duplicated from `test_yes.rs`; refactor them.
/// Run `seq`, capture some of the output, close the pipe, and verify it.
fn run(args: &[&str], expected: &[u8]) {
let mut cmd = new_ucmd!();
let mut child = cmd.args(args).run_no_wait();
let mut stdout = child.stdout.take().unwrap();
let mut buf = vec![0; expected.len()];
stdout.read_exact(&mut buf).unwrap();
drop(stdout);
assert!(child.wait().unwrap().success());
assert_eq!(buf.as_slice(), expected);
}
#[test]
fn test_neg_inf() {
run(&["--", "-inf", "0"], b"-inf\n-inf\n-inf\n");
}
#[test]
fn test_inf() {
run(&["inf"], b"1\n2\n3\n");
}
#[test]
fn test_ignore_leading_whitespace() {
new_ucmd!()
.arg(" 1")
.succeeds()
.stdout_is("1\n")
.no_stderr();
}
#[test]
fn test_trailing_whitespace_error() {
// In some locales, the GNU error message has curly quotes ()
// instead of straight quotes ('). We just test the straight single
// quotes.
new_ucmd!()
.arg("1 ")
.fails()
.no_stdout()
.stderr_contains("seq: invalid floating point argument: '1 '")
// FIXME The second line of the error message is "Try 'seq
// --help' for more information."
.stderr_contains("for more information.");
}

View file

@ -531,7 +531,7 @@ fn test_keys_invalid_field() {
new_ucmd!()
.args(&["-k", "1."])
.fails()
.stderr_only("sort: failed to parse key `1.`: failed to parse character index ``: cannot parse integer from empty string");
.stderr_only("sort: failed to parse key '1.': failed to parse character index '': cannot parse integer from empty string");
}
#[test]
@ -539,7 +539,7 @@ fn test_keys_invalid_field_option() {
new_ucmd!()
.args(&["-k", "1.1x"])
.fails()
.stderr_only("sort: failed to parse key `1.1x`: invalid option: `x`");
.stderr_only("sort: failed to parse key '1.1x': invalid option: 'x'");
}
#[test]
@ -547,7 +547,7 @@ fn test_keys_invalid_field_zero() {
new_ucmd!()
.args(&["-k", "0.1"])
.fails()
.stderr_only("sort: failed to parse key `0.1`: field index can not be 0");
.stderr_only("sort: failed to parse key '0.1': field index can not be 0");
}
#[test]
@ -555,7 +555,7 @@ fn test_keys_invalid_char_zero() {
new_ucmd!()
.args(&["-k", "1.0"])
.fails()
.stderr_only("sort: failed to parse key `1.0`: invalid character index 0 for the start position of a field");
.stderr_only("sort: failed to parse key '1.0': invalid character index 0 for the start position of a field");
}
#[test]

View file

@ -102,7 +102,7 @@ fn test_invalid_option() {
new_ucmd!().arg("-w").arg("-q").arg("/").fails();
}
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
#[cfg(unix)]
const NORMAL_FORMAT_STR: &str =
"%a %A %b %B %d %D %f %F %g %G %h %i %m %n %o %s %u %U %x %X %y %Y %z %Z"; // avoid "%w %W" (birth/creation) due to `stat` limitations and linux kernel & rust version capability variations
#[cfg(any(target_os = "linux"))]

View file

@ -25,15 +25,19 @@ fn test_stdbuf_line_buffered_stdout() {
#[cfg(not(target_os = "windows"))]
#[test]
fn test_stdbuf_no_buffer_option_fails() {
new_ucmd!().args(&["head"]).fails().stderr_is(
let ts = TestScenario::new(util_name!());
ts.ucmd().args(&["head"]).fails().stderr_is(&format!(
"error: The following required arguments were not provided:\n \
--error <MODE>\n \
--input <MODE>\n \
--output <MODE>\n\n\
USAGE:\n \
stdbuf OPTION... COMMAND\n\n\
{1} {0} OPTION... COMMAND\n\n\
For more information try --help",
);
ts.util_name,
ts.bin_path.to_string_lossy()
));
}
#[cfg(not(target_os = "windows"))]
@ -49,9 +53,16 @@ fn test_stdbuf_trailing_var_arg() {
#[cfg(not(target_os = "windows"))]
#[test]
fn test_stdbuf_line_buffering_stdin_fails() {
new_ucmd!().args(&["-i", "L", "head"]).fails().stderr_is(
"stdbuf: line buffering stdin is meaningless\nTry 'stdbuf --help' for more information.",
);
let ts = TestScenario::new(util_name!());
ts.ucmd()
.args(&["-i", "L", "head"])
.fails()
.stderr_is(&format!(
"{0}: line buffering stdin is meaningless\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
}
#[cfg(not(target_os = "windows"))]

View file

@ -59,7 +59,7 @@ fn test_invalid_file() {
at.mkdir("a");
ucmd.arg("a").fails().stderr_is("sum: 'a' Is a directory");
ucmd.arg("a").fails().stderr_is("sum: a: Is a directory");
}
#[test]
@ -68,5 +68,5 @@ fn test_invalid_metadata() {
ucmd.arg("b")
.fails()
.stderr_is("sum: 'b' No such file or directory");
.stderr_is("sum: b: No such file or directory");
}

View file

@ -1,3 +1,4 @@
// spell-checker:ignore axxbxx bxxaxx axxx axxxx xxaxx xxax xxxxa axyz zyax zyxa
use crate::common::util::*;
#[test]
@ -23,7 +24,7 @@ fn test_stdin_non_newline_separator_before() {
.args(&["-b", "-s", ":"])
.pipe_in("100:200:300:400:500")
.run()
.stdout_is("500:400:300:200:100");
.stdout_is(":500:400:300:200100");
}
#[test]
@ -74,6 +75,128 @@ fn test_no_line_separators() {
new_ucmd!().pipe_in("a").succeeds().stdout_is("a");
}
#[test]
fn test_before_trailing_separator_no_leading_separator() {
new_ucmd!()
.arg("-b")
.pipe_in("a\nb\n")
.succeeds()
.stdout_is("\n\nba");
}
#[test]
fn test_before_trailing_separator_and_leading_separator() {
new_ucmd!()
.arg("-b")
.pipe_in("\na\nb\n")
.succeeds()
.stdout_is("\n\nb\na");
}
#[test]
fn test_before_leading_separator_no_trailing_separator() {
new_ucmd!()
.arg("-b")
.pipe_in("\na\nb")
.succeeds()
.stdout_is("\nb\na");
}
#[test]
fn test_before_no_separator() {
new_ucmd!()
.arg("-b")
.pipe_in("ab")
.succeeds()
.stdout_is("ab");
}
#[test]
fn test_before_empty_file() {
new_ucmd!().arg("-b").pipe_in("").succeeds().stdout_is("");
}
#[test]
fn test_multi_char_separator() {
new_ucmd!()
.args(&["-s", "xx"])
.pipe_in("axxbxx")
.succeeds()
.stdout_is("bxxaxx");
}
#[test]
fn test_multi_char_separator_overlap() {
// The right-most pair of "x" characters in the input is treated as
// the only line separator. That is, "axxx" is interpreted as having
// one line comprising the string "ax" followed by the line
// separator "xx".
new_ucmd!()
.args(&["-s", "xx"])
.pipe_in("axxx")
.succeeds()
.stdout_is("axxx");
// Each non-overlapping pair of "x" characters in the input is
// treated as a line separator. That is, "axxxx" is interpreted as
// having two lines:
//
// * the second line is the empty string "" followed by the line
// separator "xx",
// * the first line is the string "a" followed by the line separator
// "xx".
//
// The lines are printed in reverse, resulting in "xx" followed by
// "axx".
new_ucmd!()
.args(&["-s", "xx"])
.pipe_in("axxxx")
.succeeds()
.stdout_is("xxaxx");
}
#[test]
fn test_multi_char_separator_overlap_before() {
// With the "-b" option, the line separator is assumed to be at the
// beginning of the line. In this case, That is, "axxx" is
// interpreted as having two lines:
//
// * the second line is the empty string "" preceded by the line
// separator "xx",
// * the first line is the string "ax" preceded by no line
// separator, since there are no more characters preceding it.
//
// The lines are printed in reverse, resulting in "xx" followed by
// "ax".
new_ucmd!()
.args(&["-b", "-s", "xx"])
.pipe_in("axxx")
.succeeds()
.stdout_is("xxax");
// With the "-b" option, the line separator is assumed to be at the
// beginning of the line. Each non-overlapping pair of "x"
// characters in the input is treated as a line separator. That is,
// "axxxx" is interpreted as having three lines:
//
// * the third line is the empty string "" preceded by the line
// separator "xx" (the last two "x" characters in the input
// string),
// * the second line is the empty string "" preceded by the line
// separator "xx" (the first two "x" characters in the input
// string),
// * the first line is the string "a" preceded by no line separator,
// since there are no more characters preceding it.
//
// The lines are printed in reverse, resulting in "xx" followed by
// "xx" followed by "a".
new_ucmd!()
.args(&["-b", "-s", "xx"])
.pipe_in("axxxx")
.succeeds()
.stdout_is("xxxxa");
}
#[test]
fn test_null_separator() {
new_ucmd!()
@ -82,3 +205,67 @@ fn test_null_separator() {
.succeeds()
.stdout_is("b\0a\0");
}
#[test]
fn test_regex() {
new_ucmd!()
.args(&["-r", "-s", "[xyz]+"])
.pipe_in("axyz")
.succeeds()
.no_stderr()
.stdout_is("zyax");
new_ucmd!()
.args(&["-r", "-s", ":+"])
.pipe_in("a:b::c:::d::::")
.succeeds()
.no_stderr()
.stdout_is(":::d:::c::b:a:");
new_ucmd!()
.args(&["-r", "-s", r"[\+]+[-]+[\+]+"])
// line 0 1 2
// |--||-----||--------|
.pipe_in("a+-+b++--++c+d-e+---+")
.succeeds()
.no_stderr()
// line 2 1 0
// |--------||-----||--|
.stdout_is("c+d-e+---+b++--++a+-+");
}
#[test]
fn test_regex_before() {
new_ucmd!()
.args(&["-b", "-r", "-s", "[xyz]+"])
.pipe_in("axyz")
.succeeds()
.no_stderr()
.stdout_is("zyxa");
new_ucmd!()
.args(&["-b", "-r", "-s", ":+"])
.pipe_in(":a::b:::c::::d")
.succeeds()
.stdout_is(":d::::c:::b::a");
// Because `tac` searches for matches of the regular expression from
// right to left, the second to last line is
//
// +--++b
//
// not
//
// ++--++b
//
new_ucmd!()
.args(&["-b", "-r", "-s", r"[\+]+[-]+[\+]+"])
// line 0 1 2
// |---||----||--------|
.pipe_in("+-+a++--++b+---+c+d-e")
.succeeds()
.no_stderr()
// line 2 1 0
// |--------||----||---|
.stdout_is("+---+c+d-e+--++b+-+a+");
}

View file

@ -425,3 +425,23 @@ fn test_tail_num_with_undocumented_sign_bytes() {
.succeeds()
.stdout_is("efghijklmnopqrstuvwxyz");
}
#[test]
#[cfg(unix)]
fn test_tail_bytes_for_funny_files() {
// gnu/tests/tail-2/tail-c.sh
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
for &file in &["/proc/version", "/sys/kernel/profiling"] {
if !at.file_exists(file) {
continue;
}
let args = ["--bytes", "1", file];
let result = ts.ucmd().args(&args).run();
let exp_result = unwrap_or_return!(expected_result(&ts, &args));
result
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str())
.code_is(exp_result.code());
}
}

View file

@ -35,6 +35,35 @@ fn test_solo_and_or_or_is_a_literal() {
new_ucmd!().arg("-o").succeeds();
}
#[test]
fn test_some_literals() {
let scenario = TestScenario::new(util_name!());
let tests = [
"a string",
"(",
")",
"-",
"--",
"-0",
"-f",
"--help",
"--version",
"-eq",
"-lt",
"-ef",
"[",
];
for test in &tests {
scenario.ucmd().arg(test).succeeds();
}
// run the inverse of all these tests
for test in &tests {
scenario.ucmd().arg("!").arg(test).run().status_code(1);
}
}
#[test]
fn test_double_not_is_false() {
new_ucmd!().args(&["!", "!"]).run().status_code(1);
@ -99,21 +128,6 @@ fn test_zero_len_of_empty() {
new_ucmd!().args(&["-z", ""]).succeeds();
}
#[test]
fn test_solo_parenthesis_is_literal() {
let scenario = TestScenario::new(util_name!());
let tests = [["("], [")"]];
for test in &tests {
scenario.ucmd().args(&test[..]).succeeds();
}
}
#[test]
fn test_solo_empty_parenthetical_is_error() {
new_ucmd!().args(&["(", ")"]).run().status_code(2);
}
#[test]
fn test_zero_len_equals_zero_len() {
new_ucmd!().args(&["", "=", ""]).succeeds();
@ -139,6 +153,7 @@ fn test_string_comparison() {
["contained\nnewline", "=", "contained\nnewline"],
["(", "=", "("],
["(", "!=", ")"],
["(", "!=", "="],
["!", "=", "!"],
["=", "=", "="],
];
@ -199,11 +214,13 @@ fn test_a_bunch_of_not() {
#[test]
fn test_pseudofloat_equal() {
// string comparison; test(1) doesn't support comparison of actual floats
new_ucmd!().args(&["123.45", "=", "123.45"]).succeeds();
}
#[test]
fn test_pseudofloat_not_equal() {
// string comparison; test(1) doesn't support comparison of actual floats
new_ucmd!().args(&["123.45", "!=", "123.450"]).succeeds();
}
@ -230,6 +247,16 @@ fn test_some_int_compares() {
for test in &tests {
scenario.ucmd().args(&test[..]).succeeds();
}
// run the inverse of all these tests
for test in &tests {
scenario
.ucmd()
.arg("!")
.args(&test[..])
.run()
.status_code(1);
}
}
#[test]
@ -257,6 +284,16 @@ fn test_negative_int_compare() {
for test in &tests {
scenario.ucmd().args(&test[..]).succeeds();
}
// run the inverse of all these tests
for test in &tests {
scenario
.ucmd()
.arg("!")
.args(&test[..])
.run()
.status_code(1);
}
}
#[test]
@ -283,7 +320,7 @@ fn test_invalid_utf8_integer_compare() {
cmd.run()
.status_code(2)
.stderr_is("test: invalid integer 'fo<66>o'");
.stderr_is("test: invalid integer $'fo\\x80o'");
let mut cmd = new_ucmd!();
cmd.raw.arg(arg);
@ -291,7 +328,7 @@ fn test_invalid_utf8_integer_compare() {
cmd.run()
.status_code(2)
.stderr_is("test: invalid integer 'fo<66>o'");
.stderr_is("test: invalid integer $'fo\\x80o'");
}
#[test]
@ -477,7 +514,9 @@ fn test_nonexistent_file_is_not_symlink() {
}
#[test]
#[cfg(not(windows))] // Windows has no concept of sticky bit
// FixME: freebsd fails with 'chmod: sticky_file: Inappropriate file type or format'
// Windows has no concept of sticky bit
#[cfg(not(any(windows, target_os = "freebsd")))]
fn test_file_is_sticky() {
let scenario = TestScenario::new(util_name!());
let mut ucmd = scenario.ucmd();
@ -497,6 +536,93 @@ fn test_file_is_not_sticky() {
.status_code(1);
}
#[test]
fn test_solo_empty_parenthetical_is_error() {
new_ucmd!().args(&["(", ")"]).run().status_code(2);
}
#[test]
fn test_parenthesized_literal() {
let scenario = TestScenario::new(util_name!());
let tests = [
"a string",
"(",
")",
"-",
"--",
"-0",
"-f",
"--help",
"--version",
"-e",
"-t",
"!",
"-n",
"-z",
"[",
"-a",
"-o",
];
for test in &tests {
scenario.ucmd().arg("(").arg(test).arg(")").succeeds();
}
// run the inverse of all these tests
for test in &tests {
scenario
.ucmd()
.arg("!")
.arg("(")
.arg(test)
.arg(")")
.run()
.status_code(1);
}
}
#[test]
fn test_parenthesized_op_compares_literal_parenthesis() {
// ensure we arent treating this case as “string length of literal equal
// sign”
new_ucmd!().args(&["(", "=", ")"]).run().status_code(1);
}
#[test]
fn test_parenthesized_string_comparison() {
let scenario = TestScenario::new(util_name!());
let tests = [
["(", "foo", "!=", "bar", ")"],
["(", "contained\nnewline", "=", "contained\nnewline", ")"],
["(", "(", "=", "(", ")"],
["(", "(", "!=", ")", ")"],
["(", "!", "=", "!", ")"],
["(", "=", "=", "=", ")"],
];
for test in &tests {
scenario.ucmd().args(&test[..]).succeeds();
}
// run the inverse of all these tests
for test in &tests {
scenario
.ucmd()
.arg("!")
.args(&test[..])
.run()
.status_code(1);
}
}
#[test]
fn test_parenthesized_right_parenthesis_as_literal() {
new_ucmd!()
.args(&["(", "-f", ")", ")"])
.run()
.status_code(1);
}
#[test]
#[cfg(not(windows))]
fn test_file_owned_by_euid() {

View file

@ -6,6 +6,7 @@ use self::touch::filetime::{self, FileTime};
extern crate time;
use crate::common::util::*;
use std::fs::remove_file;
use std::path::PathBuf;
fn get_file_times(at: &AtPath, path: &str) -> (FileTime, FileTime) {
@ -16,6 +17,7 @@ fn get_file_times(at: &AtPath, path: &str) -> (FileTime, FileTime) {
)
}
#[cfg(not(target_os = "freebsd"))]
fn get_symlink_times(at: &AtPath, path: &str) -> (FileTime, FileTime) {
let m = at.symlink_metadata(path);
(
@ -290,6 +292,8 @@ fn test_touch_set_both() {
}
#[test]
// FixME: Fails on freebsd because of a different nanos
#[cfg(not(target_os = "freebsd"))]
fn test_touch_no_dereference() {
let (at, mut ucmd) = at_and_ucmd!();
let file_a = "test_touch_no_dereference_a";
@ -320,7 +324,8 @@ fn test_touch_no_dereference() {
#[test]
fn test_touch_reference() {
let (at, mut ucmd) = at_and_ucmd!();
let scenario = TestScenario::new("touch");
let (at, mut _ucmd) = (scenario.fixtures.clone(), scenario.ucmd());
let file_a = "test_touch_reference_a";
let file_b = "test_touch_reference_b";
let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000");
@ -328,15 +333,21 @@ fn test_touch_reference() {
at.touch(file_a);
set_file_times(&at, file_a, start_of_year, start_of_year);
assert!(at.file_exists(file_a));
for &opt in &["-r", "--ref", "--reference"] {
scenario
.ccmd("touch")
.args(&[opt, file_a, file_b])
.succeeds()
.no_stderr();
ucmd.args(&["-r", file_a, file_b]).succeeds().no_stderr();
assert!(at.file_exists(file_b));
assert!(at.file_exists(file_b));
let (atime, mtime) = get_file_times(&at, file_b);
assert_eq!(atime, mtime);
assert_eq!(atime, start_of_year);
assert_eq!(mtime, start_of_year);
let (atime, mtime) = get_file_times(&at, file_b);
assert_eq!(atime, mtime);
assert_eq!(atime, start_of_year);
assert_eq!(mtime, start_of_year);
let _ = remove_file(file_b);
}
}
#[test]

View file

@ -286,6 +286,8 @@ fn test_interpret_backslash_at_eol_literally() {
}
#[test]
// FixME: panicked at 'failed to write to stdin of child: Broken pipe (os error 32)
#[cfg(not(target_os = "freebsd"))]
fn test_more_than_2_sets() {
new_ucmd!()
.args(&["'abcdefgh'", "'a", "'b'"])

View file

@ -65,10 +65,24 @@ fn test_wrong_argument() {
}
#[test]
#[cfg(not(windows))]
// FixME: freebsd panic
#[cfg(all(unix, not(target_os = "freebsd")))]
fn test_stdout_fail() {
let mut child = new_ucmd!().run_no_wait();
drop(child.stdout.take());
let status = child.wait().unwrap();
use std::process::{Command, Stdio};
let ts = TestScenario::new(util_name!());
// Sleep inside a shell to ensure the process doesn't finish before we've
// closed its stdout
let mut proc = Command::new("sh")
.arg("-c")
.arg(format!(
"sleep 0.2; exec {} {}",
ts.bin_path.to_str().unwrap(),
ts.util_name
))
.stdout(Stdio::piped())
.spawn()
.unwrap();
drop(proc.stdout.take());
let status = proc.wait().unwrap();
assert_eq!(status.code(), Some(3));
}

View file

@ -14,29 +14,33 @@ fn test_unlink_file() {
#[test]
fn test_unlink_multiple_files() {
let (at, mut ucmd) = at_and_ucmd!();
let ts = TestScenario::new(util_name!());
let (at, mut ucmd) = (ts.fixtures.clone(), ts.ucmd());
let file_a = "test_unlink_multiple_file_a";
let file_b = "test_unlink_multiple_file_b";
at.touch(file_a);
at.touch(file_b);
ucmd.arg(file_a).arg(file_b).fails().stderr_is(
"unlink: extra operand: 'test_unlink_multiple_file_b'\nTry 'unlink --help' \
for more information.\n",
);
ucmd.arg(file_a)
.arg(file_b)
.fails()
.stderr_contains("USAGE");
}
#[test]
fn test_unlink_directory() {
let (at, mut ucmd) = at_and_ucmd!();
let dir = "test_unlink_empty_directory";
let dir = "dir";
at.mkdir(dir);
ucmd.arg(dir).fails().stderr_is(
"unlink: cannot unlink 'test_unlink_empty_directory': Not a regular file \
or symlink\n",
let res = ucmd.arg(dir).fails();
let stderr = res.stderr_str();
assert!(
stderr == "unlink: cannot unlink 'dir': Is a directory\n"
|| stderr == "unlink: cannot unlink 'dir': Permission denied\n"
);
}
@ -44,8 +48,21 @@ fn test_unlink_directory() {
fn test_unlink_nonexistent() {
let file = "test_unlink_nonexistent";
new_ucmd!().arg(file).fails().stderr_is(
"unlink: Cannot stat 'test_unlink_nonexistent': No such file or directory \
(os error 2)\n",
);
new_ucmd!()
.arg(file)
.fails()
.stderr_is("unlink: cannot unlink 'test_unlink_nonexistent': No such file or directory\n");
}
#[test]
fn test_unlink_symlink() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("foo");
at.symlink_file("foo", "bar");
ucmd.arg("bar").succeeds().no_stderr();
assert!(at.file_exists("foo"));
assert!(!at.file_exists("bar"));
}

View file

@ -1,6 +1,6 @@
use crate::common::util::*;
// spell-checker:ignore (flags) lwmcL clmwL ; (path) bogusfile emptyfile manyemptylines moby notrailingnewline onelongemptyline onelongword
// spell-checker:ignore (flags) lwmcL clmwL ; (path) bogusfile emptyfile manyemptylines moby notrailingnewline onelongemptyline onelongword weirdchars
#[test]
fn test_count_bytes_large_stdin() {
@ -53,11 +53,16 @@ fn test_utf8() {
.args(&["-lwmcL"])
.pipe_in_fixture("UTF_8_test.txt")
.run()
.stdout_is(" 300 4969 22781 22213 79\n");
// GNU returns " 300 2086 22219 22781 79"
//
// TODO: we should fix the word, character, and byte count to
// match the behavior of GNU wc
.stdout_is(" 303 2119 22457 23025 79\n");
}
#[test]
fn test_utf8_extra() {
new_ucmd!()
.arg("-lwmcL")
.pipe_in_fixture("UTF_8_weirdchars.txt")
.run()
.stdout_is(" 25 87 442 513 48\n");
}
#[test]
@ -200,22 +205,33 @@ fn test_file_bytes_dictate_width() {
/// Test that getting counts from a directory is an error.
#[test]
fn test_read_from_directory_error() {
// TODO To match GNU `wc`, the `stdout` should be:
//
// " 0 0 0 .\n"
//
#[cfg(not(windows))]
const STDERR: &str = ".: Is a directory";
#[cfg(windows)]
const STDERR: &str = ".: Access is denied";
#[cfg(not(windows))]
const STDOUT: &str = " 0 0 0 .\n";
#[cfg(windows)]
const STDOUT: &str = "";
new_ucmd!()
.args(&["."])
.fails()
.stderr_contains(".: Is a directory\n")
.stdout_is("0 0 0 .\n");
.stderr_contains(STDERR)
.stdout_is(STDOUT);
}
/// Test that getting counts from nonexistent file is an error.
#[test]
fn test_read_from_nonexistent_file() {
#[cfg(not(windows))]
const MSG: &str = "bogusfile: No such file or directory";
#[cfg(windows)]
const MSG: &str = "bogusfile: The system cannot find the file specified";
new_ucmd!()
.args(&["bogusfile"])
.fails()
.stderr_contains("bogusfile: No such file or directory\n");
.stderr_contains(MSG)
.stdout_is("");
}

View file

@ -3,7 +3,6 @@
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
#[cfg(unix)]
use crate::common::util::*;
#[test]
@ -34,7 +33,6 @@ fn test_normal_compare_id() {
}
#[test]
#[cfg(unix)]
fn test_normal_compare_env() {
let whoami = whoami();
if whoami == "nobody" {

View file

@ -1 +1,72 @@
// ToDO: add tests
use std::io::Read;
use crate::common::util::*;
/// Run `yes`, capture some of the output, close the pipe, and verify it.
fn run(args: &[&str], expected: &[u8]) {
let mut cmd = new_ucmd!();
let mut child = cmd.args(args).run_no_wait();
let mut stdout = child.stdout.take().unwrap();
let mut buf = vec![0; expected.len()];
stdout.read_exact(&mut buf).unwrap();
drop(stdout);
assert!(child.wait().unwrap().success());
assert_eq!(buf.as_slice(), expected);
}
#[test]
fn test_simple() {
run(&[], b"y\ny\ny\ny\n");
}
#[test]
fn test_args() {
run(&["a", "bar", "c"], b"a bar c\na bar c\na ba");
}
#[test]
fn test_long_output() {
run(&[], "y\n".repeat(512 * 1024).as_bytes());
}
/// Test with an output that seems likely to get mangled in case of incomplete writes.
#[test]
fn test_long_odd_output() {
run(&["abcdef"], "abcdef\n".repeat(1024 * 1024).as_bytes());
}
/// Test with an input that doesn't fit in the standard buffer.
#[test]
fn test_long_input() {
#[cfg(not(windows))]
const TIMES: usize = 14000;
// On Windows the command line is limited to 8191 bytes.
// This is not actually enough to fill the buffer, but it's still nice to
// try something long.
#[cfg(windows)]
const TIMES: usize = 500;
let arg = "abcdefg".repeat(TIMES) + "\n";
let expected_out = arg.repeat(30);
run(&[&arg[..arg.len() - 1]], expected_out.as_bytes());
}
#[test]
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
fn test_piped_to_dev_full() {
use std::fs::OpenOptions;
for &append in &[true, false] {
{
let dev_full = OpenOptions::new()
.write(true)
.append(append)
.open("/dev/full")
.unwrap();
new_ucmd!()
.set_stdout(dev_full)
.fails()
.stderr_contains("No space left on device");
}
}
}

View file

@ -31,7 +31,11 @@ macro_rules! path_concat {
#[macro_export]
macro_rules! util_name {
() => {
module_path!().split("_").nth(1).expect("no test name")
module_path!()
.split("_")
.nth(1)
.and_then(|s| s.split("::").next())
.expect("no test name")
};
}

View file

@ -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 Rlim coreutil
//spell-checker: ignore (linux) rlimit prlimit Rlim coreutil ggroups
#![allow(dead_code)]
@ -163,25 +163,23 @@ impl CmdResult {
/// asserts that the command resulted in a success (zero) status code
pub fn success(&self) -> &CmdResult {
if !self.success {
panic!(
"Command was expected to succeed.\nstdout = {}\n stderr = {}",
self.stdout_str(),
self.stderr_str()
);
}
assert!(
self.success,
"Command was expected to succeed.\nstdout = {}\n stderr = {}",
self.stdout_str(),
self.stderr_str()
);
self
}
/// asserts that the command resulted in a failure (non-zero) status code
pub fn failure(&self) -> &CmdResult {
if self.success {
panic!(
"Command was expected to fail.\nstdout = {}\n stderr = {}",
self.stdout_str(),
self.stderr_str()
);
}
assert!(
!self.success,
"Command was expected to fail.\nstdout = {}\n stderr = {}",
self.stdout_str(),
self.stderr_str()
);
self
}
@ -197,12 +195,11 @@ impl CmdResult {
/// 1. you can not know exactly what stdout will be or
/// 2. you know that stdout will also be empty
pub fn no_stderr(&self) -> &CmdResult {
if !self.stderr.is_empty() {
panic!(
"Expected stderr to be empty, but it's:\n{}",
self.stderr_str()
);
}
assert!(
self.stderr.is_empty(),
"Expected stderr to be empty, but it's:\n{}",
self.stderr_str()
);
self
}
@ -213,12 +210,11 @@ impl CmdResult {
/// 1. you can not know exactly what stderr will be or
/// 2. you know that stderr will also be empty
pub fn no_stdout(&self) -> &CmdResult {
if !self.stdout.is_empty() {
panic!(
"Expected stdout to be empty, but it's:\n{}",
self.stderr_str()
);
}
assert!(
self.stdout.is_empty(),
"Expected stdout to be empty, but it's:\n{}",
self.stderr_str()
);
self
}
@ -514,43 +510,86 @@ impl AtPath {
}
pub fn write(&self, name: &str, contents: &str) {
log_info("open(write)", self.plus_as_string(name));
log_info("write(default)", self.plus_as_string(name));
std::fs::write(self.plus(name), contents)
.unwrap_or_else(|e| panic!("Couldn't write {}: {}", name, e));
}
pub fn write_bytes(&self, name: &str, contents: &[u8]) {
log_info("open(write)", self.plus_as_string(name));
log_info("write(default)", self.plus_as_string(name));
std::fs::write(self.plus(name), contents)
.unwrap_or_else(|e| panic!("Couldn't write {}: {}", name, e));
}
pub fn append(&self, name: &str, contents: &str) {
log_info("open(append)", self.plus_as_string(name));
log_info("write(append)", self.plus_as_string(name));
let mut f = OpenOptions::new()
.write(true)
.append(true)
.create(true)
.open(self.plus(name))
.unwrap();
f.write_all(contents.as_bytes())
.unwrap_or_else(|e| panic!("Couldn't write {}: {}", name, e));
.unwrap_or_else(|e| panic!("Couldn't write(append) {}: {}", name, e));
}
pub fn append_bytes(&self, name: &str, contents: &[u8]) {
log_info("open(append)", self.plus_as_string(name));
log_info("write(append)", self.plus_as_string(name));
let mut f = OpenOptions::new()
.write(true)
.append(true)
.create(true)
.open(self.plus(name))
.unwrap();
f.write_all(contents)
.unwrap_or_else(|e| panic!("Couldn't append to {}: {}", name, e));
.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));
fs::remove_dir(&self.plus(dir)).unwrap();
}
pub fn mkdir(&self, dir: &str) {
log_info("mkdir", self.plus_as_string(dir));
fs::create_dir(&self.plus(dir)).unwrap();
}
pub fn mkdir_all(&self, dir: &str) {
log_info("mkdir_all", self.plus_as_string(dir));
fs::create_dir_all(self.plus(dir)).unwrap();
@ -713,7 +752,7 @@ impl AtPath {
///
/// Fixtures can be found under `tests/fixtures/$util_name/`
pub struct TestScenario {
bin_path: PathBuf,
pub bin_path: PathBuf,
pub util_name: String,
pub fixtures: AtPath,
tmpd: Rc<TempDir>,
@ -868,9 +907,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 UCommand {
if self.has_run {
panic!("{}", ALREADY_RUN);
}
assert!(!self.has_run, "{}", ALREADY_RUN);
self.comm_string.push(' ');
self.comm_string
.push_str(arg.as_ref().to_str().unwrap_or_default());
@ -881,9 +918,7 @@ impl UCommand {
/// Add multiple parameters to the invocation. Path arguments are treated relative
/// to the test environment directory.
pub fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut UCommand {
if self.has_run {
panic!("{}", MULTIPLE_STDIN_MEANINGLESS);
}
assert!(!self.has_run, "{}", MULTIPLE_STDIN_MEANINGLESS);
let strings = args
.iter()
.map(|s| s.as_ref().to_os_string())
@ -901,9 +936,11 @@ impl UCommand {
/// provides standard input to feed in to the command when spawned
pub fn pipe_in<T: Into<Vec<u8>>>(&mut self, input: T) -> &mut UCommand {
if self.bytes_into_stdin.is_some() {
panic!("{}", MULTIPLE_STDIN_MEANINGLESS);
}
assert!(
!self.bytes_into_stdin.is_some(),
"{}",
MULTIPLE_STDIN_MEANINGLESS
);
self.bytes_into_stdin = Some(input.into());
self
}
@ -918,9 +955,7 @@ impl UCommand {
/// This is typically useful to test non-standard workflows
/// like feeding something to a command that does not read it
pub fn ignore_stdin_write_error(&mut self) -> &mut UCommand {
if self.bytes_into_stdin.is_none() {
panic!("{}", NO_STDIN_MEANINGLESS);
}
assert!(!self.bytes_into_stdin.is_none(), "{}", NO_STDIN_MEANINGLESS);
self.ignore_stdin_write_error = true;
self
}
@ -930,9 +965,7 @@ impl UCommand {
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
if self.has_run {
panic!("{}", ALREADY_RUN);
}
assert!(!self.has_run, "{}", ALREADY_RUN);
self.raw.env(key, val);
self
}
@ -951,9 +984,7 @@ impl UCommand {
/// Spawns the command, feeds the stdin if any, and returns the
/// child process immediately.
pub fn run_no_wait(&mut self) -> Child {
if self.has_run {
panic!("{}", ALREADY_RUN);
}
assert!(!self.has_run, "{}", ALREADY_RUN);
self.has_run = true;
log_info("run", &self.comm_string);
let mut child = self
@ -1036,6 +1067,8 @@ impl UCommand {
}
}
/// Wrapper for `child.stdout.read_exact()`.
/// Careful, this blocks indefinitely if `size` bytes is never reached.
pub fn read_size(child: &mut Child, size: usize) -> String {
let mut output = Vec::new();
output.resize(size, 0);
@ -1069,10 +1102,12 @@ pub fn whoami() -> String {
// Use environment variable to get current user instead of
// invoking `whoami` and fall back to user "nobody" on error.
std::env::var("USER").unwrap_or_else(|e| {
println!("{}: {}, using \"nobody\" instead", UUTILS_WARNING, e);
"nobody".to_string()
})
std::env::var("USER")
.or_else(|_| std::env::var("USERNAME"))
.unwrap_or_else(|e| {
println!("{}: {}, using \"nobody\" instead", UUTILS_WARNING, e);
"nobody".to_string()
})
}
/// Add prefix 'g' for `util_name` if not on linux
@ -1081,7 +1116,14 @@ pub fn host_name_for(util_name: &str) -> Cow<str> {
// 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"))]
return format!("g{}", util_name).into();
{
// make call to `host_name_for` idempotent
if util_name.starts_with('g') && util_name != "groups" {
return util_name.into();
} else {
return format!("g{}", util_name).into();
}
}
#[cfg(target_os = "linux")]
return util_name.into();
}
@ -1160,7 +1202,7 @@ pub fn check_coreutil_version(
if s.contains(&format!("(GNU coreutils) {}", version_expected)) {
Ok(format!("{}: {}", UUTILS_INFO, s.to_string()))
} else if s.contains("(GNU coreutils)") {
let version_found = s.split_whitespace().last().unwrap()[..4].parse::<f32>().unwrap_or_default();
let version_found = parse_coreutil_version(s);
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))
@ -1173,6 +1215,20 @@ pub fn check_coreutil_version(
)
}
// 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()
}
/// 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,
@ -1195,8 +1251,8 @@ pub fn check_coreutil_version(
///```
#[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);
println!("{}", check_coreutil_version(util_name, VERSION_MIN)?);
let result = ts
.cmd_keepenv(util_name.as_ref())
@ -1465,6 +1521,36 @@ mod tests {
res.normalized_newlines_stdout_is("A\r\nB\nC\n");
}
#[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"
);
}
#[test]
#[cfg(unix)]
fn test_check_coreutil_version() {
@ -1493,4 +1579,25 @@ mod tests {
let ts = TestScenario::new("no test name");
assert!(expected_result(&ts, &[]).is_err());
}
#[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");
}
}
}

View file

@ -0,0 +1 @@
7355dd5276c21cfe0c593b5063b96af3f96a454b33216f58314f44c3ade92e9cd6cec4210a0836246780e9baf927cc50b9a3d7073e8f9bd12780fddbcb930c6d input.txt

1
tests/fixtures/hashsum/md5.checkfile vendored Normal file
View file

@ -0,0 +1 @@
e4d7f1b4ed2e42d15898f4b27b019da4 input.txt

1
tests/fixtures/hashsum/sha1.checkfile vendored Normal file
View file

@ -0,0 +1 @@
b7e23ec29af22b0b4e41da31e868d57226121c84 input.txt

View file

@ -0,0 +1 @@
6e1a93e32fb44081a401f3db3ef2e6e108b7bbeeb5705afdaf01fb27 input.txt

View file

@ -0,0 +1 @@
09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b input.txt

View file

@ -0,0 +1 @@
1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e input.txt

View file

@ -0,0 +1 @@
927b362eaf84a75785bbec3370d1c9711349e93f1104eda060784221 input.txt

View file

@ -0,0 +1 @@
bfb3959527d7a3f2f09def2f6915452d55a8f122df9e164d6f31c7fcf6093e14 input.txt

View file

@ -0,0 +1 @@
fbd0c5931195aaa9517869972b372f717bb69f7f9f72bfc0884ed0531c36a16fc2db5dd6d82131968b23ffe0e90757e5 input.txt

View file

@ -0,0 +1 @@
2ed3a863a12e2f8ff140aa86232ff3603a7f24af62f0e2ca74672494ade175a9a3de42a351b5019d931a1deae0499609038d9b47268779d76198e1d410d20974 input.txt

View file

@ -0,0 +1 @@
8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9 input.txt

View file

@ -0,0 +1 @@
83d41db453072caa9953f2f316480fbbcb84a5f3505460a18b3a36a814ae8e9e input.txt

View file

@ -0,0 +1 @@
7c9896ea84a2a1b80b2183a3f2b4e43cd59b7d48471dc213bcedaccb699d6e6f7ad5d304928ab79329f1fc62f6db072d95b51209eb807683f5c9371872a2dd4e input.txt

View file

@ -1 +1 @@
97:89:83:79:73:71:67:61:59:53:47:43:41:37:31:29:23:19:17:13:11:7:5:3:2
:97:89:83:79:73:71:67:61:59:53:47:43:41:37:31:29:23:19:17:13:11:7:5:32

Binary file not shown.

25
tests/fixtures/wc/UTF_8_weirdchars.txt vendored Normal file
View file

@ -0,0 +1,25 @@
zero-width space inbetween these: xx
and inbetween two spaces: [ ]
and at the end of the line:
non-breaking space: x x [   ]  
simple unicode: xµx [ µ ] µ
wide: xx [ ]
simple emoji: x👩x [ 👩 ] 👩
complex emoji: x👩🔬x [ 👩‍🔬 ] 👩‍🔬
, !
line feed: x x [ ]
vertical tab: x x [ ]
horizontal tab: x x [ ]
this should be the longest line:
1234567 12345678 123456781234567812345678
Control character: xx [  ] 

86
tests/test_util_name.rs Normal file
View file

@ -0,0 +1,86 @@
mod common;
use common::util::TestScenario;
#[cfg(unix)]
use std::os::unix::fs::symlink as symlink_file;
#[cfg(windows)]
use std::os::windows::fs::symlink_file;
#[test]
#[cfg(feature = "ls")]
fn execution_phrase_double() {
use std::process::Command;
let scenario = TestScenario::new("ls");
let output = Command::new(&scenario.bin_path)
.arg("ls")
.arg("--some-invalid-arg")
.output()
.unwrap();
assert!(String::from_utf8(output.stderr)
.unwrap()
.contains(&format!("USAGE:\n {} ls", scenario.bin_path.display(),)));
}
#[test]
#[cfg(feature = "ls")]
fn execution_phrase_single() {
use std::process::Command;
let scenario = TestScenario::new("ls");
symlink_file(scenario.bin_path, scenario.fixtures.plus("uu-ls")).unwrap();
let output = Command::new(scenario.fixtures.plus("uu-ls"))
.arg("--some-invalid-arg")
.output()
.unwrap();
assert!(String::from_utf8(output.stderr).unwrap().contains(&format!(
"USAGE:\n {}",
scenario.fixtures.plus("uu-ls").display()
)));
}
#[test]
#[cfg(feature = "sort")]
fn util_name_double() {
use std::{
io::Write,
process::{Command, Stdio},
};
let scenario = TestScenario::new("sort");
let mut child = Command::new(&scenario.bin_path)
.arg("sort")
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
// input invalid utf8 to cause an error
child.stdin.take().unwrap().write_all(&[255]).unwrap();
let output = child.wait_with_output().unwrap();
assert!(String::from_utf8(output.stderr).unwrap().contains("sort: "));
}
#[test]
#[cfg(feature = "sort")]
fn util_name_single() {
use std::{
io::Write,
process::{Command, Stdio},
};
let scenario = TestScenario::new("sort");
symlink_file(scenario.bin_path, scenario.fixtures.plus("uu-sort")).unwrap();
let mut child = Command::new(scenario.fixtures.plus("uu-sort"))
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
// input invalid utf8 to cause an error
child.stdin.take().unwrap().write_all(&[255]).unwrap();
let output = child.wait_with_output().unwrap();
assert!(String::from_utf8(output.stderr).unwrap().contains(&format!(
"{}: ",
scenario.fixtures.plus("uu-sort").display()
)));
}