mirror of
https://github.com/uutils/diffutils.git
synced 2026-05-06 06:06:45 -04:00
9dcca24fb0
* fix: match GNU error format for unrecognized options Use single quotes and remove colon to match GNU diff/cmp output: `unrecognized option '--foobar'` instead of `unrecognized option: "--foobar"` Also use `contains` instead of `starts_with` in the integration test to handle the command prefix (e.g. `cmp: unrecognized option ...`). Follow-up to #178 / #179. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: apply cargo fmt formatting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
891 lines
28 KiB
Rust
891 lines
28 KiB
Rust
// This file is part of the uutils diffutils package.
|
|
//
|
|
// For the full copyright and license information, please view the LICENSE-*
|
|
// files that was distributed with this source code.
|
|
|
|
use assert_cmd::cargo::cargo_bin_cmd;
|
|
use predicates::prelude::*;
|
|
use std::fs::File;
|
|
#[cfg(not(windows))]
|
|
use std::fs::OpenOptions;
|
|
use std::io::Write;
|
|
use tempfile::{tempdir, NamedTempFile};
|
|
|
|
// Integration tests for the diffutils command
|
|
mod common {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn unknown_param() -> Result<(), Box<dyn std::error::Error>> {
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("patch");
|
|
cmd.assert()
|
|
.code(predicate::eq(2))
|
|
.failure()
|
|
.stderr(predicate::eq("patch: utility not supported\n"));
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.assert()
|
|
.code(predicate::eq(0))
|
|
.success()
|
|
.stderr(predicate::str::starts_with(
|
|
"Expected utility name as second argument, got nothing.\n",
|
|
));
|
|
|
|
for subcmd in ["diff", "cmp"] {
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg(subcmd);
|
|
cmd.arg("--foobar");
|
|
cmd.assert()
|
|
.code(predicate::eq(2))
|
|
.failure()
|
|
.stderr(predicate::str::contains("unrecognized option '--foobar'"));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn cannot_read_files() -> Result<(), Box<dyn std::error::Error>> {
|
|
let file = NamedTempFile::new()?;
|
|
|
|
let nofile = NamedTempFile::new()?;
|
|
let nopath = nofile.into_temp_path();
|
|
std::fs::remove_file(&nopath)?;
|
|
|
|
#[cfg(not(windows))]
|
|
let error_message = "No such file or directory";
|
|
#[cfg(windows)]
|
|
let error_message = "The system cannot find the file specified.";
|
|
|
|
for subcmd in ["diff", "cmp"] {
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg(subcmd);
|
|
cmd.arg(&nopath).arg(file.path());
|
|
cmd.assert()
|
|
.code(predicate::eq(2))
|
|
.failure()
|
|
.stderr(predicate::str::ends_with(format!(
|
|
": {}: {error_message}\n",
|
|
&nopath.as_os_str().to_string_lossy()
|
|
)));
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg(subcmd);
|
|
cmd.arg(file.path()).arg(&nopath);
|
|
cmd.assert()
|
|
.code(predicate::eq(2))
|
|
.failure()
|
|
.stderr(predicate::str::ends_with(format!(
|
|
": {}: {error_message}\n",
|
|
&nopath.as_os_str().to_string_lossy()
|
|
)));
|
|
}
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("diff");
|
|
cmd.arg(&nopath).arg(&nopath);
|
|
cmd.assert().code(predicate::eq(2)).failure().stderr(
|
|
predicate::str::contains(format!(
|
|
": {}: {error_message}\n",
|
|
&nopath.as_os_str().to_string_lossy()
|
|
))
|
|
.count(2),
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
mod diff {
|
|
use diffutilslib::assert_diff_eq;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn no_differences() -> Result<(), Box<dyn std::error::Error>> {
|
|
let file = NamedTempFile::new()?;
|
|
for option in ["", "-u", "-c", "-e"] {
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("diff");
|
|
if !option.is_empty() {
|
|
cmd.arg(option);
|
|
}
|
|
cmd.arg(file.path()).arg(file.path());
|
|
cmd.assert()
|
|
.code(predicate::eq(0))
|
|
.success()
|
|
.stdout(predicate::str::is_empty());
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn no_differences_report_identical_files() -> Result<(), Box<dyn std::error::Error>> {
|
|
// same file
|
|
let mut file1 = NamedTempFile::new()?;
|
|
file1.write_all("foo\n".as_bytes())?;
|
|
for option in ["", "-u", "-c", "-e"] {
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("diff");
|
|
if !option.is_empty() {
|
|
cmd.arg(option);
|
|
}
|
|
cmd.arg("-s").arg(file1.path()).arg(file1.path());
|
|
cmd.assert()
|
|
.code(predicate::eq(0))
|
|
.success()
|
|
.stdout(predicate::eq(format!(
|
|
"Files {} and {} are identical\n",
|
|
file1.path().to_string_lossy(),
|
|
file1.path().to_string_lossy(),
|
|
)));
|
|
}
|
|
// two files with the same content
|
|
let mut file2 = NamedTempFile::new()?;
|
|
file2.write_all("foo\n".as_bytes())?;
|
|
for option in ["", "-u", "-c", "-e"] {
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("diff");
|
|
if !option.is_empty() {
|
|
cmd.arg(option);
|
|
}
|
|
cmd.arg("-s").arg(file1.path()).arg(file2.path());
|
|
cmd.assert()
|
|
.code(predicate::eq(0))
|
|
.success()
|
|
.stdout(predicate::eq(format!(
|
|
"Files {} and {} are identical\n",
|
|
file1.path().to_string_lossy(),
|
|
file2.path().to_string_lossy(),
|
|
)));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn differences() -> Result<(), Box<dyn std::error::Error>> {
|
|
let mut file1 = NamedTempFile::new()?;
|
|
file1.write_all("foo\n".as_bytes())?;
|
|
let mut file2 = NamedTempFile::new()?;
|
|
file2.write_all("bar\n".as_bytes())?;
|
|
for option in ["", "-u", "-c", "-e"] {
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("diff");
|
|
if !option.is_empty() {
|
|
cmd.arg(option);
|
|
}
|
|
cmd.arg(file1.path()).arg(file2.path());
|
|
cmd.assert()
|
|
.code(predicate::eq(1))
|
|
.failure()
|
|
.stdout(predicate::str::is_empty().not());
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn differences_brief() -> Result<(), Box<dyn std::error::Error>> {
|
|
let mut file1 = NamedTempFile::new()?;
|
|
file1.write_all("foo\n".as_bytes())?;
|
|
let mut file2 = NamedTempFile::new()?;
|
|
file2.write_all("bar\n".as_bytes())?;
|
|
for option in ["", "-u", "-c", "-e"] {
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("diff");
|
|
if !option.is_empty() {
|
|
cmd.arg(option);
|
|
}
|
|
cmd.arg("-q").arg(file1.path()).arg(file2.path());
|
|
cmd.assert()
|
|
.code(predicate::eq(1))
|
|
.failure()
|
|
.stdout(predicate::eq(format!(
|
|
"Files {} and {} differ\n",
|
|
file1.path().to_string_lossy(),
|
|
file2.path().to_string_lossy()
|
|
)));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn missing_newline() -> Result<(), Box<dyn std::error::Error>> {
|
|
let mut file1 = NamedTempFile::new()?;
|
|
file1.write_all("foo".as_bytes())?;
|
|
let mut file2 = NamedTempFile::new()?;
|
|
file2.write_all("bar".as_bytes())?;
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("diff");
|
|
cmd.arg("-e").arg(file1.path()).arg(file2.path());
|
|
cmd.assert()
|
|
.code(predicate::eq(2))
|
|
.failure()
|
|
.stderr(predicate::str::starts_with("No newline at end of file"));
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn read_from_stdin() -> Result<(), Box<dyn std::error::Error>> {
|
|
let mut file1 = NamedTempFile::new()?;
|
|
file1.write_all("foo\n".as_bytes())?;
|
|
let mut file2 = NamedTempFile::new()?;
|
|
file2.write_all("bar\n".as_bytes())?;
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("diff");
|
|
cmd.arg("-u")
|
|
.arg(file1.path())
|
|
.arg("-")
|
|
.write_stdin("bar\n");
|
|
cmd.assert().code(predicate::eq(1)).failure();
|
|
|
|
let output = cmd.output().unwrap().stdout;
|
|
assert_diff_eq!(
|
|
output,
|
|
format!(
|
|
"--- {}\tTIMESTAMP\n+++ -\tTIMESTAMP\n@@ -1 +1 @@\n-foo\n+bar\n",
|
|
file1.path().to_string_lossy()
|
|
)
|
|
);
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("diff");
|
|
cmd.arg("-u")
|
|
.arg("-")
|
|
.arg(file2.path())
|
|
.write_stdin("foo\n");
|
|
cmd.assert().code(predicate::eq(1)).failure();
|
|
|
|
let output = cmd.output().unwrap().stdout;
|
|
assert_diff_eq!(
|
|
output,
|
|
format!(
|
|
"--- -\tTIMESTAMP\n+++ {}\tTIMESTAMP\n@@ -1 +1 @@\n-foo\n+bar\n",
|
|
file2.path().to_string_lossy()
|
|
)
|
|
);
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("diff");
|
|
cmd.arg("-u").arg("-").arg("-");
|
|
cmd.assert()
|
|
.code(predicate::eq(0))
|
|
.success()
|
|
.stdout(predicate::str::is_empty());
|
|
|
|
#[cfg(unix)]
|
|
{
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("diff");
|
|
cmd.arg("-u")
|
|
.arg(file1.path())
|
|
.arg("/dev/stdin")
|
|
.write_stdin("bar\n");
|
|
cmd.assert().code(predicate::eq(1)).failure();
|
|
|
|
let output = cmd.output().unwrap().stdout;
|
|
assert_diff_eq!(
|
|
output,
|
|
format!(
|
|
"--- {}\tTIMESTAMP\n+++ /dev/stdin\tTIMESTAMP\n@@ -1 +1 @@\n-foo\n+bar\n",
|
|
file1.path().to_string_lossy()
|
|
)
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn compare_file_to_directory() -> Result<(), Box<dyn std::error::Error>> {
|
|
let tmp_dir = tempdir()?;
|
|
|
|
let directory = tmp_dir.path().join("d");
|
|
let _ = std::fs::create_dir(&directory);
|
|
|
|
let a_path = tmp_dir.path().join("a");
|
|
let mut a = File::create(&a_path).unwrap();
|
|
a.write_all(b"a\n").unwrap();
|
|
|
|
let da_path = directory.join("a");
|
|
let mut da = File::create(&da_path).unwrap();
|
|
da.write_all(b"da\n").unwrap();
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("diff");
|
|
cmd.arg("-u").arg(&directory).arg(&a_path);
|
|
cmd.assert().code(predicate::eq(1)).failure();
|
|
|
|
let output = cmd.output().unwrap().stdout;
|
|
assert_diff_eq!(
|
|
output,
|
|
format!(
|
|
"--- {}\tTIMESTAMP\n+++ {}\tTIMESTAMP\n@@ -1 +1 @@\n-da\n+a\n",
|
|
da_path.display(),
|
|
a_path.display()
|
|
)
|
|
);
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("diff");
|
|
cmd.arg("-u").arg(&a_path).arg(&directory);
|
|
cmd.assert().code(predicate::eq(1)).failure();
|
|
|
|
let output = cmd.output().unwrap().stdout;
|
|
assert_diff_eq!(
|
|
output,
|
|
format!(
|
|
"--- {}\tTIMESTAMP\n+++ {}\tTIMESTAMP\n@@ -1 +1 @@\n-a\n+da\n",
|
|
a_path.display(),
|
|
da_path.display()
|
|
)
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
mod cmp {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn cmp_incompatible_params() -> Result<(), Box<dyn std::error::Error>> {
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("cmp");
|
|
cmd.arg("-l");
|
|
cmd.arg("-s");
|
|
cmd.arg("/etc/passwd").arg("/etc/group");
|
|
cmd.assert()
|
|
.code(predicate::eq(2))
|
|
.failure()
|
|
.stderr(predicate::str::ends_with(
|
|
": options -l and -s are incompatible\n",
|
|
));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn cmp_stdin() -> Result<(), Box<dyn std::error::Error>> {
|
|
let tmp_dir = tempdir()?;
|
|
|
|
let a_path = tmp_dir.path().join("a");
|
|
let mut a = File::create(&a_path).unwrap();
|
|
a.write_all(b"a\n").unwrap();
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("cmp");
|
|
cmd.arg(&a_path);
|
|
cmd.write_stdin("a\n");
|
|
cmd.assert()
|
|
.code(predicate::eq(0))
|
|
.success()
|
|
.stderr(predicate::str::is_empty())
|
|
.stdout(predicate::str::is_empty());
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.env("LC_ALL", "C");
|
|
cmd.arg("cmp");
|
|
cmd.arg(&a_path);
|
|
cmd.write_stdin("b\n");
|
|
cmd.assert()
|
|
.code(predicate::eq(1))
|
|
.failure()
|
|
.stderr(predicate::str::is_empty())
|
|
.stdout(predicate::str::ends_with(" - differ: char 1, line 1\n"));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn cmp_equal_files() -> Result<(), Box<dyn std::error::Error>> {
|
|
let tmp_dir = tempdir()?;
|
|
|
|
let a_path = tmp_dir.path().join("a");
|
|
let mut a = File::create(&a_path).unwrap();
|
|
a.write_all(b"a\n").unwrap();
|
|
|
|
let b_path = tmp_dir.path().join("b");
|
|
let mut b = File::create(&b_path).unwrap();
|
|
b.write_all(b"a\n").unwrap();
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("cmp");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.assert()
|
|
.code(predicate::eq(0))
|
|
.success()
|
|
.stderr(predicate::str::is_empty())
|
|
.stdout(predicate::str::is_empty());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn cmp_one_file_empty() -> Result<(), Box<dyn std::error::Error>> {
|
|
let tmp_dir = tempdir()?;
|
|
|
|
let a_path = tmp_dir.path().join("a");
|
|
let mut a = File::create(&a_path).unwrap();
|
|
a.write_all(b"a\n").unwrap();
|
|
|
|
let b_path = tmp_dir.path().join("b");
|
|
let _ = File::create(&b_path).unwrap();
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("cmp");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.assert()
|
|
.code(predicate::eq(1))
|
|
.failure()
|
|
.stderr(predicate::str::contains(" EOF on "))
|
|
.stderr(predicate::str::ends_with(" which is empty\n"));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn cmp_immediate_difference() -> Result<(), Box<dyn std::error::Error>> {
|
|
let tmp_dir = tempdir()?;
|
|
|
|
let a_path = tmp_dir.path().join("a");
|
|
let mut a = File::create(&a_path).unwrap();
|
|
a.write_all(b"abc\n").unwrap();
|
|
|
|
let b_path = tmp_dir.path().join("b");
|
|
let mut b = File::create(&b_path).unwrap();
|
|
b.write_all(b"bcd\n").unwrap();
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.env("LC_ALL", "C");
|
|
cmd.arg("cmp");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.assert()
|
|
.code(predicate::eq(1))
|
|
.failure()
|
|
.stdout(predicate::str::ends_with(" differ: char 1, line 1\n"));
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.env("LC_ALL", "C");
|
|
cmd.arg("cmp");
|
|
cmd.arg("-b");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.assert()
|
|
.code(predicate::eq(1))
|
|
.failure()
|
|
.stderr(predicate::str::is_empty())
|
|
.stdout(predicate::str::ends_with(
|
|
" differ: byte 1, line 1 is 141 a 142 b\n",
|
|
));
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.env("LC_ALL", "C");
|
|
cmd.arg("cmp");
|
|
cmd.arg("-l");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.assert()
|
|
.code(predicate::eq(1))
|
|
.failure()
|
|
.stderr(predicate::str::is_empty())
|
|
.stdout(predicate::eq("1 141 142\n2 142 143\n3 143 144\n"));
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.env("LC_ALL", "C");
|
|
cmd.arg("cmp");
|
|
cmd.arg("-l");
|
|
cmd.arg("-b");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.assert()
|
|
.code(predicate::eq(1))
|
|
.failure()
|
|
.stderr(predicate::str::is_empty())
|
|
.stdout(predicate::eq(
|
|
"1 141 a 142 b\n2 142 b 143 c\n3 143 c 144 d\n",
|
|
));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn cmp_newline_difference() -> Result<(), Box<dyn std::error::Error>> {
|
|
let tmp_dir = tempdir()?;
|
|
|
|
let a_path = tmp_dir.path().join("a");
|
|
let mut a = File::create(&a_path).unwrap();
|
|
a.write_all(b"abc\ndefg").unwrap();
|
|
|
|
let b_path = tmp_dir.path().join("b");
|
|
let mut b = File::create(&b_path).unwrap();
|
|
b.write_all(b"abc\ndef\ng").unwrap();
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.env("LC_ALL", "C");
|
|
cmd.arg("cmp");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.assert()
|
|
.code(predicate::eq(1))
|
|
.failure()
|
|
.stderr(predicate::str::is_empty())
|
|
.stdout(predicate::str::ends_with(" differ: char 8, line 2\n"));
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.env("LC_ALL", "C");
|
|
cmd.arg("cmp");
|
|
cmd.arg("-b");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.assert()
|
|
.code(predicate::eq(1))
|
|
.failure()
|
|
.stderr(predicate::str::is_empty())
|
|
.stdout(predicate::str::ends_with(
|
|
" differ: byte 8, line 2 is 147 g 12 ^J\n",
|
|
));
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.env("LC_ALL", "C");
|
|
cmd.arg("cmp");
|
|
cmd.arg("-l");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.assert()
|
|
.code(predicate::eq(1))
|
|
.failure()
|
|
.stdout(predicate::str::starts_with("8 147 12\n"))
|
|
.stderr(predicate::str::contains(" EOF on"))
|
|
.stderr(predicate::str::ends_with(" after byte 8\n"));
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.env("LC_ALL", "C");
|
|
cmd.arg("cmp");
|
|
cmd.arg("-b");
|
|
cmd.arg("-l");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.assert()
|
|
.code(predicate::eq(1))
|
|
.failure()
|
|
.stdout(predicate::str::starts_with("8 147 g 12 ^J\n"))
|
|
.stderr(predicate::str::contains(" EOF on"))
|
|
.stderr(predicate::str::ends_with(" after byte 8\n"));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn cmp_max_bytes() -> Result<(), Box<dyn std::error::Error>> {
|
|
let tmp_dir = tempdir()?;
|
|
|
|
let a_path = tmp_dir.path().join("a");
|
|
let mut a = File::create(&a_path).unwrap();
|
|
a.write_all(b"abc efg ijkl\n").unwrap();
|
|
|
|
let b_path = tmp_dir.path().join("b");
|
|
let mut b = File::create(&b_path).unwrap();
|
|
b.write_all(b"abcdefghijkl\n").unwrap();
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("cmp");
|
|
cmd.arg("-l");
|
|
cmd.arg("-b");
|
|
cmd.arg("-n");
|
|
cmd.arg("3");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.assert()
|
|
.code(predicate::eq(0))
|
|
.success()
|
|
.stderr(predicate::str::is_empty())
|
|
.stdout(predicate::str::is_empty());
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("cmp");
|
|
cmd.arg("-l");
|
|
cmd.arg("-b");
|
|
cmd.arg("-n");
|
|
cmd.arg("4");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.assert()
|
|
.code(predicate::eq(1))
|
|
.failure()
|
|
.stderr(predicate::str::is_empty())
|
|
.stdout(predicate::eq("4 40 144 d\n"));
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("cmp");
|
|
cmd.arg("-l");
|
|
cmd.arg("-b");
|
|
cmd.arg("-n");
|
|
cmd.arg("13");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.assert()
|
|
.code(predicate::eq(1))
|
|
.failure()
|
|
.stderr(predicate::str::is_empty())
|
|
.stdout(predicate::eq(" 4 40 144 d\n 8 40 150 h\n"));
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn cmp_skip_args_parsing() -> Result<(), Box<dyn std::error::Error>> {
|
|
let tmp_dir = tempdir()?;
|
|
|
|
let a_path = tmp_dir.path().join("a");
|
|
let mut a = File::create(&a_path).unwrap();
|
|
a.write_all(b"---abc\n").unwrap();
|
|
|
|
let b_path = tmp_dir.path().join("b");
|
|
let mut b = File::create(&b_path).unwrap();
|
|
b.write_all(b"###abc\n").unwrap();
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.env("LC_ALL", "C");
|
|
cmd.arg("cmp");
|
|
cmd.arg("-i");
|
|
cmd.arg("3");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.assert()
|
|
.code(predicate::eq(0))
|
|
.success()
|
|
.stderr(predicate::str::is_empty())
|
|
.stdout(predicate::str::is_empty());
|
|
|
|
// Positional skips should be ignored
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.env("LC_ALL", "C");
|
|
cmd.arg("cmp");
|
|
cmd.arg("-i");
|
|
cmd.arg("3");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.arg("1").arg("1");
|
|
cmd.assert()
|
|
.code(predicate::eq(0))
|
|
.success()
|
|
.stderr(predicate::str::is_empty())
|
|
.stdout(predicate::str::is_empty());
|
|
|
|
// Single positional argument should only affect first file.
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.env("LC_ALL", "C");
|
|
cmd.arg("cmp");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.arg("3");
|
|
cmd.assert()
|
|
.code(predicate::eq(1))
|
|
.failure()
|
|
.stderr(predicate::str::is_empty())
|
|
.stdout(predicate::str::ends_with(" differ: char 1, line 1\n"));
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.env("LC_ALL", "C");
|
|
cmd.arg("cmp");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.arg("3");
|
|
cmd.arg("3");
|
|
cmd.assert()
|
|
.code(predicate::eq(0))
|
|
.success()
|
|
.stderr(predicate::str::is_empty())
|
|
.stdout(predicate::str::is_empty());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn cmp_skip_suffix_parsing() -> Result<(), Box<dyn std::error::Error>> {
|
|
let tmp_dir = tempdir()?;
|
|
|
|
let a_path = tmp_dir.path().join("a");
|
|
let mut a = File::create(&a_path).unwrap();
|
|
writeln!(a, "{}c", "a".repeat(1024)).unwrap();
|
|
a.flush().unwrap();
|
|
|
|
let b_path = tmp_dir.path().join("b");
|
|
let mut b = File::create(&b_path).unwrap();
|
|
writeln!(b, "{}c", "b".repeat(1024)).unwrap();
|
|
b.flush().unwrap();
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("cmp");
|
|
cmd.arg("--ignore-initial=1K");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.assert()
|
|
.code(predicate::eq(0))
|
|
.success()
|
|
.stderr(predicate::str::is_empty())
|
|
.stdout(predicate::str::is_empty());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn cmp_skip() -> Result<(), Box<dyn std::error::Error>> {
|
|
let tmp_dir = tempdir()?;
|
|
|
|
let a_path = tmp_dir.path().join("a");
|
|
let mut a = File::create(&a_path).unwrap();
|
|
a.write_all(b"abc efg ijkl\n").unwrap();
|
|
|
|
let b_path = tmp_dir.path().join("b");
|
|
let mut b = File::create(&b_path).unwrap();
|
|
b.write_all(b"abcdefghijkl\n").unwrap();
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("cmp");
|
|
cmd.arg("-l");
|
|
cmd.arg("-b");
|
|
cmd.arg("-i");
|
|
cmd.arg("8");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.assert()
|
|
.code(predicate::eq(0))
|
|
.success()
|
|
.stderr(predicate::str::is_empty())
|
|
.stdout(predicate::str::is_empty());
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("cmp");
|
|
cmd.arg("-b");
|
|
cmd.arg("-i");
|
|
cmd.arg("4");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.assert()
|
|
.code(predicate::eq(1))
|
|
.failure()
|
|
.stderr(predicate::str::is_empty())
|
|
.stdout(predicate::str::ends_with(
|
|
" differ: byte 4, line 1 is 40 150 h\n",
|
|
));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn cmp_binary() -> Result<(), Box<dyn std::error::Error>> {
|
|
let tmp_dir = tempdir()?;
|
|
|
|
let mut bytes = vec![0, 15, 31, 32, 33, 40, 64, 126, 127, 128, 129, 200, 254, 255];
|
|
|
|
let a_path = tmp_dir.path().join("a");
|
|
let mut a = File::create(&a_path).unwrap();
|
|
a.write_all(&bytes).unwrap();
|
|
|
|
bytes.reverse();
|
|
|
|
let b_path = tmp_dir.path().join("b");
|
|
let mut b = File::create(&b_path).unwrap();
|
|
b.write_all(&bytes).unwrap();
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("cmp");
|
|
cmd.arg("-l");
|
|
cmd.arg("-b");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.assert()
|
|
.code(predicate::eq(1))
|
|
.failure()
|
|
.stdout(predicate::eq(concat!(
|
|
" 1 0 ^@ 377 M-^?\n",
|
|
" 2 17 ^O 376 M-~\n",
|
|
" 3 37 ^_ 310 M-H\n",
|
|
" 4 40 201 M-^A\n",
|
|
" 5 41 ! 200 M-^@\n",
|
|
" 6 50 ( 177 ^?\n",
|
|
" 7 100 @ 176 ~\n",
|
|
" 8 176 ~ 100 @\n",
|
|
" 9 177 ^? 50 (\n",
|
|
"10 200 M-^@ 41 !\n",
|
|
"11 201 M-^A 40 \n",
|
|
"12 310 M-H 37 ^_\n",
|
|
"13 376 M-~ 17 ^O\n",
|
|
"14 377 M-^? 0 ^@\n"
|
|
)));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(not(windows))]
|
|
fn cmp_fast_paths() -> Result<(), Box<dyn std::error::Error>> {
|
|
let tmp_dir = tempdir()?;
|
|
|
|
// This test mimics one found in the GNU cmp test suite. It is used for
|
|
// validating the /dev/null optimization.
|
|
let a_path = tmp_dir.path().join("a");
|
|
let a = File::create(&a_path).unwrap();
|
|
a.set_len(14 * 1024 * 1024 * 1024 * 1024).unwrap();
|
|
|
|
let b_path = tmp_dir.path().join("b");
|
|
let b = File::create(&b_path).unwrap();
|
|
b.set_len(15 * 1024 * 1024 * 1024 * 1024).unwrap();
|
|
|
|
let dev_null = OpenOptions::new().write(true).open("/dev/null").unwrap();
|
|
|
|
let mut child = std::process::Command::new(assert_cmd::cargo::cargo_bin!("diffutils"))
|
|
.arg("cmp")
|
|
.arg(&a_path)
|
|
.arg(&b_path)
|
|
.stdout(dev_null)
|
|
.spawn()
|
|
.unwrap();
|
|
|
|
// Bound the runtime to a very short time that still allows for some resource
|
|
// constraint to slow it down while also allowing very fast systems to exit as
|
|
// early as possible.
|
|
const MAX_TRIES: u8 = 50;
|
|
for tries in 0..=MAX_TRIES {
|
|
if tries == MAX_TRIES {
|
|
panic!("cmp took too long to run, /dev/null optimization probably not working")
|
|
}
|
|
match child.try_wait() {
|
|
Ok(Some(status)) => {
|
|
assert_eq!(status.code(), Some(1));
|
|
break;
|
|
}
|
|
Ok(None) => (),
|
|
Err(e) => panic!("{e:#?}"),
|
|
}
|
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
|
}
|
|
|
|
// Two stdins should be equal
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("cmp");
|
|
cmd.arg("-");
|
|
cmd.arg("-");
|
|
cmd.assert()
|
|
.code(predicate::eq(0))
|
|
.success()
|
|
.stdout(predicate::str::is_empty())
|
|
.stderr(predicate::str::is_empty());
|
|
|
|
// Files with longer than block size equal segments should still report
|
|
// the correct line number for the difference. Assumes 8KB block size (see
|
|
// https://github.com/rust-lang/rust/blob/master/library/std/src/sys_common/io.rs),
|
|
// create a 24KB equality.
|
|
let mut bytes = " ".repeat(4095);
|
|
bytes.push('\n');
|
|
bytes.push_str(&" ".repeat(4096));
|
|
|
|
let bytes = bytes.repeat(3);
|
|
let bytes = bytes.as_bytes();
|
|
|
|
let a_path = tmp_dir.path().join("a");
|
|
let mut a = File::create(&a_path).unwrap();
|
|
a.write_all(bytes).unwrap();
|
|
a.write_all(b"A").unwrap();
|
|
|
|
let b_path = tmp_dir.path().join("b");
|
|
let mut b = File::create(&b_path).unwrap();
|
|
b.write_all(bytes).unwrap();
|
|
b.write_all(b"B").unwrap();
|
|
|
|
let mut cmd = cargo_bin_cmd!("diffutils");
|
|
cmd.arg("cmp");
|
|
cmd.arg(&a_path).arg(&b_path);
|
|
cmd.env("LC_ALL", "en_US");
|
|
cmd.assert()
|
|
.code(predicate::eq(1))
|
|
.failure()
|
|
.stdout(predicate::str::ends_with(" differ: byte 24577, line 4\n"));
|
|
|
|
Ok(())
|
|
}
|
|
}
|