randomized_integration_tests.rs

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