diff --git a/text/diff.rs b/text/diff.rs index 9f9b82e1f..e15c3a712 100644 --- a/text/diff.rs +++ b/text/diff.rs @@ -14,17 +14,16 @@ mod diff_util; -use std::{fs, io, path::PathBuf}; - use clap::Parser; use diff_util::{ common::{FormatOptions, OutputFormat}, diff_exit_status::DiffExitStatus, dir_diff::DirDiff, file_diff::FileDiff, - functions::check_existance, + functions::check_existence, }; use gettextrs::{bind_textdomain_codeset, setlocale, textdomain, LocaleCategory}; +use std::{fs, io, path::PathBuf}; /// diff - compare two files #[derive(Parser, Clone)] @@ -54,6 +53,10 @@ struct Args { #[arg(short, long)] recurse: bool, + /// Print a message even when there are no differences between files + #[arg(short = 's', long = "report-identical-files")] + report_identical_files: bool, + /// Output 3 lines of unified context #[arg(short)] unified3: bool, @@ -103,17 +106,16 @@ impl From<&Args> for OutputFormat { } fn check_difference(args: Args) -> io::Result { - let path1 = PathBuf::from(&args.file1); - let path2 = PathBuf::from(&args.file2); + let path1 = PathBuf::from(args.file1.as_str()); + let path2 = PathBuf::from(args.file2.as_str()); - let path1_exists = check_existance(&path1)?; - let path2_exists = check_existance(&path2)?; + let path1_path = path1.as_path(); + let path2_path = path2.as_path(); - if !path1_exists || !path2_exists { - return Ok(DiffExitStatus::Trouble); - } + let path1_exists = check_existence(path1_path); + let path2_exists = check_existence(path2_path); - if path1 == path2 { + if !path1_exists || !path2_exists { return Ok(DiffExitStatus::Trouble); } @@ -124,18 +126,24 @@ fn check_difference(args: Args) -> io::Result { output_format, args.label, args.label2, + args.report_identical_files, ); + let format_options = format_options.unwrap(); - let path1_is_file = fs::metadata(&path1)?.is_file(); - let path2_is_file = fs::metadata(&path2)?.is_file(); + let path1_is_file = fs::metadata(path1_path)?.is_file(); + let path2_is_file = fs::metadata(path2_path)?.is_file(); if path1_is_file && path2_is_file { - FileDiff::file_diff(path1, path2, &format_options, None) + FileDiff::file_diff(path1_path, path2_path, &format_options, None) } else if !path1_is_file && !path2_is_file { - DirDiff::dir_diff(path1, path2, &format_options, args.recurse) + DirDiff::dir_diff(path1_path, path2_path, &format_options, args.recurse) } else { - FileDiff::file_dir_diff(path1, path2, &format_options) + Ok(FileDiff::file_dir_diff( + path1_path, + path2_path, + &format_options, + )?) } } @@ -151,7 +159,7 @@ fn main() -> DiffExitStatus { match result { Ok(diff_exit_status) => diff_exit_status, Err(error) => { - eprintln!("diff: {}", error); + eprintln!("diff: {error}"); DiffExitStatus::Trouble } diff --git a/text/diff_util/common.rs b/text/diff_util/common.rs index f2e3791db..918e18198 100755 --- a/text/diff_util/common.rs +++ b/text/diff_util/common.rs @@ -3,6 +3,7 @@ pub struct FormatOptions { pub output_format: OutputFormat, label1: Option, label2: Option, + pub report_identical_files: bool, } impl FormatOptions { @@ -11,6 +12,7 @@ impl FormatOptions { output_format: OutputFormat, label1: Option, label2: Option, + report_identical_files: bool, ) -> Result { if label1.is_none() && label2.is_some() { return Err("label1 can not be NONE when label2 is available"); @@ -21,6 +23,7 @@ impl FormatOptions { output_format, label1, label2, + report_identical_files, }) } diff --git a/text/diff_util/dir_data.rs b/text/diff_util/dir_data.rs index 221cb0c58..2fbd532ca 100755 --- a/text/diff_util/dir_data.rs +++ b/text/diff_util/dir_data.rs @@ -3,21 +3,19 @@ use std::{ ffi::OsString, fs::{self, DirEntry}, io, - path::PathBuf, + path::{Display, Path}, }; -use super::constants::*; - -pub struct DirData { - path: PathBuf, +pub struct DirData<'a> { + path: &'a Path, files: HashMap, } -impl DirData { - pub fn load(path: PathBuf) -> io::Result { +impl<'a> DirData<'a> { + pub fn load(path: &'a Path) -> io::Result { let mut files: HashMap = Default::default(); - let entries = fs::read_dir(&path)?; + let entries = fs::read_dir(path)?; for entry in entries { let entry = entry?; @@ -31,11 +29,11 @@ impl DirData { &self.files } - pub fn path(&self) -> &PathBuf { - &self.path + pub fn path(&'a self) -> &'a Path { + self.path } - pub fn path_str(&self) -> &str { - self.path.to_str().unwrap_or(COULD_NOT_UNWRAP_FILENAME) + pub fn path_str(&self) -> Display { + self.path.display() } } diff --git a/text/diff_util/dir_diff.rs b/text/diff_util/dir_diff.rs index 1e6892ba3..a5705f131 100755 --- a/text/diff_util/dir_diff.rs +++ b/text/diff_util/dir_diff.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, ffi::OsString, io, path::PathBuf}; +use std::{collections::HashSet, ffi::OsString, io, path::Path}; use crate::diff_util::{ constants::COULD_NOT_UNWRAP_FILENAME, diff_exit_status::DiffExitStatus, file_diff::FileDiff, @@ -7,16 +7,16 @@ use crate::diff_util::{ use super::{common::FormatOptions, dir_data::DirData}; pub struct DirDiff<'a> { - dir1: &'a mut DirData, - dir2: &'a mut DirData, + dir1: &'a mut DirData<'a>, + dir2: &'a mut DirData<'a>, format_options: &'a FormatOptions, recursive: bool, } impl<'a> DirDiff<'a> { fn new( - dir1: &'a mut DirData, - dir2: &'a mut DirData, + dir1: &'a mut DirData<'a>, + dir2: &'a mut DirData<'a>, format_options: &'a FormatOptions, recursive: bool, ) -> Self { @@ -29,8 +29,8 @@ impl<'a> DirDiff<'a> { } pub fn dir_diff( - path1: PathBuf, - path2: PathBuf, + path1: &Path, + path2: &Path, format_options: &FormatOptions, recursive: bool, ) -> io::Result { @@ -48,15 +48,7 @@ impl<'a> DirDiff<'a> { let is_file = dir_data .files() .get_key_value(file_name) - .unwrap_or_else(|| { - panic!( - "Could not find file in {}", - dir_data - .path() - .to_str() - .unwrap_or(COULD_NOT_UNWRAP_FILENAME) - ) - }) + .unwrap_or_else(|| panic!("Could not find file in {}", dir_data.path().display())) .1 .file_type()? .is_file(); @@ -141,8 +133,8 @@ impl<'a> DirDiff<'a> { } let inner_exit_status = FileDiff::file_diff( - path1, - path2, + path1.as_path(), + path2.as_path(), self.format_options, Some(show_if_different), )?; @@ -153,37 +145,23 @@ impl<'a> DirDiff<'a> { } else if !in_dir1_is_file && !in_dir2_is_file { if self.recursive { Self::dir_diff( - self.dir1.path().join(file_name), - self.dir2.path().join(file_name), + self.dir1.path().join(file_name).as_path(), + self.dir2.path().join(file_name).as_path(), self.format_options, self.recursive, )?; } else { println!( "Common subdirectories: \"{}\" and \"{}\"", - self.dir1 - .path() - .join(file_name) - .to_str() - .unwrap_or(COULD_NOT_UNWRAP_FILENAME), - self.dir2 - .path() - .join(file_name) - .to_str() - .unwrap_or(COULD_NOT_UNWRAP_FILENAME) + self.dir1.path().join(file_name).display(), + self.dir2.path().join(file_name).display() ); } } else { let (file, dir) = if in_dir1_is_file && !in_dir2_is_file { - ( - path1.to_str().unwrap_or(COULD_NOT_UNWRAP_FILENAME), - path2.to_str().unwrap_or(COULD_NOT_UNWRAP_FILENAME), - ) + (path1.display(), path2.display()) } else { - ( - path2.to_str().unwrap_or(COULD_NOT_UNWRAP_FILENAME), - path1.to_str().unwrap_or(COULD_NOT_UNWRAP_FILENAME), - ) + (path2.display(), path1.display()) }; println!( diff --git a/text/diff_util/file_data.rs b/text/diff_util/file_data.rs index f4f225813..8e51ab85a 100755 --- a/text/diff_util/file_data.rs +++ b/text/diff_util/file_data.rs @@ -1,10 +1,17 @@ -use std::{fs::File, io, mem::take, path::PathBuf, str::from_utf8, time::SystemTime}; +use std::{ + fs::File, + io, + mem::take, + path::{Display, Path}, + str::from_utf8, + time::SystemTime, +}; use super::constants::COULD_NOT_UNWRAP_FILENAME; #[derive(Debug)] pub struct FileData<'a> { - path: PathBuf, + path: &'a Path, lines: Vec<&'a str>, modified: SystemTime, ends_with_newline: bool, @@ -16,11 +23,11 @@ impl<'a> FileData<'a> { } pub fn get_file( - path: PathBuf, + path: &'a Path, lines: Vec<&'a str>, ends_with_newline: bool, ) -> io::Result { - let file = File::open(&path)?; + let file = File::open(path)?; let modified = file.metadata()?.modified()?; Ok(Self { @@ -53,8 +60,8 @@ impl<'a> FileData<'a> { COULD_NOT_UNWRAP_FILENAME } - pub fn path(&self) -> &str { - self.path.to_str().unwrap_or(COULD_NOT_UNWRAP_FILENAME) + pub fn path(&self) -> Display { + self.path.display() } } diff --git a/text/diff_util/file_diff.rs b/text/diff_util/file_diff.rs index 16f98e332..40930e1a8 100755 --- a/text/diff_util/file_diff.rs +++ b/text/diff_util/file_diff.rs @@ -3,20 +3,19 @@ use super::{ constants::COULD_NOT_UNWRAP_FILENAME, diff_exit_status::DiffExitStatus, file_data::{FileData, LineReader}, - functions::{check_existance, is_binary, system_time_to_rfc2822}, + functions::{check_existence, system_time_to_rfc2822}, hunks::Hunks, }; - use crate::diff_util::constants::NO_NEW_LINE_AT_END_OF_FILE; - use std::{ cmp::Reverse, collections::HashMap, fmt::Write, - fs::{read_to_string, File}, + fs::{self, File}, io::{self, BufReader, Read}, os::unix::fs::MetadataExt, - path::PathBuf, + path::{Display, Path}, + str, }; pub struct FileDiff<'a> { @@ -43,115 +42,146 @@ impl<'a> FileDiff<'a> { } pub fn file_diff( - path1: PathBuf, - path2: PathBuf, + path1: &Path, + path2: &Path, format_options: &FormatOptions, show_if_different: Option, ) -> io::Result { - if is_binary(&path1)? || is_binary(&path2)? { - Self::binary_file_diff(&path1, &path2) - } else { - let content1 = read_to_string(&path1)?.into_bytes(); - let linereader1 = LineReader::new(&content1); - let ends_with_newline1 = linereader1.ends_with_newline(); - let mut lines1 = Vec::new(); - for line in linereader1 { - if !format_options.ignore_trailing_white_spaces { - lines1.push(line); - } else { - lines1.push(line.trim_end()); + let path1_vec = fs::read(path1)?; + let path2_vec = fs::read(path2)?; + + // Try normal diff first + if let Ok(path1_str) = str::from_utf8(path1_vec.as_slice()) { + if let Ok(path2_str) = str::from_utf8(path2_vec.as_slice()) { + // Both files consist only of valid UTF-8 data + let linereader1 = LineReader::new(path1_str.as_bytes()); + let ends_with_newline1 = linereader1.ends_with_newline(); + let mut lines1 = Vec::new(); + for line in linereader1 { + if !format_options.ignore_trailing_white_spaces { + lines1.push(line); + } else { + lines1.push(line.trim_end()); + } } - } - let content2 = read_to_string(&path2)?.into_bytes(); - let linereader2 = LineReader::new(&content2); - let ends_with_newline2 = linereader2.ends_with_newline(); - let mut lines2 = Vec::new(); - for line in linereader2 { - if !format_options.ignore_trailing_white_spaces { - lines2.push(line); - } else { - lines2.push(line.trim_end()); + let linereader2 = LineReader::new(path2_str.as_bytes()); + let ends_with_newline2 = linereader2.ends_with_newline(); + let mut lines2 = Vec::new(); + for line in linereader2 { + if !format_options.ignore_trailing_white_spaces { + lines2.push(line); + } else { + lines2.push(line.trim_end()); + } + } + let mut file1 = FileData::get_file(path1, lines1, ends_with_newline1)?; + let mut file2 = FileData::get_file(path2, lines2, ends_with_newline2)?; + + let mut diff = FileDiff::new(&mut file1, &mut file2, format_options); + + // histogram diff + let mut lcs_indices = vec![-1_i32; diff.file1.lines().len()]; + let num_lines1 = diff.file1.lines().len(); + let num_lines2 = diff.file2.lines().len(); + FileDiff::histogram_lcs( + diff.file1, + diff.file2, + 0, + num_lines1, + 0, + num_lines2, + &mut lcs_indices, + ); + + diff.hunks + .create_hunks_from_lcs(&lcs_indices, num_lines1, num_lines2); + + if diff.hunks.hunk_count() > 0 { + diff.are_different = true; } - } - let mut file1 = FileData::get_file(path1, lines1, ends_with_newline1)?; - let mut file2 = FileData::get_file(path2, lines2, ends_with_newline2)?; - - let mut diff = FileDiff::new(&mut file1, &mut file2, format_options); - - // histogram diff - let mut lcs_indices: Vec = vec![-1; diff.file1.lines().len()]; - let num_lines1 = diff.file1.lines().len(); - let num_lines2 = diff.file2.lines().len(); - FileDiff::histogram_lcs( - diff.file1, - diff.file2, - 0, - num_lines1, - 0, - num_lines2, - &mut lcs_indices, - ); - - diff.hunks - .create_hunks_from_lcs(&lcs_indices, num_lines1, num_lines2); - - if diff.hunks.hunk_count() > 0 { - diff.are_different = true; - } - if diff.are_different { - if let Some(show_if_different) = show_if_different { - println!("{}", show_if_different); + if diff.are_different { + if let Some(show_if_different) = show_if_different { + println!("{show_if_different}"); + } + } else { + Self::print_identical_message_if_appropriate( + format_options, + path1.display(), + path2.display(), + ); } - } - diff.print() + return diff.print(); + } } + + // Fall back to binary diff + Self::binary_file_diff(format_options, path1, path2) } pub fn file_dir_diff( - path1: PathBuf, - path2: PathBuf, + path1: &Path, + path2: &Path, format_options: &FormatOptions, ) -> io::Result { let path1_file_type = path1.metadata()?.file_type(); if path1_file_type.is_file() { - let path1_file = path1.clone(); - let path1_file = path1_file.file_name().expect(COULD_NOT_UNWRAP_FILENAME); - let path2 = path2.join(path1_file); + let path1_file = path1.file_name().expect(COULD_NOT_UNWRAP_FILENAME); - if !check_existance(&path2)? { + let path2_join_path1_file = path2.join(path1_file); + + let path2_join_path1_file_path = path2_join_path1_file.as_path(); + + if !check_existence(path2_join_path1_file_path) { return Ok(DiffExitStatus::Trouble); } - FileDiff::file_diff(path1, path2, format_options, None) + FileDiff::file_diff(path1, path2_join_path1_file_path, format_options, None) } else { - let path2_file = path2.clone(); - let path2_file = path2_file.file_name().expect(COULD_NOT_UNWRAP_FILENAME); - let path1 = path1.join(path2_file); + let path2_file = path2.file_name().expect(COULD_NOT_UNWRAP_FILENAME); + + let path1_join_path2_file = path1.join(path2_file); - if !check_existance(&path1)? { + let path1_join_path2_file_path = path1_join_path2_file.as_path(); + + if !check_existence(path1_join_path2_file_path) { return Ok(DiffExitStatus::Trouble); } - FileDiff::file_diff(path1, path2, format_options, None) + FileDiff::file_diff(path1_join_path2_file_path, path2, format_options, None) } } - fn binary_file_diff(file1_path: &PathBuf, file2_path: &PathBuf) -> io::Result { - let differ_report = format!( - "Binary files {} and {} differ", - file1_path.to_str().unwrap_or(COULD_NOT_UNWRAP_FILENAME), - file2_path.to_str().unwrap_or(COULD_NOT_UNWRAP_FILENAME) - ); + fn print_identical_message_if_appropriate( + format_options: &FormatOptions, + path_1_display: Display, + path_2_display: Display, + ) { + if format_options.report_identical_files { + println!("Files {path_1_display} and {path_2_display} are identical",); + } + } + + fn binary_file_diff( + format_options: &FormatOptions, + file1_path: &Path, + file2_path: &Path, + ) -> io::Result { + let file1_path_display = file1_path.display(); + let file2_path_display = file2_path.display(); + + let differ_report = + format!("Binary files {file1_path_display} and {file2_path_display} differ",); let file1 = File::open(file1_path)?; let file2 = File::open(file2_path)?; if file1.metadata()?.size() != file2.metadata()?.size() { - println!("{}", differ_report); + println!("{differ_report}"); + return Ok(DiffExitStatus::Different); } @@ -160,12 +190,20 @@ impl<'a> FileDiff<'a> { for bytes_pair in file1.bytes().zip(file2.bytes()) { let (b1, b2) = (bytes_pair.0?, bytes_pair.1?); + if b1 != b2 { - println!("{}", differ_report); + println!("{differ_report}"); + return Ok(DiffExitStatus::Different); } } + Self::print_identical_message_if_appropriate( + format_options, + file1_path_display, + file2_path_display, + ); + Ok(DiffExitStatus::NotDifferent) } @@ -192,7 +230,7 @@ impl<'a> FileDiff<'a> { hunk_index == hunks_count - 1, ), OutputFormat::Context(_) => { - eprintln!("OutputFormat::Context should be handled in other place"); + eprintln!("diff: OutputFormat::Context should be handled in other place"); return Ok(DiffExitStatus::Trouble); } OutputFormat::ForwardEditScript => hunk.print_edit_script( @@ -201,7 +239,7 @@ impl<'a> FileDiff<'a> { hunk_index == hunks_count - 1, ), OutputFormat::Unified(_) => { - eprintln!("OutputFormat::Unified should be handled in other place"); + eprintln!("diff: OutputFormat::Unified should be handled in other place"); return Ok(DiffExitStatus::Trouble); } } diff --git a/text/diff_util/functions.rs b/text/diff_util/functions.rs index e6c2c806d..2fe84d36d 100755 --- a/text/diff_util/functions.rs +++ b/text/diff_util/functions.rs @@ -1,42 +1,16 @@ use chrono::{DateTime, Local}; -use std::{ - fs::File, - io::{self, Read}, - path::{Path, PathBuf}, - time::SystemTime, -}; - -use super::constants::UTF8_NOT_ALLOWED_BYTES; -use crate::diff_util::constants::COULD_NOT_UNWRAP_FILENAME; +use std::{path::Path, time::SystemTime}; pub fn system_time_to_rfc2822(system_time: SystemTime) -> String { Into::>::into(system_time).to_rfc2822() } -pub fn is_binary(file_path: &PathBuf) -> io::Result { - let mut file = File::open(file_path)?; - let mut buffer = [0; 1024]; - - if let Ok(count) = file.read(&mut buffer) { - for buf_item in buffer.iter().take(count) { - if UTF8_NOT_ALLOWED_BYTES.contains(buf_item) { - return Ok(true); - } - } - } - - Ok(false) -} - -pub fn check_existance(path_buf: &Path) -> io::Result { - if !path_buf.exists() { - println!( - "diff: {}: No such file or directory", - path_buf.to_str().unwrap_or(COULD_NOT_UNWRAP_FILENAME) - ); +pub fn check_existence(path: &Path) -> bool { + if !path.exists() { + eprintln!("diff: {}: No such file or directory", path.display()); - return Ok(false); + return false; } - Ok(true) + true } diff --git a/text/tests/diff-tests.rs b/text/tests/diff-tests.rs index 8f159c23b..b415438b3 100644 --- a/text/tests/diff-tests.rs +++ b/text/tests/diff-tests.rs @@ -16,18 +16,39 @@ use plib::testing::{run_test, TestPlan}; use std::{collections::HashMap, path::PathBuf, process::Stdio, sync::LazyLock}; fn diff_test(args: &[&str], expected_output: &str, expected_diff_exit_status: u8) { - let str_args = args.iter().cloned().map(str::to_owned).collect(); + let str_args = args + .iter() + .cloned() + .map(ToOwned::to_owned) + .collect::>(); run_test(TestPlan { - cmd: String::from("diff"), + cmd: "diff".to_owned(), args: str_args, - stdin_data: String::from(""), - expected_out: String::from(expected_output), - expected_err: String::from(""), + stdin_data: "".to_owned(), + expected_out: expected_output.to_owned(), + expected_err: "".to_owned(), expected_exit_code: i32::from(expected_diff_exit_status), }); } +fn diff_test_failure(args: &[&str], expected_error: &str, exit_status: i32) { + let str_args = args + .iter() + .cloned() + .map(ToOwned::to_owned) + .collect::>(); + + run_test(TestPlan { + cmd: "diff".to_owned(), + args: str_args, + stdin_data: "".to_owned(), + expected_out: "".to_owned(), + expected_err: expected_error.to_owned(), + expected_exit_code: exit_status, + }); +} + fn diff_base_path() -> PathBuf { PathBuf::from("tests").join("diff") } @@ -72,6 +93,30 @@ fn f1_txt_with_eol_spaces_path() -> String { .to_string() } +fn binary_a_path() -> String { + diff_base_path() + .join("binary-a") + .to_str() + .expect("Could not unwrap binary_a_path") + .to_string() +} + +fn binary_b_path() -> String { + diff_base_path() + .join("binary-b") + .to_str() + .expect("Could not unwrap binary_b_path") + .to_string() +} + +fn binary_c_path() -> String { + diff_base_path() + .join("binary-c") + .to_str() + .expect("Could not unwrap binary_c_path") + .to_string() +} + struct DiffTestHelper { content: String, file1_path: String, @@ -80,21 +125,29 @@ struct DiffTestHelper { impl DiffTestHelper { fn new(options: &str, file1_path: String, file2_path: String) -> Self { - let args = format!( - "run --release --bin diff --{} {} {}", - options, file1_path, file2_path - ); + let mut command = std::process::Command::new("cargo"); + + // Arguments + { + command.args(["run", "--release", "--bin", "diff", "--"]); - let args_list = args.split(' ').collect::>(); + for (us, st) in options.split(' ').enumerate() { + if us == 0_usize && st.is_empty() { + continue; + } - let output = std::process::Command::new("cargo") - .args(args_list) - // .stdout(output_file) + command.arg(st); + } + + command.args([file1_path.as_str(), file2_path.as_str()]); + } + + let output = command .stdout(Stdio::piped()) .output() .expect("Could not run cargo command!"); - let content = String::from_utf8(output.stdout).expect("Failed to read output of Command!"); + let content = String::from_utf8(output.stdout).expect("Failed to read stdout of `Command`"); Self { file1_path, @@ -119,57 +172,47 @@ impl DiffTestHelper { fn get_diff_test_helper_hash_map() -> HashMap { let diff_test_helper_init_data = [ ("", f1_txt_path(), f2_txt_path(), "test_diff_normal"), - (" -c", f1_txt_path(), f2_txt_path(), "test_diff_context3"), - (" -C 1", f1_txt_path(), f2_txt_path(), "test_diff_context1"), - ( - " -C 10", - f1_txt_path(), - f2_txt_path(), - "test_diff_context10", - ), - (" -e", f1_txt_path(), f2_txt_path(), "test_diff_edit_script"), + ("-c", f1_txt_path(), f2_txt_path(), "test_diff_context3"), + ("-C 1", f1_txt_path(), f2_txt_path(), "test_diff_context1"), + ("-C 10", f1_txt_path(), f2_txt_path(), "test_diff_context10"), + ("-e", f1_txt_path(), f2_txt_path(), "test_diff_edit_script"), ( - " -f", + "-f", f1_txt_path(), f2_txt_path(), "test_diff_forward_edit_script", ), - (" -u", f1_txt_path(), f2_txt_path(), "test_diff_unified3"), - (" -U 0", f1_txt_path(), f2_txt_path(), "test_diff_unified0"), - ( - " -U 10", - f1_txt_path(), - f2_txt_path(), - "test_diff_unified10", - ), + ("-u", f1_txt_path(), f2_txt_path(), "test_diff_unified3"), + ("-U 0", f1_txt_path(), f2_txt_path(), "test_diff_unified0"), + ("-U 10", f1_txt_path(), f2_txt_path(), "test_diff_unified10"), ("", f1_txt_path(), f2_dir_path(), "test_diff_file_directory"), ("", f1_dir_path(), f2_dir_path(), "test_diff_directories"), ( - " -r", + "-r", f1_dir_path(), f2_dir_path(), "test_diff_directories_recursive", ), ( - " -r -c", + "-r -c", f1_dir_path(), f2_dir_path(), "test_diff_directories_recursive_context", ), ( - " -r -e", + "-r -e", f1_dir_path(), f2_dir_path(), "test_diff_directories_recursive_edit_script", ), ( - " -r -f", + "-r -f", f1_dir_path(), f2_dir_path(), "test_diff_directories_recursive_forward_edit_script", ), ( - " -r -u", + "-r -u", f1_dir_path(), f2_dir_path(), "test_diff_directories_recursive_unified", @@ -181,17 +224,18 @@ fn get_diff_test_helper_hash_map() -> HashMap { "test_diff_counting_eol_spaces", ), ( - " -b", + "-b", f1_txt_path(), f1_txt_with_eol_spaces_path(), "test_diff_ignoring_eol_spaces", ), ( - " --label F1 --label2 F2 -u", + "--label F1 --label2 F2 -u", f1_txt_path(), f1_txt_with_eol_spaces_path(), "test_diff_unified_two_labels", ), + ("-s", f1_txt_path(), f1_txt_path(), "test_diff_s"), ]; let mut diff_test_helper_hash_map = @@ -433,3 +477,69 @@ fn test_diff_unified_two_labels() { EXIT_STATUS_DIFFERENCE, ); } + +// If the paths are the same, BusyBox exits with exit status 0, but does not print the +// "Files [...] and [...] are identical" message +// +// GNU Diffutils does print the message, and exits with exit status 0 +#[test] +fn test_diff_s() { + let diff_test_helper = f1_txt_path(); + + let path = diff_test_helper.as_str(); + + for short_or_long in ["-s", "--report-identical-files"] { + diff_test( + &[short_or_long, path, path], + format!("Files {path} and {path} are identical\n").as_str(), + EXIT_STATUS_NO_DIFFERENCE, + ); + } +} + +// Errors like this were being printed to stdout instead of stderr +#[test] +fn test_diff_print_errors_to_stderr() { + diff_test_failure( + &["/7349c3b5970ba9c3", "/a74c002739306869"], + "\ +diff: /7349c3b5970ba9c3: No such file or directory +diff: /a74c002739306869: No such file or directory +", + 2_i32, + ); +} + +#[test] +fn test_diff_s_binary() { + let binary_a = binary_a_path(); + let binary_b = binary_b_path(); + + let binary_a_str = binary_a.as_str(); + let binary_b_str = binary_b.as_str(); + + for short_or_long in ["-s", "--report-identical-files"] { + diff_test( + &[short_or_long, binary_a_str, binary_b_str], + format!("Files {binary_a_str} and {binary_b_str} are identical\n").as_str(), + EXIT_STATUS_NO_DIFFERENCE, + ); + } +} + +#[test] +fn test_diff_binary_differ() { + let binary_a = binary_a_path(); + let binary_c = binary_c_path(); + + let binary_a_str = binary_a.as_str(); + let binary_c_str = binary_c.as_str(); + + for short_or_long in ["-s", "--report-identical-files"] { + diff_test( + &[short_or_long, binary_a_str, binary_c_str], + format!("Binary files {binary_a_str} and {binary_c_str} differ\n").as_str(), + EXIT_STATUS_DIFFERENCE, + ); + } +} diff --git a/text/tests/diff/binary-a b/text/tests/diff/binary-a new file mode 100644 index 000000000..95a11ced0 --- /dev/null +++ b/text/tests/diff/binary-a @@ -0,0 +1 @@ +1 A À B Á 2 diff --git a/text/tests/diff/binary-b b/text/tests/diff/binary-b new file mode 100644 index 000000000..95a11ced0 --- /dev/null +++ b/text/tests/diff/binary-b @@ -0,0 +1 @@ +1 A À B Á 2 diff --git a/text/tests/diff/binary-c b/text/tests/diff/binary-c new file mode 100644 index 000000000..d8b1c95f3 --- /dev/null +++ b/text/tests/diff/binary-c @@ -0,0 +1 @@ +1 A Á B À 2