Files
diffutils/src/context_diff.rs
T

795 lines
34 KiB
Rust
Raw Normal View History

2024-01-22 21:49:07 +01:00
// 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.
2021-02-23 22:17:58 -07:00
use std::collections::VecDeque;
use std::io::Write;
2024-01-22 18:41:01 +01:00
2024-03-04 21:41:27 +01:00
use crate::utils::do_write_line;
2021-02-23 22:17:58 -07:00
#[derive(Debug, PartialEq)]
pub enum DiffLine {
Context(Vec<u8>),
2021-03-09 16:23:46 -07:00
Change(Vec<u8>),
Add(Vec<u8>),
2021-02-23 22:17:58 -07:00
}
#[derive(Debug, PartialEq)]
struct Mismatch {
2021-03-09 16:23:46 -07:00
pub line_number_expected: usize,
pub line_number_actual: usize,
pub expected: Vec<DiffLine>,
pub actual: Vec<DiffLine>,
pub expected_missing_nl: bool,
pub actual_missing_nl: bool,
pub expected_all_context: bool,
pub actual_all_context: bool,
2021-02-23 22:17:58 -07:00
}
impl Mismatch {
2021-03-09 16:23:46 -07:00
fn new(line_number_expected: usize, line_number_actual: usize) -> Mismatch {
2021-02-23 22:17:58 -07:00
Mismatch {
2021-02-24 00:23:38 -07:00
line_number_expected,
line_number_actual,
2021-03-09 16:23:46 -07:00
expected: Vec::new(),
actual: Vec::new(),
expected_missing_nl: false,
actual_missing_nl: false,
expected_all_context: false,
actual_all_context: false,
2021-02-23 22:17:58 -07:00
}
}
}
// Produces a diff between the expected output and actual output.
fn make_diff(
expected: &[u8],
actual: &[u8],
context_size: usize,
stop_early: bool,
) -> Vec<Mismatch> {
2021-02-24 00:23:38 -07:00
let mut line_number_expected = 1;
let mut line_number_actual = 1;
2021-02-23 22:17:58 -07:00
let mut context_queue: VecDeque<&[u8]> = VecDeque::with_capacity(context_size);
let mut lines_since_mismatch = context_size + 1;
let mut results = Vec::new();
let mut mismatch = Mismatch::new(0, 0);
let mut expected_lines: Vec<&[u8]> = expected.split(|&c| c == b'\n').collect();
let mut actual_lines: Vec<&[u8]> = actual.split(|&c| c == b'\n').collect();
2021-02-24 00:13:41 -07:00
debug_assert_eq!(b"".split(|&c| c == b'\n').count(), 1);
// ^ means that underflow here is impossible
2021-03-09 16:23:46 -07:00
let expected_lines_count = expected_lines.len() - 1;
let actual_lines_count = actual_lines.len() - 1;
2021-02-23 22:17:58 -07:00
if expected_lines.last() == Some(&&b""[..]) {
expected_lines.pop();
}
if actual_lines.last() == Some(&&b""[..]) {
actual_lines.pop();
}
2021-03-09 16:23:46 -07:00
// Rust only allows allocations to grow to isize::MAX, and this is bigger than that.
let mut expected_lines_change_idx: usize = !0;
2021-02-23 22:17:58 -07:00
for result in diff::slice(&expected_lines, &actual_lines) {
match result {
diff::Result::Left(str) => {
2021-03-09 16:23:46 -07:00
if lines_since_mismatch > context_size && lines_since_mismatch > 0 {
2021-02-23 22:17:58 -07:00
results.push(mismatch);
mismatch = Mismatch::new(
2021-03-09 16:23:46 -07:00
line_number_expected - context_queue.len(),
line_number_actual - context_queue.len(),
2021-02-23 22:17:58 -07:00
);
}
while let Some(line) = context_queue.pop_front() {
2021-03-09 16:23:46 -07:00
mismatch.expected.push(DiffLine::Context(line.to_vec()));
mismatch.actual.push(DiffLine::Context(line.to_vec()));
2021-02-23 22:17:58 -07:00
}
2021-03-09 16:23:46 -07:00
expected_lines_change_idx = mismatch.expected.len();
mismatch.expected.push(DiffLine::Add(str.to_vec()));
if line_number_expected > expected_lines_count {
mismatch.expected_missing_nl = true;
2021-02-23 22:17:58 -07:00
}
2021-02-24 00:23:38 -07:00
line_number_expected += 1;
2021-02-23 22:17:58 -07:00
lines_since_mismatch = 0;
}
diff::Result::Right(str) => {
2021-03-09 16:23:46 -07:00
if lines_since_mismatch > context_size && lines_since_mismatch > 0 {
2021-02-23 22:17:58 -07:00
results.push(mismatch);
mismatch = Mismatch::new(
2021-03-09 16:23:46 -07:00
line_number_expected - context_queue.len(),
line_number_actual - context_queue.len(),
2021-02-23 22:17:58 -07:00
);
2021-03-09 16:23:46 -07:00
expected_lines_change_idx = !0;
2021-02-23 22:17:58 -07:00
}
while let Some(line) = context_queue.pop_front() {
2021-03-09 16:23:46 -07:00
mismatch.expected.push(DiffLine::Context(line.to_vec()));
mismatch.actual.push(DiffLine::Context(line.to_vec()));
2021-02-23 22:17:58 -07:00
}
2021-03-09 16:23:46 -07:00
if let Some(DiffLine::Add(content)) =
mismatch.expected.get_mut(expected_lines_change_idx)
{
2024-01-22 18:41:01 +01:00
let content = std::mem::take(content);
2021-03-09 16:23:46 -07:00
mismatch.expected[expected_lines_change_idx] = DiffLine::Change(content);
expected_lines_change_idx = expected_lines_change_idx.wrapping_sub(1); // if 0, becomes !0
mismatch.actual.push(DiffLine::Change(str.to_vec()));
} else {
mismatch.actual.push(DiffLine::Add(str.to_vec()));
}
2021-02-24 00:23:38 -07:00
if line_number_actual > actual_lines_count {
2021-03-09 16:23:46 -07:00
mismatch.actual_missing_nl = true;
2021-02-23 22:17:58 -07:00
}
2021-02-24 00:23:38 -07:00
line_number_actual += 1;
2021-02-23 22:17:58 -07:00
lines_since_mismatch = 0;
}
diff::Result::Both(str, _) => {
2021-03-09 16:23:46 -07:00
expected_lines_change_idx = !0;
2021-02-24 23:05:00 -07:00
// if one of them is missing a newline and the other isn't, then they don't actually match
2021-02-24 00:23:38 -07:00
if (line_number_actual > actual_lines_count)
2021-02-24 23:05:00 -07:00
&& (line_number_expected > expected_lines_count)
2021-02-23 22:17:58 -07:00
{
2021-02-24 23:05:00 -07:00
if context_queue.len() < context_size {
while let Some(line) = context_queue.pop_front() {
2021-03-09 16:23:46 -07:00
mismatch.expected.push(DiffLine::Context(line.to_vec()));
mismatch.actual.push(DiffLine::Context(line.to_vec()));
2021-02-24 23:05:00 -07:00
}
if lines_since_mismatch < context_size {
2021-03-09 16:23:46 -07:00
mismatch.expected.push(DiffLine::Context(str.to_vec()));
mismatch.actual.push(DiffLine::Context(str.to_vec()));
mismatch.expected_missing_nl = true;
mismatch.actual_missing_nl = true;
2021-02-24 23:05:00 -07:00
}
}
lines_since_mismatch = 0;
} else if line_number_actual > actual_lines_count {
if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
2021-02-23 22:17:58 -07:00
results.push(mismatch);
mismatch = Mismatch::new(
2021-03-09 16:23:46 -07:00
line_number_expected - context_queue.len(),
line_number_actual - context_queue.len(),
2021-02-23 22:17:58 -07:00
);
}
while let Some(line) = context_queue.pop_front() {
2021-03-09 16:23:46 -07:00
mismatch.expected.push(DiffLine::Context(line.to_vec()));
mismatch.actual.push(DiffLine::Context(line.to_vec()));
2021-02-23 22:17:58 -07:00
}
2021-03-09 16:23:46 -07:00
mismatch.expected.push(DiffLine::Change(str.to_vec()));
mismatch.actual.push(DiffLine::Change(str.to_vec()));
mismatch.actual_missing_nl = true;
2021-02-23 22:17:58 -07:00
lines_since_mismatch = 0;
2021-02-24 23:05:00 -07:00
} else if line_number_expected > expected_lines_count {
if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
results.push(mismatch);
mismatch = Mismatch::new(
2021-03-09 16:23:46 -07:00
line_number_expected - context_queue.len(),
line_number_actual - context_queue.len(),
2021-02-24 23:05:00 -07:00
);
2021-02-23 22:17:58 -07:00
}
2021-02-24 23:05:00 -07:00
while let Some(line) = context_queue.pop_front() {
2021-03-09 16:23:46 -07:00
mismatch.expected.push(DiffLine::Context(line.to_vec()));
mismatch.actual.push(DiffLine::Context(line.to_vec()));
2021-02-24 23:05:00 -07:00
}
2021-03-09 16:23:46 -07:00
mismatch.expected.push(DiffLine::Change(str.to_vec()));
mismatch.expected_missing_nl = true;
mismatch.actual.push(DiffLine::Change(str.to_vec()));
2021-02-24 23:05:00 -07:00
lines_since_mismatch = 0;
2021-02-23 22:17:58 -07:00
} else {
2021-02-25 00:48:54 -07:00
debug_assert!(context_queue.len() <= context_size);
2021-02-23 22:17:58 -07:00
if context_queue.len() >= context_size {
let _ = context_queue.pop_front();
}
if lines_since_mismatch < context_size {
2021-03-09 16:23:46 -07:00
mismatch.expected.push(DiffLine::Context(str.to_vec()));
mismatch.actual.push(DiffLine::Context(str.to_vec()));
2021-02-23 22:17:58 -07:00
} else if context_size > 0 {
context_queue.push_back(str);
}
lines_since_mismatch += 1;
}
2021-02-24 00:23:38 -07:00
line_number_expected += 1;
line_number_actual += 1;
2021-02-23 22:17:58 -07:00
}
}
if stop_early && !results.is_empty() {
// Optimization: stop analyzing the files as soon as there are any differences
return results;
}
2021-02-23 22:17:58 -07:00
}
results.push(mismatch);
results.remove(0);
2024-01-22 18:41:01 +01:00
if results.is_empty() && expected_lines_count != actual_lines_count {
2021-03-09 16:23:46 -07:00
let mut mismatch = Mismatch::new(expected_lines.len(), actual_lines.len());
2021-02-23 22:17:58 -07:00
// empty diff and only expected lines has a missing line at end
2021-03-09 16:23:46 -07:00
if expected_lines_count != expected_lines.len() {
mismatch.expected.push(DiffLine::Change(
2021-02-24 00:23:38 -07:00
expected_lines
.pop()
.expect("can't be empty; produced by split()")
.to_vec(),
));
2021-03-09 16:23:46 -07:00
mismatch.expected_missing_nl = true;
mismatch.actual.push(DiffLine::Change(
2021-02-24 00:13:41 -07:00
actual_lines
.pop()
2021-02-24 00:23:38 -07:00
.expect("can't be empty; produced by split()")
2021-02-24 00:13:41 -07:00
.to_vec(),
));
2021-02-23 22:17:58 -07:00
results.push(mismatch);
2021-03-09 16:23:46 -07:00
} else if actual_lines_count != actual_lines.len() {
mismatch.expected.push(DiffLine::Change(
2021-02-24 00:13:41 -07:00
expected_lines
.pop()
2021-02-24 00:23:38 -07:00
.expect("can't be empty; produced by split()")
.to_vec(),
));
2021-03-09 16:23:46 -07:00
mismatch.actual.push(DiffLine::Change(
2021-02-24 00:23:38 -07:00
actual_lines
.pop()
.expect("can't be empty; produced by split()")
2021-02-24 00:13:41 -07:00
.to_vec(),
));
2021-03-09 16:23:46 -07:00
mismatch.actual_missing_nl = true;
2021-02-23 22:17:58 -07:00
results.push(mismatch);
}
}
2021-03-09 16:23:46 -07:00
// hunks with pure context lines get truncated to empty
for mismatch in &mut results {
2024-01-22 18:41:01 +01:00
if !mismatch
2021-03-09 16:23:46 -07:00
.expected
2024-01-22 18:41:33 +01:00
.iter()
.any(|x| !matches!(&x, DiffLine::Context(_)))
2021-03-09 16:23:46 -07:00
{
mismatch.expected_all_context = true;
}
2024-01-22 18:41:01 +01:00
if !mismatch
2021-03-09 16:23:46 -07:00
.actual
2024-01-22 18:41:33 +01:00
.iter()
.any(|x| !matches!(&x, DiffLine::Context(_)))
2021-03-09 16:23:46 -07:00
{
mismatch.actual_all_context = true;
}
}
2021-02-23 22:17:58 -07:00
results
}
2024-01-23 11:42:12 +01:00
#[must_use]
#[allow(clippy::too_many_arguments)]
2021-02-23 22:17:58 -07:00
pub fn diff(
expected: &[u8],
expected_filename: &str,
actual: &[u8],
actual_filename: &str,
context_size: usize,
stop_early: bool,
2024-03-04 21:41:27 +01:00
expand_tabs: bool,
2024-03-05 18:52:04 +01:00
tabsize: usize,
2021-02-23 22:17:58 -07:00
) -> Vec<u8> {
2024-01-23 11:42:12 +01:00
let mut output = format!("*** {expected_filename}\t\n--- {actual_filename}\t\n").into_bytes();
let diff_results = make_diff(expected, actual, context_size, stop_early);
2024-01-22 18:41:01 +01:00
if diff_results.is_empty() {
2021-02-23 22:17:58 -07:00
return Vec::new();
}
if stop_early {
return output;
}
2021-02-23 22:17:58 -07:00
for result in diff_results {
2021-02-24 23:05:00 -07:00
let mut line_number_expected = result.line_number_expected;
let mut line_number_actual = result.line_number_actual;
2021-03-09 16:23:46 -07:00
let mut expected_count = result.expected.len();
let mut actual_count = result.actual.len();
2021-02-25 00:48:54 -07:00
if expected_count == 0 {
2021-03-09 16:23:46 -07:00
line_number_expected -= 1;
expected_count = 1;
2021-02-25 00:48:54 -07:00
}
if actual_count == 0 {
2021-03-09 16:23:46 -07:00
line_number_actual -= 1;
actual_count = 1;
2021-02-25 00:48:54 -07:00
}
2021-03-09 16:23:46 -07:00
let end_line_number_expected = expected_count + line_number_expected - 1;
let end_line_number_actual = actual_count + line_number_actual - 1;
let exp_start = if end_line_number_expected == line_number_expected {
String::new()
} else {
2024-01-23 11:42:12 +01:00
format!("{line_number_expected},")
};
2021-03-09 16:23:46 -07:00
let act_start = if end_line_number_actual == line_number_actual {
String::new()
} else {
2024-01-23 11:42:12 +01:00
format!("{line_number_actual},")
};
2021-02-23 22:17:58 -07:00
writeln!(
output,
2024-01-23 11:42:12 +01:00
"***************\n*** {exp_start}{end_line_number_expected} ****"
2021-02-23 22:17:58 -07:00
)
2021-02-24 00:13:41 -07:00
.expect("write to Vec is infallible");
2021-03-09 16:23:46 -07:00
if !result.expected_all_context {
for line in result.expected {
match line {
DiffLine::Context(e) => {
write!(output, " ").expect("write to Vec is infallible");
2024-03-05 18:52:04 +01:00
do_write_line(&mut output, &e, expand_tabs, tabsize)
2024-03-04 21:41:27 +01:00
.expect("write to Vec is infallible");
2021-03-09 16:23:46 -07:00
writeln!(output).unwrap();
}
DiffLine::Change(e) => {
write!(output, "! ").expect("write to Vec is infallible");
2024-03-05 18:52:04 +01:00
do_write_line(&mut output, &e, expand_tabs, tabsize)
2024-03-04 21:41:27 +01:00
.expect("write to Vec is infallible");
2021-03-09 16:23:46 -07:00
writeln!(output).unwrap();
}
DiffLine::Add(e) => {
write!(output, "- ").expect("write to Vec is infallible");
2024-03-05 18:52:04 +01:00
do_write_line(&mut output, &e, expand_tabs, tabsize)
2024-03-04 21:41:27 +01:00
.expect("write to Vec is infallible");
2021-03-09 16:23:46 -07:00
writeln!(output).unwrap();
}
2021-02-23 22:17:58 -07:00
}
2021-03-09 16:23:46 -07:00
}
if result.expected_missing_nl {
writeln!(output, r"\ No newline at end of file")
.expect("write to Vec is infallible");
}
}
2024-01-23 11:42:12 +01:00
writeln!(output, "--- {act_start}{end_line_number_actual} ----")
2021-03-09 16:23:46 -07:00
.expect("write to Vec is infallible");
if !result.actual_all_context {
for line in result.actual {
match line {
DiffLine::Context(e) => {
write!(output, " ").expect("write to Vec is infallible");
2024-03-05 18:52:04 +01:00
do_write_line(&mut output, &e, expand_tabs, tabsize)
2024-03-04 21:41:27 +01:00
.expect("write to Vec is infallible");
2021-03-09 16:23:46 -07:00
writeln!(output).unwrap();
}
DiffLine::Change(e) => {
write!(output, "! ").expect("write to Vec is infallible");
2024-03-05 18:52:04 +01:00
do_write_line(&mut output, &e, expand_tabs, tabsize)
2024-03-04 21:41:27 +01:00
.expect("write to Vec is infallible");
2021-03-09 16:23:46 -07:00
writeln!(output).unwrap();
}
DiffLine::Add(e) => {
write!(output, "+ ").expect("write to Vec is infallible");
2024-03-05 18:52:04 +01:00
do_write_line(&mut output, &e, expand_tabs, tabsize)
2024-03-04 21:41:27 +01:00
.expect("write to Vec is infallible");
2021-03-09 16:23:46 -07:00
writeln!(output).unwrap();
}
2021-02-23 22:17:58 -07:00
}
}
2021-03-09 16:23:46 -07:00
if result.actual_missing_nl {
writeln!(output, r"\ No newline at end of file")
.expect("write to Vec is infallible");
}
2021-02-23 22:17:58 -07:00
}
}
output
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_permutations() {
// test all possible six-line files.
let target = "target/context-diff/";
let _ = std::fs::create_dir(target);
for &a in &[0, 1, 2] {
for &b in &[0, 1, 2] {
for &c in &[0, 1, 2] {
for &d in &[0, 1, 2] {
for &e in &[0, 1, 2] {
for &f in &[0, 1, 2] {
use std::fs::{self, File};
use std::io::Write;
use std::process::Command;
let mut alef = Vec::new();
let mut bet = Vec::new();
alef.write_all(if a == 0 { b"a\n" } else { b"b\n" })
.unwrap();
if a != 2 {
bet.write_all(b"b\n").unwrap();
}
alef.write_all(if b == 0 { b"c\n" } else { b"d\n" })
.unwrap();
if b != 2 {
bet.write_all(b"d\n").unwrap();
}
alef.write_all(if c == 0 { b"e\n" } else { b"f\n" })
.unwrap();
if c != 2 {
bet.write_all(b"f\n").unwrap();
}
alef.write_all(if d == 0 { b"g\n" } else { b"h\n" })
.unwrap();
if d != 2 {
bet.write_all(b"h\n").unwrap();
}
alef.write_all(if e == 0 { b"i\n" } else { b"j\n" })
.unwrap();
if e != 2 {
bet.write_all(b"j\n").unwrap();
}
alef.write_all(if f == 0 { b"k\n" } else { b"l\n" })
.unwrap();
if f != 2 {
bet.write_all(b"l\n").unwrap();
}
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff(
&alef,
"a/alef",
&bet,
&format!("{target}/alef"),
2,
false,
2024-03-04 21:41:27 +01:00
false,
2024-03-05 18:52:04 +01:00
8,
);
2024-01-23 11:42:12 +01:00
File::create(&format!("{target}/ab.diff"))
.unwrap()
.write_all(&diff)
.unwrap();
2024-01-23 11:42:12 +01:00
let mut fa = File::create(&format!("{target}/alef")).unwrap();
fa.write_all(&alef[..]).unwrap();
2024-01-23 11:42:12 +01:00
let mut fb = File::create(&format!("{target}/bet")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("patch")
.arg("-p0")
.arg("--context")
2024-01-23 11:42:12 +01:00
.stdin(File::open(&format!("{target}/ab.diff")).unwrap())
.output()
.unwrap();
2024-01-23 11:42:12 +01:00
assert!(output.status.success(), "{:?}", output);
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
2024-01-23 11:42:12 +01:00
let alef = fs::read(&format!("{target}/alef")).unwrap();
assert_eq!(alef, bet);
2021-02-23 22:17:58 -07:00
}
}
}
}
}
}
}
#[test]
fn test_permutations_empty_lines() {
let target = "target/context-diff/";
// test all possible six-line files with missing newlines.
let _ = std::fs::create_dir(target);
for &a in &[0, 1, 2] {
for &b in &[0, 1, 2] {
for &c in &[0, 1, 2] {
for &d in &[0, 1, 2] {
for &e in &[0, 1, 2] {
for &f in &[0, 1, 2] {
use std::fs::{self, File};
use std::io::Write;
use std::process::Command;
let mut alef = Vec::new();
let mut bet = Vec::new();
alef.write_all(if a == 0 { b"\n" } else { b"b\n" }).unwrap();
if a != 2 {
bet.write_all(b"b\n").unwrap();
}
alef.write_all(if b == 0 { b"\n" } else { b"d\n" }).unwrap();
if b != 2 {
bet.write_all(b"d\n").unwrap();
}
alef.write_all(if c == 0 { b"\n" } else { b"f\n" }).unwrap();
if c != 2 {
bet.write_all(b"f\n").unwrap();
}
alef.write_all(if d == 0 { b"\n" } else { b"h\n" }).unwrap();
if d != 2 {
bet.write_all(b"h\n").unwrap();
}
alef.write_all(if e == 0 { b"\n" } else { b"j\n" }).unwrap();
if e != 2 {
bet.write_all(b"j\n").unwrap();
}
alef.write_all(if f == 0 { b"\n" } else { b"l\n" }).unwrap();
if f != 2 {
bet.write_all(b"l\n").unwrap();
}
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff(
&alef,
"a/alef_",
&bet,
&format!("{target}/alef_"),
2,
false,
2024-03-04 21:41:27 +01:00
false,
2024-03-05 18:52:04 +01:00
8,
);
2024-01-23 11:42:12 +01:00
File::create(&format!("{target}/ab_.diff"))
.unwrap()
.write_all(&diff)
.unwrap();
2024-01-23 11:42:12 +01:00
let mut fa = File::create(&format!("{target}/alef_")).unwrap();
fa.write_all(&alef[..]).unwrap();
2024-01-23 11:42:12 +01:00
let mut fb = File::create(&format!("{target}/bet_")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("patch")
.arg("-p0")
.arg("--context")
2024-01-23 11:42:12 +01:00
.stdin(File::open(&format!("{target}/ab_.diff")).unwrap())
.output()
.unwrap();
2024-01-23 11:42:12 +01:00
assert!(output.status.success(), "{:?}", output);
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
2024-01-23 11:42:12 +01:00
let alef = fs::read(&format!("{target}/alef_")).unwrap();
assert_eq!(alef, bet);
2021-02-23 22:17:58 -07:00
}
}
}
}
}
}
}
2021-02-23 22:22:21 -07:00
#[test]
fn test_permutations_missing_lines() {
let target = "target/context-diff/";
// test all possible six-line files.
let _ = std::fs::create_dir(target);
for &a in &[0, 1, 2] {
for &b in &[0, 1, 2] {
for &c in &[0, 1, 2] {
for &d in &[0, 1, 2] {
for &e in &[0, 1, 2] {
for &f in &[0, 1, 2] {
use std::fs::{self, File};
use std::io::Write;
use std::process::Command;
let mut alef = Vec::new();
let mut bet = Vec::new();
alef.write_all(if a == 0 { b"a\n" } else { b"" }).unwrap();
if a != 2 {
bet.write_all(b"b\n").unwrap();
}
alef.write_all(if b == 0 { b"c\n" } else { b"" }).unwrap();
if b != 2 {
bet.write_all(b"d\n").unwrap();
}
alef.write_all(if c == 0 { b"e\n" } else { b"" }).unwrap();
if c != 2 {
bet.write_all(b"f\n").unwrap();
}
alef.write_all(if d == 0 { b"g\n" } else { b"" }).unwrap();
if d != 2 {
bet.write_all(b"h\n").unwrap();
}
alef.write_all(if e == 0 { b"i\n" } else { b"" }).unwrap();
if e != 2 {
bet.write_all(b"j\n").unwrap();
}
alef.write_all(if f == 0 { b"k\n" } else { b"" }).unwrap();
if f != 2 {
bet.write_all(b"l\n").unwrap();
}
if alef.is_empty() && bet.is_empty() {
continue;
};
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff(
&alef,
"a/alefx",
&bet,
&format!("{target}/alefx"),
2,
false,
2024-03-04 21:41:27 +01:00
false,
2024-03-05 18:52:04 +01:00
8,
);
2024-01-23 11:42:12 +01:00
File::create(&format!("{target}/abx.diff"))
.unwrap()
.write_all(&diff)
.unwrap();
2024-01-23 11:42:12 +01:00
let mut fa = File::create(&format!("{target}/alefx")).unwrap();
fa.write_all(&alef[..]).unwrap();
2024-01-23 11:42:12 +01:00
let mut fb = File::create(&format!("{target}/betx")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("patch")
.arg("-p0")
.arg("--context")
2024-01-23 11:42:12 +01:00
.stdin(File::open(&format!("{target}/abx.diff")).unwrap())
.output()
.unwrap();
2024-01-23 11:42:12 +01:00
assert!(output.status.success(), "{:?}", output);
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
2024-01-23 11:42:12 +01:00
let alef = fs::read(&format!("{target}/alefx")).unwrap();
assert_eq!(alef, bet);
2021-02-23 22:22:21 -07:00
}
}
}
}
}
}
}
#[test]
fn test_permutations_reverse() {
let target = "target/context-diff/";
// test all possible six-line files.
let _ = std::fs::create_dir(target);
for &a in &[0, 1, 2] {
for &b in &[0, 1, 2] {
for &c in &[0, 1, 2] {
for &d in &[0, 1, 2] {
for &e in &[0, 1, 2] {
for &f in &[0, 1, 2] {
use std::fs::{self, File};
use std::io::Write;
use std::process::Command;
let mut alef = Vec::new();
let mut bet = Vec::new();
alef.write_all(if a == 0 { b"a\n" } else { b"f\n" })
.unwrap();
if a != 2 {
bet.write_all(b"a\n").unwrap();
}
alef.write_all(if b == 0 { b"b\n" } else { b"e\n" })
.unwrap();
if b != 2 {
bet.write_all(b"b\n").unwrap();
}
alef.write_all(if c == 0 { b"c\n" } else { b"d\n" })
.unwrap();
if c != 2 {
bet.write_all(b"c\n").unwrap();
}
alef.write_all(if d == 0 { b"d\n" } else { b"c\n" })
.unwrap();
if d != 2 {
bet.write_all(b"d\n").unwrap();
}
alef.write_all(if e == 0 { b"e\n" } else { b"b\n" })
.unwrap();
if e != 2 {
bet.write_all(b"e\n").unwrap();
}
alef.write_all(if f == 0 { b"f\n" } else { b"a\n" })
.unwrap();
if f != 2 {
bet.write_all(b"f\n").unwrap();
}
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff(
&alef,
"a/alefr",
&bet,
&format!("{target}/alefr"),
2,
false,
2024-03-04 21:41:27 +01:00
false,
2024-03-05 18:52:04 +01:00
8,
);
2024-01-23 11:42:12 +01:00
File::create(&format!("{target}/abr.diff"))
.unwrap()
.write_all(&diff)
.unwrap();
2024-01-23 11:42:12 +01:00
let mut fa = File::create(&format!("{target}/alefr")).unwrap();
fa.write_all(&alef[..]).unwrap();
2024-01-23 11:42:12 +01:00
let mut fb = File::create(&format!("{target}/betr")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("patch")
.arg("-p0")
.arg("--context")
2024-01-23 11:42:12 +01:00
.stdin(File::open(&format!("{target}/abr.diff")).unwrap())
.output()
.unwrap();
2024-01-23 11:42:12 +01:00
assert!(output.status.success(), "{:?}", output);
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
2024-01-23 11:42:12 +01:00
let alef = fs::read(&format!("{target}/alefr")).unwrap();
assert_eq!(alef, bet);
2021-02-23 22:22:21 -07:00
}
}
}
}
}
}
}
#[test]
fn test_stop_early() {
let from_filename = "foo";
2024-03-24 14:05:44 +01:00
let from = ["a", "b", "c", ""].join("\n");
let to_filename = "bar";
2024-03-24 14:05:44 +01:00
let to = ["a", "d", "c", ""].join("\n");
let context_size: usize = 3;
let diff_full = diff(
from.as_bytes(),
from_filename,
to.as_bytes(),
to_filename,
context_size,
false,
2024-03-04 21:41:27 +01:00
false,
2024-03-05 18:52:04 +01:00
8,
);
2024-03-24 14:05:44 +01:00
let expected_full = [
"*** foo\t",
"--- bar\t",
"***************",
"*** 1,3 ****",
" a",
"! b",
" c",
"--- 1,3 ----",
" a",
"! d",
" c",
"",
]
.join("\n");
assert_eq!(diff_full, expected_full.as_bytes());
let diff_brief = diff(
from.as_bytes(),
from_filename,
to.as_bytes(),
to_filename,
context_size,
true,
2024-03-04 21:41:27 +01:00
false,
2024-03-05 18:52:04 +01:00
8,
);
2024-03-24 14:05:44 +01:00
let expected_brief = ["*** foo\t", "--- bar\t", ""].join("\n");
assert_eq!(diff_brief, expected_brief.as_bytes());
let nodiff_full = diff(
from.as_bytes(),
from_filename,
from.as_bytes(),
to_filename,
context_size,
false,
2024-03-04 21:41:27 +01:00
false,
2024-03-05 18:52:04 +01:00
8,
);
assert!(nodiff_full.is_empty());
let nodiff_brief = diff(
from.as_bytes(),
from_filename,
from.as_bytes(),
to_filename,
context_size,
true,
2024-03-04 21:41:27 +01:00
false,
2024-03-05 18:52:04 +01:00
8,
);
assert!(nodiff_brief.is_empty());
}
2021-02-23 22:22:21 -07:00
}