mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
wc: more tests and fixes
My previous commits meant to bring our wc's output and behavior in line with GNU's. There should be tests that check for these changes! I found a stupid bug in my own changes, I was not adding 1 to the indexes produced by .enumerate() when printing errors.
This commit is contained in:
parent
c4b53a44b5
commit
e5b46ea3eb
5 changed files with 199 additions and 35 deletions
|
@ -188,9 +188,9 @@ impl<'a> Inputs<'a> {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// The index of each yielded item must be tracked for error reporting.
|
// The 1-based index of each yielded item must be tracked for error reporting.
|
||||||
let mut with_idx = base.enumerate();
|
let mut with_idx = base.enumerate().map(|(i, v)| (i + 1, v));
|
||||||
let files0_from_path = settings.files0_from.as_ref().map(|i| i.as_borrowed());
|
let files0_from_path = settings.files0_from.as_ref().map(|p| p.as_borrowed());
|
||||||
|
|
||||||
let iter = iter::from_fn(move || {
|
let iter = iter::from_fn(move || {
|
||||||
let (idx, next) = with_idx.next()?;
|
let (idx, next) = with_idx.next()?;
|
||||||
|
@ -303,23 +303,9 @@ fn is_stdin_small_file() -> bool {
|
||||||
matches!(f.metadata(), Ok(meta) if meta.is_file() && meta.len() <= (10 << 20))
|
matches!(f.metadata(), Ok(meta) if meta.is_file() && meta.len() <= (10 << 20))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(not(unix))]
|
||||||
fn is_stdin_small_file() -> bool {
|
// Windows presents a piped stdin as a "normal file" with a length equal to however many bytes
|
||||||
use std::os::windows::io::{AsRawHandle, FromRawHandle};
|
// have been buffered at the time it's checked. To be safe, we must never assume it's a file.
|
||||||
// Safety: we'll rely on Rust to give us a valid RawHandle for stdin with which we can attempt
|
|
||||||
// to open a File, but only for the sake of fetching .metadata(). ManuallyDrop will ensure we
|
|
||||||
// don't do anything else to the FD if anything unexpected happens.
|
|
||||||
let f = std::mem::ManuallyDrop::new(unsafe {
|
|
||||||
let h = io::stdin().as_raw_handle();
|
|
||||||
if h.is_null() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
File::from_raw_handle(h)
|
|
||||||
});
|
|
||||||
matches!(f.metadata(), Ok(meta) if meta.is_file() && meta.len() <= (10 << 20))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(unix, windows)))]
|
|
||||||
fn is_stdin_small_file() -> bool {
|
fn is_stdin_small_file() -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
@ -268,12 +268,16 @@ fn test_multiple_default() {
|
||||||
"lorem_ipsum.txt",
|
"lorem_ipsum.txt",
|
||||||
"moby_dick.txt",
|
"moby_dick.txt",
|
||||||
"alice_in_wonderland.txt",
|
"alice_in_wonderland.txt",
|
||||||
|
"alice in wonderland.txt",
|
||||||
])
|
])
|
||||||
.run()
|
.run()
|
||||||
.stdout_is(
|
.stdout_is(concat!(
|
||||||
" 13 109 772 lorem_ipsum.txt\n 18 204 1115 moby_dick.txt\n 5 57 302 \
|
" 13 109 772 lorem_ipsum.txt\n",
|
||||||
alice_in_wonderland.txt\n 36 370 2189 total\n",
|
" 18 204 1115 moby_dick.txt\n",
|
||||||
);
|
" 5 57 302 alice_in_wonderland.txt\n",
|
||||||
|
" 5 57 302 alice in wonderland.txt\n",
|
||||||
|
" 41 427 2491 total\n",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test for an empty file.
|
/// Test for an empty file.
|
||||||
|
@ -352,17 +356,24 @@ fn test_file_bytes_dictate_width() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["-lwc", "alice_in_wonderland.txt", "lorem_ipsum.txt"])
|
.args(&["-lwc", "alice_in_wonderland.txt", "lorem_ipsum.txt"])
|
||||||
.run()
|
.run()
|
||||||
.stdout_is(
|
.stdout_is(concat!(
|
||||||
" 5 57 302 alice_in_wonderland.txt\n 13 109 772 \
|
" 5 57 302 alice_in_wonderland.txt\n",
|
||||||
lorem_ipsum.txt\n 18 166 1074 total\n",
|
" 13 109 772 lorem_ipsum.txt\n",
|
||||||
);
|
" 18 166 1074 total\n",
|
||||||
|
));
|
||||||
|
|
||||||
// . is a directory, so minimum_width should get set to 7
|
// . is a directory, so minimum_width should get set to 7
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
const STDOUT: &str = " 0 0 0 emptyfile.txt\n 0 0 0 \
|
const STDOUT: &str = concat!(
|
||||||
.\n 0 0 0 total\n";
|
" 0 0 0 emptyfile.txt\n",
|
||||||
|
" 0 0 0 .\n",
|
||||||
|
" 0 0 0 total\n",
|
||||||
|
);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
const STDOUT: &str = " 0 0 0 emptyfile.txt\n 0 0 0 total\n";
|
const STDOUT: &str = concat!(
|
||||||
|
" 0 0 0 emptyfile.txt\n",
|
||||||
|
" 0 0 0 total\n",
|
||||||
|
);
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["-lwc", "emptyfile.txt", "."])
|
.args(&["-lwc", "emptyfile.txt", "."])
|
||||||
.run()
|
.run()
|
||||||
|
@ -392,12 +403,10 @@ fn test_read_from_directory_error() {
|
||||||
/// Test that getting counts from nonexistent file is an error.
|
/// Test that getting counts from nonexistent file is an error.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_read_from_nonexistent_file() {
|
fn test_read_from_nonexistent_file() {
|
||||||
const MSG: &str = "bogusfile: No such file or directory";
|
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["bogusfile"])
|
.args(&["bogusfile"])
|
||||||
.fails()
|
.fails()
|
||||||
.stderr_contains(MSG)
|
.stderr_only("wc: bogusfile: No such file or directory\n");
|
||||||
.stdout_is("");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -421,15 +430,30 @@ fn test_files0_disabled_files_argument() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_files0_from() {
|
fn test_files0_from() {
|
||||||
|
// file
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["--files0-from=files0_list.txt"])
|
.args(&["--files0-from=files0_list.txt"])
|
||||||
.run()
|
.run()
|
||||||
|
.success()
|
||||||
.stdout_is(concat!(
|
.stdout_is(concat!(
|
||||||
" 13 109 772 lorem_ipsum.txt\n",
|
" 13 109 772 lorem_ipsum.txt\n",
|
||||||
" 18 204 1115 moby_dick.txt\n",
|
" 18 204 1115 moby_dick.txt\n",
|
||||||
" 5 57 302 alice_in_wonderland.txt\n",
|
" 5 57 302 alice_in_wonderland.txt\n",
|
||||||
" 36 370 2189 total\n",
|
" 36 370 2189 total\n",
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// stream
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["--files0-from=-"])
|
||||||
|
.pipe_in_fixture("files0_list.txt")
|
||||||
|
.run()
|
||||||
|
.success()
|
||||||
|
.stdout_is(concat!(
|
||||||
|
"13 109 772 lorem_ipsum.txt\n",
|
||||||
|
"18 204 1115 moby_dick.txt\n",
|
||||||
|
"5 57 302 alice_in_wonderland.txt\n",
|
||||||
|
"36 370 2189 total\n",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -450,7 +474,7 @@ fn test_files0_from_with_stdin_in_file() {
|
||||||
.stdout_is(concat!(
|
.stdout_is(concat!(
|
||||||
" 13 109 772 lorem_ipsum.txt\n",
|
" 13 109 772 lorem_ipsum.txt\n",
|
||||||
" 18 204 1115 moby_dick.txt\n",
|
" 18 204 1115 moby_dick.txt\n",
|
||||||
" 5 57 302 -\n",
|
" 5 57 302 -\n", // alice_in_wonderland.txt
|
||||||
" 36 370 2189 total\n",
|
" 36 370 2189 total\n",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -531,3 +555,152 @@ fn test_total_only() {
|
||||||
.run()
|
.run()
|
||||||
.stdout_is("31 313 1887\n");
|
.stdout_is("31 313 1887\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_zero_length_files() {
|
||||||
|
// A trailing zero is ignored, but otherwise empty file names are an error...
|
||||||
|
const LIST: &str = "\0moby_dick.txt\0\0alice_in_wonderland.txt\0\0lorem_ipsum.txt\0";
|
||||||
|
|
||||||
|
// Try with and without the last \0
|
||||||
|
for l in [LIST.len(), LIST.len() - 1] {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["--files0-from=-"])
|
||||||
|
.pipe_in(&LIST[..l])
|
||||||
|
.run()
|
||||||
|
.failure()
|
||||||
|
.stdout_is(concat!(
|
||||||
|
"18 204 1115 moby_dick.txt\n",
|
||||||
|
"5 57 302 alice_in_wonderland.txt\n",
|
||||||
|
"13 109 772 lorem_ipsum.txt\n",
|
||||||
|
"36 370 2189 total\n",
|
||||||
|
))
|
||||||
|
.stderr_is(concat!(
|
||||||
|
"wc: -:1: invalid zero-length file name\n",
|
||||||
|
"wc: -:3: invalid zero-length file name\n",
|
||||||
|
"wc: -:5: invalid zero-length file name\n",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// But, just as important, a zero-length file name may still be at the end...
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["--files0-from=-"])
|
||||||
|
.pipe_in(
|
||||||
|
LIST.as_bytes()
|
||||||
|
.iter()
|
||||||
|
.chain(b"\0")
|
||||||
|
.copied()
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.run()
|
||||||
|
.failure()
|
||||||
|
.stdout_is(concat!(
|
||||||
|
"18 204 1115 moby_dick.txt\n",
|
||||||
|
"5 57 302 alice_in_wonderland.txt\n",
|
||||||
|
"13 109 772 lorem_ipsum.txt\n",
|
||||||
|
"36 370 2189 total\n",
|
||||||
|
))
|
||||||
|
.stderr_is(concat!(
|
||||||
|
"wc: -:1: invalid zero-length file name\n",
|
||||||
|
"wc: -:3: invalid zero-length file name\n",
|
||||||
|
"wc: -:5: invalid zero-length file name\n",
|
||||||
|
"wc: -:7: invalid zero-length file name\n",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_files0_errors_quoting() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["--files0-from=files0 with nonexistent.txt"])
|
||||||
|
.run()
|
||||||
|
.failure()
|
||||||
|
.stderr_is(concat!(
|
||||||
|
"wc: this_file_does_not_exist.txt: No such file or directory\n",
|
||||||
|
"wc: 'files0 with nonexistent.txt':2: invalid zero-length file name\n",
|
||||||
|
"wc: 'this file does not exist.txt': No such file or directory\n",
|
||||||
|
"wc: \"this files doesn't exist either.txt\": No such file or directory\n",
|
||||||
|
))
|
||||||
|
.stdout_is("0 0 0 total\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_files0_progressive_stream() {
|
||||||
|
use std::process::Stdio;
|
||||||
|
// You should be able to run wc and have a back-and-forth exchange with wc...
|
||||||
|
let mut child = new_ucmd!()
|
||||||
|
.args(&["--files0-from=-"])
|
||||||
|
.set_stdin(Stdio::piped())
|
||||||
|
.set_stdout(Stdio::piped())
|
||||||
|
.set_stderr(Stdio::piped())
|
||||||
|
.run_no_wait();
|
||||||
|
|
||||||
|
macro_rules! chk {
|
||||||
|
($fn:ident, $exp:literal) => {
|
||||||
|
assert_eq!(child.$fn($exp.len()), $exp.as_bytes());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// File in, count out...
|
||||||
|
child.write_in("moby_dick.txt\0");
|
||||||
|
chk!(stdout_exact_bytes, "18 204 1115 moby_dick.txt\n");
|
||||||
|
child.write_in("lorem_ipsum.txt\0");
|
||||||
|
chk!(stdout_exact_bytes, "13 109 772 lorem_ipsum.txt\n");
|
||||||
|
|
||||||
|
// Introduce an error!
|
||||||
|
child.write_in("\0");
|
||||||
|
chk!(
|
||||||
|
stderr_exact_bytes,
|
||||||
|
"wc: -:3: invalid zero-length file name\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
// wc is quick to forgive, let's move on...
|
||||||
|
child.write_in("alice_in_wonderland.txt\0");
|
||||||
|
chk!(stdout_exact_bytes, "5 57 302 alice_in_wonderland.txt\n");
|
||||||
|
|
||||||
|
// Fin.
|
||||||
|
child
|
||||||
|
.wait()
|
||||||
|
.expect("wc should finish")
|
||||||
|
.failure()
|
||||||
|
.stdout_only("36 370 2189 total\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn files0_from_dir() {
|
||||||
|
// On Unix, `read(open("."))` fails. On Windows, `open(".")` fails. Thus, the errors happen in
|
||||||
|
// different contexts.
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
macro_rules! dir_err {
|
||||||
|
($p:literal) => {
|
||||||
|
concat!("wc: ", $p, ": read error: Is a directory\n")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
macro_rules! dir_err {
|
||||||
|
($p:literal) => {
|
||||||
|
concat!("wc: cannot open ", $p, " for reading: Permission denied\n")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["--files0-from=dir with spaces"])
|
||||||
|
.fails()
|
||||||
|
.stderr_only(dir_err!("'dir with spaces'"));
|
||||||
|
|
||||||
|
// Those contexts have different rules about quoting in errors...
|
||||||
|
#[cfg(windows)]
|
||||||
|
const DOT_ERR: &str = dir_err!("'.'");
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
const DOT_ERR: &str = dir_err!(".");
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["--files0-from=."])
|
||||||
|
.fails()
|
||||||
|
.stderr_only(DOT_ERR);
|
||||||
|
|
||||||
|
// That also means you cannot `< . wc --files0-from=-` on Windows.
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["--files0-from=-"])
|
||||||
|
.set_stdin(std::fs::File::open(".").unwrap())
|
||||||
|
.fails()
|
||||||
|
.stderr_only(dir_err!("-"));
|
||||||
|
}
|
||||||
|
|
5
tests/fixtures/wc/alice in wonderland.txt
vendored
Normal file
5
tests/fixtures/wc/alice in wonderland.txt
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
Alice was beginning to get very tired of sitting by
|
||||||
|
her sister on the bank, and of having nothing to do: once or twice
|
||||||
|
she had peeped into the book her sister was reading, but it had no
|
||||||
|
pictures or conversations in it, "and what is the use of a book,"
|
||||||
|
thought Alice "without pictures or conversation?"
|
0
tests/fixtures/wc/dir with spaces/.keep
vendored
Normal file
0
tests/fixtures/wc/dir with spaces/.keep
vendored
Normal file
BIN
tests/fixtures/wc/files0 with nonexistent.txt
vendored
Normal file
BIN
tests/fixtures/wc/files0 with nonexistent.txt
vendored
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue