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}