From ac684c3d1f5e0c3eb08aeeca8626d0290904b122 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Thu, 22 Feb 2024 23:01:12 +0100 Subject: [PATCH] Optimization: stop analyzing the files as soon as there are any differences --- src/context_diff.rs | 60 +++++++++++++++++++++++++++++++------- src/ed_diff.rs | 18 +++++++++--- src/main.rs | 6 ++-- src/normal_diff.rs | 26 +++++++++++------ src/unified_diff.rs | 70 ++++++++++++++++++++++++++++++++++++--------- 5 files changed, 142 insertions(+), 38 deletions(-) diff --git a/src/context_diff.rs b/src/context_diff.rs index 1c9d44f..04e1641 100644 --- a/src/context_diff.rs +++ b/src/context_diff.rs @@ -41,7 +41,12 @@ impl Mismatch { } // Produces a diff between the expected output and actual output. -fn make_diff(expected: &[u8], actual: &[u8], context_size: usize) -> Vec { +fn make_diff( + expected: &[u8], + actual: &[u8], + context_size: usize, + stop_early: bool, +) -> Vec { let mut line_number_expected = 1; let mut line_number_actual = 1; let mut context_queue: VecDeque<&[u8]> = VecDeque::with_capacity(context_size); @@ -191,6 +196,10 @@ fn make_diff(expected: &[u8], actual: &[u8], context_size: usize) -> Vec Vec { let mut output = format!("*** {expected_filename}\t\n--- {actual_filename}\t\n").into_bytes(); - let diff_results = make_diff(expected, actual, context_size); + let diff_results = make_diff(expected, actual, context_size, stop_early); if diff_results.is_empty() { return Vec::new(); - }; + } else if stop_early { + return output; + } for result in diff_results { let mut line_number_expected = result.line_number_expected; let mut line_number_actual = result.line_number_actual; @@ -404,8 +416,14 @@ mod tests { } // 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); + let diff = diff( + &alef, + "a/alef", + &bet, + &format!("{target}/alef"), + 2, + false, + ); File::create(&format!("{target}/ab.diff")) .unwrap() .write_all(&diff) @@ -477,8 +495,14 @@ mod tests { } // 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); + let diff = diff( + &alef, + "a/alef_", + &bet, + &format!("{target}/alef_"), + 2, + false, + ); File::create(&format!("{target}/ab_.diff")) .unwrap() .write_all(&diff) @@ -553,8 +577,14 @@ mod tests { }; // 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); + let diff = diff( + &alef, + "a/alefx", + &bet, + &format!("{target}/alefx"), + 2, + false, + ); File::create(&format!("{target}/abx.diff")) .unwrap() .write_all(&diff) @@ -632,8 +662,14 @@ mod tests { } // 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); + let diff = diff( + &alef, + "a/alefr", + &bet, + &format!("{target}/alefr"), + 2, + false, + ); File::create(&format!("{target}/abr.diff")) .unwrap() .write_all(&diff) @@ -662,4 +698,6 @@ mod tests { } } } + + // TODO: add tests for stop_early parameter } diff --git a/src/ed_diff.rs b/src/ed_diff.rs index 2f8c0dd..a4ab42a 100644 --- a/src/ed_diff.rs +++ b/src/ed_diff.rs @@ -42,7 +42,7 @@ impl Mismatch { } // Produces a diff between the expected output and actual output. -fn make_diff(expected: &[u8], actual: &[u8]) -> Result, DiffError> { +fn make_diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Result, DiffError> { let mut line_number_expected = 1; let mut line_number_actual = 1; let mut results = Vec::new(); @@ -94,6 +94,10 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Result, DiffError> } } } + if stop_early && !results.is_empty() { + // Optimization: stop analyzing the files as soon as there are any differences + return Ok(results); + } } if !mismatch.actual.is_empty() || !mismatch.expected.is_empty() { @@ -103,9 +107,13 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Result, DiffError> Ok(results) } -pub fn diff(expected: &[u8], actual: &[u8]) -> Result, DiffError> { +pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Result, DiffError> { let mut output = Vec::new(); - let diff_results = make_diff(expected, actual)?; + let diff_results = make_diff(expected, actual, stop_early)?; + if stop_early && !diff_results.is_empty() { + write!(&mut output, "\0").unwrap(); + return Ok(output); + } let mut lines_offset = 0; for result in diff_results { let line_number_expected: isize = result.line_number_expected as isize + lines_offset; @@ -151,7 +159,7 @@ mod tests { use super::*; use pretty_assertions::assert_eq; pub fn diff_w(expected: &[u8], actual: &[u8], filename: &str) -> Result, DiffError> { - let mut output = diff(expected, actual)?; + let mut output = diff(expected, actual, false)?; writeln!(&mut output, "w {filename}").unwrap(); Ok(output) } @@ -380,4 +388,6 @@ mod tests { } } } + + // TODO: add tests for stop_early parameter } diff --git a/src/main.rs b/src/main.rs index fcc3026..93c6cb9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -50,13 +50,14 @@ fn main() -> ExitCode { }; // run diff let result: Vec = match format { - Format::Normal => normal_diff::diff(&from_content, &to_content), + Format::Normal => normal_diff::diff(&from_content, &to_content, brief), Format::Unified => unified_diff::diff( &from_content, &from.to_string_lossy(), &to_content, &to.to_string_lossy(), context_count, + brief, ), Format::Context => context_diff::diff( &from_content, @@ -64,8 +65,9 @@ fn main() -> ExitCode { &to_content, &to.to_string_lossy(), context_count, + brief, ), - Format::Ed => ed_diff::diff(&from_content, &to_content).unwrap_or_else(|error| { + Format::Ed => ed_diff::diff(&from_content, &to_content, brief).unwrap_or_else(|error| { eprintln!("{error}"); exit(2); }), diff --git a/src/normal_diff.rs b/src/normal_diff.rs index fa9c059..5f21a78 100644 --- a/src/normal_diff.rs +++ b/src/normal_diff.rs @@ -29,7 +29,7 @@ impl Mismatch { } // Produces a diff between the expected output and actual output. -fn make_diff(expected: &[u8], actual: &[u8]) -> Vec { +fn make_diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Vec { let mut line_number_expected = 1; let mut line_number_actual = 1; let mut results = Vec::new(); @@ -100,6 +100,10 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Vec { } } } + if stop_early && !results.is_empty() { + // Optimization: stop analyzing the files as soon as there are any differences + return results; + } } if !mismatch.actual.is_empty() || !mismatch.expected.is_empty() { @@ -110,11 +114,15 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Vec { } #[must_use] -pub fn diff(expected: &[u8], actual: &[u8]) -> Vec { +pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Vec { // See https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Normal.html // for details on the syntax of the normal format. let mut output = Vec::new(); - let diff_results = make_diff(expected, actual); + let diff_results = make_diff(expected, actual, stop_early); + if stop_early && !diff_results.is_empty() { + write!(&mut output, "\0").unwrap(); + return output; + } for result in diff_results { let line_number_expected = result.line_number_expected; let line_number_actual = result.line_number_actual; @@ -212,7 +220,7 @@ mod tests { a.write_all(b"a\n").unwrap(); let mut b = Vec::new(); b.write_all(b"b\n").unwrap(); - let diff = diff(&a, &b); + let diff = diff(&a, &b, false); let expected = b"1c1\n< a\n---\n> b\n".to_vec(); assert_eq!(diff, expected); } @@ -265,7 +273,7 @@ mod tests { } // This test diff is intentionally reversed. // We want it to turn the alef into bet. - let diff = diff(&alef, &bet); + let diff = diff(&alef, &bet, false); File::create(&format!("{target}/ab.diff")) .unwrap() .write_all(&diff) @@ -357,7 +365,7 @@ mod tests { } // This test diff is intentionally reversed. // We want it to turn the alef into bet. - let diff = diff(&alef, &bet); + let diff = diff(&alef, &bet, false); File::create(&format!("{target}/abn.diff")) .unwrap() .write_all(&diff) @@ -431,7 +439,7 @@ mod tests { } // This test diff is intentionally reversed. // We want it to turn the alef into bet. - let diff = diff(&alef, &bet); + let diff = diff(&alef, &bet, false); File::create(&format!("{target}/ab_.diff")) .unwrap() .write_all(&diff) @@ -509,7 +517,7 @@ mod tests { } // This test diff is intentionally reversed. // We want it to turn the alef into bet. - let diff = diff(&alef, &bet); + let diff = diff(&alef, &bet, false); File::create(&format!("{target}/abr.diff")) .unwrap() .write_all(&diff) @@ -538,4 +546,6 @@ mod tests { } } } + + // TODO: add tests for stop_early parameter } diff --git a/src/unified_diff.rs b/src/unified_diff.rs index 2be092b..50af9fc 100644 --- a/src/unified_diff.rs +++ b/src/unified_diff.rs @@ -32,7 +32,12 @@ impl Mismatch { } // Produces a diff between the expected output and actual output. -fn make_diff(expected: &[u8], actual: &[u8], context_size: usize) -> Vec { +fn make_diff( + expected: &[u8], + actual: &[u8], + context_size: usize, + stop_early: bool, +) -> Vec { let mut line_number_expected = 1; let mut line_number_actual = 1; let mut context_queue: VecDeque<&[u8]> = VecDeque::with_capacity(context_size); @@ -180,6 +185,10 @@ fn make_diff(expected: &[u8], actual: &[u8], context_size: usize) -> Vec Vec { let mut output = format!("--- {expected_filename}\t\n+++ {actual_filename}\t\n").into_bytes(); - let diff_results = make_diff(expected, actual, context_size); + let diff_results = make_diff(expected, actual, context_size, stop_early); if diff_results.is_empty() { return Vec::new(); - }; + } else if stop_early { + return output; + } for result in diff_results { let mut line_number_expected = result.line_number_expected; let mut line_number_actual = result.line_number_actual; @@ -434,8 +446,14 @@ mod tests { } // 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); + let diff = diff( + &alef, + "a/alef", + &bet, + &format!("{target}/alef"), + 2, + false, + ); File::create(&format!("{target}/ab.diff")) .unwrap() .write_all(&diff) @@ -542,8 +560,14 @@ mod tests { } // This test diff is intentionally reversed. // We want it to turn the alef into bet. - let diff = - diff(&alef, "a/alefn", &bet, &format!("{target}/alefn"), 2); + let diff = diff( + &alef, + "a/alefn", + &bet, + &format!("{target}/alefn"), + 2, + false, + ); File::create(&format!("{target}/abn.diff")) .unwrap() .write_all(&diff) @@ -630,8 +654,14 @@ mod tests { } // 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); + let diff = diff( + &alef, + "a/alef_", + &bet, + &format!("{target}/alef_"), + 2, + false, + ); File::create(&format!("{target}/ab_.diff")) .unwrap() .write_all(&diff) @@ -703,8 +733,14 @@ mod tests { } // 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); + let diff = diff( + &alef, + "a/alefx", + &bet, + &format!("{target}/alefx"), + 2, + false, + ); File::create(&format!("{target}/abx.diff")) .unwrap() .write_all(&diff) @@ -781,8 +817,14 @@ mod tests { } // 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); + let diff = diff( + &alef, + "a/alefr", + &bet, + &format!("{target}/alefr"), + 2, + false, + ); File::create(&format!("{target}/abr.diff")) .unwrap() .write_all(&diff) @@ -810,4 +852,6 @@ mod tests { } } } + + // TODO: add tests for stop_early parameter }