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