1use as_variant::as_variant;
16use eyeball_im::VectorDiff;
17pub use matrix_sdk_base::event_cache::{Event, Gap};
18use matrix_sdk_base::{
19 event_cache::store::DEFAULT_CHUNK_CAPACITY,
20 linked_chunk::{
21 ChunkContent, ChunkIdentifierGenerator, ChunkMetadata, OrderTracker, RawChunk,
22 lazy_loader::{self, LazyLoaderError},
23 },
24};
25use matrix_sdk_common::linked_chunk::{
26 AsVector, Chunk, ChunkIdentifier, Error, Iter, IterBackward, LinkedChunk, ObservableUpdates,
27 Position,
28};
29use tracing::{instrument, trace};
30
31#[cfg(feature = "e2e-encryption")]
32use super::super::redecryptor::MaybeResolvedEvent;
33
34#[derive(Debug)]
36pub(in crate::event_cache) struct EventLinkedChunk {
37 chunks: LinkedChunk<DEFAULT_CHUNK_CAPACITY, Event, Gap>,
39
40 chunks_updates_as_vectordiffs: AsVector<Event, Gap>,
44
45 pub order_tracker: OrderTracker<Event, Gap>,
47}
48
49impl Default for EventLinkedChunk {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55impl EventLinkedChunk {
56 pub fn new() -> Self {
58 Self::with_initial_linked_chunk(None, None)
59 }
60
61 pub fn with_initial_linked_chunk(
65 linked_chunk: Option<LinkedChunk<DEFAULT_CHUNK_CAPACITY, Event, Gap>>,
66 full_linked_chunk_metadata: Option<Vec<ChunkMetadata>>,
67 ) -> Self {
68 let mut linked_chunk = linked_chunk.unwrap_or_else(LinkedChunk::new_with_update_history);
69
70 let chunks_updates_as_vectordiffs = linked_chunk
71 .as_vector()
72 .expect("`LinkedChunk` must have been built with `new_with_update_history`");
73
74 let order_tracker = linked_chunk
75 .order_tracker(full_linked_chunk_metadata)
76 .expect("`LinkedChunk` must have been built with `new_with_update_history`");
77
78 Self { chunks: linked_chunk, chunks_updates_as_vectordiffs, order_tracker }
79 }
80
81 pub fn reset(&mut self) {
86 self.chunks.clear();
87 }
88
89 #[cfg(test)]
93 pub(in crate::event_cache) fn push_events<I>(&mut self, events: I)
94 where
95 I: IntoIterator<Item = Event>,
96 I::IntoIter: ExactSizeIterator,
97 {
98 self.chunks.push_items_back(events);
99 }
100
101 #[instrument(err, skip_all, fields(gap_identifier, sentry = true))]
109 fn replace_gap_at(
110 &mut self,
111 gap_identifier: ChunkIdentifier,
112 events: Vec<Event>,
113 ) -> Result<Option<Position>, Error> {
114 let has_only_one_chunk = {
122 let mut it = self.chunks.chunks();
123
124 let _ =
126 it.next().ok_or(Error::InvalidChunkIdentifier { identifier: gap_identifier })?;
127
128 it.next().is_none()
130 };
131
132 let next_pos = if events.is_empty() && !has_only_one_chunk {
133 self.chunks.remove_empty_chunk_at(gap_identifier)?
136 } else {
137 Some(self.chunks.replace_gap_at(events, gap_identifier)?.first_position())
139 };
140
141 Ok(next_pos)
142 }
143
144 #[instrument(err, skip_all, fields(positions, sentry = true))]
148 pub fn remove_events_by_position(&mut self, mut positions: Vec<Position>) -> Result<(), Error> {
149 sort_positions_descending(&mut positions);
150
151 for position in positions {
152 self.chunks.remove_item_at(position)?;
153 }
154
155 Ok(())
156 }
157
158 #[instrument(err, skip_all, fields(position, sentry = true))]
163 pub fn replace_event_at(&mut self, position: Position, event: Event) -> Result<(), Error> {
164 self.chunks.replace_item_at(position, event)
165 }
166
167 pub fn chunk_identifier<'a, P>(&'a self, predicate: P) -> Option<ChunkIdentifier>
169 where
170 P: FnMut(&'a Chunk<DEFAULT_CHUNK_CAPACITY, Event, Gap>) -> bool,
171 {
172 self.chunks.chunk_identifier(predicate)
173 }
174
175 pub fn first_chunk(&self) -> &Chunk<DEFAULT_CHUNK_CAPACITY, Event, Gap> {
177 self.chunks.first_chunk()
178 }
179
180 pub fn chunks(&self) -> Iter<'_, DEFAULT_CHUNK_CAPACITY, Event, Gap> {
184 self.chunks.chunks()
185 }
186
187 pub fn rchunks(&self) -> IterBackward<'_, DEFAULT_CHUNK_CAPACITY, Event, Gap> {
191 self.chunks.rchunks()
192 }
193
194 pub fn revents(&self) -> impl Iterator<Item = (Position, &Event)> {
198 self.chunks.ritems()
199 }
200
201 pub fn events(&self) -> impl Iterator<Item = (Position, &Event)> {
205 self.chunks.items()
206 }
207
208 pub fn event_order(&self, event_pos: Position) -> Option<usize> {
212 self.order_tracker.ordering(event_pos)
213 }
214
215 #[cfg(any(test, debug_assertions))]
216 #[allow(dead_code)] fn assert_event_ordering(&self) {
218 let mut iter = self.chunks.items().enumerate();
219 let Some((i, (first_event_pos, _))) = iter.next() else {
220 return;
221 };
222
223 assert_eq!(i, 0);
225
226 let offset =
229 self.event_order(first_event_pos).expect("first event's ordering must be known");
230
231 for (i, (next_pos, _)) in iter {
232 let next_index =
233 self.event_order(next_pos).expect("next event's ordering must be known");
234 assert_eq!(offset + i, next_index, "event ordering must be continuous");
235 }
236 }
237
238 pub fn updates_as_vector_diffs(&mut self) -> Vec<VectorDiff<Event>> {
244 let updates = self.chunks_updates_as_vectordiffs.take();
245
246 self.order_tracker.flush_updates(false);
247
248 updates
249 }
250
251 pub(in super::super) fn store_updates(&mut self) -> &mut ObservableUpdates<Event, Gap> {
259 self.chunks.updates().expect("this is always built with an update history in the ctor")
260 }
261
262 pub fn debug_string(&self) -> Vec<String> {
265 let mut result = Vec::new();
266
267 for chunk in self.chunks.chunks() {
268 let content =
269 chunk_debug_string(chunk.identifier(), chunk.content(), &self.order_tracker);
270 let lazy_previous = if let Some(cid) = chunk.lazy_previous() {
271 format!(" (lazy previous = {})", cid.index())
272 } else {
273 "".to_owned()
274 };
275 let line = format!("chunk #{}{lazy_previous}: {content}", chunk.identifier().index());
276
277 result.push(line);
278 }
279
280 result
281 }
282
283 pub fn rgap(&self) -> Option<Gap> {
288 self.rchunks()
289 .find_map(|chunk| as_variant!(chunk.content(), ChunkContent::Gap(gap) => gap.clone()))
290 }
291
292 pub fn push_gap(&mut self, gap: Gap) {
297 let prev_chunk_to_remove = self.rchunks().next().and_then(|chunk| {
300 (chunk.is_items() && chunk.num_items() == 0).then_some(chunk.identifier())
301 });
302
303 self.chunks.push_gap_back(gap);
304
305 if let Some(prev_chunk_to_remove) = prev_chunk_to_remove {
306 self.chunks
307 .remove_empty_chunk_at(prev_chunk_to_remove)
308 .expect("we just checked the chunk is there, and it's an empty item chunk");
309 }
310 }
311
312 pub fn push_live_events(&mut self, new_gap: Option<Gap>, events: &[Event]) {
315 if let Some(new_gap) = new_gap {
316 self.push_gap(new_gap);
317 }
318 self.chunks.push_items_back(events.iter().cloned());
319 }
320
321 pub fn push_backwards_pagination_events(
337 &mut self,
338 prev_gap_id: Option<ChunkIdentifier>,
339 new_gap: Option<Gap>,
340 events: &[Event],
341 ) -> bool {
342 let first_event_pos = self.events().next().map(|(item_pos, _)| item_pos);
343
344 let insert_new_gap_pos = if let Some(gap_id) = prev_gap_id {
346 trace!("replacing previous gap with the back-paginated events");
348
349 self.replace_gap_at(gap_id, events.to_vec())
353 .expect("gap_identifier is a valid chunk id we read previously")
354 } else if let Some(pos) = first_event_pos {
355 trace!("inserted events before the first known event");
358
359 self.chunks
360 .insert_items_at(pos, events.to_vec())
361 .expect("pos is a valid position we just read above");
362
363 Some(pos)
364 } else {
365 trace!("pushing events received from back-pagination");
367
368 self.chunks.push_items_back(events.to_vec());
369
370 self.events().next().map(|(item_pos, _)| item_pos)
372 };
373
374 let has_new_gap = new_gap.is_some();
380 if let Some(new_gap) = new_gap {
381 if let Some(new_pos) = insert_new_gap_pos {
382 self.chunks
383 .insert_gap_at(new_gap, new_pos)
384 .expect("events_chunk_pos represents a valid chunk position");
385 } else {
386 self.chunks.push_gap_back(new_gap);
387 }
388 }
389
390 let has_gaps = self.chunks().any(|chunk| chunk.is_gap());
396
397 let first_chunk_is_definitive_head =
399 self.chunks().next().map(|chunk| chunk.is_definitive_head());
400
401 let network_reached_start = !has_new_gap;
402 let reached_start =
403 !has_gaps && first_chunk_is_definitive_head.unwrap_or(network_reached_start);
404
405 trace!(
406 ?network_reached_start,
407 ?has_gaps,
408 ?first_chunk_is_definitive_head,
409 ?reached_start,
410 "finished handling network back-pagination"
411 );
412
413 reached_start
414 }
415
416 pub fn push_forwards_pagination_events(
434 &mut self,
435 next_gap_id: Option<ChunkIdentifier>,
436 new_gap: Option<Gap>,
437 events: &[Event],
438 ) -> bool {
439 if let Some(gap_id) = next_gap_id {
441 trace!("replacing next gap with forward-paginated events");
443
444 self.replace_gap_at(gap_id, events.to_vec())
445 .expect("gap_identifier is a valid chunk id we read previously");
446 } else if !events.is_empty() {
447 trace!("pushing events received from forward-pagination");
449 self.chunks.push_items_back(events.to_vec());
450 }
451
452 let reached_end = new_gap.is_none();
454 if let Some(new_gap) = new_gap {
455 self.chunks.push_gap_back(new_gap);
456 }
457
458 trace!(?reached_end, "finished handling network forward-pagination");
459
460 reached_end
461 }
462
463 #[cfg(feature = "e2e-encryption")]
466 pub fn find_event(&self, event_id: &ruma::EventId) -> Option<(Position, Event)> {
467 for (position, event) in self.revents() {
468 if event.event_id() == Some(event_id) {
469 return Some((position, event.clone()));
470 }
471 }
472 None
473 }
474
475 #[cfg(feature = "e2e-encryption")]
480 pub fn replace_utds(&mut self, resolved_events: &[MaybeResolvedEvent]) -> bool {
481 let mut replaced_some = false;
482
483 for resolved_event in
484 resolved_events.iter().filter_map(|resolved_event| resolved_event.as_resolved())
485 {
486 let Some(event_id) = resolved_event.event_id() else {
487 continue;
489 };
490
491 let Some((position, _)) = self.find_event(event_id) else {
493 continue;
494 };
495
496 self.replace_event_at(position, resolved_event.clone())
497 .expect("position should be valid");
498
499 replaced_some = true;
500 }
501
502 replaced_some
503 }
504
505 pub fn first_chunk_as_gap(&self) -> Option<(ChunkIdentifier, Gap)> {
507 self.chunks().next().and_then(|chunk| {
508 if let ChunkContent::Gap(gap) = chunk.content() {
509 Some((chunk.identifier(), gap.clone()))
510 } else {
511 None
512 }
513 })
514 }
515
516 pub fn last_chunk_as_gap(&self) -> Option<(ChunkIdentifier, Gap)> {
518 self.rchunks().next().and_then(|chunk| {
519 if let ChunkContent::Gap(gap) = chunk.content() {
520 Some((chunk.identifier(), gap.clone()))
521 } else {
522 None
523 }
524 })
525 }
526}
527
528impl EventLinkedChunk {
530 fn inhibit_updates_to_ordering_tracker<F: FnOnce(&mut Self) -> R, R>(&mut self, f: F) -> R {
539 self.order_tracker.flush_updates(false);
541
542 let r = f(self);
544
545 self.order_tracker.flush_updates(true);
548
549 r
550 }
551
552 #[instrument(err, skip_all, fields(sentry = true))]
559 pub(in super::super) fn shrink_to_last_reloaded_chunk(
560 &mut self,
561 last_chunk: Option<RawChunk<Event, Gap>>,
562 chunk_identifier_generator: ChunkIdentifierGenerator,
563 full_linked_chunk_metadata: Option<Vec<ChunkMetadata>>,
564 ) -> Result<(), LazyLoaderError> {
565 self.inhibit_updates_to_ordering_tracker(move |this| {
568 lazy_loader::replace_with(&mut this.chunks, last_chunk, chunk_identifier_generator)?;
569
570 let _ = this.store_updates().take();
573
574 this.order_tracker = this
575 .chunks
576 .order_tracker(full_linked_chunk_metadata)
577 .expect("`LinkedChunk` must have been built with `new_with_update_history`");
578
579 Ok(())
580 })
581 }
582
583 #[instrument(err, skip_all, fields(sentry = true))]
585 pub(in super::super) fn insert_new_chunk_as_first(
586 &mut self,
587 raw_new_first_chunk: RawChunk<Event, Gap>,
588 ) -> Result<(), LazyLoaderError> {
589 self.inhibit_updates_to_ordering_tracker(move |this| {
592 lazy_loader::insert_new_first_chunk(&mut this.chunks, raw_new_first_chunk)
593 })
594 }
595}
596
597fn chunk_debug_string(
599 chunk_id: ChunkIdentifier,
600 content: &ChunkContent<Event, Gap>,
601 order_tracker: &OrderTracker<Event, Gap>,
602) -> String {
603 match content {
604 ChunkContent::Gap(Gap { token: prev_token }) => {
605 format!("gap['{prev_token}']")
606 }
607 ChunkContent::Items(vec) => {
608 let items = vec
609 .iter()
610 .enumerate()
611 .map(|(i, event)| {
612 event.event_id().map_or_else(
613 || "<no event id>".to_owned(),
614 |id| {
615 let pos = Position::new(chunk_id, i);
616 let order = format!("#{}: ", order_tracker.ordering(pos).unwrap());
617
618 let event_id = id.as_str().chars().take(1 + 8).collect::<String>();
620
621 format!("{order}{event_id}")
622 },
623 )
624 })
625 .collect::<Vec<_>>()
626 .join(", ");
627
628 format!("events[{items}]")
629 }
630 }
631}
632
633pub(in super::super) fn sort_positions_descending(positions: &mut [Position]) {
641 positions.sort_by(|a, b| {
642 b.chunk_identifier()
643 .cmp(&a.chunk_identifier())
644 .then_with(|| a.index().cmp(&b.index()).reverse())
645 });
646}
647
648#[cfg(test)]
649mod tests {
650 use assert_matches::assert_matches;
651 use assert_matches2::assert_let;
652 use matrix_sdk_base::linked_chunk::Update;
653 use matrix_sdk_test::{ALICE, DEFAULT_TEST_ROOM_ID, event_factory::EventFactory};
654 use ruma::{EventId, OwnedEventId, event_id, user_id};
655
656 use super::*;
657
658 macro_rules! assert_events_eq {
659 ( $events_iterator:expr, [ $( ( $event_id:ident at ( $chunk_identifier:literal, $index:literal ) ) ),* $(,)? ] ) => {
660 {
661 let mut events = $events_iterator;
662
663 $(
664 assert_let!(Some((position, event)) = events.next());
665 assert_eq!(position.chunk_identifier(), $chunk_identifier );
666 assert_eq!(position.index(), $index );
667 assert_eq!(event.event_id().unwrap(), $event_id );
668 )*
669
670 assert!(events.next().is_none(), "No more events are expected");
671 }
672 };
673 }
674
675 fn new_event(event_id: &str) -> (OwnedEventId, Event) {
676 let event_id = EventId::parse(event_id).unwrap();
677 let event = EventFactory::new()
678 .text_msg("")
679 .sender(user_id!("@mnt_io:matrix.org"))
680 .event_id(&event_id)
681 .into_event();
682
683 (event_id, event)
684 }
685
686 #[test]
687 fn test_new_event_linked_chunk_has_zero_events() {
688 let linked_chunk = EventLinkedChunk::new();
689
690 assert_eq!(linked_chunk.events().count(), 0);
691 }
692
693 #[test]
694 fn test_replace_gap_at() {
695 let (event_id_0, event_0) = new_event("$ev0");
696 let (event_id_1, event_1) = new_event("$ev1");
697 let (event_id_2, event_2) = new_event("$ev2");
698
699 let mut linked_chunk = EventLinkedChunk::new();
700
701 linked_chunk.chunks.push_items_back([event_0]);
702 linked_chunk.chunks.push_gap_back(Gap { token: "hello".to_owned() });
703
704 let gap_chunk_id = linked_chunk
705 .chunks()
706 .find_map(|chunk| chunk.is_gap().then_some(chunk.identifier()))
707 .unwrap();
708
709 linked_chunk.replace_gap_at(gap_chunk_id, vec![event_1, event_2]).unwrap();
710
711 assert_events_eq!(
712 linked_chunk.events(),
713 [
714 (event_id_0 at (0, 0)),
715 (event_id_1 at (2, 0)),
716 (event_id_2 at (2, 1)),
717 ]
718 );
719
720 {
721 let mut chunks = linked_chunk.chunks();
722
723 assert_let!(Some(chunk) = chunks.next());
724 assert!(chunk.is_items());
725
726 assert_let!(Some(chunk) = chunks.next());
727 assert!(chunk.is_items());
728
729 assert!(chunks.next().is_none());
730 }
731 }
732
733 #[test]
734 fn test_replace_gap_at_with_no_new_events() {
735 let (_, event_0) = new_event("$ev0");
736 let (_, event_1) = new_event("$ev1");
737 let (_, event_2) = new_event("$ev2");
738
739 let mut linked_chunk = EventLinkedChunk::new();
740
741 linked_chunk.chunks.push_items_back([event_0, event_1]);
742 linked_chunk.chunks.push_gap_back(Gap { token: "middle".to_owned() });
743 linked_chunk.chunks.push_items_back([event_2]);
744 linked_chunk.chunks.push_gap_back(Gap { token: "end".to_owned() });
745
746 let first_gap_id = linked_chunk
748 .chunks()
749 .find_map(|chunk| chunk.is_gap().then_some(chunk.identifier()))
750 .unwrap();
751
752 let pos = linked_chunk.replace_gap_at(first_gap_id, vec![]).unwrap();
754 assert_eq!(pos, Some(Position::new(ChunkIdentifier::new(2), 0)));
755
756 let second_gap_id = linked_chunk
758 .chunks()
759 .find_map(|chunk| chunk.is_gap().then_some(chunk.identifier()))
760 .unwrap();
761
762 let pos = linked_chunk.replace_gap_at(second_gap_id, vec![]).unwrap();
764 assert!(pos.is_none());
765 }
766
767 #[test]
768 fn test_remove_events() {
769 let (event_id_0, event_0) = new_event("$ev0");
770 let (event_id_1, event_1) = new_event("$ev1");
771 let (event_id_2, event_2) = new_event("$ev2");
772 let (event_id_3, event_3) = new_event("$ev3");
773
774 let mut linked_chunk = EventLinkedChunk::new();
776 linked_chunk.chunks.push_items_back([event_0, event_1]);
777 linked_chunk.chunks.push_gap_back(Gap { token: "hello".to_owned() });
778 linked_chunk.chunks.push_items_back([event_2, event_3]);
779
780 assert_events_eq!(
781 linked_chunk.events(),
782 [
783 (event_id_0 at (0, 0)),
784 (event_id_1 at (0, 1)),
785 (event_id_2 at (2, 0)),
786 (event_id_3 at (2, 1)),
787 ]
788 );
789 assert_eq!(linked_chunk.chunks().count(), 3);
790
791 linked_chunk
793 .remove_events_by_position(vec![
794 Position::new(ChunkIdentifier::new(2), 1),
795 Position::new(ChunkIdentifier::new(0), 1),
796 ])
797 .unwrap();
798
799 assert_events_eq!(
800 linked_chunk.events(),
801 [
802 (event_id_0 at (0, 0)),
803 (event_id_2 at (2, 0)),
804 ]
805 );
806
807 linked_chunk
809 .remove_events_by_position(vec![Position::new(ChunkIdentifier::new(2), 0)])
810 .unwrap();
811
812 assert_events_eq!(
813 linked_chunk.events(),
814 [
815 (event_id_0 at (0, 0)),
816 ]
817 );
818 assert_eq!(linked_chunk.chunks().count(), 2);
819 }
820
821 #[test]
822 fn test_remove_events_unknown_event() {
823 let mut linked_chunk = EventLinkedChunk::new();
825
826 assert_events_eq!(linked_chunk.events(), []);
827
828 linked_chunk
831 .remove_events_by_position(vec![Position::new(ChunkIdentifier::new(42), 153)])
832 .unwrap_err();
833
834 assert_events_eq!(linked_chunk.events(), []);
835
836 let mut events = linked_chunk.events();
837 assert!(events.next().is_none());
838 }
839
840 #[test]
841 fn test_reset() {
842 let (event_id_0, event_0) = new_event("$ev0");
843 let (event_id_1, event_1) = new_event("$ev1");
844 let (event_id_2, event_2) = new_event("$ev2");
845 let (event_id_3, event_3) = new_event("$ev3");
846
847 let mut linked_chunk = EventLinkedChunk::new();
849 linked_chunk.chunks.push_items_back([event_0, event_1]);
850 linked_chunk.chunks.push_gap_back(Gap { token: "raclette".to_owned() });
851 linked_chunk.chunks.push_items_back([event_2]);
852
853 let diffs = linked_chunk.updates_as_vector_diffs();
855
856 assert_eq!(diffs.len(), 2);
857
858 assert_matches!(
859 &diffs[0],
860 VectorDiff::Append { values } => {
861 assert_eq!(values.len(), 2);
862 assert_eq!(values[0].event_id(), Some(event_id_0.as_ref()));
863 assert_eq!(values[1].event_id(), Some(event_id_1.as_ref()));
864 }
865 );
866 assert_matches!(
867 &diffs[1],
868 VectorDiff::Append { values } => {
869 assert_eq!(values.len(), 1);
870 assert_eq!(values[0].event_id(), Some(event_id_2.as_ref()));
871 }
872 );
873
874 linked_chunk.reset();
876 linked_chunk.chunks.push_items_back([event_3]);
877
878 let diffs = linked_chunk.updates_as_vector_diffs();
880
881 assert_eq!(diffs.len(), 2);
882
883 assert_matches!(&diffs[0], VectorDiff::Clear);
884 assert_matches!(
885 &diffs[1],
886 VectorDiff::Append { values } => {
887 assert_eq!(values.len(), 1);
888 assert_eq!(values[0].event_id(), Some(event_id_3.as_ref()));
889 }
890 );
891 }
892
893 #[test]
894 fn test_debug_string() {
895 let event_factory = EventFactory::new().room(&DEFAULT_TEST_ROOM_ID).sender(*ALICE);
896
897 let mut linked_chunk = EventLinkedChunk::new();
898 linked_chunk.chunks.push_items_back(vec![
899 event_factory
900 .text_msg("hey")
901 .event_id(event_id!("$123456789101112131415617181920"))
902 .into_event(),
903 event_factory.text_msg("you").event_id(event_id!("$2")).into_event(),
904 ]);
905 linked_chunk.chunks.push_gap_back(Gap { token: "raclette".to_owned() });
906
907 let _ = linked_chunk.updates_as_vector_diffs();
909
910 let output = linked_chunk.debug_string();
911
912 assert_eq!(output.len(), 2);
913 assert_eq!(&output[0], "chunk #0: events[#0: $12345678, #1: $2]");
914 assert_eq!(&output[1], "chunk #1: gap['raclette']");
915 }
916
917 #[test]
918 fn test_sort_positions_descending() {
919 let mut positions = vec![
920 Position::new(ChunkIdentifier::new(2), 1),
921 Position::new(ChunkIdentifier::new(1), 0),
922 Position::new(ChunkIdentifier::new(2), 0),
923 Position::new(ChunkIdentifier::new(1), 1),
924 Position::new(ChunkIdentifier::new(0), 0),
925 ];
926
927 sort_positions_descending(&mut positions);
928
929 assert_eq!(
930 positions,
931 &[
932 Position::new(ChunkIdentifier::new(2), 1),
933 Position::new(ChunkIdentifier::new(2), 0),
934 Position::new(ChunkIdentifier::new(1), 1),
935 Position::new(ChunkIdentifier::new(1), 0),
936 Position::new(ChunkIdentifier::new(0), 0),
937 ]
938 );
939 }
940
941 #[test]
942 fn test_shrink_to_no_last_reloaded_chunk() {
943 let mut linked_chunk = EventLinkedChunk::new();
944
945 {
946 let updates = linked_chunk.store_updates().take();
947
948 assert_eq!(updates.len(), 1);
949 assert_matches!(
950 &updates[0],
951 Update::NewItemsChunk { previous, new, next } => {
952 assert!(previous.is_none());
953 assert_eq!(new.index(), 0);
954 assert!(next.is_none());
955 }
956 );
957 }
958
959 linked_chunk
962 .shrink_to_last_reloaded_chunk(None, ChunkIdentifierGenerator::new_from_scratch(), None)
963 .unwrap();
964
965 {
966 let updates = linked_chunk.store_updates().take();
967
968 assert_eq!(updates.len(), 1);
969 assert_matches!(
972 &updates[0],
973 Update::NewItemsChunk { previous, new, next } => {
974 assert!(previous.is_none());
975 assert_eq!(new.index(), 0);
976 assert!(next.is_none());
977 }
978 );
979 }
980 }
981}