Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:32:01.127Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

tests.rs

1058 lines · 35.0 KB · rust
1use super::{network::Network, *};
2use clock::ReplicaId;
3use rand::prelude::*;
4use std::{
5    cmp::Ordering,
6    env,
7    iter::Iterator,
8    time::{Duration, Instant},
9};
10
11#[cfg(test)]
12#[ctor::ctor(unsafe)]
13fn init_logger() {
14    zlog::init_test();
15}
16
17#[test]
18fn test_edit() {
19    let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "abc");
20    assert_eq!(buffer.text(), "abc");
21    buffer.edit([(3..3, "def")]);
22    assert_eq!(buffer.text(), "abcdef");
23    buffer.edit([(0..0, "ghi")]);
24    assert_eq!(buffer.text(), "ghiabcdef");
25    buffer.edit([(5..5, "jkl")]);
26    assert_eq!(buffer.text(), "ghiabjklcdef");
27    buffer.edit([(6..7, "")]);
28    assert_eq!(buffer.text(), "ghiabjlcdef");
29    buffer.edit([(4..9, "mno")]);
30    assert_eq!(buffer.text(), "ghiamnoef");
31}
32
33#[test]
34fn test_point_for_row_and_column_from_external_source() {
35    let buffer = Buffer::new(
36        ReplicaId::LOCAL,
37        BufferId::new(1).unwrap(),
38        "aéøbcdef\nsecond",
39    );
40    let snapshot = buffer.snapshot();
41
42    assert_eq!(snapshot.point_from_external_input(0, 0), Point::new(0, 0));
43    assert_eq!(snapshot.point_from_external_input(0, 4), Point::new(0, 6));
44    assert_eq!(
45        snapshot.point_from_external_input(0, 100),
46        Point::new(0, 10)
47    );
48    assert_eq!(snapshot.point_from_external_input(1, 3), Point::new(1, 3));
49}
50
51#[gpui::test(iterations = 100)]
52fn test_random_edits(mut rng: StdRng) {
53    let operations = env::var("OPERATIONS")
54        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
55        .unwrap_or(10);
56
57    let reference_string_len = rng.random_range(0..3);
58    let mut reference_string = RandomCharIter::new(&mut rng)
59        .take(reference_string_len)
60        .collect::<String>();
61    let mut buffer = Buffer::new(
62        ReplicaId::LOCAL,
63        BufferId::new(1).unwrap(),
64        reference_string.clone(),
65    );
66    LineEnding::normalize(&mut reference_string);
67
68    buffer.set_group_interval(Duration::from_millis(rng.random_range(0..=200)));
69    let mut buffer_versions = Vec::new();
70    log::info!(
71        "buffer text {:?}, version: {:?}",
72        buffer.text(),
73        buffer.version()
74    );
75
76    for _i in 0..operations {
77        let (edits, _) = buffer.randomly_edit(&mut rng, 5);
78        for (old_range, new_text) in edits.iter().rev() {
79            reference_string.replace_range(old_range.clone(), new_text);
80        }
81
82        assert_eq!(buffer.text(), reference_string);
83        log::info!(
84            "buffer text {:?}, version: {:?}",
85            buffer.text(),
86            buffer.version()
87        );
88
89        if rng.random_bool(0.25) {
90            buffer.randomly_undo_redo(&mut rng);
91            reference_string = buffer.text();
92            log::info!(
93                "buffer text {:?}, version: {:?}",
94                buffer.text(),
95                buffer.version()
96            );
97        }
98
99        let range = buffer.random_byte_range(0, &mut rng);
100        assert_eq!(
101            buffer.text_summary_for_range::<TextSummary, _>(range.clone()),
102            TextSummary::from(&reference_string[range])
103        );
104
105        buffer.check_invariants();
106
107        if rng.random_bool(0.3) {
108            buffer_versions.push((buffer.clone(), buffer.subscribe()));
109        }
110    }
111
112    for (old_buffer, subscription) in buffer_versions {
113        let edits = buffer
114            .edits_since::<usize>(&old_buffer.version)
115            .collect::<Vec<_>>();
116
117        log::info!(
118            "applying edits since version {:?} to old text: {:?}: {:?}",
119            old_buffer.version(),
120            old_buffer.text(),
121            edits,
122        );
123
124        let mut text = old_buffer.visible_text.clone();
125        for edit in edits {
126            let new_text: String = buffer.text_for_range(edit.new.clone()).collect();
127            text.replace(edit.new.start..edit.new.start + edit.old.len(), &new_text);
128        }
129        assert_eq!(text.to_string(), buffer.text());
130
131        assert_eq!(
132            buffer.rope_for_version(old_buffer.version()).to_string(),
133            old_buffer.text()
134        );
135
136        for _ in 0..5 {
137            let end_ix =
138                old_buffer.clip_offset(rng.random_range(0..=old_buffer.len()), Bias::Right);
139            let start_ix = old_buffer.clip_offset(rng.random_range(0..=end_ix), Bias::Left);
140            let range = old_buffer.anchor_before(start_ix)..old_buffer.anchor_after(end_ix);
141            let mut old_text = old_buffer.text_for_range(range.clone()).collect::<String>();
142            let edits = buffer
143                .edits_since_in_range::<usize>(&old_buffer.version, range.clone())
144                .collect::<Vec<_>>();
145            log::info!(
146                "applying edits since version {:?} to old text in range {:?}: {:?}: {:?}",
147                old_buffer.version(),
148                start_ix..end_ix,
149                old_text,
150                edits,
151            );
152
153            let new_text = buffer.text_for_range(range).collect::<String>();
154            for edit in edits {
155                old_text.replace_range(
156                    edit.new.start..edit.new.start + edit.old_len(),
157                    &new_text[edit.new],
158                );
159            }
160            assert_eq!(old_text, new_text);
161        }
162
163        assert_eq!(
164            buffer.has_edits_since(&old_buffer.version),
165            buffer
166                .edits_since::<usize>(&old_buffer.version)
167                .next()
168                .is_some(),
169        );
170
171        let subscription_edits = subscription.consume();
172        log::info!(
173            "applying subscription edits since version {:?} to old text: {:?}: {:?}",
174            old_buffer.version(),
175            old_buffer.text(),
176            subscription_edits,
177        );
178
179        let mut text = old_buffer.visible_text.clone();
180        for edit in subscription_edits.into_inner() {
181            let new_text: String = buffer.text_for_range(edit.new.clone()).collect();
182            text.replace(edit.new.start..edit.new.start + edit.old.len(), &new_text);
183        }
184        assert_eq!(text.to_string(), buffer.text());
185    }
186}
187
188#[test]
189fn test_line_endings() {
190    assert_eq!(LineEnding::detect(&"🍐✅\n".repeat(1000)), LineEnding::Unix);
191    assert_eq!(LineEnding::detect(&"abcd\n".repeat(1000)), LineEnding::Unix);
192    assert_eq!(
193        LineEnding::detect(&"🍐✅\r\n".repeat(1000)),
194        LineEnding::Windows
195    );
196    assert_eq!(
197        LineEnding::detect(&"abcd\r\n".repeat(1000)),
198        LineEnding::Windows
199    );
200
201    let mut buffer = Buffer::new(
202        ReplicaId::LOCAL,
203        BufferId::new(1).unwrap(),
204        "one\r\ntwo\rthree",
205    );
206    assert_eq!(buffer.text(), "one\ntwo\nthree");
207    assert_eq!(buffer.line_ending(), LineEnding::Windows);
208    buffer.check_invariants();
209
210    buffer.edit([(buffer.len()..buffer.len(), "\r\nfour")]);
211    buffer.edit([(0..0, "zero\r\n")]);
212    assert_eq!(buffer.text(), "zero\none\ntwo\nthree\nfour");
213    assert_eq!(buffer.line_ending(), LineEnding::Windows);
214    buffer.check_invariants();
215}
216
217#[test]
218fn test_line_len() {
219    let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "");
220    buffer.edit([(0..0, "abcd\nefg\nhij")]);
221    buffer.edit([(12..12, "kl\nmno")]);
222    buffer.edit([(18..18, "\npqrs\n")]);
223    buffer.edit([(18..21, "\nPQ")]);
224
225    assert_eq!(buffer.line_len(0), 4);
226    assert_eq!(buffer.line_len(1), 3);
227    assert_eq!(buffer.line_len(2), 5);
228    assert_eq!(buffer.line_len(3), 3);
229    assert_eq!(buffer.line_len(4), 4);
230    assert_eq!(buffer.line_len(5), 0);
231}
232
233#[test]
234fn test_common_prefix_at_position() {
235    let text = "a = str; b = δα";
236    let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), text);
237
238    let offset1 = offset_after(text, "str");
239    let offset2 = offset_after(text, "δα");
240
241    // the preceding word is a prefix of the suggestion
242    assert_eq!(
243        buffer.common_prefix_at(offset1, "string"),
244        range_of(text, "str"),
245    );
246    // a suffix of the preceding word is a prefix of the suggestion
247    assert_eq!(
248        buffer.common_prefix_at(offset1, "tree"),
249        range_of(text, "tr"),
250    );
251    // the preceding word is a substring of the suggestion, but not a prefix
252    assert_eq!(
253        buffer.common_prefix_at(offset1, "astro"),
254        empty_range_after(text, "str"),
255    );
256
257    // prefix matching is case insensitive.
258    assert_eq!(
259        buffer.common_prefix_at(offset1, "Strαngε"),
260        range_of(text, "str"),
261    );
262    assert_eq!(
263        buffer.common_prefix_at(offset2, "ΔΑΜΝ"),
264        range_of(text, "δα"),
265    );
266
267    fn offset_after(text: &str, part: &str) -> usize {
268        text.find(part).unwrap() + part.len()
269    }
270
271    fn empty_range_after(text: &str, part: &str) -> Range<usize> {
272        let offset = offset_after(text, part);
273        offset..offset
274    }
275
276    fn range_of(text: &str, part: &str) -> Range<usize> {
277        let start = text.find(part).unwrap();
278        start..start + part.len()
279    }
280}
281
282#[test]
283fn test_text_summary_for_range() {
284    let buffer = Buffer::new(
285        ReplicaId::LOCAL,
286        BufferId::new(1).unwrap(),
287        "ab\nefg\nhklm\nnopqrs\ntuvwxyz",
288    );
289    assert_eq!(
290        buffer.text_summary_for_range::<TextSummary, _>(0..2),
291        TextSummary {
292            len: 2,
293            chars: 2,
294            len_utf16: OffsetUtf16(2),
295            lines: Point::new(0, 2),
296            first_line_chars: 2,
297            last_line_chars: 2,
298            last_line_len_utf16: 2,
299            longest_row: 0,
300            longest_row_chars: 2,
301        }
302    );
303    assert_eq!(
304        buffer.text_summary_for_range::<TextSummary, _>(1..3),
305        TextSummary {
306            len: 2,
307            chars: 2,
308            len_utf16: OffsetUtf16(2),
309            lines: Point::new(1, 0),
310            first_line_chars: 1,
311            last_line_chars: 0,
312            last_line_len_utf16: 0,
313            longest_row: 0,
314            longest_row_chars: 1,
315        }
316    );
317    assert_eq!(
318        buffer.text_summary_for_range::<TextSummary, _>(1..12),
319        TextSummary {
320            len: 11,
321            chars: 11,
322            len_utf16: OffsetUtf16(11),
323            lines: Point::new(3, 0),
324            first_line_chars: 1,
325            last_line_chars: 0,
326            last_line_len_utf16: 0,
327            longest_row: 2,
328            longest_row_chars: 4,
329        }
330    );
331    assert_eq!(
332        buffer.text_summary_for_range::<TextSummary, _>(0..20),
333        TextSummary {
334            len: 20,
335            chars: 20,
336            len_utf16: OffsetUtf16(20),
337            lines: Point::new(4, 1),
338            first_line_chars: 2,
339            last_line_chars: 1,
340            last_line_len_utf16: 1,
341            longest_row: 3,
342            longest_row_chars: 6,
343        }
344    );
345    assert_eq!(
346        buffer.text_summary_for_range::<TextSummary, _>(0..22),
347        TextSummary {
348            len: 22,
349            chars: 22,
350            len_utf16: OffsetUtf16(22),
351            lines: Point::new(4, 3),
352            first_line_chars: 2,
353            last_line_chars: 3,
354            last_line_len_utf16: 3,
355            longest_row: 3,
356            longest_row_chars: 6,
357        }
358    );
359    assert_eq!(
360        buffer.text_summary_for_range::<TextSummary, _>(7..22),
361        TextSummary {
362            len: 15,
363            chars: 15,
364            len_utf16: OffsetUtf16(15),
365            lines: Point::new(2, 3),
366            first_line_chars: 4,
367            last_line_chars: 3,
368            last_line_len_utf16: 3,
369            longest_row: 1,
370            longest_row_chars: 6,
371        }
372    );
373}
374
375#[test]
376fn test_chars_at() {
377    let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "");
378    buffer.edit([(0..0, "abcd\nefgh\nij")]);
379    buffer.edit([(12..12, "kl\nmno")]);
380    buffer.edit([(18..18, "\npqrs")]);
381    buffer.edit([(18..21, "\nPQ")]);
382
383    let chars = buffer.chars_at(Point::new(0, 0));
384    assert_eq!(chars.collect::<String>(), "abcd\nefgh\nijkl\nmno\nPQrs");
385
386    let chars = buffer.chars_at(Point::new(1, 0));
387    assert_eq!(chars.collect::<String>(), "efgh\nijkl\nmno\nPQrs");
388
389    let chars = buffer.chars_at(Point::new(2, 0));
390    assert_eq!(chars.collect::<String>(), "ijkl\nmno\nPQrs");
391
392    let chars = buffer.chars_at(Point::new(3, 0));
393    assert_eq!(chars.collect::<String>(), "mno\nPQrs");
394
395    let chars = buffer.chars_at(Point::new(4, 0));
396    assert_eq!(chars.collect::<String>(), "PQrs");
397
398    // Regression test:
399    let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "");
400    buffer.edit([(0..0, "[workspace]\nmembers = [\n    \"xray_core\",\n    \"xray_server\",\n    \"xray_cli\",\n    \"xray_wasm\",\n]\n")]);
401    buffer.edit([(60..60, "\n")]);
402
403    let chars = buffer.chars_at(Point::new(6, 0));
404    assert_eq!(chars.collect::<String>(), "    \"xray_wasm\",\n]\n");
405}
406
407#[test]
408fn test_anchors() {
409    let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "");
410    buffer.edit([(0..0, "abc")]);
411    let left_anchor = buffer.anchor_before(2);
412    let right_anchor = buffer.anchor_after(2);
413
414    buffer.edit([(1..1, "def\n")]);
415    assert_eq!(buffer.text(), "adef\nbc");
416    assert_eq!(left_anchor.to_offset(&buffer), 6);
417    assert_eq!(right_anchor.to_offset(&buffer), 6);
418    assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
419    assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 });
420
421    buffer.edit([(2..3, "")]);
422    assert_eq!(buffer.text(), "adf\nbc");
423    assert_eq!(left_anchor.to_offset(&buffer), 5);
424    assert_eq!(right_anchor.to_offset(&buffer), 5);
425    assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
426    assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 });
427
428    buffer.edit([(5..5, "ghi\n")]);
429    assert_eq!(buffer.text(), "adf\nbghi\nc");
430    assert_eq!(left_anchor.to_offset(&buffer), 5);
431    assert_eq!(right_anchor.to_offset(&buffer), 9);
432    assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
433    assert_eq!(right_anchor.to_point(&buffer), Point { row: 2, column: 0 });
434
435    buffer.edit([(7..9, "")]);
436    assert_eq!(buffer.text(), "adf\nbghc");
437    assert_eq!(left_anchor.to_offset(&buffer), 5);
438    assert_eq!(right_anchor.to_offset(&buffer), 7);
439    assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 },);
440    assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 3 });
441
442    // Ensure anchoring to a point is equivalent to anchoring to an offset.
443    assert_eq!(
444        buffer.anchor_before(Point { row: 0, column: 0 }),
445        buffer.anchor_before(0)
446    );
447    assert_eq!(
448        buffer.anchor_before(Point { row: 0, column: 1 }),
449        buffer.anchor_before(1)
450    );
451    assert_eq!(
452        buffer.anchor_before(Point { row: 0, column: 2 }),
453        buffer.anchor_before(2)
454    );
455    assert_eq!(
456        buffer.anchor_before(Point { row: 0, column: 3 }),
457        buffer.anchor_before(3)
458    );
459    assert_eq!(
460        buffer.anchor_before(Point { row: 1, column: 0 }),
461        buffer.anchor_before(4)
462    );
463    assert_eq!(
464        buffer.anchor_before(Point { row: 1, column: 1 }),
465        buffer.anchor_before(5)
466    );
467    assert_eq!(
468        buffer.anchor_before(Point { row: 1, column: 2 }),
469        buffer.anchor_before(6)
470    );
471    assert_eq!(
472        buffer.anchor_before(Point { row: 1, column: 3 }),
473        buffer.anchor_before(7)
474    );
475    assert_eq!(
476        buffer.anchor_before(Point { row: 1, column: 4 }),
477        buffer.anchor_before(8)
478    );
479
480    // Comparison between anchors.
481    let anchor_at_offset_0 = buffer.anchor_before(0);
482    let anchor_at_offset_1 = buffer.anchor_before(1);
483    let anchor_at_offset_2 = buffer.anchor_before(2);
484
485    assert_eq!(
486        anchor_at_offset_0.cmp(&anchor_at_offset_0, &buffer),
487        Ordering::Equal
488    );
489    assert_eq!(
490        anchor_at_offset_1.cmp(&anchor_at_offset_1, &buffer),
491        Ordering::Equal
492    );
493    assert_eq!(
494        anchor_at_offset_2.cmp(&anchor_at_offset_2, &buffer),
495        Ordering::Equal
496    );
497
498    assert_eq!(
499        anchor_at_offset_0.cmp(&anchor_at_offset_1, &buffer),
500        Ordering::Less
501    );
502    assert_eq!(
503        anchor_at_offset_1.cmp(&anchor_at_offset_2, &buffer),
504        Ordering::Less
505    );
506    assert_eq!(
507        anchor_at_offset_0.cmp(&anchor_at_offset_2, &buffer),
508        Ordering::Less
509    );
510
511    assert_eq!(
512        anchor_at_offset_1.cmp(&anchor_at_offset_0, &buffer),
513        Ordering::Greater
514    );
515    assert_eq!(
516        anchor_at_offset_2.cmp(&anchor_at_offset_1, &buffer),
517        Ordering::Greater
518    );
519    assert_eq!(
520        anchor_at_offset_2.cmp(&anchor_at_offset_0, &buffer),
521        Ordering::Greater
522    );
523}
524
525#[test]
526fn test_anchors_at_start_and_end() {
527    let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "");
528    let before_start_anchor = buffer.anchor_before(0);
529    let after_end_anchor = buffer.anchor_after(0);
530
531    buffer.edit([(0..0, "abc")]);
532    assert_eq!(buffer.text(), "abc");
533    assert_eq!(before_start_anchor.to_offset(&buffer), 0);
534    assert_eq!(after_end_anchor.to_offset(&buffer), 3);
535
536    let after_start_anchor = buffer.anchor_after(0);
537    let before_end_anchor = buffer.anchor_before(3);
538
539    buffer.edit([(3..3, "def")]);
540    buffer.edit([(0..0, "ghi")]);
541    assert_eq!(buffer.text(), "ghiabcdef");
542    assert_eq!(before_start_anchor.to_offset(&buffer), 0);
543    assert_eq!(after_start_anchor.to_offset(&buffer), 3);
544    assert_eq!(before_end_anchor.to_offset(&buffer), 6);
545    assert_eq!(after_end_anchor.to_offset(&buffer), 9);
546}
547
548#[test]
549fn test_undo_redo() {
550    let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "1234");
551    // Set group interval to zero so as to not group edits in the undo stack.
552    buffer.set_group_interval(Duration::from_secs(0));
553
554    buffer.edit([(1..1, "abx")]);
555    buffer.edit([(3..4, "yzef")]);
556    buffer.edit([(3..5, "cd")]);
557    assert_eq!(buffer.text(), "1abcdef234");
558
559    let entries = buffer.history.undo_stack.clone();
560    assert_eq!(entries.len(), 3);
561
562    buffer.undo_or_redo(entries[0].transaction.clone());
563    assert_eq!(buffer.text(), "1cdef234");
564    buffer.undo_or_redo(entries[0].transaction.clone());
565    assert_eq!(buffer.text(), "1abcdef234");
566
567    buffer.undo_or_redo(entries[1].transaction.clone());
568    assert_eq!(buffer.text(), "1abcdx234");
569    buffer.undo_or_redo(entries[2].transaction.clone());
570    assert_eq!(buffer.text(), "1abx234");
571    buffer.undo_or_redo(entries[1].transaction.clone());
572    assert_eq!(buffer.text(), "1abyzef234");
573    buffer.undo_or_redo(entries[2].transaction.clone());
574    assert_eq!(buffer.text(), "1abcdef234");
575
576    buffer.undo_or_redo(entries[2].transaction.clone());
577    assert_eq!(buffer.text(), "1abyzef234");
578    buffer.undo_or_redo(entries[0].transaction.clone());
579    assert_eq!(buffer.text(), "1yzef234");
580    buffer.undo_or_redo(entries[1].transaction.clone());
581    assert_eq!(buffer.text(), "1234");
582}
583
584#[test]
585fn test_history() {
586    let mut now = Instant::now();
587    let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "123456");
588    buffer.set_group_interval(Duration::from_millis(300));
589
590    let transaction_1 = buffer.start_transaction_at(now).unwrap();
591    buffer.edit([(2..4, "cd")]);
592    buffer.end_transaction_at(now);
593    assert_eq!(buffer.text(), "12cd56");
594
595    buffer.start_transaction_at(now);
596    buffer.edit([(4..5, "e")]);
597    buffer.end_transaction_at(now).unwrap();
598    assert_eq!(buffer.text(), "12cde6");
599
600    now += buffer.transaction_group_interval() + Duration::from_millis(1);
601    buffer.start_transaction_at(now);
602    buffer.edit([(0..1, "a")]);
603    buffer.edit([(1..1, "b")]);
604    buffer.end_transaction_at(now).unwrap();
605    assert_eq!(buffer.text(), "ab2cde6");
606
607    // Last transaction happened past the group interval, undo it on its own.
608    buffer.undo();
609    assert_eq!(buffer.text(), "12cde6");
610
611    // First two transactions happened within the group interval, undo them together.
612    buffer.undo();
613    assert_eq!(buffer.text(), "123456");
614
615    // Redo the first two transactions together.
616    buffer.redo();
617    assert_eq!(buffer.text(), "12cde6");
618
619    // Redo the last transaction on its own.
620    buffer.redo();
621    assert_eq!(buffer.text(), "ab2cde6");
622
623    buffer.start_transaction_at(now);
624    assert!(buffer.end_transaction_at(now).is_none());
625    buffer.undo();
626    assert_eq!(buffer.text(), "12cde6");
627
628    // Redo stack gets cleared after performing an edit.
629    buffer.start_transaction_at(now);
630    buffer.edit([(0..0, "X")]);
631    buffer.end_transaction_at(now);
632    assert_eq!(buffer.text(), "X12cde6");
633    buffer.redo();
634    assert_eq!(buffer.text(), "X12cde6");
635    buffer.undo();
636    assert_eq!(buffer.text(), "12cde6");
637    buffer.undo();
638    assert_eq!(buffer.text(), "123456");
639
640    // Transactions can be grouped manually.
641    buffer.redo();
642    buffer.redo();
643    assert_eq!(buffer.text(), "X12cde6");
644    buffer.group_until_transaction(transaction_1);
645    buffer.undo();
646    assert_eq!(buffer.text(), "123456");
647    buffer.redo();
648    assert_eq!(buffer.text(), "X12cde6");
649}
650
651#[test]
652fn test_finalize_last_transaction() {
653    let now = Instant::now();
654    let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "123456");
655    buffer.history.group_interval = Duration::from_millis(1);
656
657    buffer.start_transaction_at(now);
658    buffer.edit([(2..4, "cd")]);
659    buffer.end_transaction_at(now);
660    assert_eq!(buffer.text(), "12cd56");
661
662    buffer.finalize_last_transaction();
663    buffer.start_transaction_at(now);
664    buffer.edit([(4..5, "e")]);
665    buffer.end_transaction_at(now).unwrap();
666    assert_eq!(buffer.text(), "12cde6");
667
668    buffer.start_transaction_at(now);
669    buffer.edit([(0..1, "a")]);
670    buffer.edit([(1..1, "b")]);
671    buffer.end_transaction_at(now).unwrap();
672    assert_eq!(buffer.text(), "ab2cde6");
673
674    buffer.undo();
675    assert_eq!(buffer.text(), "12cd56");
676
677    buffer.undo();
678    assert_eq!(buffer.text(), "123456");
679
680    buffer.redo();
681    assert_eq!(buffer.text(), "12cd56");
682
683    buffer.redo();
684    assert_eq!(buffer.text(), "ab2cde6");
685}
686
687#[test]
688fn test_edited_ranges_for_transaction() {
689    let now = Instant::now();
690    let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "1234567");
691
692    buffer.start_transaction_at(now);
693    buffer.edit([(2..4, "cd")]);
694    buffer.edit([(6..6, "efg")]);
695    buffer.end_transaction_at(now);
696    assert_eq!(buffer.text(), "12cd56efg7");
697
698    let tx = buffer.finalize_last_transaction().unwrap().clone();
699    assert_eq!(
700        buffer
701            .edited_ranges_for_transaction::<usize>(&tx)
702            .collect::<Vec<_>>(),
703        [2..4, 6..9]
704    );
705
706    buffer.edit([(5..5, "hijk")]);
707    assert_eq!(buffer.text(), "12cd5hijk6efg7");
708    assert_eq!(
709        buffer
710            .edited_ranges_for_transaction::<usize>(&tx)
711            .collect::<Vec<_>>(),
712        [2..4, 10..13]
713    );
714
715    buffer.edit([(4..4, "l")]);
716    assert_eq!(buffer.text(), "12cdl5hijk6efg7");
717    assert_eq!(
718        buffer
719            .edited_ranges_for_transaction::<usize>(&tx)
720            .collect::<Vec<_>>(),
721        [2..4, 11..14]
722    );
723}
724
725#[test]
726fn test_concurrent_edits() {
727    let text = "abcdef";
728
729    let mut buffer1 = Buffer::new(ReplicaId::new(1), BufferId::new(1).unwrap(), text);
730    let mut buffer2 = Buffer::new(ReplicaId::new(2), BufferId::new(1).unwrap(), text);
731    let mut buffer3 = Buffer::new(ReplicaId::new(3), BufferId::new(1).unwrap(), text);
732
733    let buf1_op = buffer1.edit([(1..2, "12")]);
734    assert_eq!(buffer1.text(), "a12cdef");
735    let buf2_op = buffer2.edit([(3..4, "34")]);
736    assert_eq!(buffer2.text(), "abc34ef");
737    let buf3_op = buffer3.edit([(5..6, "56")]);
738    assert_eq!(buffer3.text(), "abcde56");
739
740    buffer1.apply_op(buf2_op.clone());
741    buffer1.apply_op(buf3_op.clone());
742    buffer2.apply_op(buf1_op.clone());
743    buffer2.apply_op(buf3_op);
744    buffer3.apply_op(buf1_op);
745    buffer3.apply_op(buf2_op);
746
747    assert_eq!(buffer1.text(), "a12c34e56");
748    assert_eq!(buffer2.text(), "a12c34e56");
749    assert_eq!(buffer3.text(), "a12c34e56");
750}
751
752// Regression test: applying a remote edit whose FullOffset range partially
753// overlaps a fragment that was already deleted (observed but not visible)
754// used to leave the fragment unsplit, causing the rope builder to read past
755// the end of the rope.
756#[test]
757fn test_edit_partially_intersecting_a_deleted_fragment() {
758    let mut buffer = Buffer::new(ReplicaId::new(1), BufferId::new(1).unwrap(), "abcdefgh");
759
760    // Delete "cde", creating a single deleted fragment at FullOffset 2..5.
761    // After this the fragment layout is:
762    //   "ab"(vis, FullOffset 0..2)  "cde"(del, 2..5)  "fgh"(vis, 5..8)
763    buffer.edit([(2..5, "")]);
764    assert_eq!(buffer.text(), "abfgh");
765
766    // Construct a synthetic remote edit whose version includes the deletion (so
767    // the "cde" fragment is observed + deleted → !was_visible) but whose
768    // FullOffset range only partially overlaps it. This state arises in
769    // production when concurrent edits cause different fragment splits on
770    // different replicas.
771    let synthetic_timestamp = clock::Lamport {
772        replica_id: ReplicaId::new(2),
773        value: 10,
774    };
775    let synthetic_edit = Operation::Edit(EditOperation {
776        timestamp: synthetic_timestamp,
777        version: buffer.version(),
778        // Range 1..4 partially overlaps the deleted "cde" (FullOffset 2..5):
779        // it covers "b" (1..2) and only "cd" (2..4), leaving "e" (4..5) out.
780        ranges: vec![FullOffset(1)..FullOffset(4)],
781        new_text: vec!["".into()],
782    });
783
784    // Without the fix this panics with "cannot summarize past end of rope"
785    // because the full 3-byte "cde" fragment is consumed from the deleted
786    // rope instead of only the 2-byte intersection.
787    buffer.apply_ops([synthetic_edit]);
788    assert_eq!(buffer.text(), "afgh");
789
790    buffer.undo_operations([(synthetic_timestamp, u32::MAX)].into_iter().collect());
791    assert_eq!(buffer.text(), "abfgh");
792}
793
794#[gpui::test(iterations = 100)]
795fn test_random_concurrent_edits(mut rng: StdRng) {
796    let peers = env::var("PEERS")
797        .map(|i| i.parse().expect("invalid `PEERS` variable"))
798        .unwrap_or(5);
799    let operations = env::var("OPERATIONS")
800        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
801        .unwrap_or(10);
802
803    let base_text_len = rng.random_range(0..10);
804    let base_text = RandomCharIter::new(&mut rng)
805        .take(base_text_len)
806        .collect::<String>();
807    let mut replica_ids = Vec::new();
808    let mut buffers = Vec::new();
809    let mut network = Network::new(rng.clone());
810
811    for i in 0..peers {
812        let mut buffer = Buffer::new(
813            ReplicaId::new(i as u16),
814            BufferId::new(1).unwrap(),
815            base_text.clone(),
816        );
817        buffer.history.group_interval = Duration::from_millis(rng.random_range(0..=200));
818        buffers.push(buffer);
819        replica_ids.push(ReplicaId::new(i as u16));
820        network.add_peer(ReplicaId::new(i as u16));
821    }
822
823    log::info!("initial text: {:?}", base_text);
824
825    let mut mutation_count = operations;
826    loop {
827        let replica_index = rng.random_range(0..peers);
828        let replica_id = replica_ids[replica_index];
829        let buffer = &mut buffers[replica_index];
830        match rng.random_range(0..=100) {
831            0..=50 if mutation_count != 0 => {
832                let op = buffer.randomly_edit(&mut rng, 5).1;
833                network.broadcast(buffer.replica_id, vec![op]);
834                log::info!("buffer {:?} text: {:?}", buffer.replica_id, buffer.text());
835                mutation_count -= 1;
836            }
837            51..=70 if mutation_count != 0 => {
838                let ops = buffer.randomly_undo_redo(&mut rng);
839                network.broadcast(buffer.replica_id, ops);
840                mutation_count -= 1;
841            }
842            71..=100 if network.has_unreceived(replica_id) => {
843                let ops = network.receive(replica_id);
844                if !ops.is_empty() {
845                    log::info!(
846                        "peer {:?} applying {} ops from the network.",
847                        replica_id,
848                        ops.len()
849                    );
850                    buffer.apply_ops(ops);
851                }
852            }
853            _ => {}
854        }
855        buffer.check_invariants();
856
857        if mutation_count == 0 && network.is_idle() {
858            break;
859        }
860    }
861
862    let first_buffer = &buffers[0];
863    for buffer in &buffers[1..] {
864        assert_eq!(
865            buffer.text(),
866            first_buffer.text(),
867            "Replica {:?} text != Replica 0 text",
868            buffer.replica_id
869        );
870        buffer.check_invariants();
871    }
872}
873
874#[test]
875fn test_new_normalized_splits_large_base_text() {
876    // ASCII text that exceeds max_insertion_len
877    let text = "abcdefghij".repeat(10); // 100 bytes
878    let rope = Rope::from(text.as_str());
879    let buffer = Buffer::new_normalized(
880        ReplicaId::LOCAL,
881        BufferId::new(1).unwrap(),
882        LineEnding::Unix,
883        rope,
884    );
885    assert_eq!(buffer.text(), text);
886    buffer.check_invariants();
887
888    // Verify anchors at various positions, including across chunk boundaries
889    for offset in [0, 1, 15, 16, 17, 50, 99] {
890        let anchor = buffer.anchor_before(offset);
891        assert_eq!(
892            anchor.to_offset(&buffer),
893            offset,
894            "anchor_before({offset}) round-tripped incorrectly"
895        );
896        let anchor = buffer.anchor_after(offset);
897        assert_eq!(
898            anchor.to_offset(&buffer),
899            offset,
900            "anchor_after({offset}) round-tripped incorrectly"
901        );
902    }
903
904    // Verify editing works after a split initialization
905    let mut buffer = buffer;
906    buffer.edit([(50..60, "XYZ")]);
907    let mut expected = text;
908    expected.replace_range(50..60, "XYZ");
909    assert_eq!(buffer.text(), expected);
910    buffer.check_invariants();
911}
912
913#[test]
914fn test_new_normalized_splits_large_base_text_with_multibyte_chars() {
915    // Use multi-byte chars (é is 2 bytes in UTF-8) so that a naive byte-level
916    // split would land in the middle of a character.
917    let unit = "ééééééééé"; // 9 chars × 2 bytes = 18 bytes
918    let text = unit.repeat(6); // 108 bytes
919    let rope = Rope::from(text.as_str());
920    let buffer = Buffer::new_normalized(
921        ReplicaId::LOCAL,
922        BufferId::new(1).unwrap(),
923        LineEnding::Unix,
924        rope,
925    );
926    assert_eq!(buffer.text(), text);
927    buffer.check_invariants();
928
929    // Every anchor should resolve correctly even though chunks had to be
930    // rounded down to a char boundary.
931    let snapshot = buffer.snapshot();
932    for offset in (0..text.len()).filter(|o| text.is_char_boundary(*o)) {
933        let anchor = snapshot.anchor_before(offset);
934        assert_eq!(
935            anchor.to_offset(snapshot),
936            offset,
937            "anchor round-trip failed at byte offset {offset}"
938        );
939    }
940}
941
942#[test]
943fn test_new_normalized_small_text_unchanged() {
944    // Text that fits in a single chunk should produce exactly one fragment,
945    // matching the original single-fragment behaviour.
946    let text = "hello world";
947    let rope = Rope::from(text);
948    let buffer = Buffer::new_normalized(
949        ReplicaId::LOCAL,
950        BufferId::new(1).unwrap(),
951        LineEnding::Unix,
952        rope,
953    );
954    assert_eq!(buffer.text(), text);
955    buffer.check_invariants();
956    assert_eq!(buffer.snapshot().fragments.items(&None).len(), 1);
957}
958
959#[test]
960fn test_edit_splits_large_insertion() {
961    let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "abcdefghij");
962
963    let large_text: Arc<str> = "X".repeat(100).into();
964    let edits = vec![(3..7, large_text.clone())];
965
966    buffer.edit(edits);
967
968    let expected = format!("abc{}hij", large_text);
969    assert_eq!(buffer.text(), expected);
970    buffer.check_invariants();
971
972    // Anchors should resolve correctly throughout the buffer.
973    for offset in [0, 3, 50, 103, expected.len()] {
974        let anchor = buffer.anchor_before(offset);
975        assert_eq!(
976            anchor.to_offset(&buffer),
977            offset,
978            "anchor_before({offset}) round-tripped incorrectly"
979        );
980    }
981}
982
983#[test]
984fn test_edit_splits_large_insertion_with_multibyte_chars() {
985    let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "abcdefghij");
986
987    // 4-byte chars so that naive byte splits would land mid-character.
988    let large_text: Arc<str> = "😀".repeat(30).into(); // 30 × 4 = 120 bytes
989    let edits = vec![(5..5, large_text.clone())];
990
991    buffer.edit(edits);
992
993    let expected = format!("abcde{}fghij", large_text);
994    assert_eq!(buffer.text(), expected);
995    buffer.check_invariants();
996}
997
998#[test]
999fn test_edit_splits_large_insertion_among_multiple_edits() {
1000    let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "ABCDEFGHIJ");
1001
1002    let large_text: Arc<str> = "x".repeat(60).into();
1003    // Three edits: small, large, small. The large one must be split while
1004    // preserving the correct positions of the surrounding edits.
1005    let edits = vec![
1006        (1..2, Arc::from("y")),     // replace "B" with "y"
1007        (4..6, large_text.clone()), // replace "EF" with 60 x's
1008        (9..9, Arc::from("z")),     // insert "z" before "J"
1009    ];
1010
1011    buffer.edit(edits);
1012
1013    // Original: A B C D E F G H I J
1014    // After (1..2, "y"):       A y C D E F G H I J
1015    // After (4..6, large):     A y C D <60 x's> G H I J
1016    // After (9..9, "z"):       A y C D <60 x's> G H I z J
1017    let expected = format!("AyCD{}GHIzJ", large_text);
1018    assert_eq!(buffer.text(), expected);
1019    buffer.check_invariants();
1020}
1021
1022#[test]
1023fn test_edit_splits_multiple_large_insertions() {
1024    let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "ABCDE");
1025
1026    let text1: Arc<str> = "a".repeat(40).into();
1027    let text2: Arc<str> = "b".repeat(40).into();
1028    let edits = vec![
1029        (1..2, text1.clone()), // replace "B" with 40 a's
1030        (3..4, text2.clone()), // replace "D" with 40 b's
1031    ];
1032
1033    buffer.edit(edits);
1034
1035    let expected = format!("A{}C{}E", text1, text2);
1036    assert_eq!(buffer.text(), expected);
1037    buffer.check_invariants();
1038}
1039
1040#[test]
1041fn test_edit_undo_after_split() {
1042    let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "hello world");
1043    buffer.set_group_interval(Duration::from_secs(0));
1044    let original = buffer.text();
1045
1046    let large_text: Arc<str> = "Z".repeat(50).into();
1047    let edits = vec![(5..6, large_text)];
1048    buffer.edit(edits);
1049    assert_ne!(buffer.text(), original);
1050    buffer.check_invariants();
1051
1052    // Undo should restore the original text even though the edit was split
1053    // into multiple internal operations grouped in one transaction.
1054    buffer.undo();
1055    assert_eq!(buffer.text(), original);
1056    buffer.check_invariants();
1057}
1058
Served at tenant.openagents/omega Member data and write actions are omitted.