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