use std::mem::swap; use expect_test::expect;// use git::bstr::BStr;// use git_repository as git; use gix_imara_diff::BasicLineDiffPrinter;use gix_imara_diff::InternedInput;use gix_imara_diff::sources::words;use gix_imara_diff::{Algorithm, Diff, UnifiedDiffConfig}; const ALL_ALGORITHMS: [Algorithm; 2] = [Algorithm::Histogram, Algorithm::Myers]; mod fuzzed { use std::time::{Duration, Instant}; use arbitrary::Arbitrary; use gix_imara_diff::{Algorithm, Diff, InternedInput}; #[derive(Debug, Arbitrary)] struct ComprehensiveDiffInput<'a> { before: &'a [u8], before_str: &'a str, after: &'a [u8], after_str: &'a str, } fn run_comprehensive_diff_fuzz_case( ComprehensiveDiffInput { before, before_str, after, after_str, }: ComprehensiveDiffInput<'_>, ) { let input = InternedInput::new(before, after); for algorithm in [Algorithm::Histogram, Algorithm::Myers, Algorithm::MyersMinimal] { let mut diff = Diff::compute(algorithm, &input); let _ = diff.count_additions(); let _ = diff.count_removals(); for hunk in diff.hunks() { let _ = hunk.is_pure_insertion(); let _ = hunk.is_pure_removal(); let _ = hunk.invert(); } diff.postprocess_no_heuristic(&input); diff.postprocess_lines(&input); } let input = InternedInput::new(before_str, after_str); let mut word_input = InternedInput::default(); let mut word_diff = Diff::default(); let diff = Diff::compute(Algorithm::Myers, &input); for hunk in diff.hunks() { hunk.latin_word_diff(&input, &mut word_input, &mut word_diff); } } #[test] fn timeout_regression() { let input = ComprehensiveDiffInput::arbitrary(&mut arbitrary::Unstructured::new(include_bytes!( "../fixtures/clusterfuzz-testcase-minimized-gix-imara-diff-comprehensive_diff-6497314075377664" ))) .expect("testcase matches the historical fuzz target input layout"); let start = Instant::now(); run_comprehensive_diff_fuzz_case(input); let elapsed = start.elapsed(); let expected = Duration::from_secs(3); assert!( elapsed < expected, "clusterfuzz regression took {:?}, expected less than {:?}", elapsed, expected ); }} #[test]fn words_tokenizer() { let text = "Hello, imara!\n (foo-bar_baz)"; let tokens = words(text).collect::<Vec<_>>(); assert_eq!( tokens, vec![ "Hello", ",", " ", "imara", "!", "\n", " ", "(", "foo", "-", "bar_baz", ")" ] );} #[test]fn replace() { let before = r#"fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2; println!("hello world")}"#; let after = r#"const TEST: i32 = 0;fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2; println!("hello world"); println!("hello foo {TEST}");} "#; let input = InternedInput::new(before, after); for algorithm in ALL_ALGORITHMS { let mut diff = Diff::compute(algorithm, &input); diff.postprocess_lines(&input); expect![[r#" @@ -1,5 +1,8 @@ +const TEST: i32 = 0; fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2; - println!("hello world") + println!("hello world"); + println!("hello foo {TEST}"); } + "#]] .assert_eq( &diff .unified_diff( &BasicLineDiffPrinter(&input.interner), UnifiedDiffConfig::default(), &input, ) .to_string(), ); }} #[test]fn identical_files() { let file = r#"fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2;}"#; for algorithm in ALL_ALGORITHMS { let input = InternedInput::new(file, file); let mut diff = Diff::compute(algorithm, &input); diff.postprocess_lines(&input); assert_eq!( diff.unified_diff( &BasicLineDiffPrinter(&input.interner), UnifiedDiffConfig::default(), &input, ) .to_string(), "" ); }} #[test]fn simple_insert() { let before = r#"fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2;}"#; let after = r#"fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2; println("hello world")}"#; let mut input = InternedInput::new(before, after); for algorithm in ALL_ALGORITHMS { let mut diff = Diff::compute(algorithm, &input); diff.postprocess_lines(&input); expect![[r#" @@ -1,4 +1,5 @@ fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2; + println("hello world") } "#]] .assert_eq( &diff .unified_diff( &BasicLineDiffPrinter(&input.interner), UnifiedDiffConfig::default(), &input, ) .to_string(), ); swap(&mut input.before, &mut input.after); let mut diff = Diff::compute(algorithm, &input); diff.postprocess_lines(&input); expect![[r#" @@ -1,5 +1,4 @@ fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2; - println("hello world") } "#]] .assert_eq( &diff .unified_diff( &BasicLineDiffPrinter(&input.interner), UnifiedDiffConfig::default(), &input, ) .to_string(), ); swap(&mut input.before, &mut input.after); }} #[test]fn unified_diff_context_lines_near_input_start_and_end() { let before = r#"abcdefghi"#; let after = r#"abcdeditfghi"#; let input = InternedInput::new(before, after); for algorithm in ALL_ALGORITHMS { let mut diff = Diff::compute(algorithm, &input); diff.postprocess_lines(&input); expect![[r#" @@ -2,7 +2,7 @@ b c d -e +edit f g h "#]] .assert_eq( &diff .unified_diff( &BasicLineDiffPrinter(&input.interner), UnifiedDiffConfig::default(), &input, ) .to_string(), ); }} mod latin_word_diff { use crate::ALL_ALGORITHMS; use gix_imara_diff::sources::words; use gix_imara_diff::{Diff, InternedInput, Token}; use std::mem::swap; use std::ops::Range; #[test] fn pure_insertion_or_removal() { let before = r#"fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2;}"#; let after = r#"fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2; println("hello world")}"#; let mut input = InternedInput::new(before, after); for algorithm in ALL_ALGORITHMS { let mut diff_input = InternedInput::default(); let mut out = Diff::default(); let mut diff = Diff::compute(algorithm, &input); diff.postprocess_lines(&input); let mut hunks = diff.hunks(); let hunk = hunks.next().expect("missing first hunk"); hunk.latin_word_diff(&input, &mut diff_input, &mut out); hunks = out.hunks(); let first = hunks.next().expect("missing first inner hunk"); assert!(first.is_pure_insertion()); assert_eq!(first.before, 0..0); assert_eq!(first.after, 0..words(" println(\"hello world\")\n").count() as u32); assert_eq!(hunks.next(), None); assert_eq!(hunks.next(), None); assert_eq!( visualise(&diff_input, &first.after, &diff_input.after), " |println|(|\"|hello| |world|\"|)|\n" ); swap(&mut input.before, &mut input.after); let mut diff = Diff::compute(algorithm, &input); diff.postprocess_lines(&input); hunks = diff.hunks(); let hunk = hunks.next().expect("missing first hunk"); hunk.latin_word_diff(&input, &mut diff_input, &mut out); hunks = out.hunks(); let first = hunks.next().expect("missing first inner hunk"); assert!(first.is_pure_removal()); assert_eq!(first.before, 0..words(" println(\"hello world\")\n").count() as u32); assert_eq!(first.after, 0..0); assert_eq!(hunks.next(), None); assert_eq!(hunks.next(), None); assert_eq!( visualise(&diff_input, &first.before, &diff_input.before), " |println|(|\"|hello| |world|\"|)|\n" ); swap(&mut input.before, &mut input.after); } } #[test] fn modification() { let before = r#"fn foo() -> Bar { let mut foo = 2.0; foo *= 100 / 2;}"#; let after = r#"fn foo() -> Bar { let mut foo = 3.0 * 2.0; foo += 100 / 2;}"#; let mut input = InternedInput::new(before, after); for algorithm in ALL_ALGORITHMS { let mut diff_input = InternedInput::default(); let mut out = Diff::default(); let mut diff = Diff::compute(algorithm, &input); diff.postprocess_lines(&input); let mut hunks = diff.hunks(); let hunk = hunks.next().expect("missing first hunk"); hunk.latin_word_diff(&input, &mut diff_input, &mut out); hunks = out.hunks(); let first = hunks.next().expect("missing first inner hunk"); assert!(first.is_pure_insertion()); let off = words(" let mut foo = ").count() as u32; assert_eq!(first.before, off..off); let ins = words("3.0 * ").count() as u32; assert_eq!(first.after, off..ins + off); assert_eq!(visualise(&diff_input, &first.before, &diff_input.before), ""); assert_eq!(visualise(&diff_input, &first.after, &diff_input.after), "3|.|0| |*| "); let second = hunks.next().expect("missing second inner hunk"); let off = words( r#" let mut foo = 2.0; foo "#, ) .count() as u32; assert_eq!(second.before, off..1 + off); assert_eq!(second.after, ins + off..1 + ins + off); assert_eq!(visualise(&diff_input, &second.before, &diff_input.before), "*"); assert_eq!(visualise(&diff_input, &second.after, &diff_input.after), "+"); assert_eq!(hunks.next(), None); assert_eq!(hunks.next(), None); swap(&mut input.before, &mut input.after); let mut diff = Diff::compute(algorithm, &input); diff.postprocess_lines(&input); hunks = diff.hunks(); let hunk = hunks.next().expect("missing first hunk"); hunk.latin_word_diff(&input, &mut diff_input, &mut out); hunks = out.hunks(); let first = hunks.next().expect("missing first inner hunk"); assert!(first.is_pure_removal()); let off = words(" let mut foo = ").count() as u32; let rem = words("3.0 * ").count() as u32; assert_eq!(first.before, off..rem + off); assert_eq!(first.after, off..off); let second = hunks.next().expect("missing second inner hunk"); let off = words( r#" let mut foo = 2.0; foo "#, ) .count() as u32; assert_eq!(second.before, rem + off..1 + rem + off); assert_eq!(second.after, off..1 + off); assert_eq!(hunks.next(), None); assert_eq!(hunks.next(), None); swap(&mut input.before, &mut input.after); } } fn visualise(diff_input: &InternedInput<&str>, token_ids: &Range<u32>, tokens: &[Token]) -> String { token_ids .clone() .map(|id| { let id = id as usize; diff_input.interner[tokens[id]] }) .collect::<Vec<_>>() .join("|") }} #[test]#[cfg(not(miri))]fn hand_checked_unidiffs() { let before = r#"use crate::{ alpha::Alpha, beta::Beta, gamma::Gamma,}; use std::{ collections::{HashMap, HashSet}, path::Path,}; pub struct Engine { cache: HashMap<String, usize>, steps: Vec<&'static str>,} impl Engine { pub fn new() -> Self { Self { cache: HashMap::new(), steps: vec!["parse", "render"], } } pub fn update(&mut self, path: &Path) { let _ = path; self.steps.push("scan"); }} fn unchanged_one() { println!("one");} fn unchanged_two() { println!("two");} pub enum Error { InvalidPath, Unknown,} pub struct Layer { pub depth: usize,} impl Layer { pub fn parse(&self) -> Result<(), Error> { Ok(()) }}"#; let after = r#"use crate::{ alpha::Alpha, beta::Beta, gamma::Gamma,}; use std::{ collections::HashMap, mem::replace, path::Path,};Lines 1–500 of 669. Load more
use std::mem::swap; use expect_test::expect; // use git::bstr::BStr; // use git_repository as git; use gix_imara_diff::BasicLineDiffPrinter; use gix_imara_diff::InternedInput; use gix_imara_diff::sources::words; use gix_imara_diff::{Algorithm, Diff, UnifiedDiffConfig}; const ALL_ALGORITHMS: [Algorithm; 2] = [Algorithm::Histogram, Algorithm::Myers]; mod fuzzed { use std::time::{Duration, Instant}; use arbitrary::Arbitrary; use gix_imara_diff::{Algorithm, Diff, InternedInput}; #[derive(Debug, Arbitrary)] struct ComprehensiveDiffInput<'a> { before: &'a [u8], before_str: &'a str, after: &'a [u8], after_str: &'a str, } fn run_comprehensive_diff_fuzz_case( ComprehensiveDiffInput { before, before_str, after, after_str, }: ComprehensiveDiffInput<'_>, ) { let input = InternedInput::new(before, after); for algorithm in [Algorithm::Histogram, Algorithm::Myers, Algorithm::MyersMinimal] { let mut diff = Diff::compute(algorithm, &input); let _ = diff.count_additions(); let _ = diff.count_removals(); for hunk in diff.hunks() { let _ = hunk.is_pure_insertion(); let _ = hunk.is_pure_removal(); let _ = hunk.invert(); } diff.postprocess_no_heuristic(&input); diff.postprocess_lines(&input); } let input = InternedInput::new(before_str, after_str); let mut word_input = InternedInput::default(); let mut word_diff = Diff::default(); let diff = Diff::compute(Algorithm::Myers, &input); for hunk in diff.hunks() { hunk.latin_word_diff(&input, &mut word_input, &mut word_diff); } } #[test] fn timeout_regression() { let input = ComprehensiveDiffInput::arbitrary(&mut arbitrary::Unstructured::new(include_bytes!( "../fixtures/clusterfuzz-testcase-minimized-gix-imara-diff-comprehensive_diff-6497314075377664" ))) .expect("testcase matches the historical fuzz target input layout"); let start = Instant::now(); run_comprehensive_diff_fuzz_case(input); let elapsed = start.elapsed(); let expected = Duration::from_secs(3); assert!( elapsed < expected, "clusterfuzz regression took {:?}, expected less than {:?}", elapsed, expected ); } } #[test] fn words_tokenizer() { let text = "Hello, imara!\n (foo-bar_baz)"; let tokens = words(text).collect::<Vec<_>>(); assert_eq!( tokens, vec![ "Hello", ",", " ", "imara", "!", "\n", " ", "(", "foo", "-", "bar_baz", ")" ] ); } #[test] fn replace() { let before = r#"fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2; println!("hello world") } "#; let after = r#"const TEST: i32 = 0; fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2; println!("hello world"); println!("hello foo {TEST}"); } "#; let input = InternedInput::new(before, after); for algorithm in ALL_ALGORITHMS { let mut diff = Diff::compute(algorithm, &input); diff.postprocess_lines(&input); expect![[r#" @@ -1,5 +1,8 @@ +const TEST: i32 = 0; fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2; - println!("hello world") + println!("hello world"); + println!("hello foo {TEST}"); } + "#]] .assert_eq( &diff .unified_diff( &BasicLineDiffPrinter(&input.interner), UnifiedDiffConfig::default(), &input, ) .to_string(), ); } } #[test] fn identical_files() { let file = r#"fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2; }"#; for algorithm in ALL_ALGORITHMS { let input = InternedInput::new(file, file); let mut diff = Diff::compute(algorithm, &input); diff.postprocess_lines(&input); assert_eq!( diff.unified_diff( &BasicLineDiffPrinter(&input.interner), UnifiedDiffConfig::default(), &input, ) .to_string(), "" ); } } #[test] fn simple_insert() { let before = r#"fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2; }"#; let after = r#"fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2; println("hello world") }"#; let mut input = InternedInput::new(before, after); for algorithm in ALL_ALGORITHMS { let mut diff = Diff::compute(algorithm, &input); diff.postprocess_lines(&input); expect![[r#" @@ -1,4 +1,5 @@ fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2; + println("hello world") } "#]] .assert_eq( &diff .unified_diff( &BasicLineDiffPrinter(&input.interner), UnifiedDiffConfig::default(), &input, ) .to_string(), ); swap(&mut input.before, &mut input.after); let mut diff = Diff::compute(algorithm, &input); diff.postprocess_lines(&input); expect![[r#" @@ -1,5 +1,4 @@ fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2; - println("hello world") } "#]] .assert_eq( &diff .unified_diff( &BasicLineDiffPrinter(&input.interner), UnifiedDiffConfig::default(), &input, ) .to_string(), ); swap(&mut input.before, &mut input.after); } } #[test] fn unified_diff_context_lines_near_input_start_and_end() { let before = r#"a b c d e f g h i "#; let after = r#"a b c d edit f g h i "#; let input = InternedInput::new(before, after); for algorithm in ALL_ALGORITHMS { let mut diff = Diff::compute(algorithm, &input); diff.postprocess_lines(&input); expect![[r#" @@ -2,7 +2,7 @@ b c d -e +edit f g h "#]] .assert_eq( &diff .unified_diff( &BasicLineDiffPrinter(&input.interner), UnifiedDiffConfig::default(), &input, ) .to_string(), ); } } mod latin_word_diff { use crate::ALL_ALGORITHMS; use gix_imara_diff::sources::words; use gix_imara_diff::{Diff, InternedInput, Token}; use std::mem::swap; use std::ops::Range; #[test] fn pure_insertion_or_removal() { let before = r#"fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2; }"#; let after = r#"fn foo() -> Bar{ let mut foo = 2.0; foo *= 100 / 2; println("hello world") }"#; let mut input = InternedInput::new(before, after); for algorithm in ALL_ALGORITHMS { let mut diff_input = InternedInput::default(); let mut out = Diff::default(); let mut diff = Diff::compute(algorithm, &input); diff.postprocess_lines(&input); let mut hunks = diff.hunks(); let hunk = hunks.next().expect("missing first hunk"); hunk.latin_word_diff(&input, &mut diff_input, &mut out); hunks = out.hunks(); let first = hunks.next().expect("missing first inner hunk"); assert!(first.is_pure_insertion()); assert_eq!(first.before, 0..0); assert_eq!(first.after, 0..words(" println(\"hello world\")\n").count() as u32); assert_eq!(hunks.next(), None); assert_eq!(hunks.next(), None); assert_eq!( visualise(&diff_input, &first.after, &diff_input.after), " |println|(|\"|hello| |world|\"|)|\n" ); swap(&mut input.before, &mut input.after); let mut diff = Diff::compute(algorithm, &input); diff.postprocess_lines(&input); hunks = diff.hunks(); let hunk = hunks.next().expect("missing first hunk"); hunk.latin_word_diff(&input, &mut diff_input, &mut out); hunks = out.hunks(); let first = hunks.next().expect("missing first inner hunk"); assert!(first.is_pure_removal()); assert_eq!(first.before, 0..words(" println(\"hello world\")\n").count() as u32); assert_eq!(first.after, 0..0); assert_eq!(hunks.next(), None); assert_eq!(hunks.next(), None); assert_eq!( visualise(&diff_input, &first.before, &diff_input.before), " |println|(|\"|hello| |world|\"|)|\n" ); swap(&mut input.before, &mut input.after); } } #[test] fn modification() { let before = r#"fn foo() -> Bar { let mut foo = 2.0; foo *= 100 / 2; }"#; let after = r#"fn foo() -> Bar { let mut foo = 3.0 * 2.0; foo += 100 / 2; }"#; let mut input = InternedInput::new(before, after); for algorithm in ALL_ALGORITHMS { let mut diff_input = InternedInput::default(); let mut out = Diff::default(); let mut diff = Diff::compute(algorithm, &input); diff.postprocess_lines(&input); let mut hunks = diff.hunks(); let hunk = hunks.next().expect("missing first hunk"); hunk.latin_word_diff(&input, &mut diff_input, &mut out); hunks = out.hunks(); let first = hunks.next().expect("missing first inner hunk"); assert!(first.is_pure_insertion()); let off = words(" let mut foo = ").count() as u32; assert_eq!(first.before, off..off); let ins = words("3.0 * ").count() as u32; assert_eq!(first.after, off..ins + off); assert_eq!(visualise(&diff_input, &first.before, &diff_input.before), ""); assert_eq!(visualise(&diff_input, &first.after, &diff_input.after), "3|.|0| |*| "); let second = hunks.next().expect("missing second inner hunk"); let off = words( r#" let mut foo = 2.0; foo "#, ) .count() as u32; assert_eq!(second.before, off..1 + off); assert_eq!(second.after, ins + off..1 + ins + off); assert_eq!(visualise(&diff_input, &second.before, &diff_input.before), "*"); assert_eq!(visualise(&diff_input, &second.after, &diff_input.after), "+"); assert_eq!(hunks.next(), None); assert_eq!(hunks.next(), None); swap(&mut input.before, &mut input.after); let mut diff = Diff::compute(algorithm, &input); diff.postprocess_lines(&input); hunks = diff.hunks(); let hunk = hunks.next().expect("missing first hunk"); hunk.latin_word_diff(&input, &mut diff_input, &mut out); hunks = out.hunks(); let first = hunks.next().expect("missing first inner hunk"); assert!(first.is_pure_removal()); let off = words(" let mut foo = ").count() as u32; let rem = words("3.0 * ").count() as u32; assert_eq!(first.before, off..rem + off); assert_eq!(first.after, off..off); let second = hunks.next().expect("missing second inner hunk"); let off = words( r#" let mut foo = 2.0; foo "#, ) .count() as u32; assert_eq!(second.before, rem + off..1 + rem + off); assert_eq!(second.after, off..1 + off); assert_eq!(hunks.next(), None); assert_eq!(hunks.next(), None); swap(&mut input.before, &mut input.after); } } fn visualise(diff_input: &InternedInput<&str>, token_ids: &Range<u32>, tokens: &[Token]) -> String { token_ids .clone() .map(|id| { let id = id as usize; diff_input.interner[tokens[id]] }) .collect::<Vec<_>>() .join("|") } } #[test] #[cfg(not(miri))] fn hand_checked_unidiffs() { let before = r#"use crate::{ alpha::Alpha, beta::Beta, gamma::Gamma, }; use std::{ collections::{HashMap, HashSet}, path::Path, }; pub struct Engine { cache: HashMap<String, usize>, steps: Vec<&'static str>, } impl Engine { pub fn new() -> Self { Self { cache: HashMap::new(), steps: vec!["parse", "render"], } } pub fn update(&mut self, path: &Path) { let _ = path; self.steps.push("scan"); } } fn unchanged_one() { println!("one"); } fn unchanged_two() { println!("two"); } pub enum Error { InvalidPath, Unknown, } pub struct Layer { pub depth: usize, } impl Layer { pub fn parse(&self) -> Result<(), Error> { Ok(()) } } "#; let after = r#"use crate::{ alpha::Alpha, beta::Beta, gamma::Gamma, }; use std::{ collections::HashMap, mem::replace, path::Path, };