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