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