1use crate::{
2 db::{self, NewUserParams},
3 rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
4 tests::{TestClient, TestServer},
5};
6use anyhow::anyhow;
7use call::ActiveCall;
8use client::RECEIVE_TIMEOUT;
9use collections::BTreeMap;
10use fs::{FakeFs, Fs as _};
11use futures::StreamExt as _;
12use gpui::{executor::Deterministic, TestAppContext};
13use language::{range_to_lsp, FakeLspAdapter, Language, LanguageConfig, PointUtf16};
14use lsp::FakeLanguageServer;
15use parking_lot::Mutex;
16use project::{search::SearchQuery, Project};
17use rand::prelude::*;
18use std::{env, path::PathBuf, sync::Arc};
19
20#[gpui::test(iterations = 100)]
21async fn test_random_collaboration(
22 cx: &mut TestAppContext,
23 deterministic: Arc<Deterministic>,
24 rng: StdRng,
25) {
26 deterministic.forbid_parking();
27 let rng = Arc::new(Mutex::new(rng));
28
29 let max_peers = env::var("MAX_PEERS")
30 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
31 .unwrap_or(5);
32
33 let max_operations = env::var("OPERATIONS")
34 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
35 .unwrap_or(10);
36
37 let mut server = TestServer::start(&deterministic).await;
38 let db = server.app_state.db.clone();
39
40 let mut available_users = Vec::new();
41 for ix in 0..max_peers {
42 let username = format!("user-{}", ix + 1);
43 let user_id = db
44 .create_user(
45 &format!("{username}@example.com"),
46 false,
47 NewUserParams {
48 github_login: username.clone(),
49 github_user_id: (ix + 1) as i32,
50 invite_count: 0,
51 },
52 )
53 .await
54 .unwrap()
55 .user_id;
56 available_users.push((user_id, username));
57 }
58
59 for (ix, (user_id_a, _)) in available_users.iter().enumerate() {
60 for (user_id_b, _) in &available_users[ix + 1..] {
61 server
62 .app_state
63 .db
64 .send_contact_request(*user_id_a, *user_id_b)
65 .await
66 .unwrap();
67 server
68 .app_state
69 .db
70 .respond_to_contact_request(*user_id_b, *user_id_a, true)
71 .await
72 .unwrap();
73 }
74 }
75
76 let mut clients = Vec::new();
77 let mut user_ids = Vec::new();
78 let mut op_start_signals = Vec::new();
79 let mut next_entity_id = 100000;
80
81 let mut operations = 0;
82 while operations < max_operations {
83 let distribution = rng.lock().gen_range(0..100);
84 match distribution {
85 0..=19 if !available_users.is_empty() => {
86 let client_ix = rng.lock().gen_range(0..available_users.len());
87 let (_, username) = available_users.remove(client_ix);
88 log::info!("Adding new connection for {}", username);
89 next_entity_id += 100000;
90 let mut client_cx = TestAppContext::new(
91 cx.foreground_platform(),
92 cx.platform(),
93 deterministic.build_foreground(next_entity_id),
94 deterministic.build_background(),
95 cx.font_cache(),
96 cx.leak_detector(),
97 next_entity_id,
98 cx.function_name.clone(),
99 );
100
101 let op_start_signal = futures::channel::mpsc::unbounded();
102 let client = server.create_client(&mut client_cx, &username).await;
103 user_ids.push(client.current_user_id(&client_cx));
104 op_start_signals.push(op_start_signal.0);
105 clients.push(client_cx.foreground().spawn(simulate_client(
106 client,
107 op_start_signal.1,
108 rng.clone(),
109 client_cx,
110 )));
111
112 log::info!("Added connection for {}", username);
113 operations += 1;
114 }
115
116 20..=24 if clients.len() > 1 => {
117 let client_ix = rng.lock().gen_range(1..clients.len());
118 log::info!(
119 "Simulating full disconnection of user {}",
120 user_ids[client_ix]
121 );
122 let removed_user_id = user_ids.remove(client_ix);
123 let user_connection_ids = server
124 .connection_pool
125 .lock()
126 .user_connection_ids(removed_user_id)
127 .collect::<Vec<_>>();
128 assert_eq!(user_connection_ids.len(), 1);
129 let removed_peer_id = user_connection_ids[0].into();
130 let client = clients.remove(client_ix);
131 op_start_signals.remove(client_ix);
132 server.forbid_connections();
133 server.disconnect_client(removed_peer_id);
134 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
135 deterministic.start_waiting();
136 log::info!("Waiting for user {} to exit...", removed_user_id);
137 let (client, mut client_cx) = client.await;
138 deterministic.finish_waiting();
139 server.allow_connections();
140
141 for project in &client.remote_projects {
142 project.read_with(&client_cx, |project, _| assert!(project.is_read_only()));
143 }
144 for user_id in &user_ids {
145 let contacts = server.app_state.db.get_contacts(*user_id).await.unwrap();
146 let pool = server.connection_pool.lock();
147 for contact in contacts {
148 if let db::Contact::Accepted { user_id, .. } = contact {
149 if pool.is_user_online(user_id) {
150 assert_ne!(
151 user_id, removed_user_id,
152 "removed client is still a contact of another peer"
153 );
154 }
155 }
156 }
157 }
158
159 log::info!("{} removed", client.username);
160 available_users.push((removed_user_id, client.username.clone()));
161 client_cx.update(|cx| {
162 cx.clear_globals();
163 drop(client);
164 });
165
166 operations += 1;
167 }
168
169 25..=29 if clients.len() > 1 => {
170 let client_ix = rng.lock().gen_range(1..clients.len());
171 let user_id = user_ids[client_ix];
172 log::info!("Simulating temporary disconnection of user {}", user_id);
173 let user_connection_ids = server
174 .connection_pool
175 .lock()
176 .user_connection_ids(user_id)
177 .collect::<Vec<_>>();
178 assert_eq!(user_connection_ids.len(), 1);
179 let peer_id = user_connection_ids[0].into();
180 server.disconnect_client(peer_id);
181 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
182 operations += 1;
183 }
184
185 30..=34 => {
186 log::info!("Simulating server restart");
187 server.reset().await;
188 deterministic.advance_clock(RECEIVE_TIMEOUT);
189 server.start().await.unwrap();
190 deterministic.advance_clock(CLEANUP_TIMEOUT);
191 let environment = &server.app_state.config.zed_environment;
192 let stale_room_ids = server
193 .app_state
194 .db
195 .stale_room_ids(environment, server.id())
196 .await
197 .unwrap();
198 assert_eq!(stale_room_ids, vec![]);
199 }
200
201 _ if !op_start_signals.is_empty() => {
202 while operations < max_operations && rng.lock().gen_bool(0.7) {
203 op_start_signals
204 .choose(&mut *rng.lock())
205 .unwrap()
206 .unbounded_send(())
207 .unwrap();
208 operations += 1;
209 }
210
211 if rng.lock().gen_bool(0.8) {
212 deterministic.run_until_parked();
213 }
214 }
215 _ => {}
216 }
217 }
218
219 drop(op_start_signals);
220 deterministic.start_waiting();
221 let clients = futures::future::join_all(clients).await;
222 deterministic.finish_waiting();
223 deterministic.run_until_parked();
224
225 for (client, client_cx) in &clients {
226 for guest_project in &client.remote_projects {
227 guest_project.read_with(client_cx, |guest_project, cx| {
228 let host_project = clients.iter().find_map(|(client, cx)| {
229 let project = client.local_projects.iter().find(|host_project| {
230 host_project.read_with(cx, |host_project, _| {
231 host_project.remote_id() == guest_project.remote_id()
232 })
233 })?;
234 Some((project, cx))
235 });
236
237 if !guest_project.is_read_only() {
238 if let Some((host_project, host_cx)) = host_project {
239 let host_worktree_snapshots =
240 host_project.read_with(host_cx, |host_project, cx| {
241 host_project
242 .worktrees(cx)
243 .map(|worktree| {
244 let worktree = worktree.read(cx);
245 (worktree.id(), worktree.snapshot())
246 })
247 .collect::<BTreeMap<_, _>>()
248 });
249 let guest_worktree_snapshots = guest_project
250 .worktrees(cx)
251 .map(|worktree| {
252 let worktree = worktree.read(cx);
253 (worktree.id(), worktree.snapshot())
254 })
255 .collect::<BTreeMap<_, _>>();
256
257 assert_eq!(
258 guest_worktree_snapshots.keys().collect::<Vec<_>>(),
259 host_worktree_snapshots.keys().collect::<Vec<_>>(),
260 "{} has different worktrees than the host",
261 client.username
262 );
263
264 for (id, host_snapshot) in &host_worktree_snapshots {
265 let guest_snapshot = &guest_worktree_snapshots[id];
266 assert_eq!(
267 guest_snapshot.root_name(),
268 host_snapshot.root_name(),
269 "{} has different root name than the host for worktree {}",
270 client.username,
271 id
272 );
273 assert_eq!(
274 guest_snapshot.abs_path(),
275 host_snapshot.abs_path(),
276 "{} has different abs path than the host for worktree {}",
277 client.username,
278 id
279 );
280 assert_eq!(
281 guest_snapshot.entries(false).collect::<Vec<_>>(),
282 host_snapshot.entries(false).collect::<Vec<_>>(),
283 "{} has different snapshot than the host for worktree {}",
284 client.username,
285 id
286 );
287 assert_eq!(guest_snapshot.scan_id(), host_snapshot.scan_id());
288 }
289 }
290 }
291
292 guest_project.check_invariants(cx);
293 });
294 }
295
296 for (guest_project, guest_buffers) in &client.buffers {
297 let project_id = if guest_project.read_with(client_cx, |project, _| {
298 project.is_local() || project.is_read_only()
299 }) {
300 continue;
301 } else {
302 guest_project
303 .read_with(client_cx, |project, _| project.remote_id())
304 .unwrap()
305 };
306
307 let host_project = clients.iter().find_map(|(client, cx)| {
308 let project = client.local_projects.iter().find(|host_project| {
309 host_project.read_with(cx, |host_project, _| {
310 host_project.remote_id() == Some(project_id)
311 })
312 })?;
313 Some((project, cx))
314 });
315
316 let (host_project, host_cx) = if let Some((host_project, host_cx)) = host_project {
317 (host_project, host_cx)
318 } else {
319 continue;
320 };
321
322 for guest_buffer in guest_buffers {
323 let buffer_id = guest_buffer.read_with(client_cx, |buffer, _| buffer.remote_id());
324 let host_buffer = host_project.read_with(host_cx, |project, cx| {
325 project.buffer_for_id(buffer_id, cx).unwrap_or_else(|| {
326 panic!(
327 "host does not have buffer for guest:{}, peer:{:?}, id:{}",
328 client.username,
329 client.peer_id(),
330 buffer_id
331 )
332 })
333 });
334 let path = host_buffer
335 .read_with(host_cx, |buffer, cx| buffer.file().unwrap().full_path(cx));
336
337 assert_eq!(
338 guest_buffer.read_with(client_cx, |buffer, _| buffer.deferred_ops_len()),
339 0,
340 "{}, buffer {}, path {:?} has deferred operations",
341 client.username,
342 buffer_id,
343 path,
344 );
345 assert_eq!(
346 guest_buffer.read_with(client_cx, |buffer, _| buffer.text()),
347 host_buffer.read_with(host_cx, |buffer, _| buffer.text()),
348 "{}, buffer {}, path {:?}, differs from the host's buffer",
349 client.username,
350 buffer_id,
351 path
352 );
353 }
354 }
355 }
356
357 for (client, mut cx) in clients {
358 cx.update(|cx| {
359 cx.clear_globals();
360 drop(client);
361 });
362 }
363}
364
365async fn simulate_client(
366 mut client: TestClient,
367 mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
368 rng: Arc<Mutex<StdRng>>,
369 mut cx: TestAppContext,
370) -> (TestClient, TestAppContext) {
371 // Setup language server
372 let mut language = Language::new(
373 LanguageConfig {
374 name: "Rust".into(),
375 path_suffixes: vec!["rs".to_string()],
376 ..Default::default()
377 },
378 None,
379 );
380 let _fake_language_servers = language
381 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
382 name: "the-fake-language-server",
383 capabilities: lsp::LanguageServer::full_capabilities(),
384 initializer: Some(Box::new({
385 let rng = rng.clone();
386 let fs = client.fs.clone();
387 move |fake_server: &mut FakeLanguageServer| {
388 fake_server.handle_request::<lsp::request::Completion, _, _>(
389 |_, _| async move {
390 Ok(Some(lsp::CompletionResponse::Array(vec![
391 lsp::CompletionItem {
392 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
393 range: lsp::Range::new(
394 lsp::Position::new(0, 0),
395 lsp::Position::new(0, 0),
396 ),
397 new_text: "the-new-text".to_string(),
398 })),
399 ..Default::default()
400 },
401 ])))
402 },
403 );
404
405 fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
406 |_, _| async move {
407 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
408 lsp::CodeAction {
409 title: "the-code-action".to_string(),
410 ..Default::default()
411 },
412 )]))
413 },
414 );
415
416 fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
417 |params, _| async move {
418 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
419 params.position,
420 params.position,
421 ))))
422 },
423 );
424
425 fake_server.handle_request::<lsp::request::GotoDefinition, _, _>({
426 let fs = fs.clone();
427 let rng = rng.clone();
428 move |_, _| {
429 let fs = fs.clone();
430 let rng = rng.clone();
431 async move {
432 let files = fs.files().await;
433 let mut rng = rng.lock();
434 let count = rng.gen_range::<usize, _>(1..3);
435 let files = (0..count)
436 .map(|_| files.choose(&mut *rng).unwrap())
437 .collect::<Vec<_>>();
438 log::info!("LSP: Returning definitions in files {:?}", &files);
439 Ok(Some(lsp::GotoDefinitionResponse::Array(
440 files
441 .into_iter()
442 .map(|file| lsp::Location {
443 uri: lsp::Url::from_file_path(file).unwrap(),
444 range: Default::default(),
445 })
446 .collect(),
447 )))
448 }
449 }
450 });
451
452 fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>({
453 let rng = rng.clone();
454 move |_, _| {
455 let mut highlights = Vec::new();
456 let highlight_count = rng.lock().gen_range(1..=5);
457 for _ in 0..highlight_count {
458 let start_row = rng.lock().gen_range(0..100);
459 let start_column = rng.lock().gen_range(0..100);
460 let start = PointUtf16::new(start_row, start_column);
461 let end_row = rng.lock().gen_range(0..100);
462 let end_column = rng.lock().gen_range(0..100);
463 let end = PointUtf16::new(end_row, end_column);
464 let range = if start > end { end..start } else { start..end };
465 highlights.push(lsp::DocumentHighlight {
466 range: range_to_lsp(range.clone()),
467 kind: Some(lsp::DocumentHighlightKind::READ),
468 });
469 }
470 highlights.sort_unstable_by_key(|highlight| {
471 (highlight.range.start, highlight.range.end)
472 });
473 async move { Ok(Some(highlights)) }
474 }
475 });
476 }
477 })),
478 ..Default::default()
479 }))
480 .await;
481 client.language_registry.add(Arc::new(language));
482
483 while op_start_signal.next().await.is_some() {
484 if let Err(error) = randomly_mutate_client(&mut client, rng.clone(), &mut cx).await {
485 log::error!("{} error: {:?}", client.username, error);
486 }
487
488 cx.background().simulate_random_delay().await;
489 }
490 log::info!("{}: done", client.username);
491
492 (client, cx)
493}
494
495async fn randomly_mutate_client(
496 client: &mut TestClient,
497 rng: Arc<Mutex<StdRng>>,
498 cx: &mut TestAppContext,
499) -> anyhow::Result<()> {
500 let active_call = cx.read(ActiveCall::global);
501 if active_call.read_with(cx, |call, _| call.incoming().borrow().is_some()) {
502 if rng.lock().gen() {
503 log::info!("{}: accepting incoming call", client.username);
504 active_call
505 .update(cx, |call, cx| call.accept_incoming(cx))
506 .await?;
507 } else {
508 log::info!("{}: declining incoming call", client.username);
509 active_call.update(cx, |call, _| call.decline_incoming())?;
510 }
511 } else {
512 let available_contacts = client.user_store.read_with(cx, |user_store, _| {
513 user_store
514 .contacts()
515 .iter()
516 .filter(|contact| contact.online && !contact.busy)
517 .cloned()
518 .collect::<Vec<_>>()
519 });
520
521 let distribution = rng.lock().gen_range(0..100);
522 match distribution {
523 0..=29 if !available_contacts.is_empty() => {
524 let contact = available_contacts.choose(&mut *rng.lock()).unwrap();
525 log::info!(
526 "{}: inviting {}",
527 client.username,
528 contact.user.github_login
529 );
530 active_call
531 .update(cx, |call, cx| call.invite(contact.user.id, None, cx))
532 .await?;
533 }
534 30..=39 if active_call.read_with(cx, |call, _| call.room().is_some()) => {
535 log::info!("{}: hanging up", client.username);
536 active_call.update(cx, |call, cx| call.hang_up(cx))?;
537 }
538 _ => {}
539 }
540 }
541
542 let remote_projects =
543 if let Some(room) = active_call.read_with(cx, |call, _| call.room().cloned()) {
544 room.read_with(cx, |room, _| {
545 room.remote_participants()
546 .values()
547 .flat_map(|participant| participant.projects.clone())
548 .collect::<Vec<_>>()
549 })
550 } else {
551 Default::default()
552 };
553
554 let project = if remote_projects.is_empty() || rng.lock().gen() {
555 if client.local_projects.is_empty() || rng.lock().gen() {
556 let dir_paths = client.fs.directories().await;
557 let local_project = if dir_paths.is_empty() || rng.lock().gen() {
558 let root_path = client.create_new_root_dir();
559 client.fs.create_dir(&root_path).await.unwrap();
560 client
561 .fs
562 .create_file(&root_path.join("main.rs"), Default::default())
563 .await
564 .unwrap();
565 log::info!(
566 "{}: opening local project at {:?}",
567 client.username,
568 root_path
569 );
570 client.build_local_project(root_path, cx).await.0
571 } else {
572 let root_path = dir_paths.choose(&mut *rng.lock()).unwrap();
573 log::info!(
574 "{}: opening local project at {:?}",
575 client.username,
576 root_path
577 );
578 client.build_local_project(root_path, cx).await.0
579 };
580 client.local_projects.push(local_project.clone());
581 local_project
582 } else {
583 client
584 .local_projects
585 .choose(&mut *rng.lock())
586 .unwrap()
587 .clone()
588 }
589 } else {
590 if client.remote_projects.is_empty() || rng.lock().gen() {
591 let remote_project_id = remote_projects.choose(&mut *rng.lock()).unwrap().id;
592 let remote_project = if let Some(project) =
593 client.remote_projects.iter().find(|project| {
594 project.read_with(cx, |project, _| {
595 project.remote_id() == Some(remote_project_id)
596 })
597 }) {
598 project.clone()
599 } else {
600 log::info!(
601 "{}: opening remote project {}",
602 client.username,
603 remote_project_id
604 );
605 let remote_project = Project::remote(
606 remote_project_id,
607 client.client.clone(),
608 client.user_store.clone(),
609 client.language_registry.clone(),
610 FakeFs::new(cx.background()),
611 cx.to_async(),
612 )
613 .await?;
614 client.remote_projects.push(remote_project.clone());
615 remote_project
616 };
617
618 remote_project
619 } else {
620 client
621 .remote_projects
622 .choose(&mut *rng.lock())
623 .unwrap()
624 .clone()
625 }
626 };
627
628 if active_call.read_with(cx, |call, _| call.room().is_some()) {
629 if let Err(error) = active_call
630 .update(cx, |call, cx| call.share_project(project.clone(), cx))
631 .await
632 {
633 log::error!("{}: error sharing project, {:?}", client.username, error);
634 }
635 }
636
637 let buffers = client.buffers.entry(project.clone()).or_default();
638 let buffer = if buffers.is_empty() || rng.lock().gen() {
639 let worktree = if let Some(worktree) = project.read_with(cx, |project, cx| {
640 project
641 .worktrees(cx)
642 .filter(|worktree| {
643 let worktree = worktree.read(cx);
644 worktree.is_visible() && worktree.entries(false).any(|e| e.is_file())
645 })
646 .choose(&mut *rng.lock())
647 }) {
648 worktree
649 } else {
650 cx.background().simulate_random_delay().await;
651 return Ok(());
652 };
653
654 let (worktree_root_name, project_path) = worktree.read_with(cx, |worktree, _| {
655 let entry = worktree
656 .entries(false)
657 .filter(|e| e.is_file())
658 .choose(&mut *rng.lock())
659 .unwrap();
660 (
661 worktree.root_name().to_string(),
662 (worktree.id(), entry.path.clone()),
663 )
664 });
665 log::info!(
666 "{}: opening path {:?} in worktree {} ({})",
667 client.username,
668 project_path.1,
669 project_path.0,
670 worktree_root_name,
671 );
672 let buffer = project
673 .update(cx, |project, cx| {
674 project.open_buffer(project_path.clone(), cx)
675 })
676 .await?;
677 log::info!(
678 "{}: opened path {:?} in worktree {} ({}) with buffer id {}",
679 client.username,
680 project_path.1,
681 project_path.0,
682 worktree_root_name,
683 buffer.read_with(cx, |buffer, _| buffer.remote_id())
684 );
685 buffers.insert(buffer.clone());
686 buffer
687 } else {
688 buffers.iter().choose(&mut *rng.lock()).unwrap().clone()
689 };
690
691 let choice = rng.lock().gen_range(0..100);
692 match choice {
693 0..=9 => {
694 cx.update(|cx| {
695 log::info!(
696 "{}: dropping buffer {:?}",
697 client.username,
698 buffer.read(cx).file().unwrap().full_path(cx)
699 );
700 buffers.remove(&buffer);
701 drop(buffer);
702 });
703 }
704 10..=19 => {
705 let completions = project.update(cx, |project, cx| {
706 log::info!(
707 "{}: requesting completions for buffer {} ({:?})",
708 client.username,
709 buffer.read(cx).remote_id(),
710 buffer.read(cx).file().unwrap().full_path(cx)
711 );
712 let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
713 project.completions(&buffer, offset, cx)
714 });
715 let completions = cx.background().spawn(async move {
716 completions
717 .await
718 .map_err(|err| anyhow!("completions request failed: {:?}", err))
719 });
720 if rng.lock().gen_bool(0.3) {
721 log::info!("{}: detaching completions request", client.username);
722 cx.update(|cx| completions.detach_and_log_err(cx));
723 } else {
724 completions.await?;
725 }
726 }
727 20..=29 => {
728 let code_actions = project.update(cx, |project, cx| {
729 log::info!(
730 "{}: requesting code actions for buffer {} ({:?})",
731 client.username,
732 buffer.read(cx).remote_id(),
733 buffer.read(cx).file().unwrap().full_path(cx)
734 );
735 let range = buffer.read(cx).random_byte_range(0, &mut *rng.lock());
736 project.code_actions(&buffer, range, cx)
737 });
738 let code_actions = cx.background().spawn(async move {
739 code_actions
740 .await
741 .map_err(|err| anyhow!("code actions request failed: {:?}", err))
742 });
743 if rng.lock().gen_bool(0.3) {
744 log::info!("{}: detaching code actions request", client.username);
745 cx.update(|cx| code_actions.detach_and_log_err(cx));
746 } else {
747 code_actions.await?;
748 }
749 }
750 30..=39 if buffer.read_with(cx, |buffer, _| buffer.is_dirty()) => {
751 let (requested_version, save) = buffer.update(cx, |buffer, cx| {
752 log::info!(
753 "{}: saving buffer {} ({:?})",
754 client.username,
755 buffer.remote_id(),
756 buffer.file().unwrap().full_path(cx)
757 );
758 (buffer.version(), buffer.save(cx))
759 });
760 let save = cx.background().spawn(async move {
761 let (saved_version, _, _) = save
762 .await
763 .map_err(|err| anyhow!("save request failed: {:?}", err))?;
764 assert!(saved_version.observed_all(&requested_version));
765 Ok::<_, anyhow::Error>(())
766 });
767 if rng.lock().gen_bool(0.3) {
768 log::info!("{}: detaching save request", client.username);
769 cx.update(|cx| save.detach_and_log_err(cx));
770 } else {
771 save.await?;
772 }
773 }
774 40..=44 => {
775 let prepare_rename = project.update(cx, |project, cx| {
776 log::info!(
777 "{}: preparing rename for buffer {} ({:?})",
778 client.username,
779 buffer.read(cx).remote_id(),
780 buffer.read(cx).file().unwrap().full_path(cx)
781 );
782 let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
783 project.prepare_rename(buffer, offset, cx)
784 });
785 let prepare_rename = cx.background().spawn(async move {
786 prepare_rename
787 .await
788 .map_err(|err| anyhow!("prepare rename request failed: {:?}", err))
789 });
790 if rng.lock().gen_bool(0.3) {
791 log::info!("{}: detaching prepare rename request", client.username);
792 cx.update(|cx| prepare_rename.detach_and_log_err(cx));
793 } else {
794 prepare_rename.await?;
795 }
796 }
797 45..=49 => {
798 let definitions = project.update(cx, |project, cx| {
799 log::info!(
800 "{}: requesting definitions for buffer {} ({:?})",
801 client.username,
802 buffer.read(cx).remote_id(),
803 buffer.read(cx).file().unwrap().full_path(cx)
804 );
805 let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
806 project.definition(&buffer, offset, cx)
807 });
808 let definitions = cx.background().spawn(async move {
809 definitions
810 .await
811 .map_err(|err| anyhow!("definitions request failed: {:?}", err))
812 });
813 if rng.lock().gen_bool(0.3) {
814 log::info!("{}: detaching definitions request", client.username);
815 cx.update(|cx| definitions.detach_and_log_err(cx));
816 } else {
817 buffers.extend(definitions.await?.into_iter().map(|loc| loc.target.buffer));
818 }
819 }
820 50..=54 => {
821 let highlights = project.update(cx, |project, cx| {
822 log::info!(
823 "{}: requesting highlights for buffer {} ({:?})",
824 client.username,
825 buffer.read(cx).remote_id(),
826 buffer.read(cx).file().unwrap().full_path(cx)
827 );
828 let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
829 project.document_highlights(&buffer, offset, cx)
830 });
831 let highlights = cx.background().spawn(async move {
832 highlights
833 .await
834 .map_err(|err| anyhow!("highlights request failed: {:?}", err))
835 });
836 if rng.lock().gen_bool(0.3) {
837 log::info!("{}: detaching highlights request", client.username);
838 cx.update(|cx| highlights.detach_and_log_err(cx));
839 } else {
840 highlights.await?;
841 }
842 }
843 55..=59 => {
844 let search = project.update(cx, |project, cx| {
845 let query = rng.lock().gen_range('a'..='z');
846 log::info!("{}: project-wide search {:?}", client.username, query);
847 project.search(SearchQuery::text(query, false, false), cx)
848 });
849 let search = cx.background().spawn(async move {
850 search
851 .await
852 .map_err(|err| anyhow!("search request failed: {:?}", err))
853 });
854 if rng.lock().gen_bool(0.3) {
855 log::info!("{}: detaching search request", client.username);
856 cx.update(|cx| search.detach_and_log_err(cx));
857 } else {
858 buffers.extend(search.await?.into_keys());
859 }
860 }
861 60..=79 => {
862 let worktree = project
863 .read_with(cx, |project, cx| {
864 project
865 .worktrees(cx)
866 .filter(|worktree| {
867 let worktree = worktree.read(cx);
868 worktree.is_visible()
869 && worktree.entries(false).any(|e| e.is_file())
870 && worktree.root_entry().map_or(false, |e| e.is_dir())
871 })
872 .choose(&mut *rng.lock())
873 })
874 .unwrap();
875 let (worktree_id, worktree_root_name) = worktree.read_with(cx, |worktree, _| {
876 (worktree.id(), worktree.root_name().to_string())
877 });
878
879 let mut new_name = String::new();
880 for _ in 0..10 {
881 let letter = rng.lock().gen_range('a'..='z');
882 new_name.push(letter);
883 }
884
885 let is_dir = rng.lock().gen::<bool>();
886 let mut new_path = PathBuf::new();
887 new_path.push(new_name);
888 if !is_dir {
889 new_path.set_extension("rs");
890 }
891 log::info!(
892 "{}: creating {:?} in worktree {} ({})",
893 client.username,
894 new_path,
895 worktree_id,
896 worktree_root_name,
897 );
898 project
899 .update(cx, |project, cx| {
900 project.create_entry((worktree_id, new_path), is_dir, cx)
901 })
902 .unwrap()
903 .await?;
904 }
905 _ => {
906 buffer.update(cx, |buffer, cx| {
907 log::info!(
908 "{}: updating buffer {} ({:?})",
909 client.username,
910 buffer.remote_id(),
911 buffer.file().unwrap().full_path(cx)
912 );
913 if rng.lock().gen_bool(0.7) {
914 buffer.randomly_edit(&mut *rng.lock(), 5, cx);
915 } else {
916 buffer.randomly_undo_redo(&mut *rng.lock(), cx);
917 }
918 });
919 }
920 }
921
922 Ok(())
923}