randomized_integration_tests.rs

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