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