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:
commit
2dad536785
356 changed files with 11234 additions and 5621 deletions
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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");
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}};
|
||||
}
|
||||
|
||||
|
|
|
@ -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
0
tests/by-util/test_head.rs
Executable file → Normal 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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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!()
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
151
tests/by-util/test_runcon.rs
Normal file
151
tests/by-util/test_runcon.rs
Normal 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")
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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"))]
|
||||
|
|
|
@ -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"))]
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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+");
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 aren’t 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() {
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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'"])
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
|
|
@ -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("");
|
||||
}
|
||||
|
|
|
@ -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" {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
1
tests/fixtures/hashsum/b2sum.checkfile
vendored
Normal file
1
tests/fixtures/hashsum/b2sum.checkfile
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
7355dd5276c21cfe0c593b5063b96af3f96a454b33216f58314f44c3ade92e9cd6cec4210a0836246780e9baf927cc50b9a3d7073e8f9bd12780fddbcb930c6d input.txt
|
1
tests/fixtures/hashsum/md5.checkfile
vendored
Normal file
1
tests/fixtures/hashsum/md5.checkfile
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
e4d7f1b4ed2e42d15898f4b27b019da4 input.txt
|
1
tests/fixtures/hashsum/sha1.checkfile
vendored
Normal file
1
tests/fixtures/hashsum/sha1.checkfile
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
b7e23ec29af22b0b4e41da31e868d57226121c84 input.txt
|
1
tests/fixtures/hashsum/sha224.checkfile
vendored
Normal file
1
tests/fixtures/hashsum/sha224.checkfile
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
6e1a93e32fb44081a401f3db3ef2e6e108b7bbeeb5705afdaf01fb27 input.txt
|
1
tests/fixtures/hashsum/sha256.checkfile
vendored
Normal file
1
tests/fixtures/hashsum/sha256.checkfile
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b input.txt
|
1
tests/fixtures/hashsum/sha384.checkfile
vendored
Normal file
1
tests/fixtures/hashsum/sha384.checkfile
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e input.txt
|
1
tests/fixtures/hashsum/sha3_224.checkfile
vendored
Normal file
1
tests/fixtures/hashsum/sha3_224.checkfile
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
927b362eaf84a75785bbec3370d1c9711349e93f1104eda060784221 input.txt
|
1
tests/fixtures/hashsum/sha3_256.checkfile
vendored
Normal file
1
tests/fixtures/hashsum/sha3_256.checkfile
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
bfb3959527d7a3f2f09def2f6915452d55a8f122df9e164d6f31c7fcf6093e14 input.txt
|
1
tests/fixtures/hashsum/sha3_384.checkfile
vendored
Normal file
1
tests/fixtures/hashsum/sha3_384.checkfile
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
fbd0c5931195aaa9517869972b372f717bb69f7f9f72bfc0884ed0531c36a16fc2db5dd6d82131968b23ffe0e90757e5 input.txt
|
1
tests/fixtures/hashsum/sha3_512.checkfile
vendored
Normal file
1
tests/fixtures/hashsum/sha3_512.checkfile
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
2ed3a863a12e2f8ff140aa86232ff3603a7f24af62f0e2ca74672494ade175a9a3de42a351b5019d931a1deae0499609038d9b47268779d76198e1d410d20974 input.txt
|
1
tests/fixtures/hashsum/sha512.checkfile
vendored
Normal file
1
tests/fixtures/hashsum/sha512.checkfile
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9 input.txt
|
1
tests/fixtures/hashsum/shake128_256.checkfile
vendored
Normal file
1
tests/fixtures/hashsum/shake128_256.checkfile
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
83d41db453072caa9953f2f316480fbbcb84a5f3505460a18b3a36a814ae8e9e input.txt
|
1
tests/fixtures/hashsum/shake256_512.checkfile
vendored
Normal file
1
tests/fixtures/hashsum/shake256_512.checkfile
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
7c9896ea84a2a1b80b2183a3f2b4e43cd59b7d48471dc213bcedaccb699d6e6f7ad5d304928ab79329f1fc62f6db072d95b51209eb807683f5c9371872a2dd4e input.txt
|
|
@ -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
|
BIN
tests/fixtures/wc/UTF_8_test.txt
vendored
BIN
tests/fixtures/wc/UTF_8_test.txt
vendored
Binary file not shown.
25
tests/fixtures/wc/UTF_8_weirdchars.txt
vendored
Normal file
25
tests/fixtures/wc/UTF_8_weirdchars.txt
vendored
Normal 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: xwx [ w ] w
|
||||
|
||||
simple emoji: x👩x [ 👩 ] 👩
|
||||
|
||||
complex emoji: x👩🔬x [ 👩🔬 ] 👩🔬
|
||||
|
||||
Hello, world!
|
||||
|
||||
line feed: xx [ ]
|
||||
|
||||
vertical tab: xx [ ]
|
||||
|
||||
horizontal tab: x x [ ]
|
||||
this should be the longest line:
|
||||
1234567 12345678 123456781234567812345678
|
||||
|
||||
Control character: xx [ ]
|
86
tests/test_util_name.rs
Normal file
86
tests/test_util_name.rs
Normal 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()
|
||||
)));
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue