random_channel_buffer_tests.rs

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