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