random_channel_buffer_tests.rs

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