1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-08-01 21:47:46 +00:00

Merge branch 'master' into master

This commit is contained in:
Sylvestre Ledru 2021-07-04 11:57:59 +02:00 committed by GitHub
commit ae1935c3cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
313 changed files with 11869 additions and 7613 deletions

View file

@ -103,7 +103,7 @@ fn test_wrap_bad_arg() {
.arg(wrap_param)
.arg("b")
.fails()
.stderr_only("base32: Invalid wrap size: b: invalid digit found in string\n");
.stderr_only("base32: Invalid wrap size: 'b': invalid digit found in string\n");
}
}
@ -114,7 +114,7 @@ fn test_base32_extra_operand() {
.arg("a.txt")
.arg("a.txt")
.fails()
.stderr_only("base32: extra operand a.txt");
.stderr_only("base32: extra operand 'a.txt'");
}
#[test]

View file

@ -89,7 +89,7 @@ fn test_wrap_bad_arg() {
.arg(wrap_param)
.arg("b")
.fails()
.stderr_only("base64: Invalid wrap size: b: invalid digit found in string\n");
.stderr_only("base64: Invalid wrap size: 'b': invalid digit found in string\n");
}
}
@ -100,7 +100,7 @@ fn test_base64_extra_operand() {
.arg("a.txt")
.arg("a.txt")
.fails()
.stderr_only("base64: extra operand a.txt");
.stderr_only("base64: extra operand 'a.txt'");
}
#[test]

View file

@ -438,7 +438,7 @@ fn test_domain_socket() {
let child = new_ucmd!().args(&[socket_path]).run_no_wait();
barrier.wait();
let stdout = &child.wait_with_output().unwrap().stdout;
let output = String::from_utf8_lossy(&stdout);
let output = String::from_utf8_lossy(stdout);
assert_eq!("a\tb", output);
thread.join().unwrap();

View file

@ -1,4 +1,4 @@
// spell-checker:ignore (words) nosuchgroup
// spell-checker:ignore (words) nosuchgroup groupname
use crate::common::util::*;
use rust_users::*;
@ -10,6 +10,33 @@ fn test_invalid_option() {
static DIR: &str = "/tmp";
// we should always get both arguments, regardless of whether --reference was used
#[test]
fn test_help() {
new_ucmd!()
.arg("--help")
.succeeds()
.stdout_contains("ARGS:\n <GROUP> \n <FILE>... ");
}
#[test]
fn test_help_ref() {
new_ucmd!()
.arg("--help")
.arg("--reference=ref_file")
.succeeds()
.stdout_contains("ARGS:\n <GROUP> \n <FILE>... ");
}
#[test]
fn test_ref_help() {
new_ucmd!()
.arg("--reference=ref_file")
.arg("--help")
.succeeds()
.stdout_contains("ARGS:\n <GROUP> \n <FILE>... ");
}
#[test]
fn test_invalid_group() {
new_ucmd!()
@ -121,9 +148,52 @@ fn test_reference() {
fn test_reference() {
new_ucmd!()
.arg("-v")
.arg("--reference=/etc/passwd")
.arg("--reference=ref_file")
.arg("/etc")
.succeeds();
.fails()
// group name can differ, so just check the first part of the message
.stderr_contains("chgrp: changing group of '/etc': Operation not permitted (os error 1)\nfailed to change group of '/etc' from ");
}
#[test]
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
fn test_reference_multi_no_equal() {
new_ucmd!()
.arg("-v")
.arg("--reference")
.arg("ref_file")
.arg("file1")
.arg("file2")
.succeeds()
.stderr_contains("chgrp: group of 'file1' retained as ")
.stderr_contains("\nchgrp: group of 'file2' retained as ");
}
#[test]
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
fn test_reference_last() {
new_ucmd!()
.arg("-v")
.arg("file1")
.arg("file2")
.arg("file3")
.arg("--reference")
.arg("ref_file")
.succeeds()
.stderr_contains("chgrp: group of 'file1' retained as ")
.stderr_contains("\nchgrp: group of 'file2' retained as ")
.stderr_contains("\nchgrp: group of 'file3' retained as ");
}
#[test]
fn test_missing_files() {
new_ucmd!()
.arg("-v")
.arg("groupname")
.fails()
.stderr_contains(
"error: The following required arguments were not provided:\n <FILE>...\n",
);
}
#[test]
@ -135,7 +205,7 @@ fn test_big_p() {
.arg("bin")
.arg("/proc/self/cwd")
.fails()
.stderr_is(
.stderr_contains(
"chgrp: changing group of '/proc/self/cwd': Operation not permitted (os error 1)\n",
);
}

View file

@ -172,14 +172,14 @@ fn test_chown_only_colon() {
// expected:
// $ chown -v :: file.txt 2>out_err ; echo $? ; cat out_err
// 1
// chown: invalid group: ::
// chown: invalid group: '::'
scene
.ucmd()
.arg("::")
.arg("--verbose")
.arg(file1)
.fails()
.stderr_contains(&"invalid group: ::");
.stderr_contains(&"invalid group: '::'");
}
#[test]

View file

@ -618,7 +618,7 @@ fn test_cp_deref() {
// Check the content of the destination file that was copied.
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
let path_to_check = path_to_new_symlink.to_str().unwrap();
assert_eq!(at.read(&path_to_check), "Hello, World!\n");
assert_eq!(at.read(path_to_check), "Hello, World!\n");
}
#[test]
fn test_cp_no_deref() {
@ -655,7 +655,7 @@ fn test_cp_no_deref() {
// Check the content of the destination file that was copied.
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
let path_to_check = path_to_new_symlink.to_str().unwrap();
assert_eq!(at.read(&path_to_check), "Hello, World!\n");
assert_eq!(at.read(path_to_check), "Hello, World!\n");
}
#[test]
@ -726,6 +726,15 @@ fn test_cp_parents_dest_not_directory() {
.stderr_contains("with --parents, the destination must be a directory");
}
#[test]
fn test_cp_preserve_no_args() {
new_ucmd!()
.arg(TEST_COPY_FROM_FOLDER_FILE)
.arg(TEST_HELLO_WORLD_DEST)
.arg("--preserve")
.succeeds();
}
#[test]
// For now, disable the test on Windows. Symlinks aren't well support on Windows.
// It works on Unix for now and it works locally when run from a powershell
@ -823,7 +832,7 @@ fn test_cp_deref_folder_to_folder() {
// Check the content of the symlink
let path_to_check = path_to_new_symlink.to_str().unwrap();
assert_eq!(at.read(&path_to_check), "Hello, World!\n");
assert_eq!(at.read(path_to_check), "Hello, World!\n");
}
#[test]
@ -923,7 +932,7 @@ fn test_cp_no_deref_folder_to_folder() {
// Check the content of the symlink
let path_to_check = path_to_new_symlink.to_str().unwrap();
assert_eq!(at.read(&path_to_check), "Hello, World!\n");
assert_eq!(at.read(path_to_check), "Hello, World!\n");
}
#[test]
@ -1290,3 +1299,42 @@ fn test_closes_file_descriptors() {
.with_limit(Resource::NOFILE, 9, 9)
.succeeds();
}
#[test]
fn test_copy_dir_symlink() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("dir");
at.symlink_dir("dir", "dir-link");
ucmd.args(&["-r", "dir-link", "copy"]).succeeds();
assert_eq!(at.resolve_link("copy"), "dir");
}
#[test]
fn test_copy_dir_with_symlinks() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("dir");
at.make_file("dir/file");
TestScenario::new("ln")
.ucmd()
.arg("-sr")
.arg(at.subdir.join("dir/file"))
.arg(at.subdir.join("dir/file-link"))
.succeeds();
ucmd.args(&["-r", "dir", "copy"]).succeeds();
assert_eq!(at.resolve_link("copy/file-link"), "file");
}
#[test]
#[cfg(not(windows))]
fn test_copy_symlink_force() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("file");
at.symlink_file("file", "file-link");
at.touch("copy");
ucmd.args(&["file-link", "copy", "-f", "--no-dereference"])
.succeeds();
assert_eq!(at.resolve_link("copy"), "file");
}

View file

@ -157,3 +157,12 @@ fn test_directory_and_no_such_file() {
.run()
.stderr_is("cut: some: No such file or directory\n");
}
#[test]
fn test_equal_as_delimiter() {
new_ucmd!()
.args(&["-f", "2", "-d="])
.pipe_in("--dir=./out/lib")
.succeeds()
.stdout_only("./out/lib\n");
}

View file

@ -117,7 +117,7 @@ fn test_date_format_without_plus() {
new_ucmd!()
.arg("%s")
.fails()
.stderr_contains("date: invalid date %s")
.stderr_contains("date: invalid date '%s'")
.code_is(1);
}

View file

@ -1,9 +1,16 @@
// * This file is part of the uutils coreutils package.
// *
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
// spell-checker:ignore (paths) sublink subwords
use crate::common::util::*;
const SUB_DIR: &str = "subdir/deeper";
const SUB_DEEPER_DIR: &str = "subdir/deeper/deeper_dir";
const SUB_DIR_LINKS: &str = "subdir/links";
const SUB_DIR_LINKS_DEEPER_SYM_DIR: &str = "subdir/links/deeper_dir";
const SUB_FILE: &str = "subdir/links/subwords.txt";
const SUB_LINK: &str = "subdir/links/sublink.txt";
@ -16,7 +23,7 @@ fn _du_basics(s: &str) {
let answer = "32\t./subdir
8\t./subdir/deeper
24\t./subdir/links
40\t./
40\t.
";
assert_eq!(s, answer);
}
@ -25,7 +32,7 @@ fn _du_basics(s: &str) {
let answer = "28\t./subdir
8\t./subdir/deeper
16\t./subdir/links
36\t./
36\t.
";
assert_eq!(s, answer);
}
@ -49,15 +56,15 @@ fn test_du_basics_subdir() {
#[cfg(target_vendor = "apple")]
fn _du_basics_subdir(s: &str) {
assert_eq!(s, "4\tsubdir/deeper\n");
assert_eq!(s, "4\tsubdir/deeper/deeper_dir\n8\tsubdir/deeper\n");
}
#[cfg(target_os = "windows")]
fn _du_basics_subdir(s: &str) {
assert_eq!(s, "0\tsubdir/deeper\n");
assert_eq!(s, "0\tsubdir/deeper\\deeper_dir\n0\tsubdir/deeper\n");
}
#[cfg(target_os = "freebsd")]
fn _du_basics_subdir(s: &str) {
assert_eq!(s, "8\tsubdir/deeper\n");
assert_eq!(s, "8\tsubdir/deeper/deeper_dir\n16\tsubdir/deeper\n");
}
#[cfg(all(
not(target_vendor = "apple"),
@ -73,6 +80,26 @@ fn _du_basics_subdir(s: &str) {
}
}
#[test]
fn test_du_invalid_size() {
let args = &["block-size", "threshold"];
for s in args {
new_ucmd!()
.arg(format!("--{}=1fb4t", s))
.arg("/tmp")
.fails()
.code_is(1)
.stderr_only(format!("du: invalid --{} argument '1fb4t'", s));
#[cfg(not(target_pointer_width = "128"))]
new_ucmd!()
.arg(format!("--{}=1Y", s))
.arg("/tmp")
.fails()
.code_is(1)
.stderr_only(format!("du: --{} argument '1Y' too large", s));
}
}
#[test]
fn test_du_basics_bad_name() {
new_ucmd!()
@ -185,12 +212,7 @@ fn test_du_d_flag() {
{
let result_reference = scene.cmd("du").arg("-d1").run();
if result_reference.succeeded() {
assert_eq!(
// TODO: gnu `du` doesn't use trailing "/" here
// result.stdout_str(), result_reference.stdout_str()
result.stdout_str().trim_end_matches("/\n"),
result_reference.stdout_str().trim_end_matches('\n')
);
assert_eq!(result.stdout_str(), result_reference.stdout_str());
return;
}
}
@ -199,15 +221,15 @@ fn test_du_d_flag() {
#[cfg(target_vendor = "apple")]
fn _du_d_flag(s: &str) {
assert_eq!(s, "16\t./subdir\n20\t./\n");
assert_eq!(s, "20\t./subdir\n24\t.\n");
}
#[cfg(target_os = "windows")]
fn _du_d_flag(s: &str) {
assert_eq!(s, "8\t./subdir\n8\t./\n");
assert_eq!(s, "8\t.\\subdir\n8\t.\n");
}
#[cfg(target_os = "freebsd")]
fn _du_d_flag(s: &str) {
assert_eq!(s, "28\t./subdir\n36\t./\n");
assert_eq!(s, "36\t./subdir\n44\t.\n");
}
#[cfg(all(
not(target_vendor = "apple"),
@ -217,9 +239,127 @@ fn _du_d_flag(s: &str) {
fn _du_d_flag(s: &str) {
// MS-WSL linux has altered expected output
if !uucore::os::is_wsl_1() {
assert_eq!(s, "28\t./subdir\n36\t./\n");
assert_eq!(s, "28\t./subdir\n36\t.\n");
} else {
assert_eq!(s, "8\t./subdir\n8\t./\n");
assert_eq!(s, "8\t./subdir\n8\t.\n");
}
}
#[test]
fn test_du_dereference() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.symlink_dir(SUB_DEEPER_DIR, SUB_DIR_LINKS_DEEPER_SYM_DIR);
let result = scene.ucmd().arg("-L").arg(SUB_DIR_LINKS).succeeds();
#[cfg(target_os = "linux")]
{
let result_reference = scene.cmd("du").arg("-L").arg(SUB_DIR_LINKS).run();
if result_reference.succeeded() {
assert_eq!(result.stdout_str(), result_reference.stdout_str());
return;
}
}
_du_dereference(result.stdout_str());
}
#[cfg(target_vendor = "apple")]
fn _du_dereference(s: &str) {
assert_eq!(s, "4\tsubdir/links/deeper_dir\n16\tsubdir/links\n");
}
#[cfg(target_os = "windows")]
fn _du_dereference(s: &str) {
assert_eq!(s, "0\tsubdir/links\\deeper_dir\n8\tsubdir/links\n");
}
#[cfg(target_os = "freebsd")]
fn _du_dereference(s: &str) {
assert_eq!(s, "8\tsubdir/links/deeper_dir\n24\tsubdir/links\n");
}
#[cfg(all(
not(target_vendor = "apple"),
not(target_os = "windows"),
not(target_os = "freebsd")
))]
fn _du_dereference(s: &str) {
// MS-WSL linux has altered expected output
if !uucore::os::is_wsl_1() {
assert_eq!(s, "8\tsubdir/links/deeper_dir\n24\tsubdir/links\n");
} else {
assert_eq!(s, "0\tsubdir/links/deeper_dir\n8\tsubdir/links\n");
}
}
#[test]
fn test_du_inodes_basic() {
let scene = TestScenario::new(util_name!());
let result = scene.ucmd().arg("--inodes").succeeds();
#[cfg(target_os = "linux")]
{
let result_reference = scene.cmd("du").arg("--inodes").run();
assert_eq!(result.stdout_str(), result_reference.stdout_str());
}
#[cfg(not(target_os = "linux"))]
_du_inodes_basic(result.stdout_str());
}
#[cfg(target_os = "windows")]
fn _du_inodes_basic(s: &str) {
assert_eq!(
s,
"2\t.\\subdir\\deeper\\deeper_dir
4\t.\\subdir\\deeper
3\t.\\subdir\\links
8\t.\\subdir
11\t.
"
);
}
#[cfg(not(target_os = "windows"))]
fn _du_inodes_basic(s: &str) {
assert_eq!(
s,
"2\t./subdir/deeper/deeper_dir
4\t./subdir/deeper
3\t./subdir/links
8\t./subdir
11\t.
"
);
}
#[test]
fn test_du_inodes() {
let scene = TestScenario::new(util_name!());
scene
.ucmd()
.arg("--summarize")
.arg("--inodes")
.succeeds()
.stdout_only("11\t.\n");
let result = scene
.ucmd()
.arg("--separate-dirs")
.arg("--inodes")
.succeeds();
#[cfg(target_os = "windows")]
result.stdout_contains("3\t.\\subdir\\links\n");
#[cfg(not(target_os = "windows"))]
result.stdout_contains("3\t./subdir/links\n");
result.stdout_contains("3\t.\n");
#[cfg(target_os = "linux")]
{
let result_reference = scene.cmd("du").arg("--separate-dirs").arg("--inodes").run();
assert_eq!(result.stdout_str(), result_reference.stdout_str());
}
}
@ -286,7 +426,7 @@ fn test_du_no_permission() {
let result = scene.ucmd().arg(SUB_DIR_LINKS).run(); // TODO: replace with ".fails()" once `du` is fixed
result.stderr_contains(
"du: cannot read directory subdir/links: Permission denied (os error 13)",
"du: cannot read directory 'subdir/links': Permission denied (os error 13)",
);
#[cfg(target_os = "linux")]
@ -312,3 +452,134 @@ fn _du_no_permission(s: &str) {
fn _du_no_permission(s: &str) {
assert_eq!(s, "4\tsubdir/links\n");
}
#[test]
fn test_du_one_file_system() {
let scene = TestScenario::new(util_name!());
let result = scene.ucmd().arg("-x").arg(SUB_DIR).succeeds();
#[cfg(target_os = "linux")]
{
let result_reference = scene.cmd("du").arg("-x").arg(SUB_DIR).run();
if result_reference.succeeded() {
assert_eq!(result.stdout_str(), result_reference.stdout_str());
return;
}
}
_du_basics_subdir(result.stdout_str());
}
#[test]
fn test_du_threshold() {
let scene = TestScenario::new(util_name!());
let threshold = if cfg!(windows) { "7K" } else { "10K" };
scene
.ucmd()
.arg(format!("--threshold={}", threshold))
.succeeds()
.stdout_contains("links")
.stdout_does_not_contain("deeper_dir");
scene
.ucmd()
.arg(format!("--threshold=-{}", threshold))
.succeeds()
.stdout_does_not_contain("links")
.stdout_contains("deeper_dir");
}
#[test]
fn test_du_apparent_size() {
let scene = TestScenario::new(util_name!());
let result = scene.ucmd().arg("--apparent-size").succeeds();
#[cfg(target_os = "linux")]
{
let result_reference = scene.cmd("du").arg("--apparent-size").run();
assert_eq!(result.stdout_str(), result_reference.stdout_str());
}
#[cfg(not(target_os = "linux"))]
_du_apparent_size(result.stdout_str());
}
#[cfg(target_os = "windows")]
fn _du_apparent_size(s: &str) {
assert_eq!(
s,
"1\t.\\subdir\\deeper\\deeper_dir
1\t.\\subdir\\deeper
6\t.\\subdir\\links
6\t.\\subdir
6\t.
"
);
}
#[cfg(target_vendor = "apple")]
fn _du_apparent_size(s: &str) {
assert_eq!(
s,
"1\t./subdir/deeper/deeper_dir
1\t./subdir/deeper
6\t./subdir/links
6\t./subdir
6\t.
"
);
}
#[cfg(target_os = "freebsd")]
fn _du_apparent_size(s: &str) {
assert_eq!(
s,
"1\t./subdir/deeper/deeper_dir
2\t./subdir/deeper
6\t./subdir/links
8\t./subdir
8\t.
"
);
}
#[cfg(all(
not(target_vendor = "apple"),
not(target_os = "windows"),
not(target_os = "freebsd")
))]
fn _du_apparent_size(s: &str) {
assert_eq!(
s,
"5\t./subdir/deeper/deeper_dir
9\t./subdir/deeper
10\t./subdir/links
22\t./subdir
26\t.
"
);
}
#[test]
fn test_du_bytes() {
let scene = TestScenario::new(util_name!());
let result = scene.ucmd().arg("--bytes").succeeds();
#[cfg(target_os = "linux")]
{
let result_reference = scene.cmd("du").arg("--bytes").run();
assert_eq!(result.stdout_str(), result_reference.stdout_str());
}
#[cfg(target_os = "windows")]
result.stdout_contains("5145\t.\\subdir\n");
#[cfg(target_vendor = "apple")]
result.stdout_contains("5625\t./subdir\n");
#[cfg(target_os = "freebsd")]
result.stdout_contains("7193\t./subdir\n");
#[cfg(all(
not(target_vendor = "apple"),
not(target_os = "windows"),
not(target_os = "freebsd")
))]
result.stdout_contains("21529\t./subdir\n");
}

View file

@ -187,11 +187,10 @@ fn test_change_directory() {
.arg(&temporary_path)
.succeeds()
.stdout_move_str();
assert_eq!(
out.lines()
.any(|line| line.ends_with(temporary_path.file_name().unwrap().to_str().unwrap())),
false
);
assert!(!out
.lines()
.any(|line| line.ends_with(temporary_path.file_name().unwrap().to_str().unwrap())));
}
#[test]

View file

@ -1,41 +1,176 @@
use crate::common::util::*;
// spell-checker:ignore (ToDO) coreutil
// These tests run the GNU coreutils `(g)groups` binary in `$PATH` in order to gather reference values.
// If the `(g)groups` in `$PATH` doesn't include a coreutils version string,
// or the version is too low, the test is skipped.
// The reference version is 8.32. Here 8.30 was chosen because right now there's no
// ubuntu image for github action available with a higher version than 8.30.
const VERSION_MIN: &str = "8.30"; // minimum Version for the reference `groups` in $PATH
const VERSION_MIN_MULTIPLE_USERS: &str = "8.31"; // this feature was introduced in GNU's coreutils 8.31
const UUTILS_WARNING: &str = "uutils-tests-warning";
const UUTILS_INFO: &str = "uutils-tests-info";
macro_rules! unwrap_or_return {
( $e:expr ) => {
match $e {
Ok(x) => x,
Err(e) => {
println!("{}: test skipped: {}", UUTILS_INFO, e);
return;
}
}
};
}
fn whoami() -> String {
// Apparently some CI environments have configuration issues, e.g. with 'whoami' and 'id'.
//
// From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)"
// whoami: cannot find name for user ID 1001
// id --name: cannot find name for user ID 1001
// id --name: cannot find name for group ID 116
//
// However, when running "id" from within "/bin/bash" it looks fine:
// id: "uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),101(systemd-journal)"
// whoami: "runner"
// Use environment variable to get current user instead of
// invoking `whoami` and fall back to user "nobody" on error.
std::env::var("USER").unwrap_or_else(|e| {
println!("{}: {}, using \"nobody\" instead", UUTILS_WARNING, e);
"nobody".to_string()
})
}
#[test]
#[cfg(unix)]
fn test_groups() {
let result = new_ucmd!().run();
println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr_str());
if is_ci() && result.stdout_str().trim().is_empty() {
// In the CI, some server are failing to return the group.
// As seems to be a configuration issue, ignoring it
return;
}
result.success();
assert!(!result.stdout_str().trim().is_empty());
let exp_result = unwrap_or_return!(expected_result(&[]));
result
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str())
.code_is(exp_result.code());
}
#[test]
fn test_groups_arg() {
// get the username with the "id -un" command
let result = TestScenario::new("id").ucmd_keepenv().arg("-un").run();
println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr_str());
let s1 = String::from(result.stdout_str().trim());
if is_ci() && s1.parse::<f64>().is_ok() {
// In the CI, some server are failing to return id -un.
// So, if we are getting a uid, just skip this test
// As seems to be a configuration issue, ignoring it
#[cfg(unix)]
fn test_groups_username() {
let test_users = [&whoami()[..]];
let result = new_ucmd!().args(&test_users).run();
let exp_result = unwrap_or_return!(expected_result(&test_users));
result
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str())
.code_is(exp_result.code());
}
#[test]
#[cfg(unix)]
fn test_groups_username_multiple() {
// TODO: [2021-06; jhscheer] refactor this as `let util_name = host_name_for(util_name!())` when that function is added to 'tests/common'
#[cfg(target_os = "linux")]
let util_name = util_name!();
#[cfg(all(unix, not(target_os = "linux")))]
let util_name = &format!("g{}", util_name!());
let version_check_string = check_coreutil_version(util_name, VERSION_MIN_MULTIPLE_USERS);
if version_check_string.starts_with(UUTILS_WARNING) {
println!("{}\ntest skipped", version_check_string);
return;
}
let test_users = ["root", "man", "postfix", "sshd", &whoami()];
println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr_str());
result.success();
assert!(!result.stdout_str().is_empty());
let username = result.stdout_str().trim();
let result = new_ucmd!().args(&test_users).run();
let exp_result = unwrap_or_return!(expected_result(&test_users));
// call groups with the user name to check that we
// are getting something
new_ucmd!().arg(username).succeeds();
assert!(!result.stdout_str().is_empty());
result
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str())
.code_is(exp_result.code());
}
fn check_coreutil_version(util_name: &str, version_expected: &str) -> String {
// example:
// $ id --version | head -n 1
// id (GNU coreutils) 8.32.162-4eda
let scene = TestScenario::new(util_name);
let version_check = scene
.cmd_keepenv(&util_name)
.env("LC_ALL", "C")
.arg("--version")
.run();
version_check
.stdout_str()
.split('\n')
.collect::<Vec<_>>()
.get(0)
.map_or_else(
|| format!("{}: unexpected output format for reference coreutil: '{} --version'", UUTILS_WARNING, util_name),
|s| {
if s.contains(&format!("(GNU coreutils) {}", version_expected)) {
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_expected = version_expected.parse::<f32>().unwrap_or_default();
if version_found > version_expected {
format!("{}: version for the reference coreutil '{}' is higher than expected; expected: {}, found: {}", UUTILS_INFO, util_name, version_expected, version_found)
} else {
format!("{}: version for the reference coreutil '{}' does not match; expected: {}, found: {}", UUTILS_WARNING, util_name, version_expected, version_found) }
} else {
format!("{}: no coreutils version string found for reference coreutils '{} --version'", UUTILS_WARNING, util_name)
}
},
)
}
#[allow(clippy::needless_borrow)]
#[cfg(unix)]
fn expected_result(args: &[&str]) -> Result<CmdResult, String> {
// TODO: [2021-06; jhscheer] refactor this as `let util_name = host_name_for(util_name!())` when that function is added to 'tests/common'
#[cfg(target_os = "linux")]
let util_name = util_name!();
#[cfg(all(unix, not(target_os = "linux")))]
let util_name = &format!("g{}", util_name!());
let version_check_string = check_coreutil_version(util_name, VERSION_MIN);
if version_check_string.starts_with(UUTILS_WARNING) {
return Err(version_check_string);
}
println!("{}", version_check_string);
let scene = TestScenario::new(util_name);
let result = scene
.cmd_keepenv(util_name)
.env("LC_ALL", "C")
.args(args)
.run();
let (stdout, stderr): (String, String) = if cfg!(target_os = "linux") {
(
result.stdout_str().to_string(),
result.stderr_str().to_string(),
)
} else {
// strip 'g' prefix from results:
let from = util_name.to_string() + ":";
let to = &from[1..];
(
result.stdout_str().replace(&from, to),
result.stderr_str().replace(&from, to),
)
};
Ok(CmdResult::new(
Some(result.tmpd()),
Some(result.code()),
result.succeeded(),
stdout.as_bytes(),
stderr.as_bytes(),
))
}

View file

@ -1,4 +1,9 @@
// spell-checker:ignore (words) bogusfile emptyfile
// * This file is part of the uutils coreutils package.
// *
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
// spell-checker:ignore (words) bogusfile emptyfile abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstu
use crate::common::util::*;
@ -265,3 +270,61 @@ fn test_bad_utf8_lines() {
.succeeds()
.stdout_is_bytes(output);
}
#[test]
fn test_head_invalid_num() {
new_ucmd!()
.args(&["-c", "1024R", "emptyfile.txt"])
.fails()
.stderr_is("head: invalid number of bytes: '1024R'");
new_ucmd!()
.args(&["-n", "1024R", "emptyfile.txt"])
.fails()
.stderr_is("head: invalid number of lines: '1024R'");
#[cfg(not(target_pointer_width = "128"))]
new_ucmd!()
.args(&["-c", "1Y", "emptyfile.txt"])
.fails()
.stderr_is("head: invalid number of bytes: '1Y': Value too large for defined data type");
#[cfg(not(target_pointer_width = "128"))]
new_ucmd!()
.args(&["-n", "1Y", "emptyfile.txt"])
.fails()
.stderr_is("head: invalid number of lines: '1Y': Value too large for defined data type");
#[cfg(target_pointer_width = "32")]
{
let sizes = ["1000G", "10T"];
for size in &sizes {
new_ucmd!()
.args(&["-c", size])
.fails()
.code_is(1)
.stderr_only(format!(
"head: invalid number of bytes: '{}': Value too large for defined data type",
size
));
}
}
}
#[test]
fn test_head_num_with_undocumented_sign_bytes() {
// tail: '-' is not documented (8.32 man pages)
// head: '+' is not documented (8.32 man pages)
const ALPHABET: &str = "abcdefghijklmnopqrstuvwxyz";
new_ucmd!()
.args(&["-c", "5"])
.pipe_in(ALPHABET)
.succeeds()
.stdout_is("abcde");
new_ucmd!()
.args(&["-c", "-5"])
.pipe_in(ALPHABET)
.succeeds()
.stdout_is("abcdefghijklmnopqrstu");
new_ucmd!()
.args(&["-c", "+5"])
.pipe_in(ALPHABET)
.succeeds()
.stdout_is("abcde");
}

View file

@ -1,147 +1,186 @@
use crate::common::util::*;
// Apparently some CI environments have configuration issues, e.g. with 'whoami' and 'id'.
// If we are running inside the CI and "needle" is in "stderr" skipping this test is
// considered okay. If we are not inside the CI this calls assert!(result.success).
//
// From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)"
// stderr: "whoami: cannot find name for user ID 1001"
// Maybe: "adduser --uid 1001 username" can put things right?
// stderr = id: Could not find uid 1001: No such id: 1001
fn skipping_test_is_okay(result: &CmdResult, needle: &str) -> bool {
if !result.succeeded() {
println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr_str());
if is_ci() && result.stderr_str().contains(needle) {
println!("test skipped:");
return true;
} else {
result.success();
// spell-checker:ignore (ToDO) coreutil
// These tests run the GNU coreutils `(g)id` binary in `$PATH` in order to gather reference values.
// If the `(g)id` in `$PATH` doesn't include a coreutils version string,
// or the version is too low, the test is skipped.
// The reference version is 8.32. Here 8.30 was chosen because right now there's no
// ubuntu image for github action available with a higher version than 8.30.
const VERSION_MIN: &str = "8.30"; // minimum Version for the reference `id` in $PATH
const VERSION_MIN_MULTIPLE_USERS: &str = "8.31"; // this feature was introduced in GNU's coreutils 8.31
const UUTILS_WARNING: &str = "uutils-tests-warning";
const UUTILS_INFO: &str = "uutils-tests-info";
macro_rules! unwrap_or_return {
( $e:expr ) => {
match $e {
Ok(x) => x,
Err(e) => {
println!("{}: test skipped: {}", UUTILS_INFO, e);
return;
}
}
}
false
};
}
fn return_whoami_username() -> String {
let scene = TestScenario::new("whoami");
let result = scene.cmd("whoami").run();
if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") {
println!("test skipped:");
return String::from("");
}
fn whoami() -> String {
// Apparently some CI environments have configuration issues, e.g. with 'whoami' and 'id'.
//
// From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)"
// whoami: cannot find name for user ID 1001
// id --name: cannot find name for user ID 1001
// id --name: cannot find name for group ID 116
//
// However, when running "id" from within "/bin/bash" it looks fine:
// id: "uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),101(systemd-journal)"
// whoami: "runner"
result.stdout_str().trim().to_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()
})
}
#[test]
fn test_id() {
let scene = TestScenario::new(util_name!());
#[cfg(unix)]
fn test_id_no_specified_user() {
let result = new_ucmd!().run();
let exp_result = unwrap_or_return!(expected_result(&[]));
let mut _exp_stdout = exp_result.stdout_str().to_string();
let result = scene.ucmd().arg("-u").succeeds();
let uid = result.stdout_str().trim();
let result = scene.ucmd().run();
if skipping_test_is_okay(&result, "Could not find uid") {
return;
}
// Verify that the id found by --user/-u exists in the list
result.stdout_contains(uid);
}
#[test]
fn test_id_from_name() {
let username = return_whoami_username();
if username.is_empty() {
return;
}
let scene = TestScenario::new(util_name!());
let result = scene.ucmd().arg(&username).run();
if skipping_test_is_okay(&result, "Could not find uid") {
return;
}
let uid = result.stdout_str().trim();
let result = scene.ucmd().run();
if skipping_test_is_okay(&result, "Could not find uid") {
return;
#[cfg(target_os = "linux")]
{
// NOTE: (SELinux NotImplemented) strip 'context' part from exp_stdout:
if let Some(context_offset) = exp_result.stdout_str().find(" context=") {
_exp_stdout.replace_range(context_offset.._exp_stdout.len() - 1, "");
}
}
result
// Verify that the id found by --user/-u exists in the list
.stdout_contains(uid)
// Verify that the username found by whoami exists in the list
.stdout_contains(username);
.stdout_is(_exp_stdout)
.stderr_is(exp_result.stderr_str())
.code_is(exp_result.code());
}
#[test]
fn test_id_name_from_id() {
let result = new_ucmd!().arg("-nu").run();
#[cfg(unix)]
fn test_id_single_user() {
let test_users = [&whoami()[..]];
let username_id = result.stdout_str().trim();
let username_whoami = return_whoami_username();
if username_whoami.is_empty() {
return;
}
assert_eq!(username_id, username_whoami);
}
#[test]
fn test_id_group() {
let scene = TestScenario::new(util_name!());
let mut exp_result = unwrap_or_return!(expected_result(&test_users));
scene
.ucmd()
.args(&test_users)
.run()
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str().replace(": Invalid argument", ""))
.code_is(exp_result.code());
let mut result = scene.ucmd().arg("-g").succeeds();
let s1 = result.stdout_str().trim();
assert!(s1.parse::<f64>().is_ok());
result = scene.ucmd().arg("--group").succeeds();
let s1 = result.stdout_str().trim();
assert!(s1.parse::<f64>().is_ok());
}
#[test]
fn test_id_groups() {
let scene = TestScenario::new(util_name!());
let result = scene.ucmd().arg("-G").succeeds();
let groups = result.stdout_str().trim().split_whitespace();
for s in groups {
assert!(s.parse::<f64>().is_ok());
}
let result = scene.ucmd().arg("--groups").succeeds();
let groups = result.stdout_str().trim().split_whitespace();
for s in groups {
assert!(s.parse::<f64>().is_ok());
// u/g/G z/n
for &opt in &["--user", "--group", "--groups"] {
let mut args = vec![opt];
args.extend_from_slice(&test_users);
exp_result = unwrap_or_return!(expected_result(&args));
scene
.ucmd()
.args(&args)
.run()
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str().replace(": Invalid argument", ""))
.code_is(exp_result.code());
args.push("--zero");
exp_result = unwrap_or_return!(expected_result(&args));
scene
.ucmd()
.args(&args)
.run()
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str().replace(": Invalid argument", ""))
.code_is(exp_result.code());
args.push("--name");
exp_result = unwrap_or_return!(expected_result(&args));
scene
.ucmd()
.args(&args)
.run()
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str().replace(": Invalid argument", ""))
.code_is(exp_result.code());
args.pop();
exp_result = unwrap_or_return!(expected_result(&args));
scene
.ucmd()
.args(&args)
.run()
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str().replace(": Invalid argument", ""))
.code_is(exp_result.code());
}
}
#[test]
fn test_id_user() {
let scene = TestScenario::new(util_name!());
#[cfg(unix)]
fn test_id_single_user_non_existing() {
let args = &["hopefully_non_existing_username"];
let result = new_ucmd!().args(args).run();
let exp_result = unwrap_or_return!(expected_result(args));
let result = scene.ucmd().arg("-u").succeeds();
let s1 = result.stdout_str().trim();
assert!(s1.parse::<f64>().is_ok());
let result = scene.ucmd().arg("--user").succeeds();
let s1 = result.stdout_str().trim();
assert!(s1.parse::<f64>().is_ok());
// It is unknown why on macOS (and possibly others?) `id` adds "Invalid argument".
// coreutils 8.32: $ LC_ALL=C id foobar
// macOS: stderr: "id: 'foobar': no such user: Invalid argument"
// linux: stderr: "id: 'foobar': no such user"
result
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str().replace(": Invalid argument", ""))
.code_is(exp_result.code());
}
#[test]
#[cfg(unix)]
fn test_id_name() {
let scene = TestScenario::new(util_name!());
for &opt in &["--user", "--group", "--groups"] {
let args = [opt, "--name"];
let result = scene.ucmd().args(&args).run();
let exp_result = unwrap_or_return!(expected_result(&args));
result
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str())
.code_is(exp_result.code());
if opt == "--user" {
assert_eq!(result.stdout_str().trim_end(), whoami());
}
}
}
#[test]
#[cfg(unix)]
fn test_id_real() {
let scene = TestScenario::new(util_name!());
for &opt in &["--user", "--group", "--groups"] {
let args = [opt, "--real"];
let result = scene.ucmd().args(&args).run();
let exp_result = unwrap_or_return!(expected_result(&args));
result
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str())
.code_is(exp_result.code());
}
}
#[test]
#[cfg(all(unix, not(target_os = "linux")))]
fn test_id_pretty_print() {
let username = return_whoami_username();
if username.is_empty() {
return;
}
// `-p` is BSD only and not supported on GNU's `id`
let username = whoami();
let scene = TestScenario::new(util_name!());
let result = scene.ucmd().arg("-p").run();
let result = new_ucmd!().arg("-p").run();
if result.stdout_str().trim().is_empty() {
// this fails only on: "MinRustV (ubuntu-latest, feat_os_unix)"
// `rustc 1.40.0 (73528e339 2019-12-16)`
@ -150,20 +189,317 @@ fn test_id_pretty_print() {
// stdout =
// stderr = ', tests/common/util.rs:157:13
println!("test skipped:");
return;
} else {
result.success().stdout_contains(username);
}
result.success().stdout_contains(username);
}
#[test]
#[cfg(all(unix, not(target_os = "linux")))]
fn test_id_password_style() {
let username = return_whoami_username();
if username.is_empty() {
// `-P` is BSD only and not supported on GNU's `id`
let username = whoami();
let result = new_ucmd!().arg("-P").arg(&username).succeeds();
assert!(result.stdout_str().starts_with(&username));
}
#[test]
#[cfg(unix)]
fn test_id_multiple_users() {
#[cfg(target_os = "linux")]
let util_name = util_name!();
#[cfg(all(unix, not(target_os = "linux")))]
let util_name = &format!("g{}", util_name!());
let version_check_string = check_coreutil_version(util_name, VERSION_MIN_MULTIPLE_USERS);
if version_check_string.starts_with(UUTILS_WARNING) {
println!("{}\ntest skipped", version_check_string);
return;
}
let result = new_ucmd!().arg("-P").succeeds();
// Same typical users that GNU test suite is using.
let test_users = ["root", "man", "postfix", "sshd", &whoami()];
assert!(result.stdout_str().starts_with(&username));
let scene = TestScenario::new(util_name!());
let mut exp_result = unwrap_or_return!(expected_result(&test_users));
scene
.ucmd()
.args(&test_users)
.run()
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str().replace(": Invalid argument", ""))
.code_is(exp_result.code());
// u/g/G z/n
for &opt in &["--user", "--group", "--groups"] {
let mut args = vec![opt];
args.extend_from_slice(&test_users);
exp_result = unwrap_or_return!(expected_result(&args));
scene
.ucmd()
.args(&args)
.run()
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str().replace(": Invalid argument", ""))
.code_is(exp_result.code());
args.push("--zero");
exp_result = unwrap_or_return!(expected_result(&args));
scene
.ucmd()
.args(&args)
.run()
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str().replace(": Invalid argument", ""))
.code_is(exp_result.code());
args.push("--name");
exp_result = unwrap_or_return!(expected_result(&args));
scene
.ucmd()
.args(&args)
.run()
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str().replace(": Invalid argument", ""))
.code_is(exp_result.code());
args.pop();
exp_result = unwrap_or_return!(expected_result(&args));
scene
.ucmd()
.args(&args)
.run()
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str().replace(": Invalid argument", ""))
.code_is(exp_result.code());
}
}
#[test]
#[cfg(unix)]
fn test_id_multiple_users_non_existing() {
#[cfg(target_os = "linux")]
let util_name = util_name!();
#[cfg(all(unix, not(target_os = "linux")))]
let util_name = &format!("g{}", util_name!());
let version_check_string = check_coreutil_version(util_name, VERSION_MIN_MULTIPLE_USERS);
if version_check_string.starts_with(UUTILS_WARNING) {
println!("{}\ntest skipped", version_check_string);
return;
}
let test_users = [
"root",
"hopefully_non_existing_username1",
&whoami(),
"man",
"hopefully_non_existing_username2",
"hopefully_non_existing_username3",
"postfix",
"sshd",
"hopefully_non_existing_username4",
&whoami(),
];
let scene = TestScenario::new(util_name!());
let mut exp_result = unwrap_or_return!(expected_result(&test_users));
scene
.ucmd()
.args(&test_users)
.run()
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str().replace(": Invalid argument", ""))
.code_is(exp_result.code());
// u/g/G z/n
for &opt in &["--user", "--group", "--groups"] {
let mut args = vec![opt];
args.extend_from_slice(&test_users);
exp_result = unwrap_or_return!(expected_result(&args));
scene
.ucmd()
.args(&args)
.run()
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str().replace(": Invalid argument", ""))
.code_is(exp_result.code());
args.push("--zero");
exp_result = unwrap_or_return!(expected_result(&args));
scene
.ucmd()
.args(&args)
.run()
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str().replace(": Invalid argument", ""))
.code_is(exp_result.code());
args.push("--name");
exp_result = unwrap_or_return!(expected_result(&args));
scene
.ucmd()
.args(&args)
.run()
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str().replace(": Invalid argument", ""))
.code_is(exp_result.code());
args.pop();
exp_result = unwrap_or_return!(expected_result(&args));
scene
.ucmd()
.args(&args)
.run()
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str().replace(": Invalid argument", ""))
.code_is(exp_result.code());
}
}
#[test]
#[cfg(unix)]
fn test_id_default_format() {
let scene = TestScenario::new(util_name!());
for &opt1 in &["--name", "--real"] {
// id: cannot print only names or real IDs in default format
let args = [opt1];
scene
.ucmd()
.args(&args)
.fails()
.stderr_only(unwrap_or_return!(expected_result(&args)).stderr_str());
for &opt2 in &["--user", "--group", "--groups"] {
// u/g/G n/r
let args = [opt2, opt1];
let result = scene.ucmd().args(&args).run();
let exp_result = unwrap_or_return!(expected_result(&args));
result
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str())
.code_is(exp_result.code());
}
}
for &opt2 in &["--user", "--group", "--groups"] {
// u/g/G
let args = [opt2];
scene
.ucmd()
.args(&args)
.succeeds()
.stdout_only(unwrap_or_return!(expected_result(&args)).stdout_str());
}
}
#[test]
#[cfg(unix)]
fn test_id_zero() {
let scene = TestScenario::new(util_name!());
for z_flag in &["-z", "--zero"] {
// id: option --zero not permitted in default format
scene
.ucmd()
.args(&[z_flag])
.fails()
.stderr_only(unwrap_or_return!(expected_result(&[z_flag])).stderr_str());
for &opt1 in &["--name", "--real"] {
// id: cannot print only names or real IDs in default format
let args = [opt1, z_flag];
scene
.ucmd()
.args(&args)
.fails()
.stderr_only(unwrap_or_return!(expected_result(&args)).stderr_str());
for &opt2 in &["--user", "--group", "--groups"] {
// u/g/G n/r z
let args = [opt2, z_flag, opt1];
let result = scene.ucmd().args(&args).run();
let exp_result = unwrap_or_return!(expected_result(&args));
result
.stdout_is(exp_result.stdout_str())
.stderr_is(exp_result.stderr_str())
.code_is(exp_result.code());
}
}
for &opt2 in &["--user", "--group", "--groups"] {
// u/g/G z
let args = [opt2, z_flag];
scene
.ucmd()
.args(&args)
.succeeds()
.stdout_only(unwrap_or_return!(expected_result(&args)).stdout_str());
}
}
}
fn check_coreutil_version(util_name: &str, version_expected: &str) -> String {
// example:
// $ id --version | head -n 1
// id (GNU coreutils) 8.32.162-4eda
let scene = TestScenario::new(util_name);
let version_check = scene
.cmd_keepenv(&util_name)
.env("LC_ALL", "C")
.arg("--version")
.run();
version_check
.stdout_str()
.split('\n')
.collect::<Vec<_>>()
.get(0)
.map_or_else(
|| format!("{}: unexpected output format for reference coreutil: '{} --version'", UUTILS_WARNING, util_name),
|s| {
if s.contains(&format!("(GNU coreutils) {}", version_expected)) {
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_expected = version_expected.parse::<f32>().unwrap_or_default();
if version_found > version_expected {
format!("{}: version for the reference coreutil '{}' is higher than expected; expected: {}, found: {}", UUTILS_INFO, util_name, version_expected, version_found)
} else {
format!("{}: version for the reference coreutil '{}' does not match; expected: {}, found: {}", UUTILS_WARNING, util_name, version_expected, version_found) }
} else {
format!("{}: no coreutils version string found for reference coreutils '{} --version'", UUTILS_WARNING, util_name)
}
},
)
}
#[allow(clippy::needless_borrow)]
#[cfg(unix)]
fn expected_result(args: &[&str]) -> Result<CmdResult, String> {
#[cfg(target_os = "linux")]
let util_name = util_name!();
#[cfg(all(unix, not(target_os = "linux")))]
let util_name = &format!("g{}", util_name!());
let version_check_string = check_coreutil_version(util_name, VERSION_MIN);
if version_check_string.starts_with(UUTILS_WARNING) {
return Err(version_check_string);
}
println!("{}", version_check_string);
let scene = TestScenario::new(util_name);
let result = scene
.cmd_keepenv(util_name)
.env("LC_ALL", "C")
.args(args)
.run();
let (stdout, stderr): (String, String) = if cfg!(target_os = "linux") {
(
result.stdout_str().to_string(),
result.stderr_str().to_string(),
)
} else {
// strip 'g' prefix from results:
let from = util_name.to_string() + ":";
let to = &from[1..];
(
result.stdout_str().replace(&from, to),
result.stderr_str().replace(&from, to),
)
};
Ok(CmdResult::new(
Some(result.tmpd()),
Some(result.code()),
result.succeeded(),
stdout.as_bytes(),
stderr.as_bytes(),
))
}

View file

@ -674,3 +674,410 @@ fn test_install_creating_leading_dir_fails_on_long_name() {
.fails()
.stderr_contains("failed to create");
}
#[test]
fn test_install_dir() {
let (at, mut ucmd) = at_and_ucmd!();
let dir = "target_dir";
let file1 = "source_file1";
let file2 = "source_file2";
at.touch(file1);
at.touch(file2);
at.mkdir(dir);
ucmd.arg(file1)
.arg(file2)
.arg(&format!("--target-directory={}", dir))
.succeeds()
.no_stderr();
assert!(at.file_exists(file1));
assert!(at.file_exists(file2));
assert!(at.file_exists(&format!("{}/{}", dir, file1)));
assert!(at.file_exists(&format!("{}/{}", dir, file2)));
}
//
// test backup functionality
#[test]
fn test_install_backup_short_no_args_files() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_simple_backup_file_a";
let file_b = "test_install_simple_backup_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("-b")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}~", file_b)));
}
#[test]
fn test_install_backup_short_no_args_file_to_dir() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file = "test_install_simple_backup_file_a";
let dest_dir = "test_install_dest/";
let expect = format!("{}{}", dest_dir, file);
at.touch(file);
at.mkdir(dest_dir);
at.touch(&expect);
scene
.ucmd()
.arg("-b")
.arg(file)
.arg(dest_dir)
.succeeds()
.no_stderr();
assert!(at.file_exists(file));
assert!(at.file_exists(&expect));
assert!(at.file_exists(&format!("{}~", expect)));
}
// Long --backup option is tested separately as it requires a slightly different
// handling than '-b' does.
#[test]
fn test_install_backup_long_no_args_files() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_simple_backup_file_a";
let file_b = "test_install_simple_backup_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("--backup")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}~", file_b)));
}
#[test]
fn test_install_backup_long_no_args_file_to_dir() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file = "test_install_simple_backup_file_a";
let dest_dir = "test_install_dest/";
let expect = format!("{}{}", dest_dir, file);
at.touch(file);
at.mkdir(dest_dir);
at.touch(&expect);
scene
.ucmd()
.arg("--backup")
.arg(file)
.arg(dest_dir)
.succeeds()
.no_stderr();
assert!(at.file_exists(file));
assert!(at.file_exists(&expect));
assert!(at.file_exists(&format!("{}~", expect)));
}
#[test]
fn test_install_backup_short_custom_suffix() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_custom_suffix_file_a";
let file_b = "test_install_backup_custom_suffix_file_b";
let suffix = "super-suffix-of-the-century";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("-b")
.arg(format!("--suffix={}", suffix))
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}{}", file_b, suffix)));
}
#[test]
fn test_install_backup_custom_suffix_via_env() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_custom_suffix_file_a";
let file_b = "test_install_backup_custom_suffix_file_b";
let suffix = "super-suffix-of-the-century";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("-b")
.env("SIMPLE_BACKUP_SUFFIX", suffix)
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}{}", file_b, suffix)));
}
#[test]
fn test_install_backup_numbered_with_t() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("--backup=t")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}.~1~", file_b)));
}
#[test]
fn test_install_backup_numbered_with_numbered() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("--backup=numbered")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}.~1~", file_b)));
}
#[test]
fn test_install_backup_existing() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("--backup=existing")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}~", file_b)));
}
#[test]
fn test_install_backup_nil() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("--backup=nil")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}~", file_b)));
}
#[test]
fn test_install_backup_numbered_if_existing_backup_existing() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
let file_b_backup = "test_install_backup_numbering_file_b.~1~";
at.touch(file_a);
at.touch(file_b);
at.touch(file_b_backup);
scene
.ucmd()
.arg("--backup=existing")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(file_b_backup));
assert!(at.file_exists(&*format!("{}.~2~", file_b)));
}
#[test]
fn test_install_backup_numbered_if_existing_backup_nil() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
let file_b_backup = "test_install_backup_numbering_file_b.~1~";
at.touch(file_a);
at.touch(file_b);
at.touch(file_b_backup);
scene
.ucmd()
.arg("--backup=nil")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(file_b_backup));
assert!(at.file_exists(&*format!("{}.~2~", file_b)));
}
#[test]
fn test_install_backup_simple() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("--backup=simple")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}~", file_b)));
}
#[test]
fn test_install_backup_never() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("--backup=never")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}~", file_b)));
}
#[test]
fn test_install_backup_none() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("--backup=none")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(!at.file_exists(&format!("{}~", file_b)));
}
#[test]
fn test_install_backup_off() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_install_backup_numbering_file_a";
let file_b = "test_install_backup_numbering_file_b";
at.touch(file_a);
at.touch(file_b);
scene
.ucmd()
.arg("--backup=off")
.arg(file_a)
.arg(file_b)
.succeeds()
.no_stderr();
assert!(at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(!at.file_exists(&format!("{}~", file_b)));
}

View file

@ -428,20 +428,6 @@ fn test_symlink_relative() {
assert_eq!(at.resolve_link(link), file_a);
}
#[test]
fn test_hardlink_relative() {
let (at, mut ucmd) = at_and_ucmd!();
let file_a = "test_hardlink_relative_a";
let link = "test_hardlink_relative_link";
at.touch(file_a);
// relative hardlink
ucmd.args(&["-r", "-v", file_a, link])
.succeeds()
.stdout_only(format!("'{}' -> '{}'\n", link, file_a));
}
#[test]
fn test_symlink_relative_path() {
let (at, mut ucmd) = at_and_ucmd!();
@ -571,3 +557,34 @@ fn test_symlink_no_deref_file() {
assert!(at.is_symlink(link));
assert_eq!(at.resolve_link(link), file1);
}
#[test]
fn test_relative_requires_symbolic() {
new_ucmd!().args(&["-r", "foo", "bar"]).fails();
}
#[test]
fn test_relative_dst_already_symlink() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("file1");
at.symlink_file("file1", "file2");
ucmd.arg("-srf").arg("file1").arg("file2").succeeds();
at.is_symlink("file2");
}
#[test]
fn test_relative_src_already_symlink() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("file1");
at.symlink_file("file1", "file2");
ucmd.arg("-sr").arg("file2").arg("file3").succeeds();
assert!(at.resolve_link("file3").ends_with("file1"));
}
#[test]
fn test_relative_recursive() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("dir");
ucmd.args(&["-sr", "dir", "dir/recursive"]).succeeds();
assert_eq!(at.resolve_link("dir/recursive"), ".");
}

View file

@ -168,7 +168,7 @@ fn test_ls_width() {
.ucmd()
.args(&option.split(' ').collect::<Vec<_>>())
.fails()
.stderr_only("ls: invalid line width: 1a");
.stderr_only("ls: invalid line width: '1a'");
}
}
@ -398,7 +398,7 @@ fn test_ls_long_formats() {
.arg("--author")
.arg("test-long-formats")
.succeeds();
assert!(re_three.is_match(&result.stdout_str()));
assert!(re_three.is_match(result.stdout_str()));
#[cfg(unix)]
{
@ -701,20 +701,20 @@ fn test_ls_styles() {
.arg("-l")
.arg("--time-style=full-iso")
.succeeds();
assert!(re_full.is_match(&result.stdout_str()));
assert!(re_full.is_match(result.stdout_str()));
//long-iso
let result = scene
.ucmd()
.arg("-l")
.arg("--time-style=long-iso")
.succeeds();
assert!(re_long.is_match(&result.stdout_str()));
assert!(re_long.is_match(result.stdout_str()));
//iso
let result = scene.ucmd().arg("-l").arg("--time-style=iso").succeeds();
assert!(re_iso.is_match(&result.stdout_str()));
assert!(re_iso.is_match(result.stdout_str()));
//locale
let result = scene.ucmd().arg("-l").arg("--time-style=locale").succeeds();
assert!(re_locale.is_match(&result.stdout_str()));
assert!(re_locale.is_match(result.stdout_str()));
//Overwrite options tests
let result = scene
@ -723,19 +723,19 @@ fn test_ls_styles() {
.arg("--time-style=long-iso")
.arg("--time-style=iso")
.succeeds();
assert!(re_iso.is_match(&result.stdout_str()));
assert!(re_iso.is_match(result.stdout_str()));
let result = scene
.ucmd()
.arg("--time-style=iso")
.arg("--full-time")
.succeeds();
assert!(re_full.is_match(&result.stdout_str()));
assert!(re_full.is_match(result.stdout_str()));
let result = scene
.ucmd()
.arg("--full-time")
.arg("--time-style=iso")
.succeeds();
assert!(re_iso.is_match(&result.stdout_str()));
assert!(re_iso.is_match(result.stdout_str()));
let result = scene
.ucmd()
@ -743,7 +743,7 @@ fn test_ls_styles() {
.arg("--time-style=iso")
.arg("--full-time")
.succeeds();
assert!(re_full.is_match(&result.stdout_str()));
assert!(re_full.is_match(result.stdout_str()));
let result = scene
.ucmd()
@ -751,7 +751,7 @@ fn test_ls_styles() {
.arg("-x")
.arg("-l")
.succeeds();
assert!(re_full.is_match(&result.stdout_str()));
assert!(re_full.is_match(result.stdout_str()));
at.touch("test2");
let result = scene.ucmd().arg("--full-time").arg("-x").succeeds();
@ -1143,7 +1143,7 @@ fn test_ls_indicator_style() {
for opt in options {
scene
.ucmd()
.arg(format!("{}", opt))
.arg(opt.to_string())
.succeeds()
.stdout_contains(&"/");
}
@ -2021,3 +2021,28 @@ fn test_ls_path() {
.run()
.stdout_is(expected_stdout);
}
#[test]
fn test_ls_dangling_symlinks() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir("temp_dir");
at.symlink_file("does_not_exist", "temp_dir/dangle");
scene.ucmd().arg("-L").arg("temp_dir/dangle").fails();
scene.ucmd().arg("-H").arg("temp_dir/dangle").fails();
scene
.ucmd()
.arg("temp_dir/dangle")
.succeeds()
.stdout_contains("dangle");
scene
.ucmd()
.arg("-Li")
.arg("temp_dir")
.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" });
}

View file

@ -17,7 +17,10 @@ static TEST_TEMPLATE8: &str = "tempXXXl/ate";
#[cfg(windows)]
static TEST_TEMPLATE8: &str = "tempXXXl\\ate";
#[cfg(not(windows))]
const TMPDIR: &str = "TMPDIR";
#[cfg(windows)]
const TMPDIR: &str = "TMP";
#[test]
fn test_mktemp_mktemp() {
@ -122,7 +125,8 @@ fn test_mktemp_mktemp_t() {
.arg(TEST_TEMPLATE8)
.fails()
.no_stdout()
.stderr_contains("suffix cannot contain any path separators");
.stderr_contains("invalid suffix")
.stderr_contains("contains directory separator");
}
#[test]
@ -386,7 +390,7 @@ fn test_mktemp_tmpdir_one_arg() {
let scene = TestScenario::new(util_name!());
let result = scene
.ucmd()
.ucmd_keepenv()
.arg("--tmpdir")
.arg("apt-key-gpghome.XXXXXXXXXX")
.succeeds();
@ -399,7 +403,7 @@ fn test_mktemp_directory_tmpdir() {
let scene = TestScenario::new(util_name!());
let result = scene
.ucmd()
.ucmd_keepenv()
.arg("--directory")
.arg("--tmpdir")
.arg("apt-key-gpghome.XXXXXXXXXX")

View file

@ -614,7 +614,7 @@ fn test_mv_overwrite_nonempty_dir() {
// Not same error as GNU; the error message is a rust builtin
// TODO: test (and implement) correct error message (or at least decide whether to do so)
// Current: "mv: couldn't rename path (Directory not empty; from=a; to=b)"
// GNU: "mv: cannot move a to b: Directory not empty"
// GNU: "mv: cannot move 'a' to 'b': Directory not empty"
// Verbose output for the move should not be shown on failure
let result = ucmd.arg("-vT").arg(dir_a).arg(dir_b).fails();
@ -638,7 +638,7 @@ fn test_mv_backup_dir() {
.arg(dir_b)
.succeeds()
.stdout_only(format!(
"{} -> {} (backup: {}~)\n",
"'{}' -> '{}' (backup: '{}~')\n",
dir_a, dir_b, dir_b
));
@ -672,7 +672,7 @@ fn test_mv_errors() {
// $ at.touch file && at.mkdir dir
// $ mv -T file dir
// err == mv: cannot overwrite directory dir with non-directory
// err == mv: cannot overwrite directory 'dir' with non-directory
scene
.ucmd()
.arg("-T")
@ -680,13 +680,13 @@ fn test_mv_errors() {
.arg(dir)
.fails()
.stderr_is(format!(
"mv: cannot overwrite directory {} with non-directory\n",
"mv: cannot overwrite directory '{}' with non-directory\n",
dir
));
// $ at.mkdir dir && at.touch file
// $ mv dir file
// err == mv: cannot overwrite non-directory file with directory dir
// err == mv: cannot overwrite non-directory 'file' with directory 'dir'
assert!(!scene
.ucmd()
.arg(dir)
@ -713,7 +713,7 @@ fn test_mv_verbose() {
.arg(file_a)
.arg(file_b)
.succeeds()
.stdout_only(format!("{} -> {}\n", file_a, file_b));
.stdout_only(format!("'{}' -> '{}'\n", file_a, file_b));
at.touch(file_a);
scene
@ -723,12 +723,13 @@ fn test_mv_verbose() {
.arg(file_b)
.succeeds()
.stdout_only(format!(
"{} -> {} (backup: {}~)\n",
"'{}' -> '{}' (backup: '{}~')\n",
file_a, file_b, file_b
));
}
#[test]
#[cfg(target_os = "linux")] // mkdir does not support -m on windows. Freebsd doesn't return a permission error either.
fn test_mv_permission_error() {
let scene = TestScenario::new("mkdir");
let folder1 = "bar";
@ -738,12 +739,11 @@ fn test_mv_permission_error() {
scene.ucmd().arg("-m777").arg(folder2).succeeds();
scene
.cmd_keepenv(util_name!())
.ccmd("mv")
.arg(folder2)
.arg(folder_to_move)
.run()
.stderr_str()
.ends_with("Permission denied");
.fails()
.stderr_contains("Permission denied");
}
// Todo:
@ -756,5 +756,5 @@ fn test_mv_permission_error() {
// -r--r--r-- 1 user user 0 okt 25 11:21 b
// $
// $ mv -v a b
// mv: try to overwrite b, overriding mode 0444 (r--r--r--)? y
// a -> b
// mv: try to overwrite 'b', overriding mode 0444 (r--r--r--)? y
// 'a' -> 'b'

View file

@ -35,7 +35,7 @@ fn test_from_iec_i_requires_suffix() {
new_ucmd!()
.args(&["--from=iec-i", "1024"])
.fails()
.stderr_is("numfmt: missing 'i' suffix in input: 1024 (e.g Ki/Mi/Gi)");
.stderr_is("numfmt: missing 'i' suffix in input: '1024' (e.g Ki/Mi/Gi)");
}
#[test]
@ -123,7 +123,7 @@ fn test_header_error_if_non_numeric() {
new_ucmd!()
.args(&["--header=two"])
.run()
.stderr_is("numfmt: invalid header value two");
.stderr_is("numfmt: invalid header value 'two'");
}
#[test]
@ -131,7 +131,7 @@ fn test_header_error_if_0() {
new_ucmd!()
.args(&["--header=0"])
.run()
.stderr_is("numfmt: invalid header value 0");
.stderr_is("numfmt: invalid header value '0'");
}
#[test]
@ -139,7 +139,7 @@ fn test_header_error_if_negative() {
new_ucmd!()
.args(&["--header=-3"])
.run()
.stderr_is("numfmt: invalid header value -3");
.stderr_is("numfmt: invalid header value '-3'");
}
#[test]
@ -187,7 +187,7 @@ fn test_should_report_invalid_empty_number_on_empty_stdin() {
.args(&["--from=auto"])
.pipe_in("\n")
.run()
.stderr_is("numfmt: invalid number: \n");
.stderr_is("numfmt: invalid number: ''\n");
}
#[test]
@ -196,7 +196,7 @@ fn test_should_report_invalid_empty_number_on_blank_stdin() {
.args(&["--from=auto"])
.pipe_in(" \t \n")
.run()
.stderr_is("numfmt: invalid number: \n");
.stderr_is("numfmt: invalid number: ''\n");
}
#[test]
@ -205,14 +205,14 @@ fn test_should_report_invalid_suffix_on_stdin() {
.args(&["--from=auto"])
.pipe_in("1k")
.run()
.stderr_is("numfmt: invalid suffix in input: 1k\n");
.stderr_is("numfmt: invalid suffix in input: '1k'\n");
// GNU numfmt reports this one as “invalid number”
new_ucmd!()
.args(&["--from=auto"])
.pipe_in("NaN")
.run()
.stderr_is("numfmt: invalid suffix in input: NaN\n");
.stderr_is("numfmt: invalid suffix in input: 'NaN'\n");
}
#[test]
@ -222,7 +222,7 @@ fn test_should_report_invalid_number_with_interior_junk() {
.args(&["--from=auto"])
.pipe_in("1x0K")
.run()
.stderr_is("numfmt: invalid number: 1x0K\n");
.stderr_is("numfmt: invalid number: '1x0K'\n");
}
#[test]
@ -461,7 +461,7 @@ fn test_delimiter_overrides_whitespace_separator() {
.args(&["-d,"])
.pipe_in("1 234,56")
.fails()
.stderr_is("numfmt: invalid number: 1 234\n");
.stderr_is("numfmt: invalid number: '1 234'\n");
}
#[test]
@ -481,3 +481,27 @@ fn test_delimiter_with_padding_and_fields() {
.succeeds()
.stdout_only(" 1.0K| 2.0K\n");
}
#[test]
fn test_round() {
for (method, exp) in &[
("from-zero", ["9.1K", "-9.1K", "9.1K", "-9.1K"]),
("towards-zero", ["9.0K", "-9.0K", "9.0K", "-9.0K"]),
("up", ["9.1K", "-9.0K", "9.1K", "-9.0K"]),
("down", ["9.0K", "-9.1K", "9.0K", "-9.1K"]),
("nearest", ["9.0K", "-9.0K", "9.1K", "-9.1K"]),
] {
new_ucmd!()
.args(&[
"--to=si",
&format!("--round={}", method),
"--",
"9001",
"-9001",
"9099",
"-9099",
])
.succeeds()
.stdout_only(exp.join("\n") + "\n");
}
}

View file

@ -1,3 +1,8 @@
// * This file is part of the uutils coreutils package.
// *
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
extern crate unindent;
use self::unindent::*;
@ -804,3 +809,40 @@ fn test_traditional_only_label() {
",
));
}
#[test]
fn test_od_invalid_bytes() {
const INVALID_SIZE: &str = "1fb4t";
const BIG_SIZE: &str = "1Y";
// NOTE:
// GNU's od (8.32) with option '--width' does not accept 'Y' as valid suffix.
// According to the man page it should be valid in the same way it is valid for
// '--read-bytes' and '--skip-bytes'.
let options = [
"--read-bytes",
"--skip-bytes",
"--width",
// "--strings", // TODO: consider testing here once '--strings' is implemented
];
for option in &options {
new_ucmd!()
.arg(format!("{}={}", option, INVALID_SIZE))
.arg("file")
.fails()
.code_is(1)
.stderr_only(format!(
"od: invalid {} argument '{}'",
option, INVALID_SIZE
));
#[cfg(not(target_pointer_width = "128"))]
new_ucmd!()
.arg(format!("{}={}", option, BIG_SIZE))
.arg("file")
.fails()
.code_is(1)
.stderr_only(format!("od: {} argument '{}' too large", option, BIG_SIZE));
}
}

View file

@ -38,7 +38,7 @@ fn test_posix_mode() {
// fail on long path
new_ucmd!()
.args(&["-p", &"dir".repeat(libc::PATH_MAX as usize + 1).as_str()])
.args(&["-p", "dir".repeat(libc::PATH_MAX as usize + 1).as_str()])
.fails()
.no_stdout();
@ -46,7 +46,7 @@ fn test_posix_mode() {
new_ucmd!()
.args(&[
"-p",
&format!("dir/{}", "file".repeat(libc::FILENAME_MAX as usize + 1)).as_str(),
format!("dir/{}", "file".repeat(libc::FILENAME_MAX as usize + 1)).as_str(),
])
.fails()
.no_stdout();
@ -76,7 +76,7 @@ fn test_posix_special() {
// fail on long path
new_ucmd!()
.args(&["-P", &"dir".repeat(libc::PATH_MAX as usize + 1).as_str()])
.args(&["-P", "dir".repeat(libc::PATH_MAX as usize + 1).as_str()])
.fails()
.no_stdout();
@ -84,7 +84,7 @@ fn test_posix_special() {
new_ucmd!()
.args(&[
"-P",
&format!("dir/{}", "file".repeat(libc::FILENAME_MAX as usize + 1)).as_str(),
format!("dir/{}", "file".repeat(libc::FILENAME_MAX as usize + 1)).as_str(),
])
.fails()
.no_stdout();
@ -117,7 +117,7 @@ fn test_posix_all() {
.args(&[
"-p",
"-P",
&"dir".repeat(libc::PATH_MAX as usize + 1).as_str(),
"dir".repeat(libc::PATH_MAX as usize + 1).as_str(),
])
.fails()
.no_stdout();
@ -127,7 +127,7 @@ fn test_posix_all() {
.args(&[
"-p",
"-P",
&format!("dir/{}", "file".repeat(libc::FILENAME_MAX as usize + 1)).as_str(),
format!("dir/{}", "file".repeat(libc::FILENAME_MAX as usize + 1)).as_str(),
])
.fails()
.no_stdout();

View file

@ -102,9 +102,11 @@ fn expected_result(args: &[&str]) -> String {
#[cfg(target_vendor = "apple")]
let util_name = format!("g{}", util_name!());
// note: clippy::needless_borrow *false positive*
#[allow(clippy::needless_borrow)]
TestScenario::new(&util_name)
.cmd_keepenv(util_name)
.env("LANGUAGE", "C")
.env("LC_ALL", "C")
.args(args)
.succeeds()
.stdout_move_str()

View file

@ -3,6 +3,7 @@
use crate::common::util::*;
use chrono::offset::Local;
use chrono::DateTime;
use chrono::Duration;
use std::fs::metadata;
fn file_last_modified_time(ucmd: &UCommand, path: &str) -> String {
@ -20,8 +21,23 @@ fn file_last_modified_time(ucmd: &UCommand, path: &str) -> String {
.unwrap_or_default()
}
fn now_time() -> String {
Local::now().format("%b %d %H:%M %Y").to_string()
fn all_minutes(from: DateTime<Local>, to: DateTime<Local>) -> Vec<String> {
let to = to + Duration::minutes(1);
const FORMAT: &str = "%b %d %H:%M %Y";
let mut vec = vec![];
let mut current = from;
while current < to {
vec.push(current.format(FORMAT).to_string());
current = current + Duration::minutes(1);
}
vec
}
fn valid_last_modified_template_vars(from: DateTime<Local>) -> Vec<Vec<(String, String)>> {
all_minutes(from, Local::now())
.into_iter()
.map(|time| vec![("{last_modified_time}".to_string(), time)])
.collect()
}
#[test]
@ -33,10 +49,7 @@ fn test_without_any_options() {
scenario
.args(&[test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -48,10 +61,7 @@ fn test_with_numbering_option_with_number_width() {
scenario
.args(&["-n", "2", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -66,10 +76,7 @@ fn test_with_long_header_option() {
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![
(&"{last_modified_time}".to_string(), &value),
(&"{header}".to_string(), &header.to_string()),
],
&[("{last_modified_time}", &value), ("{header}", header)],
);
new_ucmd!()
@ -77,10 +84,7 @@ fn test_with_long_header_option() {
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![
(&"{last_modified_time}".to_string(), &value),
(&"{header}".to_string(), &header.to_string()),
],
&[("{last_modified_time}", &value), ("{header}", header)],
);
}
@ -93,18 +97,12 @@ fn test_with_double_space_option() {
scenario
.args(&["-d", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
new_ucmd!()
.args(&["--double-space", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -116,10 +114,7 @@ fn test_with_first_line_number_option() {
scenario
.args(&["-N", "5", "-n", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -131,10 +126,7 @@ fn test_with_first_line_number_long_option() {
scenario
.args(&["--first-line-number=5", "-n", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -146,10 +138,7 @@ fn test_with_number_option_with_custom_separator_char() {
scenario
.args(&["-nc", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -161,10 +150,7 @@ fn test_with_number_option_with_custom_separator_char_and_width() {
scenario
.args(&["-nc1", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -207,25 +193,19 @@ fn test_with_page_range() {
scenario
.args(&["--pages=15", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
new_ucmd!()
.args(&["+15", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
new_ucmd!()
.args(&["--pages=15:17", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path1,
vec![(&"{last_modified_time}".to_string(), &value)],
&[("{last_modified_time}", &value)],
);
new_ucmd!()
@ -233,7 +213,7 @@ fn test_with_page_range() {
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path1,
vec![(&"{last_modified_time}".to_string(), &value)],
&[("{last_modified_time}", &value)],
);
}
@ -246,10 +226,7 @@ fn test_with_no_header_trailer_option() {
scenario
.args(&["-t", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -262,10 +239,7 @@ fn test_with_page_length_option() {
scenario
.args(&["--pages=2:3", "-l", "100", "-n", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
new_ucmd!()
.args(&["--pages=2:3", "-l", "5", "-n", test_file_path])
@ -288,14 +262,14 @@ fn test_with_suppress_error_option() {
fn test_with_stdin() {
let expected_file_path = "stdin.log.expected";
let mut scenario = new_ucmd!();
let now = now_time();
let start = Local::now();
scenario
.pipe_in_fixture("stdin.log")
.args(&["--pages=1:2", "-n", "-"])
.run()
.stdout_is_templated_fixture(
.stdout_is_templated_fixture_any(
expected_file_path,
vec![(&"{last_modified_time}".to_string(), &now)],
&valid_last_modified_template_vars(start),
);
}
@ -308,18 +282,12 @@ fn test_with_column() {
scenario
.args(&["--pages=3:5", "--column=3", "-n", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
new_ucmd!()
.args(&["--pages=3:5", "-3", "-n", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -331,10 +299,7 @@ fn test_with_column_across_option() {
scenario
.args(&["--pages=3:5", "--column=3", "-a", "-n", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -354,10 +319,7 @@ fn test_with_column_across_option_and_column_separator() {
test_file_path,
])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
new_ucmd!()
.args(&[
@ -371,7 +333,7 @@ fn test_with_column_across_option_and_column_separator() {
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path1,
vec![(&"{last_modified_time}".to_string(), &value)],
&[("{last_modified_time}", &value)],
);
}
@ -382,25 +344,25 @@ fn test_with_mpr() {
let expected_test_file_path = "mpr.log.expected";
let expected_test_file_path1 = "mpr1.log.expected";
let expected_test_file_path2 = "mpr2.log.expected";
let now = now_time();
let start = Local::now();
new_ucmd!()
.args(&["--pages=1:2", "-m", "-n", test_file_path, test_file_path1])
.succeeds()
.stdout_is_templated_fixture(
.stdout_is_templated_fixture_any(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &now)],
&valid_last_modified_template_vars(start),
);
let now = now_time();
let start = Local::now();
new_ucmd!()
.args(&["--pages=2:4", "-m", "-n", test_file_path, test_file_path1])
.succeeds()
.stdout_is_templated_fixture(
.stdout_is_templated_fixture_any(
expected_test_file_path1,
vec![(&"{last_modified_time}".to_string(), &now)],
&valid_last_modified_template_vars(start),
);
let now = now_time();
let start = Local::now();
new_ucmd!()
.args(&[
"--pages=1:2",
@ -413,9 +375,9 @@ fn test_with_mpr() {
test_file_path,
])
.succeeds()
.stdout_is_templated_fixture(
.stdout_is_templated_fixture_any(
expected_test_file_path2,
vec![(&"{last_modified_time}".to_string(), &now)],
&valid_last_modified_template_vars(start),
);
}
@ -452,10 +414,7 @@ fn test_with_offset_space_option() {
test_file_path,
])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -497,9 +456,9 @@ fn test_with_pr_core_utils_tests() {
scenario_with_expected_status.stdout_is_templated_fixture(
test_file_path,
vec![
(&"{last_modified_time}".to_string(), &value),
(&"{file_name}".to_string(), &input_file_path.to_string()),
&[
("{last_modified_time}", &value),
("{file_name}", input_file_path),
],
);
}
@ -511,12 +470,12 @@ fn test_with_join_lines_option() {
let test_file_2 = "test.log";
let expected_file_path = "joined.log.expected";
let mut scenario = new_ucmd!();
let now = now_time();
let start = Local::now();
scenario
.args(&["+1:2", "-J", "-m", test_file_1, test_file_2])
.run()
.stdout_is_templated_fixture(
.stdout_is_templated_fixture_any(
expected_file_path,
vec![(&"{last_modified_time}".to_string(), &now)],
&valid_last_modified_template_vars(start),
);
}

View file

@ -1,3 +1,8 @@
// * This file is part of the uutils coreutils package.
// *
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
// spell-checker:ignore (words) ints
use crate::common::util::*;
@ -21,17 +26,31 @@ fn test_helper(file_name: &str, possible_args: &[&str]) {
#[test]
fn test_buffer_sizes() {
let buffer_sizes = [
"0", "50K", "50k", "1M", "100M", "1000G", "10T", "500E", "1Y",
];
let buffer_sizes = ["0", "50K", "50k", "1M", "100M"];
for buffer_size in &buffer_sizes {
new_ucmd!()
TestScenario::new(util_name!())
.ucmd_keepenv()
.arg("-n")
.arg("-S")
.arg(buffer_size)
.arg("ext_sort.txt")
.succeeds()
.stdout_is_fixture("ext_sort.expected");
#[cfg(not(target_pointer_width = "32"))]
{
let buffer_sizes = ["1000G", "10T"];
for buffer_size in &buffer_sizes {
TestScenario::new(util_name!())
.ucmd_keepenv()
.arg("-n")
.arg("-S")
.arg(buffer_size)
.arg("ext_sort.txt")
.succeeds()
.stdout_is_fixture("ext_sort.expected");
}
}
}
}
@ -43,11 +62,39 @@ fn test_invalid_buffer_size() {
.arg("-S")
.arg(invalid_buffer_size)
.fails()
.code_is(2)
.stderr_only(format!(
"sort: failed to parse buffer size `{}`: invalid digit found in string",
"sort: invalid --buffer-size argument '{}'",
invalid_buffer_size
));
}
#[cfg(not(target_pointer_width = "128"))]
new_ucmd!()
.arg("-n")
.arg("-S")
.arg("1Y")
.arg("ext_sort.txt")
.fails()
.code_is(2)
.stderr_only("sort: --buffer-size argument '1Y' too large");
#[cfg(target_pointer_width = "32")]
{
let buffer_sizes = ["1000G", "10T"];
for buffer_size in &buffer_sizes {
new_ucmd!()
.arg("-n")
.arg("-S")
.arg(buffer_size)
.arg("ext_sort.txt")
.fails()
.code_is(2)
.stderr_only(format!(
"sort: --buffer-size argument '{}' too large",
buffer_size
));
}
}
}
#[test]
@ -80,11 +127,7 @@ fn test_months_whitespace() {
#[test]
fn test_version_empty_lines() {
new_ucmd!()
.arg("-V")
.arg("version-empty-lines.txt")
.succeeds()
.stdout_is("\n\n\n\n\n\n\n1.2.3-alpha\n1.2.3-alpha2\n\t\t\t1.12.4\n11.2.3\n");
test_helper("version-empty-lines", &["-V", "--version-sort"]);
}
#[test]
@ -409,10 +452,20 @@ fn test_human_block_sizes2() {
.arg(human_numeric_sort_param)
.pipe_in(input)
.succeeds()
.stdout_only("-8T\n0.8M\n8981K\n21G\n909991M\n");
.stdout_only("-8T\n8981K\n0.8M\n909991M\n21G\n");
}
}
#[test]
fn test_human_numeric_zero_stable() {
let input = "0M\n0K\n-0K\n-P\n-0M\n";
new_ucmd!()
.arg("-hs")
.pipe_in(input)
.succeeds()
.stdout_only(input);
}
#[test]
fn test_month_default2() {
for month_sort_param in &["-M", "--month-sort", "--sort=month"] {
@ -717,26 +770,30 @@ fn test_pipe() {
#[test]
fn test_check() {
new_ucmd!()
.arg("-c")
.arg("check_fail.txt")
.fails()
.stdout_is("sort: check_fail.txt:6: disorder: 5\n");
for diagnose_arg in &["-c", "--check", "--check=diagnose-first"] {
new_ucmd!()
.arg(diagnose_arg)
.arg("check_fail.txt")
.fails()
.stdout_is("sort: check_fail.txt:6: disorder: 5\n");
new_ucmd!()
.arg("-c")
.arg("multiple_files.expected")
.succeeds()
.stdout_is("");
new_ucmd!()
.arg(diagnose_arg)
.arg("multiple_files.expected")
.succeeds()
.stdout_is("");
}
}
#[test]
fn test_check_silent() {
new_ucmd!()
.arg("-C")
.arg("check_fail.txt")
.fails()
.stdout_is("");
for silent_arg in &["-C", "--check=silent", "--check=quiet"] {
new_ucmd!()
.arg(silent_arg)
.arg("check_fail.txt")
.fails()
.stdout_is("");
}
}
#[test]
@ -790,5 +847,107 @@ fn test_nonexistent_file() {
#[test]
fn test_blanks() {
test_helper("blanks", &["-b", "--ignore-blanks"]);
test_helper("blanks", &["-b", "--ignore-leading-blanks"]);
}
#[test]
fn sort_multiple() {
new_ucmd!()
.args(&["no_trailing_newline1.txt", "no_trailing_newline2.txt"])
.succeeds()
.stdout_is("a\nb\nb\n");
}
#[test]
fn sort_empty_chunk() {
new_ucmd!()
.args(&["-S", "40b"])
.pipe_in("a\na\n")
.succeeds()
.stdout_is("a\na\n");
}
#[test]
#[cfg(target_os = "linux")]
fn test_compress() {
new_ucmd!()
.args(&[
"ext_sort.txt",
"-n",
"--compress-program",
"gzip",
"-S",
"10",
])
.succeeds()
.stdout_only_fixture("ext_sort.expected");
}
#[test]
fn test_compress_fail() {
TestScenario::new(util_name!())
.ucmd_keepenv()
.args(&[
"ext_sort.txt",
"-n",
"--compress-program",
"nonexistent-program",
"-S",
"10",
])
.fails()
.stderr_only("sort: couldn't execute compress program: errno 2");
}
#[test]
fn test_merge_batches() {
TestScenario::new(util_name!())
.ucmd_keepenv()
.args(&["ext_sort.txt", "-n", "-S", "150b"])
.succeeds()
.stdout_only_fixture("ext_sort.expected");
}
#[test]
fn test_merge_batch_size() {
TestScenario::new(util_name!())
.ucmd_keepenv()
.arg("--batch-size=2")
.arg("-m")
.arg("--unique")
.arg("merge_ints_interleaved_1.txt")
.arg("merge_ints_interleaved_2.txt")
.arg("merge_ints_interleaved_3.txt")
.arg("merge_ints_interleaved_3.txt")
.arg("merge_ints_interleaved_2.txt")
.arg("merge_ints_interleaved_1.txt")
.succeeds()
.stdout_only_fixture("merge_ints_interleaved.expected");
}
#[test]
fn test_sigpipe_panic() {
let mut cmd = new_ucmd!();
let mut child = cmd.args(&["ext_sort.txt"]).run_no_wait();
// Dropping the stdout should not lead to an error.
// The "Broken pipe" error should be silently ignored.
drop(child.stdout.take());
assert_eq!(
String::from_utf8(child.wait_with_output().unwrap().stderr),
Ok(String::new())
);
}
#[test]
fn test_conflict_check_out() {
let check_flags = ["-c=silent", "-c=quiet", "-c=diagnose-first", "-c", "-C"];
for check_flag in &check_flags {
new_ucmd!()
.arg(check_flag)
.arg("-o=/dev/null")
.fails()
.stderr_contains(
"error: The argument '--output <FILENAME>' cannot be used with '--check",
);
}
}

View file

@ -1,3 +1,8 @@
// * This file is part of the uutils coreutils package.
// *
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
extern crate rand;
extern crate regex;
@ -285,3 +290,53 @@ fn test_filter_command_fails() {
ucmd.args(&["--filter=/a/path/that/totally/does/not/exist", name])
.fails();
}
#[test]
fn test_split_lines_number() {
// Test if stdout/stderr for '--lines' option is correct
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch("file");
scene
.ucmd()
.args(&["--lines", "2", "file"])
.succeeds()
.no_stderr()
.no_stdout();
scene
.ucmd()
.args(&["--lines", "2fb", "file"])
.fails()
.code_is(1)
.stderr_only("split: invalid number of lines: '2fb'");
}
#[test]
fn test_split_invalid_bytes_size() {
new_ucmd!()
.args(&["-b", "1024R"])
.fails()
.code_is(1)
.stderr_only("split: invalid number of bytes: '1024R'");
#[cfg(not(target_pointer_width = "128"))]
new_ucmd!()
.args(&["-b", "1Y"])
.fails()
.code_is(1)
.stderr_only("split: invalid number of bytes: '1Y': Value too large for defined data type");
#[cfg(target_pointer_width = "32")]
{
let sizes = ["1000G", "10T"];
for size in &sizes {
new_ucmd!()
.args(&["-b", size])
.fails()
.code_is(1)
.stderr_only(format!(
"split: invalid number of bytes: '{}': Value too large for defined data type",
size
));
}
}
}

View file

@ -313,9 +313,11 @@ fn expected_result(args: &[&str]) -> String {
#[cfg(target_vendor = "apple")]
let util_name = format!("g{}", util_name!());
// note: clippy::needless_borrow *false positive*
#[allow(clippy::needless_borrow)]
TestScenario::new(&util_name)
.cmd_keepenv(util_name)
.env("LANGUAGE", "C")
.env("LC_ALL", "C")
.args(args)
.succeeds()
.stdout_move_str()

View file

@ -57,8 +57,18 @@ fn test_stdbuf_line_buffering_stdin_fails() {
#[cfg(not(target_os = "windows"))]
#[test]
fn test_stdbuf_invalid_mode_fails() {
new_ucmd!()
.args(&["-i", "1024R", "head"])
.fails()
.stderr_is("stdbuf: invalid mode 1024R\nTry 'stdbuf --help' for more information.");
let options = ["--input", "--output", "--error"];
for option in &options {
new_ucmd!()
.args(&[*option, "1024R", "head"])
.fails()
.code_is(125)
.stderr_only("stdbuf: invalid mode '1024R'");
#[cfg(not(target_pointer_width = "128"))]
new_ucmd!()
.args(&[*option, "1Y", "head"])
.fails()
.code_is(125)
.stderr_contains("stdbuf: invalid mode '1Y': Value too large for defined data type");
}
}

View file

@ -1,6 +1,12 @@
// * This file is part of the uutils coreutils package.
// *
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
// spell-checker:ignore (ToDO) abcdefghijklmnopqrstuvwxyz efghijklmnopqrstuvwxyz vwxyz emptyfile
extern crate tail;
use self::tail::parse_size;
use crate::common::util::*;
use std::char::from_digit;
use std::io::Write;
@ -236,41 +242,6 @@ fn test_bytes_big() {
}
}
#[test]
fn test_parse_size() {
// No suffix.
assert_eq!(Ok(1234), parse_size("1234"));
// kB is 1000
assert_eq!(Ok(9 * 1000), parse_size("9kB"));
// K is 1024
assert_eq!(Ok(2 * 1024), parse_size("2K"));
let suffixes = [
('M', 2u32),
('G', 3u32),
('T', 4u32),
('P', 5u32),
('E', 6u32),
];
for &(c, exp) in &suffixes {
let s = format!("2{}B", c);
assert_eq!(Ok(2 * (1000_u64).pow(exp)), parse_size(&s));
let s = format!("2{}", c);
assert_eq!(Ok(2 * (1024_u64).pow(exp)), parse_size(&s));
}
// Sizes that are too big.
assert!(parse_size("1Z").is_err());
assert!(parse_size("1Y").is_err());
// Bad number
assert!(parse_size("328hdsf3290").is_err()); // spell-checker:disable-line
}
#[test]
fn test_lines_with_size_suffix() {
const FILE: &str = "test_lines_with_size_suffix.txt";
@ -320,12 +291,11 @@ fn test_multiple_input_files_with_suppressed_headers() {
#[test]
fn test_multiple_input_quiet_flag_overrides_verbose_flag_for_suppressing_headers() {
// TODO: actually the later one should win, i.e. -qv should lead to headers being printed, -vq to them being suppressed
new_ucmd!()
.arg(FOOBAR_TXT)
.arg(FOOBAR_2_TXT)
.arg("-q")
.arg("-v")
.arg("-q")
.run()
.stdout_is_fixture("foobar_multiple_quiet.expected");
}
@ -388,3 +358,61 @@ fn test_positive_zero_lines() {
.succeeds()
.stdout_is("a\nb\nc\nd\ne\n");
}
#[test]
fn test_tail_invalid_num() {
new_ucmd!()
.args(&["-c", "1024R", "emptyfile.txt"])
.fails()
.stderr_is("tail: invalid number of bytes: '1024R'");
new_ucmd!()
.args(&["-n", "1024R", "emptyfile.txt"])
.fails()
.stderr_is("tail: invalid number of lines: '1024R'");
#[cfg(not(target_pointer_width = "128"))]
new_ucmd!()
.args(&["-c", "1Y", "emptyfile.txt"])
.fails()
.stderr_is("tail: invalid number of bytes: '1Y': Value too large for defined data type");
#[cfg(not(target_pointer_width = "128"))]
new_ucmd!()
.args(&["-n", "1Y", "emptyfile.txt"])
.fails()
.stderr_is("tail: invalid number of lines: '1Y': Value too large for defined data type");
#[cfg(target_pointer_width = "32")]
{
let sizes = ["1000G", "10T"];
for size in &sizes {
new_ucmd!()
.args(&["-c", size])
.fails()
.code_is(1)
.stderr_only(format!(
"tail: invalid number of bytes: '{}': Value too large for defined data type",
size
));
}
}
}
#[test]
fn test_tail_num_with_undocumented_sign_bytes() {
// tail: '-' is not documented (8.32 man pages)
// head: '+' is not documented (8.32 man pages)
const ALPHABET: &str = "abcdefghijklmnopqrstuvwxyz";
new_ucmd!()
.args(&["-c", "5"])
.pipe_in(ALPHABET)
.succeeds()
.stdout_is("vwxyz");
new_ucmd!()
.args(&["-c", "-5"])
.pipe_in(ALPHABET)
.succeeds()
.stdout_is("vwxyz");
new_ucmd!()
.args(&["-c", "+5"])
.pipe_in(ALPHABET)
.succeeds()
.stdout_is("efghijklmnopqrstuvwxyz");
}

View file

@ -8,7 +8,7 @@
// file that was distributed with this source code.
//
// spell-checker:ignore (words) pseudofloat
// spell-checker:ignore (words) egid euid pseudofloat
use crate::common::util::*;
@ -165,7 +165,7 @@ fn test_dangling_string_comparison_is_error() {
.args(&["missing_something", "="])
.run()
.status_code(2)
.stderr_is("test: missing argument after =");
.stderr_is("test: missing argument after '='");
}
#[test]
@ -265,7 +265,7 @@ fn test_float_inequality_is_error() {
.args(&["123.45", "-ge", "6"])
.run()
.status_code(2)
.stderr_is("test: invalid integer 123.45");
.stderr_is("test: invalid integer '123.45'");
}
#[test]
@ -283,7 +283,7 @@ fn test_invalid_utf8_integer_compare() {
cmd.run()
.status_code(2)
.stderr_is("test: invalid integer fo<EFBFBD>o");
.stderr_is("test: invalid integer 'fo<66>o'");
let mut cmd = new_ucmd!();
cmd.raw.arg(arg);
@ -291,7 +291,7 @@ fn test_invalid_utf8_integer_compare() {
cmd.run()
.status_code(2)
.stderr_is("test: invalid integer fo<EFBFBD>o");
.stderr_is("test: invalid integer 'fo<66>o'");
}
#[test]
@ -476,6 +476,73 @@ fn test_nonexistent_file_is_not_symlink() {
.succeeds();
}
#[test]
#[cfg(not(windows))] // Windows has no concept of sticky bit
fn test_file_is_sticky() {
let scenario = TestScenario::new(util_name!());
let mut ucmd = scenario.ucmd();
let mut chmod = scenario.cmd("chmod");
scenario.fixtures.touch("sticky_file");
chmod.args(&["+t", "sticky_file"]).succeeds();
ucmd.args(&["-k", "sticky_file"]).succeeds();
}
#[test]
fn test_file_is_not_sticky() {
new_ucmd!()
.args(&["-k", "regular_file"])
.run()
.status_code(1);
}
#[test]
#[cfg(not(windows))]
fn test_file_owned_by_euid() {
new_ucmd!().args(&["-O", "regular_file"]).succeeds();
}
#[test]
#[cfg(not(windows))]
fn test_nonexistent_file_not_owned_by_euid() {
new_ucmd!()
.args(&["-O", "nonexistent_file"])
.run()
.status_code(1);
}
#[test]
#[cfg(all(not(windows), not(target_os = "freebsd")))]
fn test_file_not_owned_by_euid() {
new_ucmd!()
.args(&["-f", "/bin/sh", "-a", "!", "-O", "/bin/sh"])
.succeeds();
}
#[test]
#[cfg(not(windows))]
fn test_file_owned_by_egid() {
new_ucmd!().args(&["-G", "regular_file"]).succeeds();
}
#[test]
#[cfg(not(windows))]
fn test_nonexistent_file_not_owned_by_egid() {
new_ucmd!()
.args(&["-G", "nonexistent_file"])
.run()
.status_code(1);
}
#[test]
#[cfg(all(not(windows), not(target_os = "freebsd")))]
fn test_file_not_owned_by_egid() {
new_ucmd!()
.args(&["-f", "/bin/sh", "-a", "!", "-G", "/bin/sh"])
.succeeds();
}
#[test]
fn test_op_precedence_and_or_1() {
new_ucmd!().args(&[" ", "-o", "", "-a", ""]).succeeds();
@ -607,7 +674,7 @@ fn test_erroneous_parenthesized_expression() {
.args(&["a", "!=", "(", "b", "-a", "b", ")", "!=", "c"])
.run()
.status_code(2)
.stderr_is("test: extra argument b");
.stderr_is("test: extra argument 'b'");
}
#[test]
@ -623,3 +690,31 @@ fn test_or_as_filename() {
fn test_string_length_and_nothing() {
new_ucmd!().args(&["-n", "a", "-a"]).run().status_code(2);
}
#[test]
fn test_bracket_syntax_success() {
let scenario = TestScenario::new("[");
let mut ucmd = scenario.ucmd();
ucmd.args(&["1", "-eq", "1", "]"]).succeeds();
}
#[test]
fn test_bracket_syntax_failure() {
let scenario = TestScenario::new("[");
let mut ucmd = scenario.ucmd();
ucmd.args(&["1", "-eq", "2", "]"]).run().status_code(1);
}
#[test]
fn test_bracket_syntax_missing_right_bracket() {
let scenario = TestScenario::new("[");
let mut ucmd = scenario.ucmd();
// Missing closing bracket takes precedence over other possible errors.
ucmd.args(&["1", "-eq"])
.run()
.status_code(2)
.stderr_is("[: missing ']'");
}

View file

@ -9,3 +9,39 @@ fn test_subcommand_return_code() {
new_ucmd!().arg("1").arg("false").run().status_code(1);
}
#[test]
fn test_command_with_args() {
new_ucmd!()
.args(&["1700", "echo", "-n", "abcd"])
.succeeds()
.stdout_only("abcd");
}
#[test]
fn test_verbose() {
for &verbose_flag in &["-v", "--verbose"] {
new_ucmd!()
.args(&[verbose_flag, ".1", "sleep", "10"])
.fails()
.stderr_only("timeout: sending signal TERM to command 'sleep'");
new_ucmd!()
.args(&[verbose_flag, "-s0", "-k.1", ".1", "sleep", "10"])
.fails()
.stderr_only("timeout: sending signal EXIT to command 'sleep'\ntimeout: sending signal KILL to command 'sleep'");
}
}
#[test]
fn test_zero_timeout() {
new_ucmd!()
.args(&["-v", "0", "sleep", ".1"])
.succeeds()
.no_stderr()
.no_stdout();
new_ucmd!()
.args(&["-v", "0", "-s0", "-k0", "sleep", ".1"])
.succeeds()
.no_stderr()
.no_stdout();
}

View file

@ -6,6 +6,7 @@ use self::touch::filetime::{self, FileTime};
extern crate time;
use crate::common::util::*;
use std::path::PathBuf;
fn get_file_times(at: &AtPath, path: &str) -> (FileTime, FileTime) {
let m = at.metadata(path);
@ -374,6 +375,24 @@ fn test_touch_set_date2() {
assert_eq!(mtime, start_of_year);
}
#[test]
fn test_touch_set_date3() {
let (at, mut ucmd) = at_and_ucmd!();
let file = "test_touch_set_date";
ucmd.args(&["-d", "@1623786360", file])
.succeeds()
.no_stderr();
assert!(at.file_exists(file));
let expected = FileTime::from_unix_time(1623786360, 0);
let (atime, mtime) = get_file_times(&at, file);
assert_eq!(atime, mtime);
assert_eq!(atime, expected);
assert_eq!(mtime, expected);
}
#[test]
fn test_touch_set_date_wrong_format() {
let (_at, mut ucmd) = at_and_ucmd!();
@ -466,3 +485,37 @@ fn test_touch_trailing_slash() {
let file = "no-file/";
ucmd.args(&[file]).fails();
}
#[test]
fn test_touch_no_such_file_error_msg() {
let dirname = "nonexistent";
let filename = "file";
let path = PathBuf::from(dirname).join(filename);
let path_str = path.to_str().unwrap();
new_ucmd!().arg(&path).fails().stderr_only(format!(
"touch: cannot touch '{}': No such file or directory",
path_str
));
}
#[test]
#[cfg(unix)]
fn test_touch_permission_denied_error_msg() {
let (at, mut ucmd) = at_and_ucmd!();
let dirname = "dir_with_read_only_access";
let filename = "file";
let path = PathBuf::from(dirname).join(filename);
let path_str = path.to_str().unwrap();
// create dest without write permissions
at.mkdir(dirname);
at.set_readonly(dirname);
let full_path = at.plus_as_string(path_str);
ucmd.arg(&full_path).fails().stderr_only(format!(
"touch: cannot touch '{}': Permission denied",
&full_path
));
}

View file

@ -1,3 +1,10 @@
// * This file is part of the uutils coreutils package.
// *
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
// spell-checker:ignore (words) RFILE
use crate::common::util::*;
use std::io::{Seek, SeekFrom, Write};
@ -45,9 +52,18 @@ fn test_reference() {
let at = &scene.fixtures;
let mut file = at.make_file(FILE2);
scene.ucmd().arg("-s").arg("+5KB").arg(FILE1).run();
// manpage: "A FILE argument that does not exist is created."
// TODO: 'truncate' does not create the file in this case,
// but should because '--no-create' wasn't specified.
at.touch(FILE1); // TODO: remove this when 'no-create' is fixed
scene.ucmd().arg("-s").arg("+5KB").arg(FILE1).succeeds();
scene.ucmd().arg("--reference").arg(FILE1).arg(FILE2).run();
scene
.ucmd()
.arg("--reference")
.arg(FILE1)
.arg(FILE2)
.succeeds();
file.seek(SeekFrom::End(0)).unwrap();
let actual = file.seek(SeekFrom::Current(0)).unwrap();
@ -231,23 +247,30 @@ fn test_size_and_reference() {
);
}
#[test]
fn test_error_filename_only() {
// truncate: you must specify either '--size' or '--reference'
new_ucmd!().args(&["file"]).fails().stderr_contains(
"error: The following required arguments were not provided:
--reference <RFILE>
--size <SIZE>",
);
}
#[test]
fn test_invalid_numbers() {
// TODO For compatibility with GNU, `truncate -s 0X` should cause
// the same error as `truncate -s 0X file`, but currently it returns
// a different error.
new_ucmd!()
.args(&["-s", "0X", "file"])
.fails()
.stderr_contains("Invalid number: 0X");
.stderr_contains("Invalid number: '0X'");
new_ucmd!()
.args(&["-s", "0XB", "file"])
.fails()
.stderr_contains("Invalid number: 0XB");
.stderr_contains("Invalid number: '0XB'");
new_ucmd!()
.args(&["-s", "0B", "file"])
.fails()
.stderr_contains("Invalid number: 0B");
.stderr_contains("Invalid number: '0B'");
}
#[test]
@ -265,3 +288,36 @@ fn test_reference_with_size_file_not_found() {
.fails()
.stderr_contains("cannot stat 'a': No such file or directory");
}
#[test]
fn test_truncate_bytes_size() {
// TODO: this should succeed without error, uncomment when '--no-create' is fixed
// new_ucmd!()
// .args(&["--no-create", "--size", "K", "file"])
// .succeeds();
new_ucmd!()
.args(&["--size", "1024R", "file"])
.fails()
.code_is(1)
.stderr_only("truncate: Invalid number: '1024R'");
#[cfg(not(target_pointer_width = "128"))]
new_ucmd!()
.args(&["--size", "1Y", "file"])
.fails()
.code_is(1)
.stderr_only("truncate: Invalid number: '1Y': Value too large for defined data type");
#[cfg(target_pointer_width = "32")]
{
let sizes = ["1000G", "10T"];
for size in &sizes {
new_ucmd!()
.args(&["--size", size, "file"])
.fails()
.code_is(1)
.stderr_only(format!(
"truncate: Invalid number: '{}': Value too large for defined data type",
size
));
}
}
}

View file

@ -1,11 +1,14 @@
use std::fs::File;
use crate::common::util::*;
#[test]
#[cfg(not(windows))]
fn test_dev_null() {
new_ucmd!()
.pipe_in("</dev/null")
.set_stdin(File::open("/dev/null").unwrap())
.fails()
.code_is(1)
.stdout_is("not a tty\n");
}
@ -14,44 +17,58 @@ fn test_dev_null() {
fn test_dev_null_silent() {
new_ucmd!()
.args(&["-s"])
.pipe_in("</dev/null")
.set_stdin(File::open("/dev/null").unwrap())
.fails()
.code_is(1)
.stdout_is("");
}
#[test]
fn test_close_stdin() {
new_ucmd!().pipe_in("<&-").fails().stdout_is("not a tty\n");
let mut child = new_ucmd!().run_no_wait();
drop(child.stdin.take());
let output = child.wait_with_output().unwrap();
assert_eq!(output.status.code(), Some(1));
assert_eq!(std::str::from_utf8(&output.stdout), Ok("not a tty\n"));
}
#[test]
fn test_close_stdin_silent() {
new_ucmd!()
.args(&["-s"])
.pipe_in("<&-")
.fails()
.stdout_is("");
let mut child = new_ucmd!().arg("-s").run_no_wait();
drop(child.stdin.take());
let output = child.wait_with_output().unwrap();
assert_eq!(output.status.code(), Some(1));
assert!(output.stdout.is_empty());
}
#[test]
fn test_close_stdin_silent_long() {
new_ucmd!()
.args(&["--silent"])
.pipe_in("<&-")
.fails()
.stdout_is("");
let mut child = new_ucmd!().arg("--silent").run_no_wait();
drop(child.stdin.take());
let output = child.wait_with_output().unwrap();
assert_eq!(output.status.code(), Some(1));
assert!(output.stdout.is_empty());
}
#[test]
fn test_close_stdin_silent_alias() {
new_ucmd!()
.args(&["--quiet"])
.pipe_in("<&-")
.fails()
.stdout_is("");
let mut child = new_ucmd!().arg("--quiet").run_no_wait();
drop(child.stdin.take());
let output = child.wait_with_output().unwrap();
assert_eq!(output.status.code(), Some(1));
assert!(output.stdout.is_empty());
}
#[test]
fn test_wrong_argument() {
new_ucmd!().args(&["a"]).fails();
new_ucmd!().args(&["a"]).fails().code_is(2);
}
#[test]
#[cfg(not(windows))]
fn test_stdout_fail() {
let mut child = new_ucmd!().run_no_wait();
drop(child.stdout.take());
let status = child.wait().unwrap();
assert_eq!(status.code(), Some(3));
}

View file

@ -13,9 +13,11 @@ fn test_users_check_name() {
#[cfg(target_vendor = "apple")]
let util_name = format!("g{}", util_name!());
// note: clippy::needless_borrow *false positive*
#[allow(clippy::needless_borrow)]
let expected = TestScenario::new(&util_name)
.cmd_keepenv(util_name)
.env("LANGUAGE", "C")
.env("LC_ALL", "C")
.succeeds()
.stdout_move_str();

View file

@ -158,13 +158,12 @@ fn test_users() {
let mut v_actual: Vec<&str> = actual.split_whitespace().collect();
let mut v_expect: Vec<&str> = expect.split_whitespace().collect();
// TODO: `--users` differs from GNU's output on macOS
// Diff < left / right > :
// <"runner console 2021-05-20 22:03 00:08 196\n"
// >"runner console 2021-05-20 22:03 old 196\n"
// TODO: `--users` sometimes differs from GNU's output on macOS (race condition?)
// actual: "runner console Jun 23 06:37 00:34 196\n"
// expect: "runner console Jun 23 06:37 old 196\n"
if cfg!(target_os = "macos") {
v_actual.remove(4);
v_expect.remove(4);
v_actual.remove(5);
v_expect.remove(5);
}
assert_eq!(v_actual, v_expect);
@ -238,9 +237,11 @@ fn expected_result(args: &[&str]) -> String {
#[cfg(target_vendor = "apple")]
let util_name = format!("g{}", util_name!());
// note: clippy::needless_borrow *false positive*
#[allow(clippy::needless_borrow)]
TestScenario::new(&util_name)
.cmd_keepenv(util_name)
.env("LANGUAGE", "C")
.env("LC_ALL", "C")
.args(args)
.succeeds()
.stdout_move_str()

View file

@ -69,6 +69,22 @@ pub struct CmdResult {
}
impl CmdResult {
pub fn new(
tmpd: Option<Rc<TempDir>>,
code: Option<i32>,
success: bool,
stdout: &[u8],
stderr: &[u8],
) -> CmdResult {
CmdResult {
tmpd,
code,
success,
stdout: stdout.to_vec(),
stderr: stderr.to_vec(),
}
}
/// Returns a reference to the program's standard output as a slice of bytes
pub fn stdout(&self) -> &[u8] {
&self.stdout
@ -207,6 +223,18 @@ impl CmdResult {
self
}
/// like `stdout_is`, but succeeds if any elements of `expected` matches stdout.
pub fn stdout_is_any<T: AsRef<str> + std::fmt::Debug>(&self, expected: Vec<T>) -> &CmdResult {
if !expected.iter().any(|msg| self.stdout_str() == msg.as_ref()) {
panic!(
"stdout was {}\nExpected any of {:#?}",
self.stdout_str(),
expected
)
}
self
}
/// Like `stdout_is` but newlines are normalized to `\n`.
pub fn normalized_newlines_stdout_is<T: AsRef<str>>(&self, msg: T) -> &CmdResult {
let msg = msg.as_ref().replace("\r\n", "\n");
@ -231,7 +259,7 @@ impl CmdResult {
pub fn stdout_is_templated_fixture<T: AsRef<OsStr>>(
&self,
file_rel_path: T,
template_vars: Vec<(&String, &String)>,
template_vars: &[(&str, &str)],
) -> &CmdResult {
let mut contents =
String::from_utf8(read_scenario_fixture(&self.tmpd, file_rel_path)).unwrap();
@ -241,6 +269,23 @@ impl CmdResult {
self.stdout_is(contents)
}
/// like `stdout_is_templated_fixture`, but succeeds if any replacement by `template_vars` results in the actual stdout.
pub fn stdout_is_templated_fixture_any<T: AsRef<OsStr>>(
&self,
file_rel_path: T,
template_vars: &[Vec<(String, String)>],
) {
let contents = String::from_utf8(read_scenario_fixture(&self.tmpd, file_rel_path)).unwrap();
let possible_values = template_vars.iter().map(|vars| {
let mut contents = contents.clone();
for kv in vars.iter() {
contents = contents.replace(&kv.0, &kv.1);
}
contents
});
self.stdout_is_any(possible_values.collect());
}
/// asserts that the command resulted in stderr stream output that equals the
/// passed in value, when both are trimmed of trailing whitespace
/// stderr_only is a better choice unless stdout may or will be non-empty
@ -625,11 +670,20 @@ impl AtPath {
// Source:
// http://stackoverflow.com/questions/31439011/getfinalpathnamebyhandle-without-prepended
let prefix = "\\\\?\\";
// FixME: replace ...
#[allow(clippy::manual_strip)]
if s.starts_with(prefix) {
String::from(&s[prefix.len()..])
} else {
s
}
// ... with ...
// if let Some(stripped) = s.strip_prefix(prefix) {
// String::from(stripped)
// } else {
// s
// }
// ... when using MSRV with stabilized `strip_prefix()`
}
}

1
tests/fixtures/chgrp/file1 vendored Normal file
View file

@ -0,0 +1 @@
target file 1

1
tests/fixtures/chgrp/file2 vendored Normal file
View file

@ -0,0 +1 @@
target file 2

1
tests/fixtures/chgrp/file3 vendored Normal file
View file

@ -0,0 +1 @@
target file 3

1
tests/fixtures/chgrp/ref_file vendored Normal file
View file

@ -0,0 +1 @@
Reference file

View file

@ -0,0 +1 @@
hello world!

View file

@ -1,3 +1,4 @@
0K
K
844K
981K

View file

@ -1,3 +1,6 @@
0K
__
__
K
^ no match for key
_

View file

@ -9,4 +9,5 @@
844K
981K
13M
K
K
0K

View file

@ -0,0 +1,2 @@
a
b

View file

@ -0,0 +1 @@
b

View file

@ -8,4 +8,8 @@
1.2.3-alpha
1.2.3-alpha2
11.2.3
bar2
bar2.0.0
foo0.1
foo1.0
1.12.4

View file

@ -0,0 +1,45 @@
^ no match for key
^ no match for key
^ no match for key
^ no match for key
^ no match for key
^ no match for key
^ no match for key
^ no match for key
^ no match for key
^ no match for key
^ no match for key
^ no match for key
^ no match for key
^ no match for key
1.2.3-alpha
___________
___________
1.2.3-alpha2
____________
____________
11.2.3
______
______
bar2
____
____
bar2.0.0
________
________
foo0.1
______
______
foo1.0
______
______
>>>1.12.4
_________
_________

View file

@ -9,3 +9,7 @@
1.12.4
foo1.0
foo0.1
bar2.0.0
bar2