From 0159019850f15fa58a1cccad19d4ec0ec1ed9ddb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Nov 2021 18:28:07 -0800 Subject: [PATCH] Simplify assertions in randomized patch test, fix some patch bugs --- crates/editor/src/display_map/patch.rs | 162 ++++++++++++++++++------- 1 file changed, 116 insertions(+), 46 deletions(-) diff --git a/crates/editor/src/display_map/patch.rs b/crates/editor/src/display_map/patch.rs index 8da560ea39..97af438d45 100644 --- a/crates/editor/src/display_map/patch.rs +++ b/crates/editor/src/display_map/patch.rs @@ -15,6 +15,8 @@ impl Patch { let mut intermediate_end = 0; for mut new_edit in new.0.iter().cloned() { + eprintln!("edit {:?}", new_edit); + let new_edit_delta = new_edit.new.len() as i32 - new_edit.old.len() as i32; if let Some(last_edit) = composed.last_mut() { @@ -25,6 +27,8 @@ impl Patch { intermediate_end = new_edit.old.end; } last_edit.new.end = (last_edit.new.end as i32 + new_edit_delta) as u32; + new_delta += new_edit_delta; + eprintln!(" merged {:?}", &composed); continue; } } @@ -44,6 +48,7 @@ impl Patch { new_edit.old.start = (new_edit.old.start as i32 - old_edit_delta) as u32; new_edit.old.end = (new_edit.old.end as i32 - old_edit_delta) as u32; composed.push(old_edit); + eprintln!(" pushed preceding {:?}", &composed); } else if old_edit.new.start <= intermediate_end { if old_edit.new.start < intermediate_start { new_edit.new.start -= intermediate_start - old_edit.new.start; @@ -54,6 +59,7 @@ impl Patch { new_edit.old.end += old_edit.new.end - intermediate_end; intermediate_end = old_edit.new.end; } + eprintln!(" expanded w/ intersecting {:?} - {:?}", old_edit, new_edit); new_edit.old.end = (new_edit.old.end as i32 - old_edit_delta) as u32; } else { break; @@ -65,9 +71,25 @@ impl Patch { new_delta += new_edit_delta; composed.push(new_edit); + eprintln!(" pushing {:?}", &composed); } while let Some(mut old_edit) = old_edits.next() { + let old_edit_delta = old_edit.new.len() as i32 - old_edit.old.len() as i32; + + if let Some(last_edit) = composed.last_mut() { + if intermediate_end >= old_edit.new.start { + if old_edit.new.end > intermediate_end { + last_edit.old.end += old_edit.new.end - intermediate_end; + last_edit.new.end += old_edit.new.end - intermediate_end; + intermediate_end = old_edit.new.end; + } + last_edit.old.end = (last_edit.old.end as i32 - old_edit_delta) as u32; + eprintln!(" merged {:?}", &composed); + continue; + } + } + old_edit.new.start = (old_edit.new.start as i32 + new_delta) as u32; old_edit.new.end = (old_edit.new.end as i32 + new_delta) as u32; composed.push(old_edit); @@ -255,71 +277,119 @@ mod tests { ); } - #[gpui::test(iterations = 1000, seed = 131)] + #[gpui::test] + fn test_two_new_edits_touching_one_old_edit() { + assert_patch_composition( + Patch(vec![ + Edit { + old: 2..3, + new: 2..4, + }, + Edit { + old: 7..7, + new: 8..11, + }, + ]), + Patch(vec![ + Edit { + old: 2..3, + new: 2..2, + }, + Edit { + old: 4..4, + new: 3..4, + }, + ]), + Patch(vec![ + Edit { + old: 2..3, + new: 2..4, + }, + Edit { + old: 7..7, + new: 8..11, + }, + ]), + ); + } + + #[gpui::test(iterations = 1000)] fn test_random(mut rng: StdRng) { let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) - .unwrap_or(3); + .unwrap_or(2); - let initial_chars = (0..rng.gen_range(0..=5)) + let initial_chars = (0..rng.gen_range(0..=10)) .map(|_| rng.gen_range(b'a'..=b'z') as char) .collect::>(); - let mut final_chars = initial_chars.clone(); - let mut patches = Vec::new(); - println!("initial chars: {:?}", initial_chars); - for _ in 0..operations { - let end = rng.gen_range(0..=final_chars.len()); - let start = rng.gen_range(0..=end); - let mut len = rng.gen_range(0..=3); - if start == end && len == 0 { - len += 1; - } - let new_chars = (0..len) - .map(|_| rng.gen_range(b'a'..=b'z') as char) - .collect::>(); - println!( - "editing {:?}: {:?}", - start..end, - new_chars.iter().collect::() - ); - let patch = Patch(vec![Edit { - old: start as u32..end as u32, - new: start as u32..start as u32 + new_chars.len() as u32, - }]); - if patches.is_empty() || rng.gen() { - println!("pushing singleton patch: {:?}", patch.0); - patches.push(patch); - } else { - let patch = patches.pop().unwrap().compose(&patch); - println!("composed patches: {:?}", patch.0); - patches.push(patch); + // Generate two sequential patches + let mut patches = Vec::new(); + let mut expected_chars = initial_chars.clone(); + for i in 0..2 { + println!("patch {}:", i); + + let mut delta = 0i32; + let mut last_edit_end = 0; + let mut edits = Vec::new(); + for _ in 0..operations { + if last_edit_end >= expected_chars.len() { + break; + } + + let end = rng.gen_range(last_edit_end..=expected_chars.len()); + let start = rng.gen_range(last_edit_end..=end); + let old_len = end - start; + + let mut new_len = rng.gen_range(0..=3); + if start == end && new_len == 0 { + new_len += 1; + } + + last_edit_end = start + new_len + 1; + + let new_chars = (0..new_len) + .map(|_| rng.gen_range(b'A'..=b'Z') as char) + .collect::>(); + println!( + " editing {:?}: {:?}", + start..end, + new_chars.iter().collect::() + ); + edits.push(Edit { + old: (start as i32 - delta) as u32..(end as i32 - delta) as u32, + new: start as u32..(start + new_len) as u32, + }); + expected_chars.splice(start..end, new_chars); + + delta += new_len as i32 - old_len as i32; } - final_chars.splice(start..end, new_chars); + + patches.push(Patch(edits)); } - println!("final chars: {:?}", final_chars); - println!("final patches: {:?}", patches); + println!("old patch: {:?}", &patches[0]); + println!("new patch: {:?}", &patches[1]); + println!("initial chars: {:?}", initial_chars); + println!("final chars: {:?}", expected_chars); - let mut composed = Patch::default(); - for patch in patches { - println!("composing patches {:?} and {:?}", composed, patch); - composed = composed.compose(&patch); - println!("composed {:?}", composed); - } - println!("composed edits: {:?}", composed); - let mut chars = initial_chars.clone(); + // Compose the patches, and verify that it has the same effect as applying the + // two patches separately. + let composed = patches[0].compose(&patches[1]); + println!("composed patch: {:?}", &composed); + + let mut actual_chars = initial_chars.clone(); for edit in composed.0 { - chars.splice( + actual_chars.splice( edit.new.start as usize..edit.new.start as usize + edit.old.len(), - final_chars[edit.new.start as usize..edit.new.end as usize] + expected_chars[edit.new.start as usize..edit.new.end as usize] .iter() .copied(), ); } - assert_eq!(chars, final_chars); + assert_eq!(actual_chars, expected_chars); } #[track_caller]