randomized_integration_tests.rs

   1use crate::{
   2    db::{self, NewUserParams, UserId},
   3    rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
   4    tests::{TestClient, TestServer},
   5};
   6use anyhow::{anyhow, Result};
   7use call::ActiveCall;
   8use client::RECEIVE_TIMEOUT;
   9use collections::{BTreeMap, HashSet};
  10use fs::Fs as _;
  11use futures::StreamExt as _;
  12use gpui::{executor::Deterministic, ModelHandle, 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::{
  19    env,
  20    ops::Range,
  21    path::{Path, PathBuf},
  22    rc::Rc,
  23    sync::Arc,
  24};
  25
  26#[gpui::test(iterations = 100)]
  27async fn test_random_collaboration(
  28    cx: &mut TestAppContext,
  29    deterministic: Arc<Deterministic>,
  30    mut rng: StdRng,
  31) {
  32    deterministic.forbid_parking();
  33
  34    let max_peers = env::var("MAX_PEERS")
  35        .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
  36        .unwrap_or(5);
  37
  38    let max_operations = env::var("OPERATIONS")
  39        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
  40        .unwrap_or(10);
  41
  42    let mut server = TestServer::start(&deterministic).await;
  43    let db = server.app_state.db.clone();
  44
  45    let mut users = Vec::new();
  46    for ix in 0..max_peers {
  47        let username = format!("user-{}", ix + 1);
  48        let user_id = db
  49            .create_user(
  50                &format!("{username}@example.com"),
  51                false,
  52                NewUserParams {
  53                    github_login: username.clone(),
  54                    github_user_id: (ix + 1) as i32,
  55                    invite_count: 0,
  56                },
  57            )
  58            .await
  59            .unwrap()
  60            .user_id;
  61        users.push(UserTestPlan {
  62            user_id,
  63            username,
  64            online: false,
  65            next_root_id: 0,
  66        });
  67    }
  68
  69    for (ix, user_a) in users.iter().enumerate() {
  70        for user_b in &users[ix + 1..] {
  71            server
  72                .app_state
  73                .db
  74                .send_contact_request(user_a.user_id, user_b.user_id)
  75                .await
  76                .unwrap();
  77            server
  78                .app_state
  79                .db
  80                .respond_to_contact_request(user_b.user_id, user_a.user_id, true)
  81                .await
  82                .unwrap();
  83        }
  84    }
  85
  86    let plan = Arc::new(Mutex::new(TestPlan {
  87        users,
  88        allow_server_restarts: rng.gen_bool(0.7),
  89        allow_client_reconnection: rng.gen_bool(0.7),
  90        allow_client_disconnection: rng.gen_bool(0.1),
  91        rng,
  92    }));
  93
  94    let mut clients = Vec::new();
  95    let mut client_tasks = Vec::new();
  96    let mut operation_channels = Vec::new();
  97    let mut next_entity_id = 100000;
  98
  99    let mut i = 0;
 100    while i < max_operations {
 101        let next_operation = plan.lock().next_operation(&clients).await;
 102        match next_operation {
 103            Operation::AddConnection { user_id } => {
 104                let username = {
 105                    let mut plan = plan.lock();
 106                    let mut user = plan.user(user_id);
 107                    user.online = true;
 108                    user.username.clone()
 109                };
 110                log::info!("Adding new connection for {}", username);
 111                next_entity_id += 100000;
 112                let mut client_cx = TestAppContext::new(
 113                    cx.foreground_platform(),
 114                    cx.platform(),
 115                    deterministic.build_foreground(next_entity_id),
 116                    deterministic.build_background(),
 117                    cx.font_cache(),
 118                    cx.leak_detector(),
 119                    next_entity_id,
 120                    cx.function_name.clone(),
 121                );
 122
 123                let (operation_tx, operation_rx) = futures::channel::mpsc::unbounded();
 124                let client = Rc::new(server.create_client(&mut client_cx, &username).await);
 125                operation_channels.push(operation_tx);
 126                clients.push((client.clone(), client_cx.clone()));
 127                client_tasks.push(client_cx.foreground().spawn(simulate_client(
 128                    client,
 129                    operation_rx,
 130                    plan.clone(),
 131                    client_cx,
 132                )));
 133
 134                log::info!("Added connection for {}", username);
 135                i += 1;
 136            }
 137
 138            Operation::RemoveConnection { user_id } => {
 139                log::info!("Simulating full disconnection of user {}", user_id);
 140                let client_ix = clients
 141                    .iter()
 142                    .position(|(client, cx)| client.current_user_id(cx) == user_id)
 143                    .unwrap();
 144                let user_connection_ids = server
 145                    .connection_pool
 146                    .lock()
 147                    .user_connection_ids(user_id)
 148                    .collect::<Vec<_>>();
 149                assert_eq!(user_connection_ids.len(), 1);
 150                let removed_peer_id = user_connection_ids[0].into();
 151                let (client, mut client_cx) = clients.remove(client_ix);
 152                let client_task = client_tasks.remove(client_ix);
 153                operation_channels.remove(client_ix);
 154                server.forbid_connections();
 155                server.disconnect_client(removed_peer_id);
 156                deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 157                deterministic.start_waiting();
 158                log::info!("Waiting for user {} to exit...", user_id);
 159                client_task.await;
 160                deterministic.finish_waiting();
 161                server.allow_connections();
 162
 163                for project in client.remote_projects().iter() {
 164                    project.read_with(&client_cx, |project, _| {
 165                        assert!(
 166                            project.is_read_only(),
 167                            "project {:?} should be read only",
 168                            project.remote_id()
 169                        )
 170                    });
 171                }
 172
 173                for (client, cx) in &clients {
 174                    let contacts = server
 175                        .app_state
 176                        .db
 177                        .get_contacts(client.current_user_id(cx))
 178                        .await
 179                        .unwrap();
 180                    let pool = server.connection_pool.lock();
 181                    for contact in contacts {
 182                        if let db::Contact::Accepted { user_id: id, .. } = contact {
 183                            if pool.is_user_online(id) {
 184                                assert_ne!(
 185                                    id, user_id,
 186                                    "removed client is still a contact of another peer"
 187                                );
 188                            }
 189                        }
 190                    }
 191                }
 192
 193                log::info!("{} removed", client.username);
 194                plan.lock().user(user_id).online = false;
 195                client_cx.update(|cx| {
 196                    cx.clear_globals();
 197                    drop(client);
 198                });
 199                i += 1;
 200            }
 201
 202            Operation::BounceConnection { user_id } => {
 203                log::info!("Simulating temporary disconnection of user {}", user_id);
 204                let user_connection_ids = server
 205                    .connection_pool
 206                    .lock()
 207                    .user_connection_ids(user_id)
 208                    .collect::<Vec<_>>();
 209                assert_eq!(user_connection_ids.len(), 1);
 210                let peer_id = user_connection_ids[0].into();
 211                server.disconnect_client(peer_id);
 212                deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 213                i += 1;
 214            }
 215
 216            Operation::RestartServer => {
 217                log::info!("Simulating server restart");
 218                server.reset().await;
 219                deterministic.advance_clock(RECEIVE_TIMEOUT);
 220                server.start().await.unwrap();
 221                deterministic.advance_clock(CLEANUP_TIMEOUT);
 222                let environment = &server.app_state.config.zed_environment;
 223                let stale_room_ids = server
 224                    .app_state
 225                    .db
 226                    .stale_room_ids(environment, server.id())
 227                    .await
 228                    .unwrap();
 229                assert_eq!(stale_room_ids, vec![]);
 230                i += 1;
 231            }
 232
 233            Operation::MutateClients { user_ids, quiesce } => {
 234                for user_id in user_ids {
 235                    let client_ix = clients
 236                        .iter()
 237                        .position(|(client, cx)| client.current_user_id(cx) == user_id)
 238                        .unwrap();
 239                    operation_channels[client_ix].unbounded_send(()).unwrap();
 240                    i += 1;
 241                }
 242
 243                if quiesce {
 244                    deterministic.run_until_parked();
 245                }
 246            }
 247        }
 248    }
 249
 250    drop(operation_channels);
 251    deterministic.start_waiting();
 252    futures::future::join_all(client_tasks).await;
 253    deterministic.finish_waiting();
 254    deterministic.run_until_parked();
 255
 256    for (client, client_cx) in &clients {
 257        for guest_project in client.remote_projects().iter() {
 258            guest_project.read_with(client_cx, |guest_project, cx| {
 259                let host_project = clients.iter().find_map(|(client, cx)| {
 260                    let project = client
 261                        .local_projects()
 262                        .iter()
 263                        .find(|host_project| {
 264                            host_project.read_with(cx, |host_project, _| {
 265                                host_project.remote_id() == guest_project.remote_id()
 266                            })
 267                        })?
 268                        .clone();
 269                    Some((project, cx))
 270                });
 271
 272                if !guest_project.is_read_only() {
 273                    if let Some((host_project, host_cx)) = host_project {
 274                        let host_worktree_snapshots =
 275                            host_project.read_with(host_cx, |host_project, cx| {
 276                                host_project
 277                                    .worktrees(cx)
 278                                    .map(|worktree| {
 279                                        let worktree = worktree.read(cx);
 280                                        (worktree.id(), worktree.snapshot())
 281                                    })
 282                                    .collect::<BTreeMap<_, _>>()
 283                            });
 284                        let guest_worktree_snapshots = guest_project
 285                            .worktrees(cx)
 286                            .map(|worktree| {
 287                                let worktree = worktree.read(cx);
 288                                (worktree.id(), worktree.snapshot())
 289                            })
 290                            .collect::<BTreeMap<_, _>>();
 291
 292                        assert_eq!(
 293                            guest_worktree_snapshots.keys().collect::<Vec<_>>(),
 294                            host_worktree_snapshots.keys().collect::<Vec<_>>(),
 295                            "{} has different worktrees than the host",
 296                            client.username
 297                        );
 298
 299                        for (id, host_snapshot) in &host_worktree_snapshots {
 300                            let guest_snapshot = &guest_worktree_snapshots[id];
 301                            assert_eq!(
 302                                guest_snapshot.root_name(),
 303                                host_snapshot.root_name(),
 304                                "{} has different root name than the host for worktree {}",
 305                                client.username,
 306                                id
 307                            );
 308                            assert_eq!(
 309                                guest_snapshot.abs_path(),
 310                                host_snapshot.abs_path(),
 311                                "{} has different abs path than the host for worktree {}",
 312                                client.username,
 313                                id
 314                            );
 315                            assert_eq!(
 316                                guest_snapshot.entries(false).collect::<Vec<_>>(),
 317                                host_snapshot.entries(false).collect::<Vec<_>>(),
 318                                "{} has different snapshot than the host for worktree {} ({:?}) and project {:?}",
 319                                client.username,
 320                                id,
 321                                host_snapshot.abs_path(),
 322                                host_project.read_with(host_cx, |project, _| project.remote_id())
 323                            );
 324                            assert_eq!(guest_snapshot.scan_id(), host_snapshot.scan_id());
 325                        }
 326                    }
 327                }
 328
 329                guest_project.check_invariants(cx);
 330            });
 331        }
 332
 333        let buffers = client.buffers().clone();
 334        for (guest_project, guest_buffers) in &buffers {
 335            let project_id = if guest_project.read_with(client_cx, |project, _| {
 336                project.is_local() || project.is_read_only()
 337            }) {
 338                continue;
 339            } else {
 340                guest_project
 341                    .read_with(client_cx, |project, _| project.remote_id())
 342                    .unwrap()
 343            };
 344            let guest_user_id = client.user_id().unwrap();
 345
 346            let host_project = clients.iter().find_map(|(client, cx)| {
 347                let project = client
 348                    .local_projects()
 349                    .iter()
 350                    .find(|host_project| {
 351                        host_project.read_with(cx, |host_project, _| {
 352                            host_project.remote_id() == Some(project_id)
 353                        })
 354                    })?
 355                    .clone();
 356                Some((client.user_id().unwrap(), project, cx))
 357            });
 358
 359            let (host_user_id, host_project, host_cx) =
 360                if let Some((host_user_id, host_project, host_cx)) = host_project {
 361                    (host_user_id, host_project, host_cx)
 362                } else {
 363                    continue;
 364                };
 365
 366            for guest_buffer in guest_buffers {
 367                let buffer_id = guest_buffer.read_with(client_cx, |buffer, _| buffer.remote_id());
 368                let host_buffer = host_project.read_with(host_cx, |project, cx| {
 369                    project.buffer_for_id(buffer_id, cx).unwrap_or_else(|| {
 370                        panic!(
 371                            "host does not have buffer for guest:{}, peer:{:?}, id:{}",
 372                            client.username,
 373                            client.peer_id(),
 374                            buffer_id
 375                        )
 376                    })
 377                });
 378                let path = host_buffer
 379                    .read_with(host_cx, |buffer, cx| buffer.file().unwrap().full_path(cx));
 380
 381                assert_eq!(
 382                    guest_buffer.read_with(client_cx, |buffer, _| buffer.deferred_ops_len()),
 383                    0,
 384                    "{}, buffer {}, path {:?} has deferred operations",
 385                    client.username,
 386                    buffer_id,
 387                    path,
 388                );
 389                assert_eq!(
 390                    guest_buffer.read_with(client_cx, |buffer, _| buffer.text()),
 391                    host_buffer.read_with(host_cx, |buffer, _| buffer.text()),
 392                    "{}, buffer {}, path {:?}, differs from the host's buffer",
 393                    client.username,
 394                    buffer_id,
 395                    path
 396                );
 397
 398                let host_file = host_buffer.read_with(host_cx, |b, _| b.file().cloned());
 399                let guest_file = guest_buffer.read_with(client_cx, |b, _| b.file().cloned());
 400                match (host_file, guest_file) {
 401                    (Some(host_file), Some(guest_file)) => {
 402                        assert_eq!(guest_file.path(), host_file.path());
 403                        assert_eq!(guest_file.is_deleted(), host_file.is_deleted());
 404                        assert_eq!(
 405                            guest_file.mtime(),
 406                            host_file.mtime(),
 407                            "guest {} mtime does not match host {} for path {:?} in project {}",
 408                            guest_user_id,
 409                            host_user_id,
 410                            guest_file.path(),
 411                            project_id,
 412                        );
 413                    }
 414                    (None, None) => {}
 415                    (None, _) => panic!("host's file is None, guest's isn't "),
 416                    (_, None) => panic!("guest's file is None, hosts's isn't "),
 417                }
 418            }
 419        }
 420    }
 421
 422    for (client, mut cx) in clients {
 423        cx.update(|cx| {
 424            cx.clear_globals();
 425            drop(client);
 426        });
 427    }
 428}
 429
 430struct TestPlan {
 431    rng: StdRng,
 432    users: Vec<UserTestPlan>,
 433    allow_server_restarts: bool,
 434    allow_client_reconnection: bool,
 435    allow_client_disconnection: bool,
 436}
 437
 438struct UserTestPlan {
 439    user_id: UserId,
 440    username: String,
 441    next_root_id: usize,
 442    online: bool,
 443}
 444
 445#[derive(Debug)]
 446enum Operation {
 447    AddConnection {
 448        user_id: UserId,
 449    },
 450    RemoveConnection {
 451        user_id: UserId,
 452    },
 453    BounceConnection {
 454        user_id: UserId,
 455    },
 456    RestartServer,
 457    MutateClients {
 458        user_ids: Vec<UserId>,
 459        quiesce: bool,
 460    },
 461}
 462
 463#[derive(Debug)]
 464enum ClientOperation {
 465    AcceptIncomingCall,
 466    RejectIncomingCall,
 467    LeaveCall,
 468    InviteContactToCall {
 469        user_id: UserId,
 470    },
 471    OpenLocalProject {
 472        first_root_name: String,
 473    },
 474    OpenRemoteProject {
 475        host_id: UserId,
 476        first_root_name: String,
 477    },
 478    AddWorktreeToProject {
 479        project_root_name: String,
 480        new_root_path: PathBuf,
 481    },
 482    CloseRemoteProject {
 483        project_root_name: String,
 484    },
 485    OpenBuffer {
 486        project_root_name: String,
 487        full_path: PathBuf,
 488    },
 489    SearchProject {
 490        project_root_name: String,
 491        query: String,
 492        detach: bool,
 493    },
 494    EditBuffer {
 495        project_root_name: String,
 496        full_path: PathBuf,
 497        edits: Vec<(Range<usize>, Arc<str>)>,
 498    },
 499    CloseBuffer {
 500        project_root_name: String,
 501        full_path: PathBuf,
 502    },
 503    SaveBuffer {
 504        project_root_name: String,
 505        full_path: PathBuf,
 506        detach: bool,
 507    },
 508    RequestLspDataInBuffer {
 509        project_root_name: String,
 510        full_path: PathBuf,
 511        offset: usize,
 512        kind: LspRequestKind,
 513        detach: bool,
 514    },
 515    Other,
 516}
 517
 518#[derive(Debug)]
 519enum LspRequestKind {
 520    Rename,
 521    Completion,
 522    CodeAction,
 523    Definition,
 524    Highlights,
 525}
 526
 527impl TestPlan {
 528    async fn next_operation(&mut self, clients: &[(Rc<TestClient>, TestAppContext)]) -> Operation {
 529        let operation = loop {
 530            break match self.rng.gen_range(0..100) {
 531                0..=29 if clients.len() < self.users.len() => {
 532                    let user = self
 533                        .users
 534                        .iter()
 535                        .filter(|u| !u.online)
 536                        .choose(&mut self.rng)
 537                        .unwrap();
 538                    Operation::AddConnection {
 539                        user_id: user.user_id,
 540                    }
 541                }
 542                30..=34 if clients.len() > 1 && self.allow_client_disconnection => {
 543                    let (client, cx) = &clients[self.rng.gen_range(0..clients.len())];
 544                    let user_id = client.current_user_id(cx);
 545                    Operation::RemoveConnection { user_id }
 546                }
 547                35..=39 if clients.len() > 1 && self.allow_client_reconnection => {
 548                    let (client, cx) = &clients[self.rng.gen_range(0..clients.len())];
 549                    let user_id = client.current_user_id(cx);
 550                    Operation::BounceConnection { user_id }
 551                }
 552                40..=44 if self.allow_server_restarts && clients.len() > 1 => {
 553                    Operation::RestartServer
 554                }
 555                _ if !clients.is_empty() => {
 556                    let user_ids = (0..self.rng.gen_range(0..10))
 557                        .map(|_| {
 558                            let ix = self.rng.gen_range(0..clients.len());
 559                            let (client, cx) = &clients[ix];
 560                            client.current_user_id(cx)
 561                        })
 562                        .collect();
 563                    Operation::MutateClients {
 564                        user_ids,
 565                        quiesce: self.rng.gen(),
 566                    }
 567                }
 568                _ => continue,
 569            };
 570        };
 571        operation
 572    }
 573
 574    async fn next_client_operation(
 575        &mut self,
 576        client: &TestClient,
 577        cx: &TestAppContext,
 578    ) -> ClientOperation {
 579        let user_id = client.current_user_id(cx);
 580        let call = cx.read(ActiveCall::global);
 581        let operation = loop {
 582            match self.rng.gen_range(0..100) {
 583                // Mutate the call
 584                0..=29 => {
 585                    // Respond to an incoming call
 586                    if call.read_with(cx, |call, _| call.incoming().borrow().is_some()) {
 587                        break if self.rng.gen_bool(0.7) {
 588                            ClientOperation::AcceptIncomingCall
 589                        } else {
 590                            ClientOperation::RejectIncomingCall
 591                        };
 592                    }
 593
 594                    match self.rng.gen_range(0..100_u32) {
 595                        // Invite a contact to the current call
 596                        0..=70 => {
 597                            let available_contacts =
 598                                client.user_store.read_with(cx, |user_store, _| {
 599                                    user_store
 600                                        .contacts()
 601                                        .iter()
 602                                        .filter(|contact| contact.online && !contact.busy)
 603                                        .cloned()
 604                                        .collect::<Vec<_>>()
 605                                });
 606                            if !available_contacts.is_empty() {
 607                                let contact = available_contacts.choose(&mut self.rng).unwrap();
 608                                break ClientOperation::InviteContactToCall {
 609                                    user_id: UserId(contact.user.id as i32),
 610                                };
 611                            }
 612                        }
 613
 614                        // Leave the current call
 615                        71.. => {
 616                            if self.allow_client_disconnection
 617                                && call.read_with(cx, |call, _| call.room().is_some())
 618                            {
 619                                break ClientOperation::LeaveCall;
 620                            }
 621                        }
 622                    }
 623                }
 624
 625                // Mutate projects
 626                39..=59 => match self.rng.gen_range(0..100_u32) {
 627                    // Open a new project
 628                    0..=70 => {
 629                        // Open a remote project
 630                        if let Some(room) = call.read_with(cx, |call, _| call.room().cloned()) {
 631                            let existing_remote_project_ids = cx.read(|cx| {
 632                                client
 633                                    .remote_projects()
 634                                    .iter()
 635                                    .map(|p| p.read(cx).remote_id().unwrap())
 636                                    .collect::<Vec<_>>()
 637                            });
 638                            let new_remote_projects = room.read_with(cx, |room, _| {
 639                                room.remote_participants()
 640                                    .values()
 641                                    .flat_map(|participant| {
 642                                        participant.projects.iter().filter_map(|project| {
 643                                            if existing_remote_project_ids.contains(&project.id) {
 644                                                None
 645                                            } else {
 646                                                Some((
 647                                                    UserId::from_proto(participant.user.id),
 648                                                    project.worktree_root_names[0].clone(),
 649                                                ))
 650                                            }
 651                                        })
 652                                    })
 653                                    .collect::<Vec<_>>()
 654                            });
 655                            if !new_remote_projects.is_empty() {
 656                                let (host_id, first_root_name) =
 657                                    new_remote_projects.choose(&mut self.rng).unwrap().clone();
 658                                break ClientOperation::OpenRemoteProject {
 659                                    host_id,
 660                                    first_root_name,
 661                                };
 662                            }
 663                        }
 664                        // Open a local project
 665                        else {
 666                            let first_root_name = self.next_root_dir_name(user_id);
 667                            break ClientOperation::OpenLocalProject { first_root_name };
 668                        }
 669                    }
 670
 671                    // Close a remote project
 672                    71..=80 => {
 673                        if !client.remote_projects().is_empty() {
 674                            let project = client
 675                                .remote_projects()
 676                                .choose(&mut self.rng)
 677                                .unwrap()
 678                                .clone();
 679                            let first_root_name = root_name_for_project(&project, cx);
 680                            break ClientOperation::CloseRemoteProject {
 681                                project_root_name: first_root_name,
 682                            };
 683                        }
 684                    }
 685
 686                    // Add a worktree to a local project
 687                    81.. => {
 688                        if !client.local_projects().is_empty() {
 689                            let project = client
 690                                .local_projects()
 691                                .choose(&mut self.rng)
 692                                .unwrap()
 693                                .clone();
 694                            let project_root_name = root_name_for_project(&project, cx);
 695
 696                            let mut paths = client.fs.paths().await;
 697                            paths.remove(0);
 698                            let new_root_path = if paths.is_empty() || self.rng.gen() {
 699                                Path::new("/").join(&self.next_root_dir_name(user_id))
 700                            } else {
 701                                paths.choose(&mut self.rng).unwrap().clone()
 702                            };
 703
 704                            break ClientOperation::AddWorktreeToProject {
 705                                project_root_name,
 706                                new_root_path,
 707                            };
 708                        }
 709                    }
 710                },
 711
 712                // Query and mutate buffers
 713                60.. => {
 714                    let Some(project) = choose_random_project(client, &mut self.rng) else { continue };
 715                    let project_root_name = root_name_for_project(&project, cx);
 716
 717                    match self.rng.gen_range(0..100_u32) {
 718                        // Manipulate an existing buffer
 719                        0..=70 => {
 720                            let Some(buffer) = client
 721                                .buffers_for_project(&project)
 722                                .iter()
 723                                .choose(&mut self.rng)
 724                                .cloned() else { continue };
 725
 726                            let full_path = buffer
 727                                .read_with(cx, |buffer, cx| buffer.file().unwrap().full_path(cx));
 728
 729                            match self.rng.gen_range(0..100_u32) {
 730                                // Close the buffer
 731                                0..=15 => {
 732                                    break ClientOperation::CloseBuffer {
 733                                        project_root_name,
 734                                        full_path,
 735                                    };
 736                                }
 737                                // Save the buffer
 738                                16..=29 if buffer.read_with(cx, |b, _| b.is_dirty()) => {
 739                                    let detach = self.rng.gen_bool(0.3);
 740                                    break ClientOperation::SaveBuffer {
 741                                        project_root_name,
 742                                        full_path,
 743                                        detach,
 744                                    };
 745                                }
 746                                // Edit the buffer
 747                                30..=69 => {
 748                                    let edits = buffer.read_with(cx, |buffer, _| {
 749                                        buffer.get_random_edits(&mut self.rng, 3)
 750                                    });
 751                                    break ClientOperation::EditBuffer {
 752                                        project_root_name,
 753                                        full_path,
 754                                        edits,
 755                                    };
 756                                }
 757                                // Make an LSP request
 758                                _ => {
 759                                    let offset = buffer.read_with(cx, |buffer, _| {
 760                                        buffer.clip_offset(
 761                                            self.rng.gen_range(0..=buffer.len()),
 762                                            language::Bias::Left,
 763                                        )
 764                                    });
 765                                    let detach = self.rng.gen();
 766                                    break ClientOperation::RequestLspDataInBuffer {
 767                                        project_root_name,
 768                                        full_path,
 769                                        offset,
 770                                        kind: match self.rng.gen_range(0..5_u32) {
 771                                            0 => LspRequestKind::Rename,
 772                                            1 => LspRequestKind::Highlights,
 773                                            2 => LspRequestKind::Definition,
 774                                            3 => LspRequestKind::CodeAction,
 775                                            4.. => LspRequestKind::Completion,
 776                                        },
 777                                        detach,
 778                                    };
 779                                }
 780                            }
 781                        }
 782
 783                        71..=80 => {
 784                            let query = self.rng.gen_range('a'..='z').to_string();
 785                            let detach = self.rng.gen_bool(0.3);
 786                            break ClientOperation::SearchProject {
 787                                project_root_name,
 788                                query,
 789                                detach,
 790                            };
 791                        }
 792
 793                        // Open a buffer
 794                        81.. => {
 795                            let worktree = project.read_with(cx, |project, cx| {
 796                                project
 797                                    .worktrees(cx)
 798                                    .filter(|worktree| {
 799                                        let worktree = worktree.read(cx);
 800                                        worktree.is_visible()
 801                                            && worktree.entries(false).any(|e| e.is_file())
 802                                    })
 803                                    .choose(&mut self.rng)
 804                            });
 805                            let Some(worktree) = worktree else { continue };
 806                            let full_path = worktree.read_with(cx, |worktree, _| {
 807                                let entry = worktree
 808                                    .entries(false)
 809                                    .filter(|e| e.is_file())
 810                                    .choose(&mut self.rng)
 811                                    .unwrap();
 812                                if entry.path.as_ref() == Path::new("") {
 813                                    Path::new(worktree.root_name()).into()
 814                                } else {
 815                                    Path::new(worktree.root_name()).join(&entry.path)
 816                                }
 817                            });
 818                            break ClientOperation::OpenBuffer {
 819                                project_root_name,
 820                                full_path,
 821                            };
 822                        }
 823                    }
 824                }
 825
 826                _ => break ClientOperation::Other,
 827            }
 828        };
 829        operation
 830    }
 831
 832    fn next_root_dir_name(&mut self, user_id: UserId) -> String {
 833        let user_ix = self
 834            .users
 835            .iter()
 836            .position(|user| user.user_id == user_id)
 837            .unwrap();
 838        let root_id = util::post_inc(&mut self.users[user_ix].next_root_id);
 839        format!("dir-{user_id}-{root_id}")
 840    }
 841
 842    fn user(&mut self, user_id: UserId) -> &mut UserTestPlan {
 843        let ix = self
 844            .users
 845            .iter()
 846            .position(|user| user.user_id == user_id)
 847            .unwrap();
 848        &mut self.users[ix]
 849    }
 850}
 851
 852async fn simulate_client(
 853    client: Rc<TestClient>,
 854    mut operation_rx: futures::channel::mpsc::UnboundedReceiver<()>,
 855    plan: Arc<Mutex<TestPlan>>,
 856    mut cx: TestAppContext,
 857) {
 858    // Setup language server
 859    let mut language = Language::new(
 860        LanguageConfig {
 861            name: "Rust".into(),
 862            path_suffixes: vec!["rs".to_string()],
 863            ..Default::default()
 864        },
 865        None,
 866    );
 867    let _fake_language_servers = language
 868        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
 869            name: "the-fake-language-server",
 870            capabilities: lsp::LanguageServer::full_capabilities(),
 871            initializer: Some(Box::new({
 872                let plan = plan.clone();
 873                let fs = client.fs.clone();
 874                move |fake_server: &mut FakeLanguageServer| {
 875                    fake_server.handle_request::<lsp::request::Completion, _, _>(
 876                        |_, _| async move {
 877                            Ok(Some(lsp::CompletionResponse::Array(vec![
 878                                lsp::CompletionItem {
 879                                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 880                                        range: lsp::Range::new(
 881                                            lsp::Position::new(0, 0),
 882                                            lsp::Position::new(0, 0),
 883                                        ),
 884                                        new_text: "the-new-text".to_string(),
 885                                    })),
 886                                    ..Default::default()
 887                                },
 888                            ])))
 889                        },
 890                    );
 891
 892                    fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
 893                        |_, _| async move {
 894                            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 895                                lsp::CodeAction {
 896                                    title: "the-code-action".to_string(),
 897                                    ..Default::default()
 898                                },
 899                            )]))
 900                        },
 901                    );
 902
 903                    fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
 904                        |params, _| async move {
 905                            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
 906                                params.position,
 907                                params.position,
 908                            ))))
 909                        },
 910                    );
 911
 912                    fake_server.handle_request::<lsp::request::GotoDefinition, _, _>({
 913                        let fs = fs.clone();
 914                        let plan = plan.clone();
 915                        move |_, _| {
 916                            let fs = fs.clone();
 917                            let plan = plan.clone();
 918                            async move {
 919                                let files = fs.files().await;
 920                                let mut plan = plan.lock();
 921                                let count = plan.rng.gen_range::<usize, _>(1..3);
 922                                let files = (0..count)
 923                                    .map(|_| files.choose(&mut plan.rng).unwrap())
 924                                    .collect::<Vec<_>>();
 925                                log::info!("LSP: Returning definitions in files {:?}", &files);
 926                                Ok(Some(lsp::GotoDefinitionResponse::Array(
 927                                    files
 928                                        .into_iter()
 929                                        .map(|file| lsp::Location {
 930                                            uri: lsp::Url::from_file_path(file).unwrap(),
 931                                            range: Default::default(),
 932                                        })
 933                                        .collect(),
 934                                )))
 935                            }
 936                        }
 937                    });
 938
 939                    fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>({
 940                        let plan = plan.clone();
 941                        move |_, _| {
 942                            let mut highlights = Vec::new();
 943                            let highlight_count = plan.lock().rng.gen_range(1..=5);
 944                            for _ in 0..highlight_count {
 945                                let start_row = plan.lock().rng.gen_range(0..100);
 946                                let start_column = plan.lock().rng.gen_range(0..100);
 947                                let start = PointUtf16::new(start_row, start_column);
 948                                let end_row = plan.lock().rng.gen_range(0..100);
 949                                let end_column = plan.lock().rng.gen_range(0..100);
 950                                let end = PointUtf16::new(end_row, end_column);
 951                                let range = if start > end { end..start } else { start..end };
 952                                highlights.push(lsp::DocumentHighlight {
 953                                    range: range_to_lsp(range.clone()),
 954                                    kind: Some(lsp::DocumentHighlightKind::READ),
 955                                });
 956                            }
 957                            highlights.sort_unstable_by_key(|highlight| {
 958                                (highlight.range.start, highlight.range.end)
 959                            });
 960                            async move { Ok(Some(highlights)) }
 961                        }
 962                    });
 963                }
 964            })),
 965            ..Default::default()
 966        }))
 967        .await;
 968    client.language_registry.add(Arc::new(language));
 969
 970    while operation_rx.next().await.is_some() {
 971        let operation = plan.lock().next_client_operation(&client, &cx).await;
 972        if let Err(error) = apply_client_operation(&client, plan.clone(), operation, &mut cx).await
 973        {
 974            log::error!("{} error: {}", client.username, error);
 975        }
 976        cx.background().simulate_random_delay().await;
 977    }
 978    log::info!("{}: done", client.username);
 979}
 980
 981async fn apply_client_operation(
 982    client: &TestClient,
 983    plan: Arc<Mutex<TestPlan>>,
 984    operation: ClientOperation,
 985    cx: &mut TestAppContext,
 986) -> Result<()> {
 987    match operation {
 988        ClientOperation::AcceptIncomingCall => {
 989            log::info!("{}: accepting incoming call", client.username);
 990            let active_call = cx.read(ActiveCall::global);
 991            active_call
 992                .update(cx, |call, cx| call.accept_incoming(cx))
 993                .await?;
 994        }
 995
 996        ClientOperation::RejectIncomingCall => {
 997            log::info!("{}: declining incoming call", client.username);
 998            let active_call = cx.read(ActiveCall::global);
 999            active_call.update(cx, |call, _| call.decline_incoming())?;
1000        }
1001
1002        ClientOperation::LeaveCall => {
1003            log::info!("{}: hanging up", client.username);
1004            let active_call = cx.read(ActiveCall::global);
1005            active_call.update(cx, |call, cx| call.hang_up(cx))?;
1006        }
1007
1008        ClientOperation::InviteContactToCall { user_id } => {
1009            log::info!("{}: inviting {}", client.username, user_id,);
1010            let active_call = cx.read(ActiveCall::global);
1011            active_call
1012                .update(cx, |call, cx| call.invite(user_id.to_proto(), None, cx))
1013                .await?;
1014        }
1015
1016        ClientOperation::OpenLocalProject { first_root_name } => {
1017            log::info!(
1018                "{}: opening local project at {:?}",
1019                client.username,
1020                first_root_name
1021            );
1022            let root_path = Path::new("/").join(&first_root_name);
1023            client.fs.create_dir(&root_path).await.unwrap();
1024            client
1025                .fs
1026                .create_file(&root_path.join("main.rs"), Default::default())
1027                .await
1028                .unwrap();
1029            let project = client.build_local_project(root_path, cx).await.0;
1030            ensure_project_shared(&project, client, cx).await;
1031            client.local_projects_mut().push(project.clone());
1032        }
1033
1034        ClientOperation::AddWorktreeToProject {
1035            project_root_name,
1036            new_root_path,
1037        } => {
1038            log::info!(
1039                "{}: finding/creating local worktree at {:?} to project with root path {}",
1040                client.username,
1041                new_root_path,
1042                project_root_name
1043            );
1044            let project = project_for_root_name(client, &project_root_name, cx)
1045                .expect("invalid project in test operation");
1046            ensure_project_shared(&project, client, cx).await;
1047            if !client.fs.paths().await.contains(&new_root_path) {
1048                client.fs.create_dir(&new_root_path).await.unwrap();
1049            }
1050            project
1051                .update(cx, |project, cx| {
1052                    project.find_or_create_local_worktree(&new_root_path, true, cx)
1053                })
1054                .await
1055                .unwrap();
1056        }
1057
1058        ClientOperation::CloseRemoteProject { project_root_name } => {
1059            log::info!(
1060                "{}: closing remote project with root path {}",
1061                client.username,
1062                project_root_name,
1063            );
1064            let ix = project_ix_for_root_name(&*client.remote_projects(), &project_root_name, cx)
1065                .expect("invalid project in test operation");
1066            cx.update(|_| client.remote_projects_mut().remove(ix));
1067        }
1068
1069        ClientOperation::OpenRemoteProject {
1070            host_id,
1071            first_root_name,
1072        } => {
1073            log::info!(
1074                "{}: joining remote project of user {}, root name {}",
1075                client.username,
1076                host_id,
1077                first_root_name,
1078            );
1079            let active_call = cx.read(ActiveCall::global);
1080            let project_id = active_call
1081                .read_with(cx, |call, cx| {
1082                    let room = call.room().cloned()?;
1083                    let participant = room
1084                        .read(cx)
1085                        .remote_participants()
1086                        .get(&host_id.to_proto())?;
1087                    let project = participant
1088                        .projects
1089                        .iter()
1090                        .find(|project| project.worktree_root_names[0] == first_root_name)?;
1091                    Some(project.id)
1092                })
1093                .expect("invalid project in test operation");
1094            let project = client.build_remote_project(project_id, cx).await;
1095            client.remote_projects_mut().push(project);
1096        }
1097
1098        ClientOperation::OpenBuffer {
1099            project_root_name,
1100            full_path,
1101        } => {
1102            log::info!(
1103                "{}: opening buffer {:?} in project {}",
1104                client.username,
1105                full_path,
1106                project_root_name,
1107            );
1108            let project = project_for_root_name(client, &project_root_name, cx)
1109                .expect("invalid project in test operation");
1110            // ensure_project_shared(&project, client, cx).await;
1111            let mut components = full_path.components();
1112            let root_name = components.next().unwrap().as_os_str().to_str().unwrap();
1113            let path = components.as_path();
1114            let worktree_id = project
1115                .read_with(cx, |project, cx| {
1116                    project.worktrees(cx).find_map(|worktree| {
1117                        let worktree = worktree.read(cx);
1118                        if worktree.root_name() == root_name {
1119                            Some(worktree.id())
1120                        } else {
1121                            None
1122                        }
1123                    })
1124                })
1125                .expect("invalid buffer path in test operation");
1126            let buffer = project
1127                .update(cx, |project, cx| {
1128                    project.open_buffer((worktree_id, &path), cx)
1129                })
1130                .await?;
1131            client.buffers_for_project(&project).insert(buffer);
1132        }
1133
1134        ClientOperation::EditBuffer {
1135            project_root_name,
1136            full_path,
1137            edits,
1138        } => {
1139            log::info!(
1140                "{}: editing buffer {:?} in project {} with {:?}",
1141                client.username,
1142                full_path,
1143                project_root_name,
1144                edits
1145            );
1146            let project = project_for_root_name(client, &project_root_name, cx)
1147                .expect("invalid project in test operation");
1148            let buffer =
1149                buffer_for_full_path(&*client.buffers_for_project(&project), &full_path, cx)
1150                    .expect("invalid buffer path in test operation");
1151            buffer.update(cx, |buffer, cx| {
1152                buffer.edit(edits, None, cx);
1153            });
1154        }
1155
1156        ClientOperation::CloseBuffer {
1157            project_root_name,
1158            full_path,
1159        } => {
1160            log::info!(
1161                "{}: dropping buffer {:?} in project {}",
1162                client.username,
1163                full_path,
1164                project_root_name
1165            );
1166            let project = project_for_root_name(client, &project_root_name, cx)
1167                .expect("invalid project in test operation");
1168            let buffer =
1169                buffer_for_full_path(&*client.buffers_for_project(&project), &full_path, cx)
1170                    .expect("invalid buffer path in test operation");
1171            cx.update(|_| {
1172                client.buffers_for_project(&project).remove(&buffer);
1173                drop(buffer);
1174            });
1175        }
1176
1177        ClientOperation::SaveBuffer {
1178            project_root_name,
1179            full_path,
1180            detach,
1181        } => {
1182            log::info!(
1183                "{}: saving buffer {:?} in project {}{}",
1184                client.username,
1185                full_path,
1186                project_root_name,
1187                if detach { ", detaching" } else { ", awaiting" }
1188            );
1189            let project = project_for_root_name(client, &project_root_name, cx)
1190                .expect("invalid project in test operation");
1191            let buffer =
1192                buffer_for_full_path(&*client.buffers_for_project(&project), &full_path, cx)
1193                    .expect("invalid buffer path in test operation");
1194            let (requested_version, save) =
1195                buffer.update(cx, |buffer, cx| (buffer.version(), buffer.save(cx)));
1196            let save = cx.background().spawn(async move {
1197                let (saved_version, _, _) = save
1198                    .await
1199                    .map_err(|err| anyhow!("save request failed: {:?}", err))?;
1200                assert!(saved_version.observed_all(&requested_version));
1201                anyhow::Ok(())
1202            });
1203            if detach {
1204                log::info!("{}: detaching save request", client.username);
1205                cx.update(|cx| save.detach_and_log_err(cx));
1206            } else {
1207                save.await?;
1208            }
1209        }
1210
1211        ClientOperation::RequestLspDataInBuffer {
1212            project_root_name,
1213            full_path,
1214            offset,
1215            kind,
1216            detach,
1217        } => {
1218            log::info!(
1219                "{}: request LSP {:?} for buffer {:?} in project {}{}",
1220                client.username,
1221                kind,
1222                full_path,
1223                project_root_name,
1224                if detach { ", detaching" } else { ", awaiting" }
1225            );
1226
1227            let project = project_for_root_name(client, &project_root_name, cx)
1228                .expect("invalid project in test operation");
1229            let buffer =
1230                buffer_for_full_path(&*client.buffers_for_project(&project), &full_path, cx)
1231                    .expect("invalid buffer path in test operation");
1232            let request = match kind {
1233                LspRequestKind::Rename => cx.spawn(|mut cx| async move {
1234                    project
1235                        .update(&mut cx, |p, cx| p.prepare_rename(buffer, offset, cx))
1236                        .await?;
1237                    anyhow::Ok(())
1238                }),
1239                LspRequestKind::Completion => cx.spawn(|mut cx| async move {
1240                    project
1241                        .update(&mut cx, |p, cx| p.completions(&buffer, offset, cx))
1242                        .await?;
1243                    Ok(())
1244                }),
1245                LspRequestKind::CodeAction => cx.spawn(|mut cx| async move {
1246                    project
1247                        .update(&mut cx, |p, cx| p.code_actions(&buffer, offset..offset, cx))
1248                        .await?;
1249                    Ok(())
1250                }),
1251                LspRequestKind::Definition => cx.spawn(|mut cx| async move {
1252                    project
1253                        .update(&mut cx, |p, cx| p.definition(&buffer, offset, cx))
1254                        .await?;
1255                    Ok(())
1256                }),
1257                LspRequestKind::Highlights => cx.spawn(|mut cx| async move {
1258                    project
1259                        .update(&mut cx, |p, cx| p.document_highlights(&buffer, offset, cx))
1260                        .await?;
1261                    Ok(())
1262                }),
1263            };
1264            if detach {
1265                request.detach();
1266            } else {
1267                request.await?;
1268            }
1269        }
1270
1271        ClientOperation::SearchProject {
1272            project_root_name,
1273            query,
1274            detach,
1275        } => {
1276            log::info!(
1277                "{}: search project {} for {:?}{}",
1278                client.username,
1279                project_root_name,
1280                query,
1281                if detach { ", detaching" } else { ", awaiting" }
1282            );
1283            let project = project_for_root_name(client, &project_root_name, cx)
1284                .expect("invalid project in test operation");
1285            let search = project.update(cx, |project, cx| {
1286                project.search(SearchQuery::text(query, false, false), cx)
1287            });
1288            let search = cx.background().spawn(async move {
1289                search
1290                    .await
1291                    .map_err(|err| anyhow!("search request failed: {:?}", err))
1292            });
1293            if detach {
1294                log::info!("{}: detaching save request", client.username);
1295                cx.update(|cx| search.detach_and_log_err(cx));
1296            } else {
1297                search.await?;
1298            }
1299        }
1300
1301        ClientOperation::Other => {
1302            let choice = plan.lock().rng.gen_range(0..100);
1303            match choice {
1304                0..=59
1305                    if !client.local_projects().is_empty()
1306                        || !client.remote_projects().is_empty() =>
1307                {
1308                    randomly_mutate_worktrees(client, &plan, cx).await?;
1309                }
1310                _ => randomly_mutate_fs(client, &plan).await,
1311            }
1312        }
1313    }
1314    Ok(())
1315}
1316
1317fn buffer_for_full_path(
1318    buffers: &HashSet<ModelHandle<language::Buffer>>,
1319    full_path: &PathBuf,
1320    cx: &TestAppContext,
1321) -> Option<ModelHandle<language::Buffer>> {
1322    buffers
1323        .iter()
1324        .find(|buffer| {
1325            buffer.read_with(cx, |buffer, cx| {
1326                buffer.file().unwrap().full_path(cx) == *full_path
1327            })
1328        })
1329        .cloned()
1330}
1331
1332fn project_for_root_name(
1333    client: &TestClient,
1334    root_name: &str,
1335    cx: &TestAppContext,
1336) -> Option<ModelHandle<Project>> {
1337    if let Some(ix) = project_ix_for_root_name(&*client.local_projects(), root_name, cx) {
1338        return Some(client.local_projects()[ix].clone());
1339    }
1340    if let Some(ix) = project_ix_for_root_name(&*client.remote_projects(), root_name, cx) {
1341        return Some(client.remote_projects()[ix].clone());
1342    }
1343    None
1344}
1345
1346fn project_ix_for_root_name(
1347    projects: &[ModelHandle<Project>],
1348    root_name: &str,
1349    cx: &TestAppContext,
1350) -> Option<usize> {
1351    projects.iter().position(|project| {
1352        project.read_with(cx, |project, cx| {
1353            let worktree = project.visible_worktrees(cx).next().unwrap();
1354            worktree.read(cx).root_name() == root_name
1355        })
1356    })
1357}
1358
1359fn root_name_for_project(project: &ModelHandle<Project>, cx: &TestAppContext) -> String {
1360    project.read_with(cx, |project, cx| {
1361        project
1362            .visible_worktrees(cx)
1363            .next()
1364            .unwrap()
1365            .read(cx)
1366            .root_name()
1367            .to_string()
1368    })
1369}
1370
1371async fn ensure_project_shared(
1372    project: &ModelHandle<Project>,
1373    client: &TestClient,
1374    cx: &mut TestAppContext,
1375) {
1376    let first_root_name = root_name_for_project(project, cx);
1377    let active_call = cx.read(ActiveCall::global);
1378    if active_call.read_with(cx, |call, _| call.room().is_some())
1379        && project.read_with(cx, |project, _| project.is_local() && !project.is_shared())
1380    {
1381        match active_call
1382            .update(cx, |call, cx| call.share_project(project.clone(), cx))
1383            .await
1384        {
1385            Ok(project_id) => {
1386                log::info!(
1387                    "{}: shared project {} with id {}",
1388                    client.username,
1389                    first_root_name,
1390                    project_id
1391                );
1392            }
1393            Err(error) => {
1394                log::error!(
1395                    "{}: error sharing project {}: {:?}",
1396                    client.username,
1397                    first_root_name,
1398                    error
1399                );
1400            }
1401        }
1402    }
1403}
1404
1405async fn randomly_mutate_fs(client: &TestClient, plan: &Arc<Mutex<TestPlan>>) {
1406    let is_dir = plan.lock().rng.gen::<bool>();
1407    let mut new_path = client
1408        .fs
1409        .directories()
1410        .await
1411        .choose(&mut plan.lock().rng)
1412        .unwrap()
1413        .clone();
1414    new_path.push(gen_file_name(&mut plan.lock().rng));
1415    if is_dir {
1416        log::info!("{}: creating local dir at {:?}", client.username, new_path);
1417        client.fs.create_dir(&new_path).await.unwrap();
1418    } else {
1419        new_path.set_extension("rs");
1420        log::info!("{}: creating local file at {:?}", client.username, new_path);
1421        client
1422            .fs
1423            .create_file(&new_path, Default::default())
1424            .await
1425            .unwrap();
1426    }
1427}
1428
1429async fn randomly_mutate_worktrees(
1430    client: &TestClient,
1431    plan: &Arc<Mutex<TestPlan>>,
1432    cx: &mut TestAppContext,
1433) -> Result<()> {
1434    let project = choose_random_project(client, &mut plan.lock().rng).unwrap();
1435    let Some(worktree) = project.read_with(cx, |project, cx| {
1436        project
1437            .worktrees(cx)
1438            .filter(|worktree| {
1439                let worktree = worktree.read(cx);
1440                worktree.is_visible()
1441                    && worktree.entries(false).any(|e| e.is_file())
1442                    && worktree.root_entry().map_or(false, |e| e.is_dir())
1443            })
1444            .choose(&mut plan.lock().rng)
1445    }) else {
1446        return Ok(())
1447    };
1448
1449    let (worktree_id, worktree_root_name) = worktree.read_with(cx, |worktree, _| {
1450        (worktree.id(), worktree.root_name().to_string())
1451    });
1452
1453    let is_dir = plan.lock().rng.gen::<bool>();
1454    let mut new_path = PathBuf::new();
1455    new_path.push(gen_file_name(&mut plan.lock().rng));
1456    if !is_dir {
1457        new_path.set_extension("rs");
1458    }
1459    log::info!(
1460        "{}: creating {:?} in worktree {} ({})",
1461        client.username,
1462        new_path,
1463        worktree_id,
1464        worktree_root_name,
1465    );
1466    project
1467        .update(cx, |project, cx| {
1468            project.create_entry((worktree_id, new_path), is_dir, cx)
1469        })
1470        .unwrap()
1471        .await?;
1472    Ok(())
1473}
1474
1475fn choose_random_project(client: &TestClient, rng: &mut StdRng) -> Option<ModelHandle<Project>> {
1476    client
1477        .local_projects()
1478        .iter()
1479        .chain(client.remote_projects().iter())
1480        .choose(rng)
1481        .cloned()
1482}
1483
1484fn gen_file_name(rng: &mut StdRng) -> String {
1485    let mut name = String::new();
1486    for _ in 0..10 {
1487        let letter = rng.gen_range('a'..='z');
1488        name.push(letter);
1489    }
1490    name
1491}