Make local edit randomized tests pass with locators

Antonio Scandurra created

Change summary

crates/text/src/tests.rs | 54 +++++++++++++++++++++++++----------------
crates/text/src/text.rs  | 20 +++------------
2 files changed, 37 insertions(+), 37 deletions(-)

Detailed changes

crates/text/src/tests.rs 🔗

@@ -51,7 +51,7 @@ fn test_random_edits(mut rng: StdRng) {
     );
 
     for _i in 0..operations {
-        let (old_ranges, new_text, _) = buffer.randomly_edit(&mut rng, 1);
+        let (old_ranges, new_text, _) = buffer.randomly_edit(&mut rng, 5);
         for old_range in old_ranges.iter().rev() {
             reference_string.replace_range(old_range.clone(), &new_text);
         }
@@ -78,26 +78,7 @@ fn test_random_edits(mut rng: StdRng) {
             TextSummary::from(&reference_string[range])
         );
 
-        // Ensure every fragment is ordered by locator in the fragment tree and corresponds
-        // to an insertion fragment in the insertions tree.
-        let mut prev_fragment_id = Locator::min();
-        for fragment in buffer.snapshot.fragments.items(&None) {
-            assert!(fragment.id > prev_fragment_id);
-            prev_fragment_id = fragment.id.clone();
-
-            let insertion_fragment = buffer
-                .snapshot
-                .insertions
-                .get(
-                    &InsertionFragmentKey {
-                        timestamp: fragment.insertion_timestamp,
-                        split_offset: fragment.insertion_offset,
-                    },
-                    &(),
-                )
-                .unwrap();
-            assert_eq!(insertion_fragment.fragment_id, fragment.id);
-        }
+        buffer.check_invariants();
 
         if rng.gen_bool(0.3) {
             buffer_versions.push((buffer.clone(), buffer.subscribe()));
@@ -639,6 +620,37 @@ struct Network<T: Clone, R: rand::Rng> {
     rng: R,
 }
 
+impl Buffer {
+    fn check_invariants(&self) {
+        // Ensure every fragment is ordered by locator in the fragment tree and corresponds
+        // to an insertion fragment in the insertions tree.
+        let mut prev_fragment_id = Locator::min();
+        for fragment in self.snapshot.fragments.items(&None) {
+            assert!(fragment.id > prev_fragment_id);
+            prev_fragment_id = fragment.id.clone();
+
+            let insertion_fragment = self
+                .snapshot
+                .insertions
+                .get(
+                    &InsertionFragmentKey {
+                        timestamp: fragment.insertion_timestamp,
+                        split_offset: fragment.insertion_offset,
+                    },
+                    &(),
+                )
+                .unwrap();
+            assert_eq!(insertion_fragment.fragment_id, fragment.id);
+        }
+
+        let insertions = self.snapshot.insertions.items(&());
+        assert_eq!(
+            HashSet::from_iter(insertions.iter().map(|i| &i.fragment_id)).len(),
+            insertions.len()
+        );
+    }
+}
+
 impl<T: Clone, R: rand::Rng> Network<T, R> {
     fn new(rng: R) -> Self {
         Network {

crates/text/src/text.rs 🔗

@@ -580,6 +580,7 @@ impl Buffer {
             new_text: None,
         };
         let mut new_insertions = Vec::new();
+        let mut insertion_offset = 0;
 
         let mut ranges = ranges
             .map(|range| range.start.to_offset(&*self)..range.end.to_offset(&*self))
@@ -626,12 +627,6 @@ impl Buffer {
                 let mut prefix = old_fragments.item().unwrap().clone();
                 prefix.len = range.start - fragment_start;
                 prefix.insertion_offset += fragment_start - old_fragments.start().visible;
-
-                // log::info!(
-                //     "pushing prefix between {:?} and {:?}",
-                //     new_fragments.summary().max_id,
-                //     prefix.id
-                // );
                 prefix.id = Locator::between(&new_fragments.summary().max_id, &prefix.id);
                 new_insertions.push(InsertionFragment::insert_new(&prefix));
                 new_ropes.push_fragment(&prefix, prefix.visible);
@@ -646,15 +641,6 @@ impl Buffer {
                     old: fragment_start..fragment_start,
                     new: new_start..new_start + new_text.len(),
                 });
-
-                // log::info!(
-                //     "pushing new fragment between {:?} and {:?}",
-                //     new_fragments.summary().max_id,
-                //     old_fragments
-                //         .item()
-                //         .map_or(&Locator::max(), |old_fragment| &old_fragment.id)
-                // );
-
                 let fragment = Fragment {
                     id: Locator::between(
                         &new_fragments.summary().max_id,
@@ -663,7 +649,7 @@ impl Buffer {
                             .map_or(&Locator::max(), |old_fragment| &old_fragment.id),
                     ),
                     insertion_timestamp: timestamp,
-                    insertion_offset: 0,
+                    insertion_offset,
                     len: new_text.len(),
                     deletions: Default::default(),
                     max_undos: Default::default(),
@@ -672,6 +658,7 @@ impl Buffer {
                 new_insertions.push(InsertionFragment::insert_new(&fragment));
                 new_ropes.push_str(new_text);
                 new_fragments.push(fragment, &None);
+                insertion_offset += new_text.len();
             }
 
             // Advance through every fragment that intersects this range, marking the intersecting
@@ -683,6 +670,7 @@ impl Buffer {
                 let intersection_end = cmp::min(range.end, fragment_end);
                 if fragment.visible {
                     intersection.len = intersection_end - fragment_start;
+                    intersection.insertion_offset += fragment_start - old_fragments.start().visible;
                     intersection.id =
                         Locator::between(&new_fragments.summary().max_id, &intersection.id);
                     intersection.deletions.insert(timestamp.local());