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_channel(&format!("channel-{ix}"), None, 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.channel_dag_entries().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 = buffer.read_with(cx, |b, _| b.channel().name.clone());
102                        break ChannelBufferOperation::LeaveChannelNotes { channel_name };
103                    }
104                }
105
106                _ => {
107                    if let Some(buffer) = channel_buffers.iter().choose(rng) {
108                        break buffer.read_with(cx, |b, _| {
109                            let channel_name = b.channel().name.clone();
110                            let edits = b
111                                .buffer()
112                                .read_with(cx, |buffer, _| buffer.get_random_edits(rng, 3));
113                            ChannelBufferOperation::EditChannelNotes {
114                                channel_name,
115                                edits,
116                            }
117                        });
118                    }
119                }
120            }
121        }
122    }
123
124    async fn apply_operation(
125        client: &TestClient,
126        operation: ChannelBufferOperation,
127        cx: &mut TestAppContext,
128    ) -> Result<(), TestError> {
129        match operation {
130            ChannelBufferOperation::JoinChannelNotes { channel_name } => {
131                let buffer = client.channel_store().update(cx, |store, cx| {
132                    let channel_id = store
133                        .channel_dag_entries()
134                        .find(|(_, c)| c.name == channel_name)
135                        .unwrap()
136                        .1
137                        .id;
138                    if store.has_open_channel_buffer(channel_id, cx) {
139                        Err(TestError::Inapplicable)
140                    } else {
141                        Ok(store.open_channel_buffer(channel_id, cx))
142                    }
143                })?;
144
145                log::info!(
146                    "{}: opening notes for channel {channel_name}",
147                    client.username
148                );
149                client.channel_buffers().insert(buffer.await?);
150            }
151
152            ChannelBufferOperation::LeaveChannelNotes { channel_name } => {
153                let buffer = cx.update(|cx| {
154                    let mut left_buffer = Err(TestError::Inapplicable);
155                    client.channel_buffers().retain(|buffer| {
156                        if buffer.read(cx).channel().name == channel_name {
157                            left_buffer = Ok(buffer.clone());
158                            false
159                        } else {
160                            true
161                        }
162                    });
163                    left_buffer
164                })?;
165
166                log::info!(
167                    "{}: closing notes for channel {channel_name}",
168                    client.username
169                );
170                cx.update(|_| drop(buffer));
171            }
172
173            ChannelBufferOperation::EditChannelNotes {
174                channel_name,
175                edits,
176            } => {
177                let channel_buffer = cx
178                    .read(|cx| {
179                        client
180                            .channel_buffers()
181                            .iter()
182                            .find(|buffer| buffer.read(cx).channel().name == channel_name)
183                            .cloned()
184                    })
185                    .ok_or_else(|| TestError::Inapplicable)?;
186
187                log::info!(
188                    "{}: editing notes for channel {channel_name} with {:?}",
189                    client.username,
190                    edits
191                );
192
193                channel_buffer.update(cx, |buffer, cx| {
194                    let buffer = buffer.buffer();
195                    buffer.update(cx, |buffer, cx| {
196                        let snapshot = buffer.snapshot();
197                        buffer.edit(
198                            edits.into_iter().map(|(range, text)| {
199                                let start = snapshot.clip_offset(range.start, Bias::Left);
200                                let end = snapshot.clip_offset(range.end, Bias::Right);
201                                (start..end, text)
202                            }),
203                            None,
204                            cx,
205                        );
206                    });
207                });
208            }
209
210            ChannelBufferOperation::Noop => Err(TestError::Inapplicable)?,
211        }
212        Ok(())
213    }
214
215    async fn on_client_added(client: &Rc<TestClient>, cx: &mut TestAppContext) {
216        let channel_store = client.channel_store();
217        while channel_store.read_with(cx, |store, _| store.channel_count() == 0) {
218            channel_store.next_notification(cx).await;
219        }
220    }
221
222    async fn on_quiesce(server: &mut TestServer, clients: &mut [(Rc<TestClient>, TestAppContext)]) {
223        let channels = server.app_state.db.all_channels().await.unwrap();
224
225        for (client, client_cx) in clients.iter_mut() {
226            client_cx.update(|cx| {
227                client
228                    .channel_buffers()
229                    .retain(|b| b.read(cx).is_connected());
230            });
231        }
232
233        for (channel_id, channel_name) in channels {
234            let mut prev_text: Option<(u64, String)> = None;
235
236            let mut collaborator_user_ids = server
237                .app_state
238                .db
239                .get_channel_buffer_collaborators(channel_id)
240                .await
241                .unwrap()
242                .into_iter()
243                .map(|id| id.to_proto())
244                .collect::<Vec<_>>();
245            collaborator_user_ids.sort();
246
247            for (client, client_cx) in clients.iter() {
248                let user_id = client.user_id().unwrap();
249                client_cx.read(|cx| {
250                    if let Some(channel_buffer) = client
251                        .channel_buffers()
252                        .iter()
253                        .find(|b| b.read(cx).channel().id == 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.clone()));
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}