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