randomized_integration_tests.rs

  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}