diff --git a/Cargo.lock b/Cargo.lock index b1296d78339..8aab3ce0199 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3519,6 +3519,7 @@ dependencies = [ "matrix-sdk-base", "matrix-sdk-test", "mime", + "mock_instant", "once_cell", "pin-project-lite", "ruma", @@ -3609,6 +3610,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mock_instant" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcebb6db83796481097dedc7747809243cc81d9ed83e6a938b76d4ea0b249cf" + [[package]] name = "multiverse" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index cf07a01a28a..c16e31d0a9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ growable-bloom-filter = "2.1.0" http = "1.1.0" imbl = "3.0.0" itertools = "0.12.0" +mock_instant = "0.5.1" once_cell = "1.16.0" pin-project-lite = "0.2.9" rand = "0.8.5" diff --git a/bindings/matrix-sdk-ffi/src/room_list.rs b/bindings/matrix-sdk-ffi/src/room_list.rs index 48a6c100397..af62b02438c 100644 --- a/bindings/matrix-sdk-ffi/src/room_list.rs +++ b/bindings/matrix-sdk-ffi/src/room_list.rs @@ -356,7 +356,7 @@ impl From for RoomListServiceState { S::Init => Self::Initial, S::SettingUp => Self::SettingUp, S::Recovering => Self::Recovering, - S::Running => Self::Running, + S::Running { .. } => Self::Running, S::Error { .. } => Self::Error, S::Terminated { .. } => Self::Terminated, } diff --git a/crates/matrix-sdk-ui/Cargo.toml b/crates/matrix-sdk-ui/Cargo.toml index db4bac2952b..d360000d402 100644 --- a/crates/matrix-sdk-ui/Cargo.toml +++ b/crates/matrix-sdk-ui/Cargo.toml @@ -15,6 +15,8 @@ rustls-tls = ["matrix-sdk/rustls-tls"] uniffi = ["dep:uniffi", "matrix-sdk/uniffi", "matrix-sdk-base/uniffi"] +testing = ["dep:mock_instant", "matrix-sdk/testing", "matrix-sdk-base/testing"] + # Add support for encrypted extensible events. unstable-msc3956 = ["ruma/unstable-msc3956"] @@ -38,6 +40,7 @@ itertools = { workspace = true } matrix-sdk = { workspace = true, features = ["experimental-sliding-sync", "e2e-encryption"] } matrix-sdk-base = { workspace = true } mime = "0.3.16" +mock_instant = { workspace = true, optional = true } once_cell = { workspace = true } pin-project-lite = { workspace = true } ruma = { workspace = true, features = ["html", "unstable-msc3381"] } @@ -57,9 +60,14 @@ assert_matches2 = { workspace = true } eyeball-im-util = { workspace = true } matrix-sdk = { workspace = true, features = ["testing"] } matrix-sdk-test = { workspace = true } +mock_instant = { workspace = true } stream_assert = { workspace = true } tempfile = "3.3.0" wiremock = { workspace = true } +[[test]] +name = "integration" +required-features = ["testing"] + [lints] workspace = true diff --git a/crates/matrix-sdk-ui/src/room_list_service/mod.rs b/crates/matrix-sdk-ui/src/room_list_service/mod.rs index 1b5a57a3a96..76e2f8962fe 100644 --- a/crates/matrix-sdk-ui/src/room_list_service/mod.rs +++ b/crates/matrix-sdk-ui/src/room_list_service/mod.rs @@ -315,7 +315,7 @@ impl RoomListService { (SyncIndicator::Show, delay_before_showing) } - State::SettingUp | State::Recovering | State::Running | State::Terminated { .. } => { + State::SettingUp | State::Recovering | State::Running {..} | State::Terminated { .. } => { (SyncIndicator::Hide, delay_before_hiding) } }; @@ -610,13 +610,17 @@ mod tests { let _ = sync.next().await; // State is `Terminated`, as expected! - assert_eq!(room_list.state.get(), State::Terminated { from: Box::new(State::Running) }); + assert_matches!(room_list.state.get(), State::Terminated { from } => { + assert_matches!(from.as_ref(), State::Running { .. }); + }); // Now, let's make the sliding sync session to expire. room_list.expire_sync_session().await; // State is `Error`, as a regular session expiration would generate! - assert_eq!(room_list.state.get(), State::Error { from: Box::new(State::Running) }); + assert_matches!(room_list.state.get(), State::Error { from } => { + assert_matches!(from.as_ref(), State::Running { .. }); + }); Ok(()) } diff --git a/crates/matrix-sdk-ui/src/room_list_service/room_list.rs b/crates/matrix-sdk-ui/src/room_list_service/room_list.rs index de7b4870270..623443e61bb 100644 --- a/crates/matrix-sdk-ui/src/room_list_service/room_list.rs +++ b/crates/matrix-sdk-ui/src/room_list_service/room_list.rs @@ -88,7 +88,7 @@ impl RoomList { match state { Terminated { .. } | Error { .. } | Init => (), - SettingUp | Recovering | Running => break, + SettingUp | Recovering | Running { .. } => break, } } diff --git a/crates/matrix-sdk-ui/src/room_list_service/state.rs b/crates/matrix-sdk-ui/src/room_list_service/state.rs index edf189cbb64..92804a4b671 100644 --- a/crates/matrix-sdk-ui/src/room_list_service/state.rs +++ b/crates/matrix-sdk-ui/src/room_list_service/state.rs @@ -15,8 +15,12 @@ //! States and actions for the `RoomList` state machine. use std::future::ready; +#[cfg(not(any(test, feature = "testing")))] +use std::time::Instant; use matrix_sdk::{sliding_sync::Range, SlidingSync, SlidingSyncMode}; +#[cfg(any(test, feature = "testing"))] +use mock_instant::global::Instant; use super::Error; @@ -37,7 +41,7 @@ pub enum State { Recovering, /// At this state, all rooms are syncing. - Running, + Running { last_time: Instant }, /// At this state, the sync has been stopped because an error happened. Error { from: Box }, @@ -57,10 +61,18 @@ impl State { SettingUp | Recovering => { set_all_rooms_to_growing_sync_mode(sliding_sync).await?; - Running + Running { last_time: Instant::now() } } - Running => Running, + Running { last_time } => { + // We haven't sync for a while so we should go back to recovering + if last_time.elapsed().as_secs() > 1800 { + set_all_rooms_to_selective_sync_mode(sliding_sync).await?; + Recovering + } else { + Running { last_time: Instant::now() } + } + } Error { from: previous_state } | Terminated { from: previous_state } => { match previous_state.as_ref() { @@ -72,7 +84,7 @@ impl State { } // If the previous state was `Running`, we enter the `Recovering` state. - Running => { + Running { .. } => { set_all_rooms_to_selective_sync_mode(sliding_sync).await?; Recovering } @@ -121,6 +133,7 @@ pub const ALL_ROOMS_DEFAULT_GROWING_BATCH_SIZE: u32 = 100; #[cfg(test)] mod tests { + use assert_matches::assert_matches; use matrix_sdk_test::async_test; use super::{super::tests::new_room_list, *}; @@ -173,7 +186,7 @@ mod tests { // Next state. let state = state.next(sliding_sync).await?; - assert_eq!(state, State::Running); + assert_matches!(state, State::Running { .. }); // Hypothetical error. { @@ -185,7 +198,7 @@ mod tests { let state = state.next(sliding_sync).await?; // Now, back to the previous state. - assert_eq!(state, State::Running); + assert_matches!(state, State::Running { .. }); } // Hypothetical termination. @@ -199,7 +212,7 @@ mod tests { let state = state.next(sliding_sync).await?; // Now, back to the previous state. - assert_eq!(state, State::Running); + assert_matches!(state, State::Running { .. }); } // Hypothetical error when recovering. diff --git a/crates/matrix-sdk-ui/tests/integration/room_list_service.rs b/crates/matrix-sdk-ui/tests/integration/room_list_service.rs index b37ec389f9c..0a200927e76 100644 --- a/crates/matrix-sdk-ui/tests/integration/room_list_service.rs +++ b/crates/matrix-sdk-ui/tests/integration/room_list_service.rs @@ -1,7 +1,4 @@ -use std::{ - ops::Not, - time::{Duration, Instant}, -}; +use std::{ops::Not, time::Duration}; use assert_matches::assert_matches; use eyeball_im::VectorDiff; @@ -19,6 +16,7 @@ use matrix_sdk_ui::{ timeline::{TimelineItemKind, VirtualTimelineItem}, RoomListService, }; +use mock_instant::global::MockClock; use ruma::{ api::client::room::create_room::v3::Request as CreateRoomRequest, assign, event_id, @@ -36,6 +34,7 @@ use wiremock::{ use crate::timeline::sliding_sync::{assert_timeline_stream, timeline_event}; async fn new_room_list_service() -> Result<(Client, MockServer, RoomListService), Error> { + MockClock::set_time(Duration::ZERO); let (client, server) = logged_in_client_with_server().await; let room_list = RoomListService::new(client.clone()).await?; @@ -370,7 +369,7 @@ async fn test_sync_all_states() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = SettingUp => Running, + states = SettingUp => Running { .. }, assert request = { "conn_id": "room-list", "lists": { @@ -394,7 +393,7 @@ async fn test_sync_all_states() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = Running => Running, + states = Running { .. } => Running { .. }, assert request = { "conn_id": "room-list", "lists": { @@ -418,7 +417,7 @@ async fn test_sync_all_states() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = Running => Running, + states = Running { .. } => Running { .. }, assert request = { "conn_id": "room-list", "lists": { @@ -442,7 +441,7 @@ async fn test_sync_all_states() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = Running => Running, + states = Running { .. } => Running { .. }, assert request = { "conn_id": "room-list", "lists": { @@ -505,7 +504,7 @@ async fn test_sync_resumes_from_previous_state() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = SettingUp => Running, + states = SettingUp => Running { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -532,7 +531,7 @@ async fn test_sync_resumes_from_previous_state() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = Running => Running, + states = Running { .. } => Running { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -555,6 +554,103 @@ async fn test_sync_resumes_from_previous_state() -> Result<(), Error> { Ok(()) } +#[async_test] +async fn test_sync_resumes_after_a_while() -> Result<(), Error> { + let (_, server, room_list) = new_room_list_service().await?; + + let sync = room_list.sync(); + pin_mut!(sync); + + sync_then_assert_request_and_fake_response! { + [server, room_list, sync] + states = Init => SettingUp, + assert request >= { + "lists": { + ALL_ROOMS: { + "ranges": [[0, 19]], + }, + }, + }, + respond with = { + "pos": "0", + "lists": { + ALL_ROOMS: { + "count": 200, + }, + }, + "rooms": {}, + }, + }; + + sync_then_assert_request_and_fake_response! { + [server, room_list, sync] + states = SettingUp => Running { .. }, + assert request >= { + "lists": { + ALL_ROOMS: { + "ranges": [[0, 99]], + }, + }, + }, + respond with = { + "pos": "1", + "lists": { + ALL_ROOMS: { + "count": 200, + }, + }, + "rooms": {}, + }, + }; + + MockClock::advance(Duration::from_secs(1800 + 1)); + + // We haven't sync for a while so we should be back to paginated sync + sync_then_assert_request_and_fake_response! { + [server, room_list, sync] + states = Running { .. } => Recovering, + assert request >= { + "lists": { + ALL_ROOMS: { + "ranges": [[0, 19]], + }, + }, + }, + respond with = { + "pos": "0", + "lists": { + ALL_ROOMS: { + "count": 200, + }, + }, + "rooms": {}, + }, + }; + + sync_then_assert_request_and_fake_response! { + [server, room_list, sync] + states = Recovering => Running { .. }, + assert request >= { + "lists": { + ALL_ROOMS: { + "ranges": [[0, 99]], + }, + }, + }, + respond with = { + "pos": "1", + "lists": { + ALL_ROOMS: { + "count": 200, + }, + }, + "rooms": {}, + }, + }; + + Ok(()) +} + #[async_test] async fn test_sync_resumes_from_error() -> Result<(), Error> { let (_, server, room_list) = new_room_list_service().await?; @@ -658,7 +754,7 @@ async fn test_sync_resumes_from_error() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = Recovering => Running, + states = Recovering => Running { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -681,7 +777,7 @@ async fn test_sync_resumes_from_error() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] sync matches Some(Err(_)), - states = Running => Error { .. }, + states = Running { .. } => Error { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -727,7 +823,7 @@ async fn test_sync_resumes_from_error() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = Recovering => Running, + states = Recovering => Running { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -749,7 +845,7 @@ async fn test_sync_resumes_from_error() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = Running => Running, + states = Running { .. } => Running { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -772,7 +868,7 @@ async fn test_sync_resumes_from_error() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] sync matches Some(Err(_)), - states = Running => Error { .. }, + states = Running { .. } => Error { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -819,7 +915,7 @@ async fn test_sync_resumes_from_error() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = Recovering => Running, + states = Recovering => Running { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -910,7 +1006,7 @@ async fn test_sync_resumes_from_terminated() -> Result<(), Error> { // Do a regular sync from the `Recovering` state. sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = Recovering => Running, + states = Recovering => Running { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -964,7 +1060,7 @@ async fn test_sync_resumes_from_terminated() -> Result<(), Error> { // Do a regular sync from the `Recovering` state. sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = Recovering => Running, + states = Recovering => Running { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -987,7 +1083,7 @@ async fn test_sync_resumes_from_terminated() -> Result<(), Error> { // Do a regular sync from the `Running` state. sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = Running => Running, + states = Running { .. } => Running { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -1058,7 +1154,7 @@ async fn test_loading_states() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = SettingUp => Running, + states = SettingUp => Running { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -1088,7 +1184,7 @@ async fn test_loading_states() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = Running => Running, + states = Running { .. } => Running { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -1224,7 +1320,7 @@ async fn test_entries_stream() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = SettingUp => Running, + states = SettingUp => Running { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -1326,7 +1422,7 @@ async fn test_dynamic_entries_stream() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = SettingUp => Running, + states = SettingUp => Running { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -1435,7 +1531,7 @@ async fn test_dynamic_entries_stream() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = Running => Running, + states = Running { .. } => Running { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -1601,7 +1697,7 @@ async fn test_dynamic_entries_stream() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = Running => Running, + states = Running { .. } => Running { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -1769,7 +1865,7 @@ async fn test_room_sorting() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = SettingUp => Running, + states = SettingUp => Running { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -1854,7 +1950,7 @@ async fn test_room_sorting() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = Running => Running, + states = Running { .. } => Running { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -1928,7 +2024,7 @@ async fn test_room_sorting() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = Running => Running, + states = Running { .. } => Running { .. }, assert request >= { "lists": { ALL_ROOMS: { @@ -2581,7 +2677,7 @@ async fn test_sync_indicator() -> Result<(), Error> { }; ($sync_indicator:ident, $pattern:pat, under $time:expr $(,)?) => { - let now = Instant::now(); + let now = std::time::Instant::now(); assert_matches!($sync_indicator.next().await, Some($pattern)); assert!(now.elapsed() < $time); }; @@ -2682,7 +2778,7 @@ async fn test_sync_indicator() -> Result<(), Error> { // Request 2. sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = SettingUp => Running, + states = SettingUp => Running { .. }, assert request >= {}, respond with = { "pos": "1", @@ -2696,7 +2792,7 @@ async fn test_sync_indicator() -> Result<(), Error> { sync_then_assert_request_and_fake_response! { [server, room_list, sync] sync matches Some(Err(_)), - states = Running => Error { .. }, + states = Running { .. } => Error { .. }, assert request >= {}, respond with = (code 400) { "error": "foo", @@ -2725,7 +2821,7 @@ async fn test_sync_indicator() -> Result<(), Error> { // Request 5. sync_then_assert_request_and_fake_response! { [server, room_list, sync] - states = Recovering => Running, + states = Recovering => Running { .. }, assert request >= {}, respond with = { "pos": "3", diff --git a/testing/matrix-sdk-integration-testing/Cargo.toml b/testing/matrix-sdk-integration-testing/Cargo.toml index 83ec826ecd1..29740491641 100644 --- a/testing/matrix-sdk-integration-testing/Cargo.toml +++ b/testing/matrix-sdk-integration-testing/Cargo.toml @@ -18,7 +18,7 @@ futures-util = { workspace = true } http = { workspace = true } matrix-sdk = {workspace = true, default-features = true, features = ["testing", "qrcode"] } matrix-sdk-base = { workspace = true, default-features = true, features = ["testing", "qrcode"] } -matrix-sdk-ui = { workspace = true, default-features = true } +matrix-sdk-ui = { workspace = true, default-features = true, features = ["testing"] } matrix-sdk-test = { workspace = true } once_cell = { workspace = true } rand = { workspace = true }