random_channel_buffer_tests.rs

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