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