From 13e4e552f1ce0ee2746f11ca50a91a0f27c97e39 Mon Sep 17 00:00:00 2001 From: Sichen Date: Sat, 26 Sep 2026 06:04:24 +0000 Subject: [PATCH 1/2] grep: report file errors with -q Quiet mode suppresses normal output and lets a match determine exit status, but it does not suppress file error diagnostics. Remove the quiet check from report_io_error so only -s suppresses those diagnostics. GNU grep 3.12 reports a missing or unreadable file encountered before a quiet match, while -s removes that diagnostic. The existing quiet_match_overrides_file_error test (added in #11) needs only its stderr expectation changed for the -q case; retain its status and other cases. Add separate exact-byte tests for operand order and -s. --- src/output.rs | 2 +- tests/test_grep.rs | 59 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/output.rs b/src/output.rs index 371a37a..d74d5da 100644 --- a/src/output.rs +++ b/src/output.rs @@ -201,7 +201,7 @@ impl<'a> OutputWriter<'a> { /// Write an IO error to stderr. pub fn report_io_error(&self, label: &OsStr, err: &io::Error) { - if !self.config.no_messages && !self.config.quiet { + if !self.config.no_messages { // Strip the trailing " (os error XX)" so the message matches GNU grep. let _ = writeln!( io::stderr(), diff --git a/tests/test_grep.rs b/tests/test_grep.rs index 9f63e12..e226fc0 100644 --- a/tests/test_grep.rs +++ b/tests/test_grep.rs @@ -252,14 +252,18 @@ fn quiet_match_overrides_file_error() { // With -q, a match makes grep exit 0 even if an earlier file could not be // opened. Without -q the missing file still yields exit 2, and -q with no // match keeps the error status. - // -q exits at the first match without draining stdin, so the harness's - // write can lose the race and fail with EPIPE; that error is expected here. + #[cfg(not(windows))] + let expected = "grep: no-such-file: No such file or directory\n"; + #[cfg(windows)] + let expected = "grep: no-such-file: The system cannot find the file specified.\n"; let (_s, mut c) = ucmd(); c.args(&["-q", "abc", "no-such-file", "-"]) + // -q may exit before the harness finishes writing stdin (EPIPE). .ignore_stdin_write_error() .pipe_in("abcd\n") .succeeds() - .no_output(); + .no_stdout() + .stderr_is(expected); let (_s, mut c) = ucmd(); c.args(&["abc", "no-such-file", "-"]) @@ -272,6 +276,55 @@ fn quiet_match_overrides_file_error() { .fails_with_code(2); } +#[test] +fn quiet_file_errors_and_no_messages() { + // -q keeps file diagnostics until a match; -s suppresses them. + #[cfg(not(windows))] + let missing_error = "grep: missing: No such file or directory\n"; + #[cfg(windows)] + let missing_error = "grep: missing: The system cannot find the file specified.\n"; + + let (scene, mut c) = ucmd(); + scene.fixtures.write("hit", "x\n"); + scene.fixtures.write("miss", "y\n"); + + c.args(&["-q", "x", "missing", "hit"]) + .succeeds() + .no_stdout() + .stderr_is(missing_error); + + let mut c = scene.cmd(env!("CARGO_BIN_EXE_grep")); + c.args(&["-q", "x", "miss", "missing"]) + .fails_with_code(2) + .no_stdout() + .stderr_is(missing_error); + + let mut c = scene.cmd(env!("CARGO_BIN_EXE_grep")); + c.args(&["-q", "x", "hit", "missing"]) + .succeeds() + .no_output(); + + let mut c = scene.cmd(env!("CARGO_BIN_EXE_grep")); + c.args(&["-s", "x", "missing"]) + .fails_with_code(2) + .no_output(); + + let mut c = scene.cmd(env!("CARGO_BIN_EXE_grep")); + c.args(&["-qs", "x", "missing"]) + .fails_with_code(2) + .no_output(); + + #[cfg(unix)] + { + let (scene, mut c) = ucmd(); + scene.fixtures.mkdir("dir"); + c.args(&["-q", "x", "dir"]) + .fails_with_code(2) + .no_stdout() + .stderr_is("grep: dir: Is a directory\n"); + } +} + #[test] fn initial_tab_skips_empty_lines() { // -T aligns content with a tab, but GNU omits the tab for an empty line From 009af81231fdf2cac6aaf5ba35d80163d8abb2b8 Mon Sep 17 00:00:00 2001 From: Sichen Date: Wed, 30 Sep 2026 00:39:14 +0000 Subject: [PATCH 2/2] test: fold -q file-error cases into one test The separate quiet_file_errors_and_no_messages test overlapped with quiet_match_overrides_file_error: both covered a missing file followed by a match, and a missing file with no match. Keep those two in quiet_match_overrides_file_error, where the no-match case now also asserts empty stdout and the diagnostic, as printed by GNU grep 3.12. Move in the cases it lacked (a match before the missing file, -qs, and a directory operand) and drop the separate test. --- tests/test_grep.rs | 38 +++++++------------------------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/tests/test_grep.rs b/tests/test_grep.rs index e226fc0..a38d570 100644 --- a/tests/test_grep.rs +++ b/tests/test_grep.rs @@ -252,6 +252,8 @@ fn quiet_match_overrides_file_error() { // With -q, a match makes grep exit 0 even if an earlier file could not be // opened. Without -q the missing file still yields exit 2, and -q with no // match keeps the error status. + // -q still reports a file it cannot read, but files after the first match + // are never opened. -qs suppresses the report; the status is still 2. #[cfg(not(windows))] let expected = "grep: no-such-file: No such file or directory\n"; #[cfg(windows)] @@ -273,44 +275,18 @@ fn quiet_match_overrides_file_error() { let (_s, mut c) = ucmd(); c.args(&["-q", "zzz", "no-such-file", "-"]) .pipe_in("abcd\n") - .fails_with_code(2); -} - -#[test] -fn quiet_file_errors_and_no_messages() { - // -q keeps file diagnostics until a match; -s suppresses them. - #[cfg(not(windows))] - let missing_error = "grep: missing: No such file or directory\n"; - #[cfg(windows)] - let missing_error = "grep: missing: The system cannot find the file specified.\n"; - - let (scene, mut c) = ucmd(); - scene.fixtures.write("hit", "x\n"); - scene.fixtures.write("miss", "y\n"); - - c.args(&["-q", "x", "missing", "hit"]) - .succeeds() - .no_stdout() - .stderr_is(missing_error); - - let mut c = scene.cmd(env!("CARGO_BIN_EXE_grep")); - c.args(&["-q", "x", "miss", "missing"]) .fails_with_code(2) .no_stdout() - .stderr_is(missing_error); + .stderr_is(expected); - let mut c = scene.cmd(env!("CARGO_BIN_EXE_grep")); - c.args(&["-q", "x", "hit", "missing"]) + let (scene, mut c) = ucmd(); + scene.fixtures.write("hit", "x\n"); + c.args(&["-q", "x", "hit", "no-such-file"]) .succeeds() .no_output(); let mut c = scene.cmd(env!("CARGO_BIN_EXE_grep")); - c.args(&["-s", "x", "missing"]) - .fails_with_code(2) - .no_output(); - - let mut c = scene.cmd(env!("CARGO_BIN_EXE_grep")); - c.args(&["-qs", "x", "missing"]) + c.args(&["-qs", "x", "no-such-file"]) .fails_with_code(2) .no_output();