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