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