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