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