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, 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}