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
15 changes: 11 additions & 4 deletions src/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ pub fn main(opts: Peekable<ArgsOs>) -> ExitCode {
}

// run diff
let mut side_by_side_differs = false;
let result: Vec<u8> = match params.format {
Format::Normal => normal_diff::diff(&from_content, &to_content, &params),
Format::Unified => unified_diff::diff(&from_content, &to_content, &params),
Expand All @@ -80,11 +81,17 @@ pub fn main(opts: Peekable<ArgsOs>) -> ExitCode {
exit(2);
}),
Format::SideBySide => {
let mut output = stdout().lock();
side_diff::diff(&from_content, &to_content, &mut output, &params)
side_by_side_differs = if params.brief {
side_diff::diff(&from_content, &to_content, &mut io::sink(), &params)
} else {
let mut output = stdout().lock();
side_diff::diff(&from_content, &to_content, &mut output, &params)
};
Vec::new()
}
};
if params.brief && !result.is_empty() {
let differs = side_by_side_differs || !result.is_empty();
if params.brief && differs {
println!(
"Files {} and {} differ",
params.from.to_string_lossy(),
Expand All @@ -93,7 +100,7 @@ pub fn main(opts: Peekable<ArgsOs>) -> ExitCode {
} else {
io::stdout().write_all(&result).unwrap();
}
if result.is_empty() {
if !differs {
maybe_report_identical_files();
ExitCode::SUCCESS
} else {
Expand Down
22 changes: 12 additions & 10 deletions src/side_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

use core::cmp::{max, min};
use diff::Result;
use std::{io::Write, vec};
use std::io::Write;
use unicode_width::UnicodeWidthStr;

use crate::params::Params;
Expand Down Expand Up @@ -308,12 +308,7 @@ fn push_output<T: Write>(
Ok(())
}

pub fn diff<T: Write>(
from_file: &[u8],
to_file: &[u8],
output: &mut T,
params: &Params,
) -> Vec<u8> {
pub fn diff<T: Write>(from_file: &[u8], to_file: &[u8], output: &mut T, params: &Params) -> bool {
// ^ The left file ^ The right file

let mut left_lines: Vec<&[u8]> = from_file.split_inclusive(|&c| c == b'\n').collect();
Expand Down Expand Up @@ -350,17 +345,24 @@ pub fn diff<T: Write>(
More studies are needed to cover GNU diff side by side with 100% accuracy, which is one of
the goals of this project : )
*/
let mut differs = false;
for result in diff::slice(&left_lines, &right_lines) {
match result {
Result::Left(left_ln) => push_output(left_ln, b"", b'<', output, &config).unwrap(),
Result::Right(right_ln) => push_output(b"", right_ln, b'>', output, &config).unwrap(),
Result::Left(left_ln) => {
differs = true;
push_output(left_ln, b"", b'<', output, &config).unwrap()
}
Result::Right(right_ln) => {
differs = true;
push_output(b"", right_ln, b'>', output, &config).unwrap()
}
Result::Both(left_ln, right_ln) => {
push_output(left_ln, right_ln, b' ', output, &config).unwrap()
}
}
}

vec![]
differs
}

#[cfg(test)]
Expand Down
111 changes: 111 additions & 0 deletions tests/by-util/test_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,117 @@ fn no_differences() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}

const SIDE_BY_SIDE_CASES: [(&[u8], &[u8], i32); 9] = [
(b"alpha\n", b"alpha\n", 0),
(b"alpha\n", b"alpha\nbeta\n", 1),
(b"alpha\n", b"bravo\n", 1),
(b"alpha", b"alpha\n", 1),
(b"alpha", b"alpha", 0),
(b"\xff\n", b"\xfe\n", 1),
(b"", b"alpha\n", 1),
(b"alpha\n", b"", 1),
(b"", b"", 0),
];

#[test]
fn side_by_side_exit_status() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let left = dir.path().join("left");
let right = dir.path().join("right");
for (left_content, right_content, code) in SIDE_BY_SIDE_CASES {
std::fs::write(&left, left_content)?;
std::fs::write(&right, right_content)?;
for options in [&["-y"][..], &["-y", "--width=40"]] {
let mut cmd = cargo_bin_cmd!("diffutils");
cmd.arg("diff").args(options).arg(&left).arg(&right);
cmd.assert()
.code(predicate::eq(code))
.stderr(predicate::str::is_empty());
}
}
Ok(())
}

#[test]
fn side_by_side_brief() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let left = dir.path().join("left");
let right = dir.path().join("right");
for (left_content, right_content, code) in SIDE_BY_SIDE_CASES {
std::fs::write(&left, left_content)?;
std::fs::write(&right, right_content)?;
let expected = if code == 1 {
format!("Files {} and {} differ\n", left.display(), right.display())
} else {
String::new()
};
let mut cmd = cargo_bin_cmd!("diffutils");
cmd.arg("diff").arg("-y").arg("-q").arg(&left).arg(&right);
cmd.assert()
.code(predicate::eq(code))
.stdout(predicate::eq(expected))
.stderr(predicate::str::is_empty());
}
Ok(())
}

#[test]
fn side_by_side_report_identical_files() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let left = dir.path().join("left");
let right = dir.path().join("right");
for (left_content, right_content, code) in SIDE_BY_SIDE_CASES {
std::fs::write(&left, left_content)?;
std::fs::write(&right, right_content)?;
let identical = format!(
"Files {} and {} are identical\n",
left.display(),
right.display()
);
let mut cmd = cargo_bin_cmd!("diffutils");
cmd.arg("diff").arg("-y").arg("-s").arg(&left).arg(&right);
let assertion = cmd
.assert()
.code(predicate::eq(code))
.stderr(predicate::str::is_empty());
if code == 0 {
assertion.stdout(predicate::str::ends_with(identical));
} else {
assertion.stdout(predicate::function(|stdout: &[u8]| {
!stdout
.windows(identical.len())
.any(|window| window == identical.as_bytes())
}));
}
}
Ok(())
}

#[test]
fn side_by_side_missing_file() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let left = dir.path().join("left");
let right = dir.path().join("right");
std::fs::write(&left, b"alpha\n")?;

#[cfg(not(windows))]
let error_message = "No such file or directory";
#[cfg(windows)]
let error_message = "The system cannot find the file specified.";

let mut cmd = cargo_bin_cmd!("diffutils");
cmd.arg("diff").arg("-y").arg(&left).arg(&right);
cmd.assert()
.code(predicate::eq(2))
.failure()
.stdout(predicate::str::is_empty())
.stderr(predicate::eq(format!(
"diff: {}: {error_message}\n",
right.display()
)));
Ok(())
}

#[test]
fn no_differences_report_identical_files() -> Result<(), Box<dyn std::error::Error>> {
// same file
Expand Down