Skip to content

Commit cd53aef

Browse files
committed
test(base): Add test_linked_chunk_incremental_loading.
This patch adds a test for all event cache store implementations that tests a linked chunk incremental loading, i.e. the `EventCacheStore::load_last_chunk` and `EventCacheStore::load_previous_chunk` methods.
1 parent 07f5d49 commit cd53aef

File tree

3 files changed

+245
-6
lines changed

3 files changed

+245
-6
lines changed

crates/matrix-sdk-base/src/event_cache/store/integration_tests.rs

Lines changed: 229 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ use matrix_sdk_common::{
2222
VerificationState,
2323
},
2424
linked_chunk::{
25-
ChunkContent, ChunkIdentifier as CId, LinkedChunk, LinkedChunkBuilderTest, Position,
26-
RawChunk, Update,
25+
ChunkContent, ChunkIdentifier as CId, LinkedChunk, LinkedChunkBuilder,
26+
LinkedChunkBuilderTest, Position, RawChunk, Update,
2727
},
2828
};
2929
use matrix_sdk_test::{event_factory::EventFactory, ALICE, DEFAULT_TEST_ROOM_ID};
@@ -34,7 +34,7 @@ use ruma::{
3434

3535
use super::{media::IgnoreMediaRetentionPolicy, DynEventCacheStore};
3636
use crate::{
37-
event_cache::{Event, Gap},
37+
event_cache::{store::DEFAULT_CHUNK_CAPACITY, Event, Gap},
3838
media::{MediaFormat, MediaRequestParameters, MediaThumbnailSettings},
3939
};
4040

@@ -114,6 +114,10 @@ pub trait EventCacheStoreIntegrationTests {
114114
/// the store.
115115
async fn test_handle_updates_and_rebuild_linked_chunk(&self);
116116

117+
/// Test loading a linked chunk incrementally (chunk by chunk) from the
118+
/// store.
119+
async fn test_linked_chunk_incremental_loading(&self);
120+
117121
/// Test that rebuilding a linked chunk from an empty store doesn't return
118122
/// anything.
119123
async fn test_rebuild_empty_linked_chunk(&self);
@@ -341,7 +345,7 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore {
341345
},
342346
// another items chunk
343347
Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
344-
// new items on 0
348+
// new items on 2
345349
Update::PushItems {
346350
at: Position::new(CId::new(2), 0),
347351
items: vec![make_test_event(room_id, "sup")],
@@ -392,6 +396,220 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore {
392396
assert!(chunks.next().is_none());
393397
}
394398

399+
async fn test_linked_chunk_incremental_loading(&self) {
400+
let room_id = room_id!("!r0:matrix.org");
401+
let event = |msg: &str| make_test_event(room_id, msg);
402+
403+
// Load the last chunk, but none exists yet.
404+
{
405+
let (last_chunk, chunk_identifier_generator) =
406+
self.load_last_chunk(room_id).await.unwrap();
407+
408+
assert!(last_chunk.is_none());
409+
assert_eq!(chunk_identifier_generator.current(), 0);
410+
}
411+
412+
self.handle_linked_chunk_updates(
413+
room_id,
414+
vec![
415+
// new chunk for items
416+
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
417+
// new items on 0
418+
Update::PushItems {
419+
at: Position::new(CId::new(0), 0),
420+
items: vec![event("a"), event("b")],
421+
},
422+
// new chunk for a gap
423+
Update::NewGapChunk {
424+
previous: Some(CId::new(0)),
425+
new: CId::new(1),
426+
next: None,
427+
gap: Gap { prev_token: "morbier".to_owned() },
428+
},
429+
// new chunk for items
430+
Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
431+
// new items on 2
432+
Update::PushItems {
433+
at: Position::new(CId::new(2), 0),
434+
items: vec![event("c"), event("d"), event("e")],
435+
},
436+
],
437+
)
438+
.await
439+
.unwrap();
440+
441+
// Load the last chunk.
442+
let mut linked_chunk = {
443+
let (last_chunk, chunk_identifier_generator) =
444+
self.load_last_chunk(room_id).await.unwrap();
445+
446+
assert_eq!(chunk_identifier_generator.current(), 2);
447+
448+
let linked_chunk = LinkedChunkBuilder::from_last_chunk::<DEFAULT_CHUNK_CAPACITY, _, _>(
449+
last_chunk,
450+
chunk_identifier_generator,
451+
)
452+
.unwrap() // unwrap the `Result`
453+
.unwrap(); // unwrap the `Option`
454+
455+
let mut rchunks = linked_chunk.rchunks();
456+
457+
// A unique chunk.
458+
assert_matches!(rchunks.next(), Some(chunk) => {
459+
assert_eq!(chunk.identifier(), 2);
460+
461+
assert_matches!(chunk.content(), ChunkContent::Items(events) => {
462+
assert_eq!(events.len(), 3);
463+
check_test_event(&events[0], "c");
464+
check_test_event(&events[1], "d");
465+
check_test_event(&events[2], "e");
466+
});
467+
});
468+
469+
assert!(rchunks.next().is_none());
470+
471+
linked_chunk
472+
};
473+
474+
// Load the previous chunk: this is a gap.
475+
{
476+
let first_chunk = linked_chunk.chunks().next().unwrap().identifier();
477+
let mut previous_chunk =
478+
self.load_previous_chunk(room_id, first_chunk).await.unwrap().unwrap();
479+
480+
// Pretend it's the first chunk.
481+
previous_chunk.previous = None;
482+
483+
let _ = LinkedChunkBuilder::insert_new_first_chunk(&mut linked_chunk, previous_chunk)
484+
.unwrap();
485+
486+
let mut rchunks = linked_chunk.rchunks();
487+
488+
// The last chunk.
489+
assert_matches!(rchunks.next(), Some(chunk) => {
490+
assert_eq!(chunk.identifier(), 2);
491+
492+
// Already asserted, but let's be sure nothing breaks.
493+
assert_matches!(chunk.content(), ChunkContent::Items(events) => {
494+
assert_eq!(events.len(), 3);
495+
check_test_event(&events[0], "c");
496+
check_test_event(&events[1], "d");
497+
check_test_event(&events[2], "e");
498+
});
499+
});
500+
501+
// The new chunk.
502+
assert_matches!(rchunks.next(), Some(chunk) => {
503+
assert_eq!(chunk.identifier(), 1);
504+
505+
assert_matches!(chunk.content(), ChunkContent::Gap(gap) => {
506+
assert_eq!(gap.prev_token, "morbier");
507+
});
508+
});
509+
510+
assert!(rchunks.next().is_none());
511+
}
512+
513+
// Load the previous chunk: these are items.
514+
{
515+
let first_chunk = linked_chunk.chunks().next().unwrap().identifier();
516+
let mut previous_chunk =
517+
self.load_previous_chunk(room_id, first_chunk).await.unwrap().unwrap();
518+
519+
// Pretend it's the first chunk.
520+
previous_chunk.previous = None;
521+
522+
let _ = LinkedChunkBuilder::insert_new_first_chunk(&mut linked_chunk, previous_chunk)
523+
.unwrap();
524+
525+
let mut rchunks = linked_chunk.rchunks();
526+
527+
// The last chunk.
528+
assert_matches!(rchunks.next(), Some(chunk) => {
529+
assert_eq!(chunk.identifier(), 2);
530+
531+
// Already asserted, but let's be sure nothing breaks.
532+
assert_matches!(chunk.content(), ChunkContent::Items(events) => {
533+
assert_eq!(events.len(), 3);
534+
check_test_event(&events[0], "c");
535+
check_test_event(&events[1], "d");
536+
check_test_event(&events[2], "e");
537+
});
538+
});
539+
540+
// Its previous chunk.
541+
assert_matches!(rchunks.next(), Some(chunk) => {
542+
assert_eq!(chunk.identifier(), 1);
543+
544+
// Already asserted, but let's be sure nothing breaks.
545+
assert_matches!(chunk.content(), ChunkContent::Gap(gap) => {
546+
assert_eq!(gap.prev_token, "morbier");
547+
});
548+
});
549+
550+
// The new chunk.
551+
assert_matches!(rchunks.next(), Some(chunk) => {
552+
assert_eq!(chunk.identifier(), 0);
553+
554+
assert_matches!(chunk.content(), ChunkContent::Items(events) => {
555+
assert_eq!(events.len(), 2);
556+
check_test_event(&events[0], "a");
557+
check_test_event(&events[1], "b");
558+
});
559+
});
560+
561+
assert!(rchunks.next().is_none());
562+
}
563+
564+
// Load the previous chunk: there is none.
565+
{
566+
let first_chunk = linked_chunk.chunks().next().unwrap().identifier();
567+
let previous_chunk = self.load_previous_chunk(room_id, first_chunk).await.unwrap();
568+
569+
assert!(previous_chunk.is_none());
570+
}
571+
572+
// One last check: a round of assert by using the forwards chunk iterator
573+
// instead of the backwards chunk iterator.
574+
{
575+
let mut chunks = linked_chunk.chunks();
576+
577+
// The first chunk.
578+
assert_matches!(chunks.next(), Some(chunk) => {
579+
assert_eq!(chunk.identifier(), 0);
580+
581+
assert_matches!(chunk.content(), ChunkContent::Items(events) => {
582+
assert_eq!(events.len(), 2);
583+
check_test_event(&events[0], "a");
584+
check_test_event(&events[1], "b");
585+
});
586+
});
587+
588+
// The second chunk.
589+
assert_matches!(chunks.next(), Some(chunk) => {
590+
assert_eq!(chunk.identifier(), 1);
591+
592+
assert_matches!(chunk.content(), ChunkContent::Gap(gap) => {
593+
assert_eq!(gap.prev_token, "morbier");
594+
});
595+
});
596+
597+
// The third and last chunk.
598+
assert_matches!(chunks.next(), Some(chunk) => {
599+
assert_eq!(chunk.identifier(), 2);
600+
601+
assert_matches!(chunk.content(), ChunkContent::Items(events) => {
602+
assert_eq!(events.len(), 3);
603+
check_test_event(&events[0], "c");
604+
check_test_event(&events[1], "d");
605+
check_test_event(&events[2], "e");
606+
});
607+
});
608+
609+
assert!(chunks.next().is_none());
610+
}
611+
}
612+
395613
async fn test_rebuild_empty_linked_chunk(&self) {
396614
// When I rebuild a linked chunk from an empty store, it's empty.
397615
let raw_parts = self.load_all_chunks(&DEFAULT_TEST_ROOM_ID).await.unwrap();
@@ -564,6 +782,13 @@ macro_rules! event_cache_store_integration_tests {
564782
event_cache_store.test_handle_updates_and_rebuild_linked_chunk().await;
565783
}
566784

785+
#[async_test]
786+
async fn test_linked_chunk_incremental_loading() {
787+
let event_cache_store =
788+
get_event_cache_store().await.unwrap().into_event_cache_store();
789+
event_cache_store.test_linked_chunk_incremental_loading().await;
790+
}
791+
567792
#[async_test]
568793
async fn test_rebuild_empty_linked_chunk() {
569794
let event_cache_store =

crates/matrix-sdk-common/src/linked_chunk/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,14 @@ impl ChunkIdentifierGenerator {
10501050

10511051
ChunkIdentifier(previous + 1)
10521052
}
1053+
1054+
/// Get the current chunk identifier.
1055+
//
1056+
// This is hidden because it's used only in the tests.
1057+
#[doc(hidden)]
1058+
pub fn current(&self) -> ChunkIdentifier {
1059+
ChunkIdentifier(self.next.load(Ordering::Relaxed))
1060+
}
10531061
}
10541062

10551063
/// The unique identifier of a chunk in a [`LinkedChunk`].

crates/matrix-sdk-sqlite/src/event_cache_store.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -635,9 +635,15 @@ impl EventCacheStore for SqliteEventCacheStore {
635635
.query_row(
636636
(&hashed_room_id,),
637637
|row| {
638-
row.get::<_, u64>(0)
638+
// Read the `MAX(id)` as an `Option<u64>` instead
639+
// of `u64` in case the `SELECT` returns nothing.
640+
// Indeed, if it returns no line, the `MAX(id)` is
641+
// set to `Null`.
642+
row.get::<_, Option<u64>>(0)
639643
}
640-
).optional()?
644+
)
645+
.optional()?
646+
.flatten()
641647
{
642648
Some(last_chunk_identifier) => {
643649
ChunkIdentifierGenerator::new_from_previous_chunk_identifier(

0 commit comments

Comments
 (0)