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