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}