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