randomized_integration_tests.rs

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