random_channel_buffer_tests.rs

  1use collab::db::*;
  2
  3use super::{RandomizedTest, TestClient, TestError, TestServer, UserTestPlan, run_randomized_test};
  4use anyhow::Result;
  5use async_trait::async_trait;
  6use gpui::{BackgroundExecutor, SharedString, TestAppContext};
  7use rand::prelude::*;
  8use serde::{Deserialize, Serialize};
  9use std::{
 10    ops::{Deref, DerefMut, Range},
 11    rc::Rc,
 12    sync::Arc,
 13};
 14use text::Bias;
 15
 16#[gpui::test(iterations = 100, on_failure = "crate::save_randomized_test_plan")]
 17async fn test_random_channel_buffers(
 18    cx: &mut TestAppContext,
 19    executor: BackgroundExecutor,
 20    rng: StdRng,
 21) {
 22    run_randomized_test::<RandomChannelBufferTest>(cx, executor, rng).await;
 23}
 24
 25struct RandomChannelBufferTest;
 26
 27#[derive(Clone, Serialize, Deserialize)]
 28enum ChannelBufferOperation {
 29    JoinChannelNotes {
 30        channel_name: SharedString,
 31    },
 32    LeaveChannelNotes {
 33        channel_name: SharedString,
 34    },
 35    EditChannelNotes {
 36        channel_name: SharedString,
 37        edits: Vec<(Range<usize>, Arc<str>)>,
 38    },
 39    Noop,
 40}
 41
 42const CHANNEL_COUNT: usize = 3;
 43
 44#[async_trait(?Send)]
 45impl RandomizedTest for RandomChannelBufferTest {
 46    type Operation = ChannelBufferOperation;
 47
 48    async fn initialize(server: &mut TestServer, users: &[UserTestPlan]) {
 49        let db = &server.app_state.db;
 50        for ix in 0..CHANNEL_COUNT {
 51            let id = db
 52                .create_root_channel(&format!("channel-{ix}"), users[0].user_id)
 53                .await
 54                .unwrap();
 55            for user in &users[1..] {
 56                db.invite_channel_member(id, user.user_id, users[0].user_id, ChannelRole::Member)
 57                    .await
 58                    .unwrap();
 59                db.respond_to_channel_invite(id, user.user_id, true)
 60                    .await
 61                    .unwrap();
 62            }
 63        }
 64    }
 65
 66    fn generate_operation(
 67        client: &TestClient,
 68        rng: &mut StdRng,
 69        _: &mut UserTestPlan,
 70        cx: &TestAppContext,
 71    ) -> ChannelBufferOperation {
 72        let channel_store = client.channel_store().clone();
 73        let mut channel_buffers = client.channel_buffers();
 74
 75        // When signed out, we can't do anything unless a channel buffer is
 76        // already open.
 77        if channel_buffers.deref_mut().is_empty()
 78            && channel_store.read_with(cx, |store, _| store.channel_count() == 0)
 79        {
 80            return ChannelBufferOperation::Noop;
 81        }
 82
 83        loop {
 84            match rng.random_range(0..100_u32) {
 85                0..=29 => {
 86                    let channel_name = client.channel_store().read_with(cx, |store, cx| {
 87                        store.ordered_channels().find_map(|(_, channel)| {
 88                            if store.has_open_channel_buffer(channel.id, cx) {
 89                                None
 90                            } else {
 91                                Some(channel.name.clone())
 92                            }
 93                        })
 94                    });
 95                    if let Some(channel_name) = channel_name {
 96                        break ChannelBufferOperation::JoinChannelNotes { channel_name };
 97                    }
 98                }
 99
100                30..=40 => {
101                    if let Some(buffer) = channel_buffers.deref().iter().choose(rng) {
102                        let channel_name =
103                            buffer.read_with(cx, |b, cx| b.channel(cx).unwrap().name.clone());
104                        break ChannelBufferOperation::LeaveChannelNotes { channel_name };
105                    }
106                }
107
108                _ => {
109                    if let Some(buffer) = channel_buffers.deref().iter().choose(rng) {
110                        break buffer.read_with(cx, |b, cx| {
111                            let channel_name = b.channel(cx).unwrap().name.clone();
112                            let edits = b
113                                .buffer()
114                                .read_with(cx, |buffer, _| buffer.get_random_edits(rng, 3));
115                            ChannelBufferOperation::EditChannelNotes {
116                                channel_name,
117                                edits,
118                            }
119                        });
120                    }
121                }
122            }
123        }
124    }
125
126    async fn apply_operation(
127        client: &TestClient,
128        operation: ChannelBufferOperation,
129        cx: &mut TestAppContext,
130    ) -> Result<(), TestError> {
131        match operation {
132            ChannelBufferOperation::JoinChannelNotes { channel_name } => {
133                let buffer = client.channel_store().update(cx, |store, cx| {
134                    let channel_id = store
135                        .ordered_channels()
136                        .find(|(_, c)| c.name == channel_name)
137                        .unwrap()
138                        .1
139                        .id;
140                    if store.has_open_channel_buffer(channel_id, cx) {
141                        Err(TestError::Inapplicable)
142                    } else {
143                        Ok(store.open_channel_buffer(channel_id, cx))
144                    }
145                })?;
146
147                log::info!(
148                    "{}: opening notes for channel {channel_name}",
149                    client.username
150                );
151                client.channel_buffers().deref_mut().insert(buffer.await?);
152            }
153
154            ChannelBufferOperation::LeaveChannelNotes { channel_name } => {
155                let buffer = cx.update(|cx| {
156                    let mut left_buffer = Err(TestError::Inapplicable);
157                    client.channel_buffers().deref_mut().retain(|buffer| {
158                        if buffer.read(cx).channel(cx).unwrap().name == channel_name {
159                            left_buffer = Ok(buffer.clone());
160                            false
161                        } else {
162                            true
163                        }
164                    });
165                    left_buffer
166                })?;
167
168                log::info!(
169                    "{}: closing notes for channel {channel_name}",
170                    client.username
171                );
172                cx.update(|_| drop(buffer));
173            }
174
175            ChannelBufferOperation::EditChannelNotes {
176                channel_name,
177                edits,
178            } => {
179                let channel_buffer = cx
180                    .read(|cx| {
181                        client
182                            .channel_buffers()
183                            .deref()
184                            .iter()
185                            .find(|buffer| {
186                                buffer.read(cx).channel(cx).unwrap().name == channel_name
187                            })
188                            .cloned()
189                    })
190                    .ok_or_else(|| TestError::Inapplicable)?;
191
192                log::info!(
193                    "{}: editing notes for channel {channel_name} with {:?}",
194                    client.username,
195                    edits
196                );
197
198                channel_buffer.update(cx, |buffer, cx| {
199                    let buffer = buffer.buffer();
200                    buffer.update(cx, |buffer, cx| {
201                        let snapshot = buffer.snapshot();
202                        buffer.edit(
203                            edits.into_iter().map(|(range, text)| {
204                                let start = snapshot.clip_offset(range.start, Bias::Left);
205                                let end = snapshot.clip_offset(range.end, Bias::Right);
206                                (start..end, text)
207                            }),
208                            None,
209                            cx,
210                        );
211                    });
212                });
213            }
214
215            ChannelBufferOperation::Noop => Err(TestError::Inapplicable)?,
216        }
217        Ok(())
218    }
219
220    async fn on_quiesce(server: &mut TestServer, clients: &mut [(Rc<TestClient>, TestAppContext)]) {
221        let channels = server.app_state.db.all_channels().await.unwrap();
222
223        for (client, client_cx) in clients.iter_mut() {
224            client_cx.update(|cx| {
225                client
226                    .channel_buffers()
227                    .deref_mut()
228                    .retain(|b| b.read(cx).is_connected());
229            });
230        }
231
232        for (channel_id, channel_name) in channels {
233            let mut prev_text: Option<(u64, String)> = None;
234
235            let mut collaborator_user_ids = server
236                .app_state
237                .db
238                .get_channel_buffer_collaborators(channel_id)
239                .await
240                .unwrap()
241                .into_iter()
242                .map(|id| id.to_proto())
243                .collect::<Vec<_>>();
244            collaborator_user_ids.sort();
245
246            for (client, client_cx) in clients.iter() {
247                let user_id = client.user_id().unwrap();
248                client_cx.read(|cx| {
249                    if let Some(channel_buffer) = client
250                        .channel_buffers()
251                        .deref()
252                        .iter()
253                        .find(|b| b.read(cx).channel_id.0 == channel_id.to_proto())
254                    {
255                        let channel_buffer = channel_buffer.read(cx);
256
257                        // Assert that channel buffer's text matches other clients' copies.
258                        let text = channel_buffer.buffer().read(cx).text();
259                        if let Some((prev_user_id, prev_text)) = &prev_text {
260                            assert_eq!(
261                                &text,
262                                prev_text,
263                                "client {user_id} has different text than client {prev_user_id} for channel {channel_name}",
264                            );
265                        } else {
266                            prev_text = Some((user_id, text));
267                        }
268
269                        // Assert that all clients and the server agree about who is present in the
270                        // channel buffer.
271                        let collaborators = channel_buffer.collaborators();
272                        let mut user_ids =
273                            collaborators.values().map(|c| c.user_id).collect::<Vec<_>>();
274                        user_ids.sort();
275                        assert_eq!(
276                            user_ids,
277                            collaborator_user_ids,
278                            "client {user_id} has different user ids for channel {channel_name} than the server",
279                        );
280                    }
281                });
282            }
283        }
284    }
285}