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::{FakeFs, 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 = active_call
 535                .update(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_id = participant
 542                        .projects
 543                        .iter()
 544                        .find(|project| project.worktree_root_names[0] == first_root_name)?
 545                        .id;
 546                    Some(room.update(cx, |room, cx| {
 547                        room.join_project(
 548                            project_id,
 549                            client.language_registry.clone(),
 550                            FakeFs::new(cx.background().clone()),
 551                            cx,
 552                        )
 553                    }))
 554                })
 555                .expect("invalid project in test operation")
 556                .await?;
 557            client.remote_projects_mut().push(project.clone());
 558        }
 559
 560        ClientOperation::CreateWorktreeEntry {
 561            project_root_name,
 562            is_local,
 563            full_path,
 564            is_dir,
 565        } => {
 566            log::info!(
 567                "{}: creating {} at path {:?} in {} project {}",
 568                client.username,
 569                if is_dir { "dir" } else { "file" },
 570                full_path,
 571                if is_local { "local" } else { "remote" },
 572                project_root_name,
 573            );
 574
 575            let project = project_for_root_name(client, &project_root_name, cx)
 576                .expect("invalid project in test operation");
 577            ensure_project_shared(&project, client, cx).await;
 578            let project_path = project_path_for_full_path(&project, &full_path, cx)
 579                .expect("invalid worktree path in test operation");
 580            project
 581                .update(cx, |p, cx| p.create_entry(project_path, is_dir, cx))
 582                .unwrap()
 583                .await?;
 584        }
 585
 586        ClientOperation::OpenBuffer {
 587            project_root_name,
 588            is_local,
 589            full_path,
 590        } => {
 591            log::info!(
 592                "{}: opening buffer {:?} in {} project {}",
 593                client.username,
 594                full_path,
 595                if is_local { "local" } else { "remote" },
 596                project_root_name,
 597            );
 598
 599            let project = project_for_root_name(client, &project_root_name, cx)
 600                .expect("invalid project in test operation");
 601            ensure_project_shared(&project, client, cx).await;
 602            let project_path = project_path_for_full_path(&project, &full_path, cx)
 603                .expect("invalid buffer path in test operation");
 604            let buffer = project
 605                .update(cx, |project, cx| project.open_buffer(project_path, cx))
 606                .await?;
 607            client.buffers_for_project(&project).insert(buffer);
 608        }
 609
 610        ClientOperation::EditBuffer {
 611            project_root_name,
 612            is_local,
 613            full_path,
 614            edits,
 615        } => {
 616            log::info!(
 617                "{}: editing buffer {:?} in {} project {} with {:?}",
 618                client.username,
 619                full_path,
 620                if is_local { "local" } else { "remote" },
 621                project_root_name,
 622                edits
 623            );
 624
 625            let project = project_for_root_name(client, &project_root_name, cx)
 626                .expect("invalid project in test operation");
 627            ensure_project_shared(&project, client, cx).await;
 628            let buffer =
 629                buffer_for_full_path(&*client.buffers_for_project(&project), &full_path, cx)
 630                    .expect("invalid buffer path in test operation");
 631            buffer.update(cx, |buffer, cx| {
 632                buffer.edit(edits, None, cx);
 633            });
 634        }
 635
 636        ClientOperation::CloseBuffer {
 637            project_root_name,
 638            is_local,
 639            full_path,
 640        } => {
 641            log::info!(
 642                "{}: dropping buffer {:?} in {} project {}",
 643                client.username,
 644                full_path,
 645                if is_local { "local" } else { "remote" },
 646                project_root_name
 647            );
 648
 649            let project = project_for_root_name(client, &project_root_name, cx)
 650                .expect("invalid project in test operation");
 651            ensure_project_shared(&project, client, cx).await;
 652            let buffer =
 653                buffer_for_full_path(&*client.buffers_for_project(&project), &full_path, cx)
 654                    .expect("invalid buffer path in test operation");
 655            cx.update(|_| {
 656                client.buffers_for_project(&project).remove(&buffer);
 657                drop(buffer);
 658            });
 659        }
 660
 661        ClientOperation::SaveBuffer {
 662            project_root_name,
 663            is_local,
 664            full_path,
 665            detach,
 666        } => {
 667            log::info!(
 668                "{}: saving buffer {:?} in {} project {}{}",
 669                client.username,
 670                full_path,
 671                if is_local { "local" } else { "remote" },
 672                project_root_name,
 673                if detach { ", detaching" } else { ", awaiting" }
 674            );
 675
 676            let project = project_for_root_name(client, &project_root_name, cx)
 677                .expect("invalid project in test operation");
 678            ensure_project_shared(&project, client, cx).await;
 679            let buffer =
 680                buffer_for_full_path(&*client.buffers_for_project(&project), &full_path, cx)
 681                    .expect("invalid buffer path in test operation");
 682            let (requested_version, save) =
 683                buffer.update(cx, |buffer, cx| (buffer.version(), buffer.save(cx)));
 684            let save = cx.background().spawn(async move {
 685                let (saved_version, _, _) = save
 686                    .await
 687                    .map_err(|err| anyhow!("save request failed: {:?}", err))?;
 688                assert!(saved_version.observed_all(&requested_version));
 689                anyhow::Ok(())
 690            });
 691            if detach {
 692                log::info!("{}: detaching save request", client.username);
 693                cx.update(|cx| save.detach_and_log_err(cx));
 694            } else {
 695                save.await?;
 696            }
 697        }
 698
 699        ClientOperation::RequestLspDataInBuffer {
 700            project_root_name,
 701            is_local,
 702            full_path,
 703            offset,
 704            kind,
 705            detach,
 706        } => {
 707            log::info!(
 708                "{}: request LSP {:?} for buffer {:?} in {} project {}{}",
 709                client.username,
 710                kind,
 711                full_path,
 712                if is_local { "local" } else { "remote" },
 713                project_root_name,
 714                if detach { ", detaching" } else { ", awaiting" }
 715            );
 716
 717            let project = project_for_root_name(client, &project_root_name, cx)
 718                .expect("invalid project in test operation");
 719            let buffer =
 720                buffer_for_full_path(&*client.buffers_for_project(&project), &full_path, cx)
 721                    .expect("invalid buffer path in test operation");
 722            let request = match kind {
 723                LspRequestKind::Rename => cx.spawn(|mut cx| async move {
 724                    project
 725                        .update(&mut cx, |p, cx| p.prepare_rename(buffer, offset, cx))
 726                        .await?;
 727                    anyhow::Ok(())
 728                }),
 729                LspRequestKind::Completion => cx.spawn(|mut cx| async move {
 730                    project
 731                        .update(&mut cx, |p, cx| p.completions(&buffer, offset, cx))
 732                        .await?;
 733                    Ok(())
 734                }),
 735                LspRequestKind::CodeAction => cx.spawn(|mut cx| async move {
 736                    project
 737                        .update(&mut cx, |p, cx| p.code_actions(&buffer, offset..offset, cx))
 738                        .await?;
 739                    Ok(())
 740                }),
 741                LspRequestKind::Definition => cx.spawn(|mut cx| async move {
 742                    project
 743                        .update(&mut cx, |p, cx| p.definition(&buffer, offset, cx))
 744                        .await?;
 745                    Ok(())
 746                }),
 747                LspRequestKind::Highlights => cx.spawn(|mut cx| async move {
 748                    project
 749                        .update(&mut cx, |p, cx| p.document_highlights(&buffer, offset, cx))
 750                        .await?;
 751                    Ok(())
 752                }),
 753            };
 754            if detach {
 755                request.detach();
 756            } else {
 757                request.await?;
 758            }
 759        }
 760
 761        ClientOperation::SearchProject {
 762            project_root_name,
 763            query,
 764            detach,
 765        } => {
 766            log::info!(
 767                "{}: search project {} for {:?}{}",
 768                client.username,
 769                project_root_name,
 770                query,
 771                if detach { ", detaching" } else { ", awaiting" }
 772            );
 773            let project = project_for_root_name(client, &project_root_name, cx)
 774                .expect("invalid project in test operation");
 775            let search = project.update(cx, |project, cx| {
 776                project.search(SearchQuery::text(query, false, false), cx)
 777            });
 778            let search = cx.background().spawn(async move {
 779                search
 780                    .await
 781                    .map_err(|err| anyhow!("search request failed: {:?}", err))
 782            });
 783            if detach {
 784                log::info!("{}: detaching save request", client.username);
 785                cx.update(|cx| search.detach_and_log_err(cx));
 786            } else {
 787                search.await?;
 788            }
 789        }
 790
 791        ClientOperation::CreateFsEntry { path, is_dir } => {
 792            log::info!(
 793                "{}: creating {} at {:?}",
 794                client.username,
 795                if is_dir { "dir" } else { "file" },
 796                path
 797            );
 798            if is_dir {
 799                client.fs.create_dir(&path).await.unwrap();
 800            } else {
 801                client
 802                    .fs
 803                    .create_file(&path, Default::default())
 804                    .await
 805                    .unwrap();
 806            }
 807        }
 808    }
 809    Ok(())
 810}
 811
 812struct TestPlan {
 813    rng: StdRng,
 814    max_operations: usize,
 815    operation_ix: usize,
 816    users: Vec<UserTestPlan>,
 817    allow_server_restarts: bool,
 818    allow_client_reconnection: bool,
 819    allow_client_disconnection: bool,
 820}
 821
 822struct UserTestPlan {
 823    user_id: UserId,
 824    username: String,
 825    next_root_id: usize,
 826    online: bool,
 827}
 828
 829#[derive(Debug)]
 830enum Operation {
 831    AddConnection {
 832        user_id: UserId,
 833    },
 834    RemoveConnection {
 835        user_id: UserId,
 836    },
 837    BounceConnection {
 838        user_id: UserId,
 839    },
 840    RestartServer,
 841    MutateClients {
 842        user_ids: Vec<UserId>,
 843        quiesce: bool,
 844    },
 845}
 846
 847#[derive(Debug)]
 848enum ClientOperation {
 849    AcceptIncomingCall,
 850    RejectIncomingCall,
 851    LeaveCall,
 852    InviteContactToCall {
 853        user_id: UserId,
 854    },
 855    OpenLocalProject {
 856        first_root_name: String,
 857    },
 858    OpenRemoteProject {
 859        host_id: UserId,
 860        first_root_name: String,
 861    },
 862    AddWorktreeToProject {
 863        project_root_name: String,
 864        new_root_path: PathBuf,
 865    },
 866    CloseRemoteProject {
 867        project_root_name: String,
 868    },
 869    OpenBuffer {
 870        project_root_name: String,
 871        is_local: bool,
 872        full_path: PathBuf,
 873    },
 874    SearchProject {
 875        project_root_name: String,
 876        query: String,
 877        detach: bool,
 878    },
 879    EditBuffer {
 880        project_root_name: String,
 881        is_local: bool,
 882        full_path: PathBuf,
 883        edits: Vec<(Range<usize>, Arc<str>)>,
 884    },
 885    CloseBuffer {
 886        project_root_name: String,
 887        is_local: bool,
 888        full_path: PathBuf,
 889    },
 890    SaveBuffer {
 891        project_root_name: String,
 892        is_local: bool,
 893        full_path: PathBuf,
 894        detach: bool,
 895    },
 896    RequestLspDataInBuffer {
 897        project_root_name: String,
 898        is_local: bool,
 899        full_path: PathBuf,
 900        offset: usize,
 901        kind: LspRequestKind,
 902        detach: bool,
 903    },
 904    CreateWorktreeEntry {
 905        project_root_name: String,
 906        is_local: bool,
 907        full_path: PathBuf,
 908        is_dir: bool,
 909    },
 910    CreateFsEntry {
 911        path: PathBuf,
 912        is_dir: bool,
 913    },
 914}
 915
 916#[derive(Debug)]
 917enum LspRequestKind {
 918    Rename,
 919    Completion,
 920    CodeAction,
 921    Definition,
 922    Highlights,
 923}
 924
 925impl TestPlan {
 926    async fn next_operation(
 927        &mut self,
 928        clients: &[(Rc<TestClient>, TestAppContext)],
 929    ) -> Option<Operation> {
 930        if self.operation_ix == self.max_operations {
 931            return None;
 932        }
 933
 934        let operation = loop {
 935            break match self.rng.gen_range(0..100) {
 936                0..=29 if clients.len() < self.users.len() => {
 937                    let user = self
 938                        .users
 939                        .iter()
 940                        .filter(|u| !u.online)
 941                        .choose(&mut self.rng)
 942                        .unwrap();
 943                    self.operation_ix += 1;
 944                    Operation::AddConnection {
 945                        user_id: user.user_id,
 946                    }
 947                }
 948                30..=34 if clients.len() > 1 && self.allow_client_disconnection => {
 949                    let (client, cx) = &clients[self.rng.gen_range(0..clients.len())];
 950                    let user_id = client.current_user_id(cx);
 951                    self.operation_ix += 1;
 952                    Operation::RemoveConnection { user_id }
 953                }
 954                35..=39 if clients.len() > 1 && self.allow_client_reconnection => {
 955                    let (client, cx) = &clients[self.rng.gen_range(0..clients.len())];
 956                    let user_id = client.current_user_id(cx);
 957                    self.operation_ix += 1;
 958                    Operation::BounceConnection { user_id }
 959                }
 960                40..=44 if self.allow_server_restarts && clients.len() > 1 => {
 961                    self.operation_ix += 1;
 962                    Operation::RestartServer
 963                }
 964                _ if !clients.is_empty() => {
 965                    let count = self
 966                        .rng
 967                        .gen_range(1..10)
 968                        .min(self.max_operations - self.operation_ix);
 969                    let user_ids = (0..count)
 970                        .map(|_| {
 971                            let ix = self.rng.gen_range(0..clients.len());
 972                            let (client, cx) = &clients[ix];
 973                            client.current_user_id(cx)
 974                        })
 975                        .collect();
 976                    Operation::MutateClients {
 977                        user_ids,
 978                        quiesce: self.rng.gen(),
 979                    }
 980                }
 981                _ => continue,
 982            };
 983        };
 984        Some(operation)
 985    }
 986
 987    async fn next_client_operation(
 988        &mut self,
 989        client: &TestClient,
 990        cx: &TestAppContext,
 991    ) -> Option<ClientOperation> {
 992        if self.operation_ix == self.max_operations {
 993            return None;
 994        }
 995
 996        let user_id = client.current_user_id(cx);
 997        let call = cx.read(ActiveCall::global);
 998        let operation = loop {
 999            match self.rng.gen_range(0..100_u32) {
1000                // Mutate the call
1001                0..=29 => {
1002                    // Respond to an incoming call
1003                    if call.read_with(cx, |call, _| call.incoming().borrow().is_some()) {
1004                        break if self.rng.gen_bool(0.7) {
1005                            ClientOperation::AcceptIncomingCall
1006                        } else {
1007                            ClientOperation::RejectIncomingCall
1008                        };
1009                    }
1010
1011                    match self.rng.gen_range(0..100_u32) {
1012                        // Invite a contact to the current call
1013                        0..=70 => {
1014                            let available_contacts =
1015                                client.user_store.read_with(cx, |user_store, _| {
1016                                    user_store
1017                                        .contacts()
1018                                        .iter()
1019                                        .filter(|contact| contact.online && !contact.busy)
1020                                        .cloned()
1021                                        .collect::<Vec<_>>()
1022                                });
1023                            if !available_contacts.is_empty() {
1024                                let contact = available_contacts.choose(&mut self.rng).unwrap();
1025                                break ClientOperation::InviteContactToCall {
1026                                    user_id: UserId(contact.user.id as i32),
1027                                };
1028                            }
1029                        }
1030
1031                        // Leave the current call
1032                        71.. => {
1033                            if self.allow_client_disconnection
1034                                && call.read_with(cx, |call, _| call.room().is_some())
1035                            {
1036                                break ClientOperation::LeaveCall;
1037                            }
1038                        }
1039                    }
1040                }
1041
1042                // Mutate projects
1043                30..=59 => match self.rng.gen_range(0..100_u32) {
1044                    // Open a new project
1045                    0..=70 => {
1046                        // Open a remote project
1047                        if let Some(room) = call.read_with(cx, |call, _| call.room().cloned()) {
1048                            let existing_remote_project_ids = cx.read(|cx| {
1049                                client
1050                                    .remote_projects()
1051                                    .iter()
1052                                    .map(|p| p.read(cx).remote_id().unwrap())
1053                                    .collect::<Vec<_>>()
1054                            });
1055                            let new_remote_projects = room.read_with(cx, |room, _| {
1056                                room.remote_participants()
1057                                    .values()
1058                                    .flat_map(|participant| {
1059                                        participant.projects.iter().filter_map(|project| {
1060                                            if existing_remote_project_ids.contains(&project.id) {
1061                                                None
1062                                            } else {
1063                                                Some((
1064                                                    UserId::from_proto(participant.user.id),
1065                                                    project.worktree_root_names[0].clone(),
1066                                                ))
1067                                            }
1068                                        })
1069                                    })
1070                                    .collect::<Vec<_>>()
1071                            });
1072                            if !new_remote_projects.is_empty() {
1073                                let (host_id, first_root_name) =
1074                                    new_remote_projects.choose(&mut self.rng).unwrap().clone();
1075                                break ClientOperation::OpenRemoteProject {
1076                                    host_id,
1077                                    first_root_name,
1078                                };
1079                            }
1080                        }
1081                        // Open a local project
1082                        else {
1083                            let first_root_name = self.next_root_dir_name(user_id);
1084                            break ClientOperation::OpenLocalProject { first_root_name };
1085                        }
1086                    }
1087
1088                    // Close a remote project
1089                    71..=80 => {
1090                        if !client.remote_projects().is_empty() {
1091                            let project = client
1092                                .remote_projects()
1093                                .choose(&mut self.rng)
1094                                .unwrap()
1095                                .clone();
1096                            let first_root_name = root_name_for_project(&project, cx);
1097                            break ClientOperation::CloseRemoteProject {
1098                                project_root_name: first_root_name,
1099                            };
1100                        }
1101                    }
1102
1103                    // Mutate project worktrees
1104                    81.. => match self.rng.gen_range(0..100_u32) {
1105                        // Add a worktree to a local project
1106                        0..=50 => {
1107                            let Some(project) = client
1108                                    .local_projects()
1109                                    .choose(&mut self.rng)
1110                                    .cloned() else { continue };
1111                            let project_root_name = root_name_for_project(&project, cx);
1112                            let mut paths = client.fs.paths().await;
1113                            paths.remove(0);
1114                            let new_root_path = if paths.is_empty() || self.rng.gen() {
1115                                Path::new("/").join(&self.next_root_dir_name(user_id))
1116                            } else {
1117                                paths.choose(&mut self.rng).unwrap().clone()
1118                            };
1119                            break ClientOperation::AddWorktreeToProject {
1120                                project_root_name,
1121                                new_root_path,
1122                            };
1123                        }
1124
1125                        // Add an entry to a worktree
1126                        _ => {
1127                            let Some(project) = choose_random_project(client, &mut self.rng) else { continue };
1128                            let project_root_name = root_name_for_project(&project, cx);
1129                            let is_local = project.read_with(cx, |project, _| project.is_local());
1130                            let worktree = project.read_with(cx, |project, cx| {
1131                                project
1132                                    .worktrees(cx)
1133                                    .filter(|worktree| {
1134                                        let worktree = worktree.read(cx);
1135                                        worktree.is_visible()
1136                                            && worktree.entries(false).any(|e| e.is_file())
1137                                            && worktree.root_entry().map_or(false, |e| e.is_dir())
1138                                    })
1139                                    .choose(&mut self.rng)
1140                            });
1141                            let Some(worktree) = worktree else { continue };
1142                            let is_dir = self.rng.gen::<bool>();
1143                            let mut full_path =
1144                                worktree.read_with(cx, |w, _| PathBuf::from(w.root_name()));
1145                            full_path.push(gen_file_name(&mut self.rng));
1146                            if !is_dir {
1147                                full_path.set_extension("rs");
1148                            }
1149                            break ClientOperation::CreateWorktreeEntry {
1150                                project_root_name,
1151                                is_local,
1152                                full_path,
1153                                is_dir,
1154                            };
1155                        }
1156                    },
1157                },
1158
1159                // Query and mutate buffers
1160                60..=95 => {
1161                    let Some(project) = choose_random_project(client, &mut self.rng) else { continue };
1162                    let project_root_name = root_name_for_project(&project, cx);
1163                    let is_local = project.read_with(cx, |project, _| project.is_local());
1164
1165                    match self.rng.gen_range(0..100_u32) {
1166                        // Manipulate an existing buffer
1167                        0..=70 => {
1168                            let Some(buffer) = client
1169                                .buffers_for_project(&project)
1170                                .iter()
1171                                .choose(&mut self.rng)
1172                                .cloned() else { continue };
1173
1174                            let full_path = buffer
1175                                .read_with(cx, |buffer, cx| buffer.file().unwrap().full_path(cx));
1176
1177                            match self.rng.gen_range(0..100_u32) {
1178                                // Close the buffer
1179                                0..=15 => {
1180                                    break ClientOperation::CloseBuffer {
1181                                        project_root_name,
1182                                        is_local,
1183                                        full_path,
1184                                    };
1185                                }
1186                                // Save the buffer
1187                                16..=29 if buffer.read_with(cx, |b, _| b.is_dirty()) => {
1188                                    let detach = self.rng.gen_bool(0.3);
1189                                    break ClientOperation::SaveBuffer {
1190                                        project_root_name,
1191                                        is_local,
1192                                        full_path,
1193                                        detach,
1194                                    };
1195                                }
1196                                // Edit the buffer
1197                                30..=69 => {
1198                                    let edits = buffer.read_with(cx, |buffer, _| {
1199                                        buffer.get_random_edits(&mut self.rng, 3)
1200                                    });
1201                                    break ClientOperation::EditBuffer {
1202                                        project_root_name,
1203                                        is_local,
1204                                        full_path,
1205                                        edits,
1206                                    };
1207                                }
1208                                // Make an LSP request
1209                                _ => {
1210                                    let offset = buffer.read_with(cx, |buffer, _| {
1211                                        buffer.clip_offset(
1212                                            self.rng.gen_range(0..=buffer.len()),
1213                                            language::Bias::Left,
1214                                        )
1215                                    });
1216                                    let detach = self.rng.gen();
1217                                    break ClientOperation::RequestLspDataInBuffer {
1218                                        project_root_name,
1219                                        full_path,
1220                                        offset,
1221                                        is_local,
1222                                        kind: match self.rng.gen_range(0..5_u32) {
1223                                            0 => LspRequestKind::Rename,
1224                                            1 => LspRequestKind::Highlights,
1225                                            2 => LspRequestKind::Definition,
1226                                            3 => LspRequestKind::CodeAction,
1227                                            4.. => LspRequestKind::Completion,
1228                                        },
1229                                        detach,
1230                                    };
1231                                }
1232                            }
1233                        }
1234
1235                        71..=80 => {
1236                            let query = self.rng.gen_range('a'..='z').to_string();
1237                            let detach = self.rng.gen_bool(0.3);
1238                            break ClientOperation::SearchProject {
1239                                project_root_name,
1240                                query,
1241                                detach,
1242                            };
1243                        }
1244
1245                        // Open a buffer
1246                        81.. => {
1247                            let worktree = project.read_with(cx, |project, cx| {
1248                                project
1249                                    .worktrees(cx)
1250                                    .filter(|worktree| {
1251                                        let worktree = worktree.read(cx);
1252                                        worktree.is_visible()
1253                                            && worktree.entries(false).any(|e| e.is_file())
1254                                    })
1255                                    .choose(&mut self.rng)
1256                            });
1257                            let Some(worktree) = worktree else { continue };
1258                            let full_path = worktree.read_with(cx, |worktree, _| {
1259                                let entry = worktree
1260                                    .entries(false)
1261                                    .filter(|e| e.is_file())
1262                                    .choose(&mut self.rng)
1263                                    .unwrap();
1264                                if entry.path.as_ref() == Path::new("") {
1265                                    Path::new(worktree.root_name()).into()
1266                                } else {
1267                                    Path::new(worktree.root_name()).join(&entry.path)
1268                                }
1269                            });
1270                            break ClientOperation::OpenBuffer {
1271                                project_root_name,
1272                                is_local,
1273                                full_path,
1274                            };
1275                        }
1276                    }
1277                }
1278
1279                // Create a file or directory
1280                96.. => {
1281                    let is_dir = self.rng.gen::<bool>();
1282                    let mut path = client
1283                        .fs
1284                        .directories()
1285                        .await
1286                        .choose(&mut self.rng)
1287                        .unwrap()
1288                        .clone();
1289                    path.push(gen_file_name(&mut self.rng));
1290                    if !is_dir {
1291                        path.set_extension("rs");
1292                    }
1293                    break ClientOperation::CreateFsEntry { path, is_dir };
1294                }
1295            }
1296        };
1297        self.operation_ix += 1;
1298        Some(operation)
1299    }
1300
1301    fn next_root_dir_name(&mut self, user_id: UserId) -> String {
1302        let user_ix = self
1303            .users
1304            .iter()
1305            .position(|user| user.user_id == user_id)
1306            .unwrap();
1307        let root_id = util::post_inc(&mut self.users[user_ix].next_root_id);
1308        format!("dir-{user_id}-{root_id}")
1309    }
1310
1311    fn user(&mut self, user_id: UserId) -> &mut UserTestPlan {
1312        let ix = self
1313            .users
1314            .iter()
1315            .position(|user| user.user_id == user_id)
1316            .unwrap();
1317        &mut self.users[ix]
1318    }
1319}
1320
1321async fn simulate_client(
1322    client: Rc<TestClient>,
1323    mut operation_rx: futures::channel::mpsc::UnboundedReceiver<()>,
1324    plan: Arc<Mutex<TestPlan>>,
1325    mut cx: TestAppContext,
1326) {
1327    // Setup language server
1328    let mut language = Language::new(
1329        LanguageConfig {
1330            name: "Rust".into(),
1331            path_suffixes: vec!["rs".to_string()],
1332            ..Default::default()
1333        },
1334        None,
1335    );
1336    let _fake_language_servers = language
1337        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1338            name: "the-fake-language-server",
1339            capabilities: lsp::LanguageServer::full_capabilities(),
1340            initializer: Some(Box::new({
1341                let plan = plan.clone();
1342                let fs = client.fs.clone();
1343                move |fake_server: &mut FakeLanguageServer| {
1344                    fake_server.handle_request::<lsp::request::Completion, _, _>(
1345                        |_, _| async move {
1346                            Ok(Some(lsp::CompletionResponse::Array(vec![
1347                                lsp::CompletionItem {
1348                                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1349                                        range: lsp::Range::new(
1350                                            lsp::Position::new(0, 0),
1351                                            lsp::Position::new(0, 0),
1352                                        ),
1353                                        new_text: "the-new-text".to_string(),
1354                                    })),
1355                                    ..Default::default()
1356                                },
1357                            ])))
1358                        },
1359                    );
1360
1361                    fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
1362                        |_, _| async move {
1363                            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
1364                                lsp::CodeAction {
1365                                    title: "the-code-action".to_string(),
1366                                    ..Default::default()
1367                                },
1368                            )]))
1369                        },
1370                    );
1371
1372                    fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
1373                        |params, _| async move {
1374                            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
1375                                params.position,
1376                                params.position,
1377                            ))))
1378                        },
1379                    );
1380
1381                    fake_server.handle_request::<lsp::request::GotoDefinition, _, _>({
1382                        let fs = fs.clone();
1383                        let plan = plan.clone();
1384                        move |_, _| {
1385                            let fs = fs.clone();
1386                            let plan = plan.clone();
1387                            async move {
1388                                let files = fs.files().await;
1389                                let mut plan = plan.lock();
1390                                let count = plan.rng.gen_range::<usize, _>(1..3);
1391                                let files = (0..count)
1392                                    .map(|_| files.choose(&mut plan.rng).unwrap())
1393                                    .collect::<Vec<_>>();
1394                                log::info!("LSP: Returning definitions in files {:?}", &files);
1395                                Ok(Some(lsp::GotoDefinitionResponse::Array(
1396                                    files
1397                                        .into_iter()
1398                                        .map(|file| lsp::Location {
1399                                            uri: lsp::Url::from_file_path(file).unwrap(),
1400                                            range: Default::default(),
1401                                        })
1402                                        .collect(),
1403                                )))
1404                            }
1405                        }
1406                    });
1407
1408                    fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>({
1409                        let plan = plan.clone();
1410                        move |_, _| {
1411                            let mut highlights = Vec::new();
1412                            let highlight_count = plan.lock().rng.gen_range(1..=5);
1413                            for _ in 0..highlight_count {
1414                                let start_row = plan.lock().rng.gen_range(0..100);
1415                                let start_column = plan.lock().rng.gen_range(0..100);
1416                                let start = PointUtf16::new(start_row, start_column);
1417                                let end_row = plan.lock().rng.gen_range(0..100);
1418                                let end_column = plan.lock().rng.gen_range(0..100);
1419                                let end = PointUtf16::new(end_row, end_column);
1420                                let range = if start > end { end..start } else { start..end };
1421                                highlights.push(lsp::DocumentHighlight {
1422                                    range: range_to_lsp(range.clone()),
1423                                    kind: Some(lsp::DocumentHighlightKind::READ),
1424                                });
1425                            }
1426                            highlights.sort_unstable_by_key(|highlight| {
1427                                (highlight.range.start, highlight.range.end)
1428                            });
1429                            async move { Ok(Some(highlights)) }
1430                        }
1431                    });
1432                }
1433            })),
1434            ..Default::default()
1435        }))
1436        .await;
1437    client.language_registry.add(Arc::new(language));
1438
1439    while operation_rx.next().await.is_some() {
1440        let Some(operation) = plan.lock().next_client_operation(&client, &cx).await else { break };
1441        if let Err(error) = apply_client_operation(&client, operation, &mut cx).await {
1442            log::error!("{} error: {}", client.username, error);
1443        }
1444        cx.background().simulate_random_delay().await;
1445    }
1446    log::info!("{}: done", client.username);
1447}
1448
1449fn buffer_for_full_path(
1450    buffers: &HashSet<ModelHandle<language::Buffer>>,
1451    full_path: &PathBuf,
1452    cx: &TestAppContext,
1453) -> Option<ModelHandle<language::Buffer>> {
1454    buffers
1455        .iter()
1456        .find(|buffer| {
1457            buffer.read_with(cx, |buffer, cx| {
1458                buffer.file().unwrap().full_path(cx) == *full_path
1459            })
1460        })
1461        .cloned()
1462}
1463
1464fn project_for_root_name(
1465    client: &TestClient,
1466    root_name: &str,
1467    cx: &TestAppContext,
1468) -> Option<ModelHandle<Project>> {
1469    if let Some(ix) = project_ix_for_root_name(&*client.local_projects(), root_name, cx) {
1470        return Some(client.local_projects()[ix].clone());
1471    }
1472    if let Some(ix) = project_ix_for_root_name(&*client.remote_projects(), root_name, cx) {
1473        return Some(client.remote_projects()[ix].clone());
1474    }
1475    None
1476}
1477
1478fn project_ix_for_root_name(
1479    projects: &[ModelHandle<Project>],
1480    root_name: &str,
1481    cx: &TestAppContext,
1482) -> Option<usize> {
1483    projects.iter().position(|project| {
1484        project.read_with(cx, |project, cx| {
1485            let worktree = project.visible_worktrees(cx).next().unwrap();
1486            worktree.read(cx).root_name() == root_name
1487        })
1488    })
1489}
1490
1491fn root_name_for_project(project: &ModelHandle<Project>, cx: &TestAppContext) -> String {
1492    project.read_with(cx, |project, cx| {
1493        project
1494            .visible_worktrees(cx)
1495            .next()
1496            .unwrap()
1497            .read(cx)
1498            .root_name()
1499            .to_string()
1500    })
1501}
1502
1503fn project_path_for_full_path(
1504    project: &ModelHandle<Project>,
1505    full_path: &Path,
1506    cx: &TestAppContext,
1507) -> Option<ProjectPath> {
1508    let mut components = full_path.components();
1509    let root_name = components.next().unwrap().as_os_str().to_str().unwrap();
1510    let path = components.as_path().into();
1511    let worktree_id = project.read_with(cx, |project, cx| {
1512        project.worktrees(cx).find_map(|worktree| {
1513            let worktree = worktree.read(cx);
1514            if worktree.root_name() == root_name {
1515                Some(worktree.id())
1516            } else {
1517                None
1518            }
1519        })
1520    })?;
1521    Some(ProjectPath { worktree_id, path })
1522}
1523
1524async fn ensure_project_shared(
1525    project: &ModelHandle<Project>,
1526    client: &TestClient,
1527    cx: &mut TestAppContext,
1528) {
1529    let first_root_name = root_name_for_project(project, cx);
1530    let active_call = cx.read(ActiveCall::global);
1531    if active_call.read_with(cx, |call, _| call.room().is_some())
1532        && project.read_with(cx, |project, _| project.is_local() && !project.is_shared())
1533    {
1534        match active_call
1535            .update(cx, |call, cx| call.share_project(project.clone(), cx))
1536            .await
1537        {
1538            Ok(project_id) => {
1539                log::info!(
1540                    "{}: shared project {} with id {}",
1541                    client.username,
1542                    first_root_name,
1543                    project_id
1544                );
1545            }
1546            Err(error) => {
1547                log::error!(
1548                    "{}: error sharing project {}: {:?}",
1549                    client.username,
1550                    first_root_name,
1551                    error
1552                );
1553            }
1554        }
1555    }
1556}
1557
1558fn choose_random_project(client: &TestClient, rng: &mut StdRng) -> Option<ModelHandle<Project>> {
1559    client
1560        .local_projects()
1561        .iter()
1562        .chain(client.remote_projects().iter())
1563        .choose(rng)
1564        .cloned()
1565}
1566
1567fn gen_file_name(rng: &mut StdRng) -> String {
1568    let mut name = String::new();
1569    for _ in 0..10 {
1570        let letter = rng.gen_range('a'..='z');
1571        name.push(letter);
1572    }
1573    name
1574}