Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions src/cmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ pub struct Params {
max_bytes: Option<BytesLimitU64>,
verbose: bool,
quiet: bool,
stdout_is_dev_null: bool,
}

impl Params {
/// Whether the difference and EOF reports are skipped. Error messages
/// are only hidden by `quiet`.
fn no_report(&self) -> bool {
self.quiet || self.stdout_is_dev_null
}
}

#[inline]
Expand Down Expand Up @@ -223,21 +232,21 @@ pub fn parse_params<I: Iterator<Item = OsString>>(mut opts: Peekable<I>) -> Resu
}
}

// Do as GNU cmp, and completely disable printing if we are
if params.quiet && params.verbose {
return Err(format!(
"{executable_str}: options -l and -s are incompatible"
));
}

// Do as GNU cmp, and disable reporting the differences if we are
// outputting to /dev/null.
#[cfg(not(target_os = "windows"))]
if is_stdout_dev_null() {
params.quiet = true;
params.stdout_is_dev_null = true;
params.verbose = false;
params.print_bytes = false;
}

if params.quiet && params.verbose {
return Err(format!(
"{executable_str}: options -l and -s are incompatible"
));
}

params.from = if let Some(from) = from {
from
} else if let Some(param) = opts.next() {
Expand Down Expand Up @@ -316,7 +325,7 @@ pub fn cmp(params: &Params) -> Result<Cmp, String> {

// If the files have different sizes, we already know they are not identical. If we have not
// been asked to show even the first difference, we can quit early.
if params.quiet && a_size != b_size {
if params.no_report() && a_size != b_size {
return Ok(Cmp::Different);
}

Expand Down Expand Up @@ -630,7 +639,7 @@ fn report_eof(
eof_on: &str,
params: &Params,
) {
if params.quiet {
if params.no_report() {
return;
}

Expand Down Expand Up @@ -674,7 +683,7 @@ fn report_difference(
at_line: u64,
params: &Params,
) {
if params.quiet {
if params.no_report() {
return;
}

Expand Down
46 changes: 46 additions & 0 deletions tests/by-util/test_cmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,52 @@ fn cmp_stdin(#[case] input: &str, #[case] code: i32, #[case] expected: &str) {
.stdout_only(expected);
}

// cmp turns the difference output off when its stdout is /dev/null.
#[cfg(unix)]
fn dev_null() -> std::fs::File {
OpenOptions::new().write(true).open("/dev/null").unwrap()
}

// Unlike -s, sending stdout to /dev/null must not hide error messages.
#[test]
#[cfg(unix)]
fn cmp_dev_null_stdout_still_reports_errors() {
let (at, ts) = at_and_ts!();
at.write("a", "abc");

ts.ucmd()
.args(&["a", "missing"])
.set_stdout(dev_null())
.fails_with_code(2)
.stderr_contains("No such file or directory");
ts.ucmd()
.args(&["-l", "-s", "a"])
.set_stdout(dev_null())
.fails_with_code(2)
.stderr_str_check(|s| s.ends_with(": options -l and -s are incompatible\n"));
}

// The difference and EOF reports stay off when stdout is /dev/null.
#[test]
#[cfg(unix)]
fn cmp_dev_null_stdout_reports_no_difference() {
let (at, ts) = at_and_ts!();
at.write("a", "abcdef");
at.write("shorter", "abc");
at.write("changed", "abcdeX");

for other in ["shorter", "changed"] {
for option in [&[][..], &["-l"], &["-b"]] {
ts.ucmd()
.args(option)
.args(&["a", other])
.set_stdout(dev_null())
.fails_with_code(1)
.no_stderr();
}
}
}

#[test]
fn cmp_equal_files() {
let (at, ts) = at_and_ts!();
Expand Down
Loading