atagen

matrix-rust-sdk

public
Star0
Watch0

Commit b04108dd8339

tarball

atagen <boss@atagen.co>2026-10-25 10:29 UTC

2 files changed+18-333
tests: align limited sync tests with retained history

The fork keeps retained events and records a HistoryGap on a limited sync instead of shrinking to the last chunk, so tests that waited for the shrink Clear or relied on its pagination reset no longer described real behaviour. The deduplication and relations tests now check the continuous window and fill the history gap, and the two reset-while-paginating tests are gone since no limited sync resets the room any more.
Modified file: crates/matrix-sdk-ui/tests/integration/timeline/pagination.rs+1 additions−158 deletions
@@ -30,7 +30,7 @@ use matrix_sdk::{
3030 event_cache::PaginationStatus,
3131 test_utils::mocks::{MatrixMockServer, RoomMessagesResponseTemplate},
3232 };
33-use matrix_sdk_test::{ALICE, BOB, JoinedRoomBuilder, async_test, event_factory::EventFactory};
33+use matrix_sdk_test::{ALICE, JoinedRoomBuilder, async_test, event_factory::EventFactory};
3434 use matrix_sdk_ui::timeline::{AnyOtherStateEventContentChange, RoomExt, TimelineItemContent};
3535 use ruma::{
3636 EventId, event_id,
@@ -538,163 +538,6 @@ async fn test_dedup_pagination() {
538538 server.server().verify().await;
539539 }
540540
541-#[async_test]
542-async fn test_timeline_reset_while_paginating() {
543- let room_id = room_id!("!a98sd12bjh:example.org");
544- let server = MatrixMockServer::new().await;
545- let client = server.client_builder().build().await;
546-
547- let f = EventFactory::new();
548- let room = server.sync_joined_room(&client, room_id).await;
549- let timeline = Arc::new(room.timeline().await.unwrap());
550-
551- let alice_event = f.text_msg("live event!").sender(&ALICE).room(room_id).into_raw_timeline();
552- server
553- .mock_sync()
554- .ok(|b| {
555- b.add_joined_room(
556- JoinedRoomBuilder::new(room_id)
557- .add_timeline_event(alice_event.clone().cast())
558- .set_timeline_prev_batch("pagination_1".to_owned())
559- .set_timeline_limited(),
560- );
561- })
562- .mock_once()
563- .mount()
564- .await;
565- client.sync_once(Default::default()).await.unwrap();
566-
567- // Next sync response will respond with pagination_2 token and limited
568- // response, resetting the timeline.
569- let sync_mock_guard = server
570- .mock_sync()
571- .ok(|b| {
572- b.add_joined_room(
573- JoinedRoomBuilder::new(room_id)
574- .add_timeline_event(f.text_msg("new live event.").sender(&BOB))
575- .set_timeline_prev_batch("pagination_2".to_owned())
576- .set_timeline_limited(),
577- );
578- })
579- .mock_once()
580- .mount_as_scoped()
581- .await;
582-
583- // The pagination with the first token will be hit twice:
584- // - first, before the sync response comes, then the gap is stored in the cache.
585- // - second, after all other gaps have been resolved, we get back to resolving
586- // this one.
587- server
588- .mock_room_messages()
589- .match_from("pagination_1")
590- .respond_with(
591- ResponseTemplate::new(200)
592- .set_body_json(json!({
593- "chunk": [],
594- "start": "pagination_1",
595- "end": "pagination_3",
596- }))
597- // Make sure the concurrent sync request returns first
598- .set_delay(Duration::from_millis(200)),
599- )
600- .expect(2)
601- .named("pagination_1")
602- .mount()
603- .await;
604-
605- // The pagination with the second token must return Alice's event, to be
606- // consistent with the room's timeline (Alice's event was observed before
607- // Bob's event).
608- server
609- .mock_room_messages()
610- .match_from("pagination_2")
611- .respond_with(ResponseTemplate::new(200).set_body_json(json!({
612- "chunk": [alice_event],
613- "start": "pagination_2",
614- })))
615- .expect(1)
616- .named("pagination_2")
617- .mount()
618- .await;
619-
620- // pagination with third token
621- server
622- .mock_room_messages()
623- .match_from("pagination_3")
624- .respond_with(ResponseTemplate::new(200).set_body_json(json!({
625- "chunk": [],
626- "start": "pagination_3",
627- })))
628- .expect(1)
629- .named("pagination_3")
630- .mount()
631- .await;
632-
633- let (_, mut back_pagination_status) = timeline.live_back_pagination_status().await.unwrap();
634-
635- let paginate = async {
636- let mut hit_start;
637- loop {
638- hit_start = timeline.paginate_backwards(10).await.unwrap();
639- if hit_start {
640- break;
641- }
642- }
643- hit_start
644- };
645-
646- let observe_paginating = async {
647- let mut seen_paginating = false;
648- let mut seen_idle_no_start = false;
649-
650- // Observe paginating updates: we want to make sure we see at least once
651- // Paginating, and that it settles with Idle.
652- while let Ok(update) =
653- timeout(Duration::from_millis(500), back_pagination_status.next()).await
654- {
655- match update {
656- Some(state) => {
657- if state == PaginationStatus::Paginating {
658- seen_paginating = true;
659- }
660- if matches!(state, PaginationStatus::Idle { hit_timeline_start: false }) {
661- seen_idle_no_start = true;
662- }
663- }
664- None => break,
665- }
666- }
667-
668- assert!(seen_paginating);
669- assert!(seen_idle_no_start);
670-
671- let (status, _) = timeline.live_back_pagination_status().await.unwrap();
672-
673- // Timeline start reached because second pagination response contains no end
674- // field.
675- assert_eq!(status, PaginationStatus::Idle { hit_timeline_start: true });
676- };
677-
678- let sync = async {
679- client.sync_once(Default::default()).await.unwrap();
680- };
681-
682- let (hit_start, _, _) =
683- timeout(Duration::from_secs(5), join3(paginate, observe_paginating, sync)).await.unwrap();
684-
685- // Timeline start reached because second pagination response contains no end
686- // field.
687- assert!(hit_start);
688-
689- // No events in back-pagination responses, start of timeline + date divider +
690- // all events from the previous syncs are present.
691- assert_eq!(timeline.items().await.len(), 4);
692-
693- // Make sure both pagination mocks were called.
694- drop(sync_mock_guard);
695- server.server().verify().await;
696-}
697-
698541 pub static ROOM_MESSAGES_BATCH_1: LazyLock<JsonValue> = LazyLock::new(|| {
699542 json!({
700543 "chunk": [
Modified file: crates/matrix-sdk/tests/integration/event_cache/mod.rs+17 additions−175 deletions
@@ -9,8 +9,8 @@ use matrix_sdk::{
99 assert_let_timeout, assert_next_matches_with_timeout,
1010 deserialized_responses::TimelineEvent,
1111 event_cache::{
12- BackPaginationOutcome, EventCacheError, PaginationStatus, RoomEventCacheUpdate,
13- TimelineVectorDiffs,
12+ BackPaginationOutcome, EventCacheError, HistoryGapFillOutcome, PaginationStatus,
13+ RoomEventCacheUpdate, TimelineVectorDiffs,
1414 },
1515 linked_chunk::{ChunkIdentifier, LinkedChunkId, Position, Update},
1616 store::StoreConfig,
@@ -567,134 +567,6 @@ async fn test_backpaginate_many_times_with_one_iteration() {
567567 }
568568
569569 #[async_test]
570-async fn test_reset_while_backpaginating() {
571- let server = MatrixMockServer::new().await;
572- let client = server.client_builder().build().await;
573-
574- let event_cache = client.event_cache();
575-
576- // Immediately subscribe the event cache to sync updates.
577- event_cache.subscribe().unwrap();
578-
579- // If I sync and get informed I've joined The Room, and get a previous batch
580- // token,
581- let room_id = room_id!("!omelette:fromage.fr");
582-
583- let f = EventFactory::new().room(room_id).sender(user_id!("@a:b.c"));
584-
585- server
586- .sync_room(
587- &client,
588- JoinedRoomBuilder::new(room_id)
589- // Note to self: a timeline must have at least single event to be properly
590- // serialized.
591- .add_timeline_event(f.text_msg("heyo").into_raw_sync())
592- .set_timeline_prev_batch("first_backpagination".to_owned())
593- .set_timeline_limited(),
594- )
595- .await;
596-
597- let (room_event_cache, _drop_handles) =
598- client.get_room(room_id).unwrap().event_cache().await.unwrap();
599-
600- let (events, mut room_stream) = room_event_cache.subscribe().await.unwrap();
601-
602- wait_for_initial_events(events, &mut room_stream).await;
603-
604- // We're going to cause a small race:
605- // - a pagination request to sync will be sent,
606- // - a backpagination will be sent concurrently.
607- //
608- // So events have to happen in this order:
609- // - the backpagination request is sent, with a prev-batch A
610- // - the sync endpoint returns *after* the backpagination started, before the
611- // backpagination ends
612- // - the backpagination ends, with a prev-batch token that's now stale.
613- //
614- // The backpagination should result in an unknown-token-error.
615-
616- // Mock the first back-pagination request, with a delay.
617- server
618- .mock_room_messages()
619- .match_from("first_backpagination")
620- .ok(RoomMessagesResponseTemplate::default()
621- .events(vec![f.text_msg("lalala").into_raw_timeline()])
622- .with_delay(Duration::from_millis(500)))
623- .mock_once()
624- .mount()
625- .await;
626-
627- // Mock the second back-pagination request, that will be hit after the reset
628- // caused by the sync.
629- server
630- .mock_room_messages()
631- .match_from("second_backpagination")
632- .ok(RoomMessagesResponseTemplate::default()
633- .end_token("third_backpagination")
634- .events(vec![f.text_msg("finally!").into_raw_timeline()]))
635- .mock_once()
636- .mount()
637- .await;
638-
639- // Run the pagination!
640- let backpagination = spawn({
641- let pagination = room_event_cache.pagination();
642- async move { pagination.run_backwards_once(20).await }
643- });
644-
645- // Receive the sync response (which clears the timeline).
646- server
647- .sync_room(
648- &client,
649- JoinedRoomBuilder::new(room_id)
650- // Note to self: a timeline must have at least single event to be properly
651- // serialized.
652- .add_timeline_event(f.text_msg("heyo").into_raw_sync())
653- .set_timeline_prev_batch("second_backpagination".to_owned())
654- .set_timeline_limited(),
655- )
656- .await;
657-
658- let outcome = backpagination.await.expect("join failed").unwrap();
659-
660- // Backpagination will automatically restart, so eventually we get the events.
661- let BackPaginationOutcome { events, .. } = outcome;
662- assert!(!events.is_empty());
663-
664- // Assert the updates as diffs.
665- {
666- // The room shrinks the linked chunk to the last event chunk, so it clears and
667- // re-adds the latest event.
668- assert_let_timeout!(
669- Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
670- room_stream.recv()
671- );
672- assert_eq!(diffs.len(), 2);
673- assert_matches!(&diffs[0], VectorDiff::Clear);
674- assert_matches!(&diffs[1], VectorDiff::Append { values } => {
675- assert_eq!(values.len(), 1);
676- assert_event_matches_msg(&values[0], "heyo");
677- });
678- }
679-
680- {
681- // Then we receive the event from the restarted back-pagination with
682- // `second_backpagination`.
683- assert_let_timeout!(
684- Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
685- room_stream.recv()
686- );
687- assert_eq!(diffs.len(), 1);
688- assert_matches!(&diffs[0], VectorDiff::Insert { index, value: event } => {
689- assert_eq!(*index, 0);
690- assert_event_matches_msg(event, "finally!");
691- });
692- }
693-
694- assert!(room_stream.is_empty());
695-}
696-
697-#[async_test]
698570 async fn test_backpaginating_without_token() {
699571 let server = MatrixMockServer::new().await;
700572 let client = server.client_builder().build().await;
@@ -1212,6 +1084,7 @@ async fn test_no_gap_stored_after_deduplicated_backpagination() {
12121084 }
12131085
12141086 drop(events);
1087+ let events_before_expansion = f.text_msg("sup").event_id(event_id!("$3")).into_event();
12151088
12161089 // Now, simulate that we expanded the timeline window with sliding sync, by
12171090 // returning more items.
@@ -1229,45 +1102,23 @@ async fn test_no_gap_stored_after_deduplicated_backpagination() {
12291102 )
12301103 .await;
12311104
1105+ // The retained `$3` is part of the expanded window, so the sync is continuous
1106+ // with it: prev-batch2 is dropped, and `$3` moves behind `$1` and `$2`.
12321107 assert_let_timeout!(
12331108 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
12341109 stream.recv()
12351110 );
1236- assert_eq!(diffs.len(), 2);
1237-
1238- assert_matches!(&diffs[0], VectorDiff::Clear);
1239-
1240- // Then the latest event chunk is reloaded.
1241- // `$ev1`, `$ev2` and `$ev3` are added.
1242- assert_matches!(&diffs[1], VectorDiff::Append { values: events } => {
1243- assert_eq!(events.len(), 3);
1244- assert_eq!(events[0].event_id().unwrap().as_str(), "$1");
1245- assert_eq!(events[1].event_id().unwrap().as_str(), "$2");
1246- assert_eq!(events[2].event_id().unwrap().as_str(), "$3");
1247- });
1111+ let mut visible = imbl::vector![events_before_expansion];
1112+ for diff in diffs {
1113+ diff.apply(&mut visible);
1114+ }
1115+ let ids: Vec<_> = visible.iter().filter_map(|event| event.event_id()).collect();
1116+ assert_eq!(ids, [event_id!("$1"), event_id!("$2"), event_id!("$3")]);
12481117
12491118 assert!(stream.is_empty());
12501119
1251- // For prev-batch2, the back-pagination returns nothing.
1252- server
1253- .mock_room_messages()
1254- .match_from("prev-batch2")
1255- .ok(RoomMessagesResponseTemplate::default())
1256- .mock_once()
1257- .mount()
1258- .await;
1259-
1260- // Run pagination once: it will consume prev-batch2 first, which is the most
1261- // recent token, which returns an empty batch, thus indicating the start of the
1262- // room; but we still have a chunk in storage, so it appears like it's not the
1263- // start *yet*.
12641120 let pagination = room_event_cache.pagination();
12651121
1266- let outcome = pagination.run_backwards_once(20).await.unwrap();
1267- assert!(outcome.reached_start.not());
1268- assert!(outcome.events.is_empty());
1269- assert!(stream.is_empty());
1270-
12711122 // For prev-batch, the back-pagination returns two events we already know, and a
12721123 // previous batch token.
12731124 server
@@ -2652,8 +2503,8 @@ async fn test_relations_ordering() {
26522503 .edit(target_event_id, RoomMessageEventContentWithoutRelation::text_plain("hello world"))
26532504 .event_id(edit4);
26542505
2655- // We receive two edit events via sync, as well as a gap; this will shrink the
2656- // linked chunk.
2506+ // We receive two edit events via sync, as well as a gap, which keeps the
2507+ // retained target and records a history gap after it.
26572508 server
26582509 .sync_room(
26592510 &client,
@@ -2671,8 +2522,7 @@ async fn test_relations_ordering() {
26712522 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
26722523 listener.recv()
26732524 );
2674- // We've received the shrink.
2675- if diffs.iter().any(|diff| matches!(diff, VectorDiff::Clear)) {
2525+ if diffs.iter().any(|diff| matches!(diff, VectorDiff::Append { .. })) {
26762526 break;
26772527 }
26782528 }
@@ -2696,17 +2546,9 @@ async fn test_relations_ordering() {
26962546 .mount()
26972547 .await;
26982548
2699- // Run the pagination.
2700- let outcome = room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2701- assert!(outcome.reached_start.not());
2702- assert_eq!(outcome.events.len(), 1);
2703-
2704- {
2705- // Sanity check: we load the first chunk with the first event, from disk, and
2706- // reach the start of the timeline.
2707- let outcome = room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2708- assert!(outcome.reached_start);
2709- }
2549+ let gap_id = room_event_cache.history_gaps().await.unwrap()[0].id.clone();
2550+ let outcome = room_event_cache.fill_history_gap_once(gap_id).await.unwrap();
2551+ assert_matches!(outcome, HistoryGapFillOutcome::Updated { events: 1, .. });
27102552
27112553 // Relations are returned accordingly.
27122554 let (_, relations) =