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