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