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