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 }
361 }
362
363 for (client, mut cx) in clients {
364 cx.update(|cx| {
365 cx.clear_globals();
366 drop(client);
367 });
368 }
369}
370
371async fn simulate_client(
372 mut client: TestClient,
373 mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
374 rng: Arc<Mutex<StdRng>>,
375 mut cx: TestAppContext,
376) -> (TestClient, TestAppContext) {
377 // Setup language server
378 let mut language = Language::new(
379 LanguageConfig {
380 name: "Rust".into(),
381 path_suffixes: vec!["rs".to_string()],
382 ..Default::default()
383 },
384 None,
385 );
386 let _fake_language_servers = language
387 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
388 name: "the-fake-language-server",
389 capabilities: lsp::LanguageServer::full_capabilities(),
390 initializer: Some(Box::new({
391 let rng = rng.clone();
392 let fs = client.fs.clone();
393 move |fake_server: &mut FakeLanguageServer| {
394 fake_server.handle_request::<lsp::request::Completion, _, _>(
395 |_, _| async move {
396 Ok(Some(lsp::CompletionResponse::Array(vec![
397 lsp::CompletionItem {
398 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
399 range: lsp::Range::new(
400 lsp::Position::new(0, 0),
401 lsp::Position::new(0, 0),
402 ),
403 new_text: "the-new-text".to_string(),
404 })),
405 ..Default::default()
406 },
407 ])))
408 },
409 );
410
411 fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
412 |_, _| async move {
413 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
414 lsp::CodeAction {
415 title: "the-code-action".to_string(),
416 ..Default::default()
417 },
418 )]))
419 },
420 );
421
422 fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
423 |params, _| async move {
424 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
425 params.position,
426 params.position,
427 ))))
428 },
429 );
430
431 fake_server.handle_request::<lsp::request::GotoDefinition, _, _>({
432 let fs = fs.clone();
433 let rng = rng.clone();
434 move |_, _| {
435 let fs = fs.clone();
436 let rng = rng.clone();
437 async move {
438 let files = fs.files().await;
439 let mut rng = rng.lock();
440 let count = rng.gen_range::<usize, _>(1..3);
441 let files = (0..count)
442 .map(|_| files.choose(&mut *rng).unwrap())
443 .collect::<Vec<_>>();
444 log::info!("LSP: Returning definitions in files {:?}", &files);
445 Ok(Some(lsp::GotoDefinitionResponse::Array(
446 files
447 .into_iter()
448 .map(|file| lsp::Location {
449 uri: lsp::Url::from_file_path(file).unwrap(),
450 range: Default::default(),
451 })
452 .collect(),
453 )))
454 }
455 }
456 });
457
458 fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>({
459 let rng = rng.clone();
460 move |_, _| {
461 let mut highlights = Vec::new();
462 let highlight_count = rng.lock().gen_range(1..=5);
463 for _ in 0..highlight_count {
464 let start_row = rng.lock().gen_range(0..100);
465 let start_column = rng.lock().gen_range(0..100);
466 let start = PointUtf16::new(start_row, start_column);
467 let end_row = rng.lock().gen_range(0..100);
468 let end_column = rng.lock().gen_range(0..100);
469 let end = PointUtf16::new(end_row, end_column);
470 let range = if start > end { end..start } else { start..end };
471 highlights.push(lsp::DocumentHighlight {
472 range: range_to_lsp(range.clone()),
473 kind: Some(lsp::DocumentHighlightKind::READ),
474 });
475 }
476 highlights.sort_unstable_by_key(|highlight| {
477 (highlight.range.start, highlight.range.end)
478 });
479 async move { Ok(Some(highlights)) }
480 }
481 });
482 }
483 })),
484 ..Default::default()
485 }))
486 .await;
487 client.language_registry.add(Arc::new(language));
488
489 while op_start_signal.next().await.is_some() {
490 if let Err(error) = randomly_mutate_client(&mut client, rng.clone(), &mut cx).await {
491 log::error!("{} error: {:?}", client.username, error);
492 }
493
494 cx.background().simulate_random_delay().await;
495 }
496 log::info!("{}: done", client.username);
497
498 (client, cx)
499}
500
501async fn randomly_mutate_client(
502 client: &mut TestClient,
503 rng: Arc<Mutex<StdRng>>,
504 cx: &mut TestAppContext,
505) -> anyhow::Result<()> {
506 let active_call = cx.read(ActiveCall::global);
507 if active_call.read_with(cx, |call, _| call.incoming().borrow().is_some()) {
508 if rng.lock().gen() {
509 log::info!("{}: accepting incoming call", client.username);
510 active_call
511 .update(cx, |call, cx| call.accept_incoming(cx))
512 .await?;
513 } else {
514 log::info!("{}: declining incoming call", client.username);
515 active_call.update(cx, |call, _| call.decline_incoming())?;
516 }
517 } else {
518 let available_contacts = client.user_store.read_with(cx, |user_store, _| {
519 user_store
520 .contacts()
521 .iter()
522 .filter(|contact| contact.online && !contact.busy)
523 .cloned()
524 .collect::<Vec<_>>()
525 });
526
527 let distribution = rng.lock().gen_range(0..100);
528 match distribution {
529 0..=29 if !available_contacts.is_empty() => {
530 let contact = available_contacts.choose(&mut *rng.lock()).unwrap();
531 log::info!(
532 "{}: inviting {}",
533 client.username,
534 contact.user.github_login
535 );
536 active_call
537 .update(cx, |call, cx| call.invite(contact.user.id, None, cx))
538 .await?;
539 }
540 30..=39 if active_call.read_with(cx, |call, _| call.room().is_some()) => {
541 log::info!("{}: hanging up", client.username);
542 active_call.update(cx, |call, cx| call.hang_up(cx))?;
543 }
544 _ => {}
545 }
546 }
547
548 let remote_projects =
549 if let Some(room) = active_call.read_with(cx, |call, _| call.room().cloned()) {
550 room.read_with(cx, |room, _| {
551 room.remote_participants()
552 .values()
553 .flat_map(|participant| participant.projects.clone())
554 .collect::<Vec<_>>()
555 })
556 } else {
557 Default::default()
558 };
559
560 let project = if remote_projects.is_empty() || rng.lock().gen() {
561 if client.local_projects.is_empty() || rng.lock().gen() {
562 let dir_paths = client.fs.directories().await;
563 let local_project = if dir_paths.is_empty() || rng.lock().gen() {
564 let root_path = client.create_new_root_dir();
565 client.fs.create_dir(&root_path).await.unwrap();
566 client
567 .fs
568 .create_file(&root_path.join("main.rs"), Default::default())
569 .await
570 .unwrap();
571 log::info!(
572 "{}: opening local project at {:?}",
573 client.username,
574 root_path
575 );
576 client.build_local_project(root_path, cx).await.0
577 } else {
578 let root_path = dir_paths.choose(&mut *rng.lock()).unwrap();
579 log::info!(
580 "{}: opening local project at {:?}",
581 client.username,
582 root_path
583 );
584 client.build_local_project(root_path, cx).await.0
585 };
586 client.local_projects.push(local_project.clone());
587 local_project
588 } else {
589 client
590 .local_projects
591 .choose(&mut *rng.lock())
592 .unwrap()
593 .clone()
594 }
595 } else {
596 if client.remote_projects.is_empty() || rng.lock().gen() {
597 let remote_project_id = remote_projects.choose(&mut *rng.lock()).unwrap().id;
598 let remote_project = if let Some(project) =
599 client.remote_projects.iter().find(|project| {
600 project.read_with(cx, |project, _| {
601 project.remote_id() == Some(remote_project_id)
602 })
603 }) {
604 project.clone()
605 } else {
606 log::info!(
607 "{}: opening remote project {}",
608 client.username,
609 remote_project_id
610 );
611 let call = cx.read(ActiveCall::global);
612 let room = call.read_with(cx, |call, _| call.room().unwrap().clone());
613 let remote_project = room
614 .update(cx, |room, cx| {
615 room.join_project(
616 remote_project_id,
617 client.language_registry.clone(),
618 FakeFs::new(cx.background().clone()),
619 cx,
620 )
621 })
622 .await?;
623 client.remote_projects.push(remote_project.clone());
624 remote_project
625 };
626
627 remote_project
628 } else {
629 client
630 .remote_projects
631 .choose(&mut *rng.lock())
632 .unwrap()
633 .clone()
634 }
635 };
636
637 if active_call.read_with(cx, |call, _| call.room().is_some()) {
638 if let Err(error) = active_call
639 .update(cx, |call, cx| call.share_project(project.clone(), cx))
640 .await
641 {
642 log::error!("{}: error sharing project, {:?}", client.username, error);
643 }
644 }
645
646 let buffers = client.buffers.entry(project.clone()).or_default();
647 let buffer = if buffers.is_empty() || rng.lock().gen() {
648 let worktree = if let Some(worktree) = project.read_with(cx, |project, cx| {
649 project
650 .worktrees(cx)
651 .filter(|worktree| {
652 let worktree = worktree.read(cx);
653 worktree.is_visible() && worktree.entries(false).any(|e| e.is_file())
654 })
655 .choose(&mut *rng.lock())
656 }) {
657 worktree
658 } else {
659 cx.background().simulate_random_delay().await;
660 return Ok(());
661 };
662
663 let (worktree_root_name, project_path) = worktree.read_with(cx, |worktree, _| {
664 let entry = worktree
665 .entries(false)
666 .filter(|e| e.is_file())
667 .choose(&mut *rng.lock())
668 .unwrap();
669 (
670 worktree.root_name().to_string(),
671 (worktree.id(), entry.path.clone()),
672 )
673 });
674 log::info!(
675 "{}: opening path {:?} in worktree {} ({})",
676 client.username,
677 project_path.1,
678 project_path.0,
679 worktree_root_name,
680 );
681 let buffer = project
682 .update(cx, |project, cx| {
683 project.open_buffer(project_path.clone(), cx)
684 })
685 .await?;
686 log::info!(
687 "{}: opened path {:?} in worktree {} ({}) with buffer id {}",
688 client.username,
689 project_path.1,
690 project_path.0,
691 worktree_root_name,
692 buffer.read_with(cx, |buffer, _| buffer.remote_id())
693 );
694 buffers.insert(buffer.clone());
695 buffer
696 } else {
697 buffers.iter().choose(&mut *rng.lock()).unwrap().clone()
698 };
699
700 let choice = rng.lock().gen_range(0..100);
701 match choice {
702 0..=9 => {
703 cx.update(|cx| {
704 log::info!(
705 "{}: dropping buffer {:?}",
706 client.username,
707 buffer.read(cx).file().unwrap().full_path(cx)
708 );
709 buffers.remove(&buffer);
710 drop(buffer);
711 });
712 }
713 10..=19 => {
714 let completions = project.update(cx, |project, cx| {
715 log::info!(
716 "{}: requesting completions for buffer {} ({:?})",
717 client.username,
718 buffer.read(cx).remote_id(),
719 buffer.read(cx).file().unwrap().full_path(cx)
720 );
721 let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
722 project.completions(&buffer, offset, cx)
723 });
724 let completions = cx.background().spawn(async move {
725 completions
726 .await
727 .map_err(|err| anyhow!("completions request failed: {:?}", err))
728 });
729 if rng.lock().gen_bool(0.3) {
730 log::info!("{}: detaching completions request", client.username);
731 cx.update(|cx| completions.detach_and_log_err(cx));
732 } else {
733 completions.await?;
734 }
735 }
736 20..=29 => {
737 let code_actions = project.update(cx, |project, cx| {
738 log::info!(
739 "{}: requesting code actions for buffer {} ({:?})",
740 client.username,
741 buffer.read(cx).remote_id(),
742 buffer.read(cx).file().unwrap().full_path(cx)
743 );
744 let range = buffer.read(cx).random_byte_range(0, &mut *rng.lock());
745 project.code_actions(&buffer, range, cx)
746 });
747 let code_actions = cx.background().spawn(async move {
748 code_actions
749 .await
750 .map_err(|err| anyhow!("code actions request failed: {:?}", err))
751 });
752 if rng.lock().gen_bool(0.3) {
753 log::info!("{}: detaching code actions request", client.username);
754 cx.update(|cx| code_actions.detach_and_log_err(cx));
755 } else {
756 code_actions.await?;
757 }
758 }
759 30..=39 if buffer.read_with(cx, |buffer, _| buffer.is_dirty()) => {
760 let (requested_version, save) = buffer.update(cx, |buffer, cx| {
761 log::info!(
762 "{}: saving buffer {} ({:?})",
763 client.username,
764 buffer.remote_id(),
765 buffer.file().unwrap().full_path(cx)
766 );
767 (buffer.version(), buffer.save(cx))
768 });
769 let save = cx.background().spawn(async move {
770 let (saved_version, _, _) = save
771 .await
772 .map_err(|err| anyhow!("save request failed: {:?}", err))?;
773 assert!(saved_version.observed_all(&requested_version));
774 Ok::<_, anyhow::Error>(())
775 });
776 if rng.lock().gen_bool(0.3) {
777 log::info!("{}: detaching save request", client.username);
778 cx.update(|cx| save.detach_and_log_err(cx));
779 } else {
780 save.await?;
781 }
782 }
783 40..=44 => {
784 let prepare_rename = project.update(cx, |project, cx| {
785 log::info!(
786 "{}: preparing rename for buffer {} ({:?})",
787 client.username,
788 buffer.read(cx).remote_id(),
789 buffer.read(cx).file().unwrap().full_path(cx)
790 );
791 let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
792 project.prepare_rename(buffer, offset, cx)
793 });
794 let prepare_rename = cx.background().spawn(async move {
795 prepare_rename
796 .await
797 .map_err(|err| anyhow!("prepare rename request failed: {:?}", err))
798 });
799 if rng.lock().gen_bool(0.3) {
800 log::info!("{}: detaching prepare rename request", client.username);
801 cx.update(|cx| prepare_rename.detach_and_log_err(cx));
802 } else {
803 prepare_rename.await?;
804 }
805 }
806 45..=49 => {
807 let definitions = project.update(cx, |project, cx| {
808 log::info!(
809 "{}: requesting definitions for buffer {} ({:?})",
810 client.username,
811 buffer.read(cx).remote_id(),
812 buffer.read(cx).file().unwrap().full_path(cx)
813 );
814 let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
815 project.definition(&buffer, offset, cx)
816 });
817 let definitions = cx.background().spawn(async move {
818 definitions
819 .await
820 .map_err(|err| anyhow!("definitions request failed: {:?}", err))
821 });
822 if rng.lock().gen_bool(0.3) {
823 log::info!("{}: detaching definitions request", client.username);
824 cx.update(|cx| definitions.detach_and_log_err(cx));
825 } else {
826 buffers.extend(definitions.await?.into_iter().map(|loc| loc.target.buffer));
827 }
828 }
829 50..=54 => {
830 let highlights = project.update(cx, |project, cx| {
831 log::info!(
832 "{}: requesting highlights for buffer {} ({:?})",
833 client.username,
834 buffer.read(cx).remote_id(),
835 buffer.read(cx).file().unwrap().full_path(cx)
836 );
837 let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
838 project.document_highlights(&buffer, offset, cx)
839 });
840 let highlights = cx.background().spawn(async move {
841 highlights
842 .await
843 .map_err(|err| anyhow!("highlights request failed: {:?}", err))
844 });
845 if rng.lock().gen_bool(0.3) {
846 log::info!("{}: detaching highlights request", client.username);
847 cx.update(|cx| highlights.detach_and_log_err(cx));
848 } else {
849 highlights.await?;
850 }
851 }
852 55..=59 => {
853 let search = project.update(cx, |project, cx| {
854 let query = rng.lock().gen_range('a'..='z');
855 log::info!("{}: project-wide search {:?}", client.username, query);
856 project.search(SearchQuery::text(query, false, false), cx)
857 });
858 let search = cx.background().spawn(async move {
859 search
860 .await
861 .map_err(|err| anyhow!("search request failed: {:?}", err))
862 });
863 if rng.lock().gen_bool(0.3) {
864 log::info!("{}: detaching search request", client.username);
865 cx.update(|cx| search.detach_and_log_err(cx));
866 } else {
867 buffers.extend(search.await?.into_keys());
868 }
869 }
870 60..=79 => {
871 let worktree = project
872 .read_with(cx, |project, cx| {
873 project
874 .worktrees(cx)
875 .filter(|worktree| {
876 let worktree = worktree.read(cx);
877 worktree.is_visible()
878 && worktree.entries(false).any(|e| e.is_file())
879 && worktree.root_entry().map_or(false, |e| e.is_dir())
880 })
881 .choose(&mut *rng.lock())
882 })
883 .unwrap();
884 let (worktree_id, worktree_root_name) = worktree.read_with(cx, |worktree, _| {
885 (worktree.id(), worktree.root_name().to_string())
886 });
887
888 let mut new_name = String::new();
889 for _ in 0..10 {
890 let letter = rng.lock().gen_range('a'..='z');
891 new_name.push(letter);
892 }
893
894 let is_dir = rng.lock().gen::<bool>();
895 let mut new_path = PathBuf::new();
896 new_path.push(new_name);
897 if !is_dir {
898 new_path.set_extension("rs");
899 }
900 log::info!(
901 "{}: creating {:?} in worktree {} ({})",
902 client.username,
903 new_path,
904 worktree_id,
905 worktree_root_name,
906 );
907 project
908 .update(cx, |project, cx| {
909 project.create_entry((worktree_id, new_path), is_dir, cx)
910 })
911 .unwrap()
912 .await?;
913 }
914 _ => {
915 buffer.update(cx, |buffer, cx| {
916 log::info!(
917 "{}: updating buffer {} ({:?})",
918 client.username,
919 buffer.remote_id(),
920 buffer.file().unwrap().full_path(cx)
921 );
922 if rng.lock().gen_bool(0.7) {
923 buffer.randomly_edit(&mut *rng.lock(), 5, cx);
924 } else {
925 buffer.randomly_undo_redo(&mut *rng.lock(), cx);
926 }
927 });
928 }
929 }
930
931 Ok(())
932}