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