From 6489ef2f02e96d8b2efeaa4dbc4d5c351367319c Mon Sep 17 00:00:00 2001 From: sarankumar1325 <120080287+sarankumar1325@users.noreply.github.com> Date: Mon, 9 Nov 2026 19:53:09 +0530 Subject: [PATCH 1/2] diff: report both files when -N is given and neither exists -N and --unidirectional-new-file turned every missing operand into an empty file, so two missing files compared as identical and diff exited 0. Like GNU diff, a missing operand is now only treated as empty when the other one exists; otherwise both errors are reported and diff exits 2. Fixes #360 --- src/diff.rs | 20 ++++++++++++++++---- tests/by-util/test_diff.rs | 23 +++++++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/diff.rs b/src/diff.rs index cc67c1f..7ec89a2 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -97,10 +97,20 @@ pub fn compare_files(params: &Params, header: Option<&[u8]>) -> Outcome { } } let mut io_error = false; - let from_content = match read_file_contents(¶ms.from) { + let from_result = read_file_contents(¶ms.from); + let to_result = read_file_contents(¶ms.to); + // Like GNU diff, -N and --unidirectional-new-file only treat a missing + // operand as empty when the other one exists, so two missing files are + // still reported as errors. + let is_missing = + |r: &io::Result>| matches!(r, Err(e) if e.kind() == io::ErrorKind::NotFound); + let both_missing = is_missing(&from_result) && is_missing(&to_result); + let from_content = match from_result { Ok(from_content) => from_content, Err(e) - if e.kind() == io::ErrorKind::NotFound && params.missing_file != MissingFile::Error => + if e.kind() == io::ErrorKind::NotFound + && params.missing_file != MissingFile::Error + && !both_missing => { vec![] } @@ -110,10 +120,12 @@ pub fn compare_files(params: &Params, header: Option<&[u8]>) -> Outcome { vec![] } }; - let to_content = match read_file_contents(¶ms.to) { + let to_content = match to_result { Ok(to_content) => to_content, Err(e) - if e.kind() == io::ErrorKind::NotFound && params.missing_file == MissingFile::Empty => + if e.kind() == io::ErrorKind::NotFound + && params.missing_file == MissingFile::Empty + && !both_missing => { vec![] } diff --git a/tests/by-util/test_diff.rs b/tests/by-util/test_diff.rs index 71bfcad..2f83fc9 100644 --- a/tests/by-util/test_diff.rs +++ b/tests/by-util/test_diff.rs @@ -246,6 +246,29 @@ fn missing_file_as_empty() -> Result<(), Box> { Ok(()) } +#[test] +fn new_file_both_missing_is_an_error() -> Result<(), Box> { + let tmp_dir = tempdir()?; + let missing1 = tmp_dir.path().join("missing1"); + let missing2 = tmp_dir.path().join("missing2"); + + for option in ["-N", "--new-file", "--unidirectional-new-file"] { + let mut cmd = cargo_bin_cmd!("diffutils"); + cmd.arg("diff").arg(option).arg(&missing1).arg(&missing2); + cmd.assert() + .code(predicate::eq(2)) + .failure() + .stdout(predicate::str::is_empty()) + .stderr(predicate::str::contains( + "missing1: No such file or directory", + )) + .stderr(predicate::str::contains( + "missing2: No such file or directory", + )); + } + Ok(()) +} + #[test] fn binary_files() -> Result<(), Box> { let tmp_dir = tempdir()?; From 3126c0357f277066f069c3f8cad2b36dce921c71 Mon Sep 17 00:00:00 2001 From: sarankumar1325 <120080287+sarankumar1325@users.noreply.github.com> Date: Mon, 9 Nov 2026 20:22:17 +0530 Subject: [PATCH 2/2] diff: match the Windows error text in the -N both-missing test Windows reports a missing file as "The system cannot find the file specified.", so pick the expected message per platform, as cannot_read_files in test_common.rs already does. --- tests/by-util/test_diff.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/by-util/test_diff.rs b/tests/by-util/test_diff.rs index 2f83fc9..69133d6 100644 --- a/tests/by-util/test_diff.rs +++ b/tests/by-util/test_diff.rs @@ -252,6 +252,11 @@ fn new_file_both_missing_is_an_error() -> Result<(), Box> let missing1 = tmp_dir.path().join("missing1"); let missing2 = tmp_dir.path().join("missing2"); + #[cfg(not(windows))] + let error_message = "No such file or directory"; + #[cfg(windows)] + let error_message = "The system cannot find the file specified."; + for option in ["-N", "--new-file", "--unidirectional-new-file"] { let mut cmd = cargo_bin_cmd!("diffutils"); cmd.arg("diff").arg(option).arg(&missing1).arg(&missing2); @@ -259,12 +264,12 @@ fn new_file_both_missing_is_an_error() -> Result<(), Box> .code(predicate::eq(2)) .failure() .stdout(predicate::str::is_empty()) - .stderr(predicate::str::contains( - "missing1: No such file or directory", - )) - .stderr(predicate::str::contains( - "missing2: No such file or directory", - )); + .stderr(predicate::str::contains(format!( + "missing1: {error_message}" + ))) + .stderr(predicate::str::contains(format!( + "missing2: {error_message}" + ))); } Ok(()) }