integration_tests.rs

   1use crate::{
   2    db::{tests::TestDb, ProjectId, UserId},
   3    rpc::{Executor, Server, Store},
   4    AppState,
   5};
   6use ::rpc::Peer;
   7use anyhow::anyhow;
   8use client::{
   9    self, proto, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Connection,
  10    Credentials, EstablishConnectionError, ProjectMetadata, UserStore, RECEIVE_TIMEOUT,
  11};
  12use collections::{BTreeMap, HashMap, HashSet};
  13use editor::{
  14    self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Input, Redo, Rename,
  15    ToOffset, ToggleCodeActions, Undo,
  16};
  17use futures::{channel::mpsc, Future, StreamExt as _};
  18use gpui::{
  19    executor::{self, Deterministic},
  20    geometry::vector::vec2f,
  21    ModelHandle, Task, TestAppContext, ViewHandle,
  22};
  23use language::{
  24    range_to_lsp, tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
  25    LanguageConfig, LanguageRegistry, LineEnding, OffsetRangeExt, Point, Rope,
  26};
  27use lsp::{self, FakeLanguageServer};
  28use parking_lot::Mutex;
  29use project::{
  30    fs::{FakeFs, Fs as _},
  31    search::SearchQuery,
  32    worktree::WorktreeHandle,
  33    DiagnosticSummary, Project, ProjectPath, ProjectStore, WorktreeId,
  34};
  35use rand::prelude::*;
  36use rpc::PeerId;
  37use serde_json::json;
  38use settings::{FormatOnSave, Settings};
  39use sqlx::types::time::OffsetDateTime;
  40use std::{
  41    cell::RefCell,
  42    env,
  43    ops::Deref,
  44    path::{Path, PathBuf},
  45    rc::Rc,
  46    sync::{
  47        atomic::{AtomicBool, Ordering::SeqCst},
  48        Arc,
  49    },
  50    time::Duration,
  51};
  52use theme::ThemeRegistry;
  53use workspace::{Item, SplitDirection, ToggleFollow, Workspace};
  54
  55#[ctor::ctor]
  56fn init_logger() {
  57    if std::env::var("RUST_LOG").is_ok() {
  58        env_logger::init();
  59    }
  60}
  61
  62#[gpui::test(iterations = 10)]
  63async fn test_share_project(
  64    deterministic: Arc<Deterministic>,
  65    cx_a: &mut TestAppContext,
  66    cx_b: &mut TestAppContext,
  67    cx_b2: &mut TestAppContext,
  68) {
  69    cx_a.foreground().forbid_parking();
  70    let (window_b, _) = cx_b.add_window(|_| EmptyView);
  71    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
  72    let client_a = server.create_client(cx_a, "user_a").await;
  73    let client_b = server.create_client(cx_b, "user_b").await;
  74    server
  75        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
  76        .await;
  77
  78    client_a
  79        .fs
  80        .insert_tree(
  81            "/a",
  82            json!({
  83                ".gitignore": "ignored-dir",
  84                "a.txt": "a-contents",
  85                "b.txt": "b-contents",
  86                "ignored-dir": {
  87                    "c.txt": "",
  88                    "d.txt": "",
  89                }
  90            }),
  91        )
  92        .await;
  93
  94    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
  95    let project_id = project_a.read_with(cx_a, |project, _| project.remote_id().unwrap());
  96
  97    // Join that project as client B
  98    let client_b_peer_id = client_b.peer_id;
  99    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 100    let replica_id_b = project_b.read_with(cx_b, |project, _| {
 101        assert_eq!(
 102            project
 103                .collaborators()
 104                .get(&client_a.peer_id)
 105                .unwrap()
 106                .user
 107                .github_login,
 108            "user_a"
 109        );
 110        project.replica_id()
 111    });
 112
 113    deterministic.run_until_parked();
 114    project_a.read_with(cx_a, |project, _| {
 115        let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
 116        assert_eq!(client_b_collaborator.replica_id, replica_id_b);
 117        assert_eq!(client_b_collaborator.user.github_login, "user_b");
 118    });
 119    project_b.read_with(cx_b, |project, cx| {
 120        let worktree = project.worktrees(cx).next().unwrap().read(cx);
 121        assert_eq!(
 122            worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
 123            [
 124                Path::new(".gitignore"),
 125                Path::new("a.txt"),
 126                Path::new("b.txt"),
 127                Path::new("ignored-dir"),
 128                Path::new("ignored-dir/c.txt"),
 129                Path::new("ignored-dir/d.txt"),
 130            ]
 131        );
 132    });
 133
 134    // Open the same file as client B and client A.
 135    let buffer_b = project_b
 136        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
 137        .await
 138        .unwrap();
 139    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
 140    project_a.read_with(cx_a, |project, cx| {
 141        assert!(project.has_open_buffer((worktree_id, "b.txt"), cx))
 142    });
 143    let buffer_a = project_a
 144        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
 145        .await
 146        .unwrap();
 147
 148    let editor_b = cx_b.add_view(window_b, |cx| Editor::for_buffer(buffer_b, None, cx));
 149
 150    // TODO
 151    // // Create a selection set as client B and see that selection set as client A.
 152    // buffer_a
 153    //     .condition(&cx_a, |buffer, _| buffer.selection_sets().count() == 1)
 154    //     .await;
 155
 156    // Edit the buffer as client B and see that edit as client A.
 157    editor_b.update(cx_b, |editor, cx| {
 158        editor.handle_input(&Input("ok, ".into()), cx)
 159    });
 160    buffer_a
 161        .condition(&cx_a, |buffer, _| buffer.text() == "ok, b-contents")
 162        .await;
 163
 164    // TODO
 165    // // Remove the selection set as client B, see those selections disappear as client A.
 166    cx_b.update(move |_| drop(editor_b));
 167    // buffer_a
 168    //     .condition(&cx_a, |buffer, _| buffer.selection_sets().count() == 0)
 169    //     .await;
 170
 171    // Client B can join again on a different window because they are already a participant.
 172    let client_b2 = server.create_client(cx_b2, "user_b").await;
 173    let project_b2 = Project::remote(
 174        project_id,
 175        client_b2.client.clone(),
 176        client_b2.user_store.clone(),
 177        client_b2.project_store.clone(),
 178        client_b2.language_registry.clone(),
 179        FakeFs::new(cx_b2.background()),
 180        cx_b2.to_async(),
 181    )
 182    .await
 183    .unwrap();
 184    deterministic.run_until_parked();
 185    project_a.read_with(cx_a, |project, _| {
 186        assert_eq!(project.collaborators().len(), 2);
 187    });
 188    project_b.read_with(cx_b, |project, _| {
 189        assert_eq!(project.collaborators().len(), 2);
 190    });
 191    project_b2.read_with(cx_b2, |project, _| {
 192        assert_eq!(project.collaborators().len(), 2);
 193    });
 194
 195    // Dropping client B's first project removes only that from client A's collaborators.
 196    cx_b.update(move |_| drop(project_b));
 197    deterministic.run_until_parked();
 198    project_a.read_with(cx_a, |project, _| {
 199        assert_eq!(project.collaborators().len(), 1);
 200    });
 201    project_b2.read_with(cx_b2, |project, _| {
 202        assert_eq!(project.collaborators().len(), 1);
 203    });
 204}
 205
 206#[gpui::test(iterations = 10)]
 207async fn test_unshare_project(
 208    deterministic: Arc<Deterministic>,
 209    cx_a: &mut TestAppContext,
 210    cx_b: &mut TestAppContext,
 211) {
 212    cx_a.foreground().forbid_parking();
 213    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 214    let client_a = server.create_client(cx_a, "user_a").await;
 215    let client_b = server.create_client(cx_b, "user_b").await;
 216    server
 217        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
 218        .await;
 219
 220    client_a
 221        .fs
 222        .insert_tree(
 223            "/a",
 224            json!({
 225                "a.txt": "a-contents",
 226                "b.txt": "b-contents",
 227            }),
 228        )
 229        .await;
 230
 231    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
 232    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
 233    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 234    assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 235
 236    project_b
 237        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 238        .await
 239        .unwrap();
 240
 241    // When client B leaves the project, it gets automatically unshared.
 242    cx_b.update(|_| drop(project_b));
 243    deterministic.run_until_parked();
 244    assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
 245
 246    // When client B joins again, the project gets re-shared.
 247    let project_b2 = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 248    assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 249    project_b2
 250        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 251        .await
 252        .unwrap();
 253
 254    // When client A (the host) leaves, the project gets unshared and guests are notified.
 255    cx_a.update(|_| drop(project_a));
 256    deterministic.run_until_parked();
 257    project_b2.read_with(cx_b, |project, _| {
 258        assert!(project.is_read_only());
 259        assert!(project.collaborators().is_empty());
 260    });
 261}
 262
 263#[gpui::test(iterations = 10)]
 264async fn test_host_disconnect(
 265    deterministic: Arc<Deterministic>,
 266    cx_a: &mut TestAppContext,
 267    cx_b: &mut TestAppContext,
 268    cx_c: &mut TestAppContext,
 269) {
 270    cx_b.update(editor::init);
 271    deterministic.forbid_parking();
 272    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 273    let client_a = server.create_client(cx_a, "user_a").await;
 274    let client_b = server.create_client(cx_b, "user_b").await;
 275    let client_c = server.create_client(cx_c, "user_c").await;
 276    server
 277        .make_contacts(vec![
 278            (&client_a, cx_a),
 279            (&client_b, cx_b),
 280            (&client_c, cx_c),
 281        ])
 282        .await;
 283
 284    client_a
 285        .fs
 286        .insert_tree(
 287            "/a",
 288            json!({
 289                "a.txt": "a-contents",
 290                "b.txt": "b-contents",
 291            }),
 292        )
 293        .await;
 294
 295    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
 296    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
 297    let project_id = project_a.read_with(cx_a, |project, _| project.remote_id().unwrap());
 298
 299    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 300    assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 301
 302    let (_, workspace_b) = cx_b.add_window(|cx| Workspace::new(project_b.clone(), cx));
 303    let editor_b = workspace_b
 304        .update(cx_b, |workspace, cx| {
 305            workspace.open_path((worktree_id, "b.txt"), true, cx)
 306        })
 307        .await
 308        .unwrap()
 309        .downcast::<Editor>()
 310        .unwrap();
 311    cx_b.read(|cx| {
 312        assert_eq!(
 313            cx.focused_view_id(workspace_b.window_id()),
 314            Some(editor_b.id())
 315        );
 316    });
 317    editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
 318    assert!(cx_b.is_window_edited(workspace_b.window_id()));
 319
 320    // Request to join that project as client C
 321    let project_c = cx_c.spawn(|cx| {
 322        Project::remote(
 323            project_id,
 324            client_c.client.clone(),
 325            client_c.user_store.clone(),
 326            client_c.project_store.clone(),
 327            client_c.language_registry.clone(),
 328            FakeFs::new(cx.background()),
 329            cx,
 330        )
 331    });
 332    deterministic.run_until_parked();
 333
 334    // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
 335    server.disconnect_client(client_a.current_user_id(cx_a));
 336    cx_a.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
 337    project_a
 338        .condition(cx_a, |project, _| project.collaborators().is_empty())
 339        .await;
 340    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
 341    project_b
 342        .condition(cx_b, |project, _| project.is_read_only())
 343        .await;
 344    assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
 345    assert!(matches!(
 346        project_c.await.unwrap_err(),
 347        project::JoinProjectError::HostWentOffline
 348    ));
 349
 350    // Ensure client B's edited state is reset and that the whole window is blurred.
 351    cx_b.read(|cx| {
 352        assert_eq!(cx.focused_view_id(workspace_b.window_id()), None);
 353    });
 354    assert!(!cx_b.is_window_edited(workspace_b.window_id()));
 355
 356    // Ensure client B is not prompted to save edits when closing window after disconnecting.
 357    workspace_b
 358        .update(cx_b, |workspace, cx| {
 359            workspace.close(&Default::default(), cx)
 360        })
 361        .unwrap()
 362        .await
 363        .unwrap();
 364    assert_eq!(cx_b.window_ids().len(), 0);
 365    cx_b.update(|_| {
 366        drop(workspace_b);
 367        drop(project_b);
 368    });
 369
 370    // Ensure guests can still join.
 371    let project_b2 = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 372    assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 373    project_b2
 374        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 375        .await
 376        .unwrap();
 377}
 378
 379#[gpui::test(iterations = 10)]
 380async fn test_decline_join_request(
 381    deterministic: Arc<Deterministic>,
 382    cx_a: &mut TestAppContext,
 383    cx_b: &mut TestAppContext,
 384) {
 385    cx_a.foreground().forbid_parking();
 386    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 387    let client_a = server.create_client(cx_a, "user_a").await;
 388    let client_b = server.create_client(cx_b, "user_b").await;
 389    server
 390        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
 391        .await;
 392
 393    client_a.fs.insert_tree("/a", json!({})).await;
 394
 395    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
 396    let project_id = project_a.read_with(cx_a, |project, _| project.remote_id().unwrap());
 397
 398    // Request to join that project as client B
 399    let project_b = cx_b.spawn(|cx| {
 400        Project::remote(
 401            project_id,
 402            client_b.client.clone(),
 403            client_b.user_store.clone(),
 404            client_b.project_store.clone(),
 405            client_b.language_registry.clone(),
 406            FakeFs::new(cx.background()),
 407            cx,
 408        )
 409    });
 410    deterministic.run_until_parked();
 411    project_a.update(cx_a, |project, cx| {
 412        project.respond_to_join_request(client_b.user_id().unwrap(), false, cx)
 413    });
 414    assert!(matches!(
 415        project_b.await.unwrap_err(),
 416        project::JoinProjectError::HostDeclined
 417    ));
 418
 419    // Request to join the project again as client B
 420    let project_b = cx_b.spawn(|cx| {
 421        Project::remote(
 422            project_id,
 423            client_b.client.clone(),
 424            client_b.user_store.clone(),
 425            client_b.project_store.clone(),
 426            client_b.language_registry.clone(),
 427            FakeFs::new(cx.background()),
 428            cx,
 429        )
 430    });
 431
 432    // Close the project on the host
 433    deterministic.run_until_parked();
 434    cx_a.update(|_| drop(project_a));
 435    deterministic.run_until_parked();
 436    assert!(matches!(
 437        project_b.await.unwrap_err(),
 438        project::JoinProjectError::HostClosedProject
 439    ));
 440}
 441
 442#[gpui::test(iterations = 10)]
 443async fn test_cancel_join_request(
 444    deterministic: Arc<Deterministic>,
 445    cx_a: &mut TestAppContext,
 446    cx_b: &mut TestAppContext,
 447) {
 448    cx_a.foreground().forbid_parking();
 449    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 450    let client_a = server.create_client(cx_a, "user_a").await;
 451    let client_b = server.create_client(cx_b, "user_b").await;
 452    server
 453        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
 454        .await;
 455
 456    client_a.fs.insert_tree("/a", json!({})).await;
 457    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
 458    let project_id = project_a.read_with(cx_a, |project, _| project.remote_id().unwrap());
 459
 460    let user_b = client_a
 461        .user_store
 462        .update(cx_a, |store, cx| {
 463            store.fetch_user(client_b.user_id().unwrap(), cx)
 464        })
 465        .await
 466        .unwrap();
 467
 468    let project_a_events = Rc::new(RefCell::new(Vec::new()));
 469    project_a.update(cx_a, {
 470        let project_a_events = project_a_events.clone();
 471        move |_, cx| {
 472            cx.subscribe(&cx.handle(), move |_, _, event, _| {
 473                project_a_events.borrow_mut().push(event.clone());
 474            })
 475            .detach();
 476        }
 477    });
 478
 479    // Request to join that project as client B
 480    let project_b = cx_b.spawn(|cx| {
 481        Project::remote(
 482            project_id,
 483            client_b.client.clone(),
 484            client_b.user_store.clone(),
 485            client_b.project_store.clone(),
 486            client_b.language_registry.clone().clone(),
 487            FakeFs::new(cx.background()),
 488            cx,
 489        )
 490    });
 491    deterministic.run_until_parked();
 492    assert_eq!(
 493        &*project_a_events.borrow(),
 494        &[project::Event::ContactRequestedJoin(user_b.clone())]
 495    );
 496    project_a_events.borrow_mut().clear();
 497
 498    // Cancel the join request by leaving the project
 499    client_b
 500        .client
 501        .send(proto::LeaveProject { project_id })
 502        .unwrap();
 503    drop(project_b);
 504
 505    deterministic.run_until_parked();
 506    assert_eq!(
 507        &*project_a_events.borrow(),
 508        &[project::Event::ContactCancelledJoinRequest(user_b.clone())]
 509    );
 510}
 511
 512#[gpui::test(iterations = 10)]
 513async fn test_offline_projects(
 514    deterministic: Arc<Deterministic>,
 515    cx_a: &mut TestAppContext,
 516    cx_b: &mut TestAppContext,
 517    cx_c: &mut TestAppContext,
 518) {
 519    cx_a.foreground().forbid_parking();
 520    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 521    let client_a = server.create_client(cx_a, "user_a").await;
 522    let client_b = server.create_client(cx_b, "user_b").await;
 523    let client_c = server.create_client(cx_c, "user_c").await;
 524    let user_a = UserId::from_proto(client_a.user_id().unwrap());
 525    server
 526        .make_contacts(vec![
 527            (&client_a, cx_a),
 528            (&client_b, cx_b),
 529            (&client_c, cx_c),
 530        ])
 531        .await;
 532
 533    // Set up observers of the project and user stores. Any time either of
 534    // these models update, they should be in a consistent state with each
 535    // other. There should not be an observable moment where the current
 536    // user's contact entry contains a project that does not match one of
 537    // the current open projects. That would cause a duplicate entry to be
 538    // shown in the contacts panel.
 539    let mut subscriptions = vec![];
 540    let (window_id, view) = cx_a.add_window(|cx| {
 541        subscriptions.push(cx.observe(&client_a.user_store, {
 542            let project_store = client_a.project_store.clone();
 543            let user_store = client_a.user_store.clone();
 544            move |_, _, cx| check_project_list(project_store.clone(), user_store.clone(), cx)
 545        }));
 546
 547        subscriptions.push(cx.observe(&client_a.project_store, {
 548            let project_store = client_a.project_store.clone();
 549            let user_store = client_a.user_store.clone();
 550            move |_, _, cx| check_project_list(project_store.clone(), user_store.clone(), cx)
 551        }));
 552
 553        fn check_project_list(
 554            project_store: ModelHandle<ProjectStore>,
 555            user_store: ModelHandle<UserStore>,
 556            cx: &mut gpui::MutableAppContext,
 557        ) {
 558            let open_project_ids = project_store
 559                .read(cx)
 560                .projects(cx)
 561                .filter_map(|project| project.read(cx).remote_id())
 562                .collect::<Vec<_>>();
 563
 564            let user_store = user_store.read(cx);
 565            for contact in user_store.contacts() {
 566                if contact.user.id == user_store.current_user().unwrap().id {
 567                    for project in &contact.projects {
 568                        if !open_project_ids.contains(&project.id) {
 569                            panic!(
 570                                concat!(
 571                                    "current user's contact data has a project",
 572                                    "that doesn't match any open project {:?}",
 573                                ),
 574                                project
 575                            );
 576                        }
 577                    }
 578                }
 579            }
 580        }
 581
 582        EmptyView
 583    });
 584
 585    // Build an offline project with two worktrees.
 586    client_a
 587        .fs
 588        .insert_tree(
 589            "/code",
 590            json!({
 591                "crate1": { "a.rs": "" },
 592                "crate2": { "b.rs": "" },
 593            }),
 594        )
 595        .await;
 596    let project = cx_a.update(|cx| {
 597        Project::local(
 598            false,
 599            client_a.client.clone(),
 600            client_a.user_store.clone(),
 601            client_a.project_store.clone(),
 602            client_a.language_registry.clone(),
 603            client_a.fs.clone(),
 604            cx,
 605        )
 606    });
 607    project
 608        .update(cx_a, |p, cx| {
 609            p.find_or_create_local_worktree("/code/crate1", true, cx)
 610        })
 611        .await
 612        .unwrap();
 613    project
 614        .update(cx_a, |p, cx| {
 615            p.find_or_create_local_worktree("/code/crate2", true, cx)
 616        })
 617        .await
 618        .unwrap();
 619    project
 620        .update(cx_a, |p, cx| p.restore_state(cx))
 621        .await
 622        .unwrap();
 623
 624    // When a project is offline, we still create it on the server but is invisible
 625    // to other users.
 626    deterministic.run_until_parked();
 627    assert!(server
 628        .store
 629        .lock()
 630        .await
 631        .project_metadata_for_user(user_a)
 632        .is_empty());
 633    project.read_with(cx_a, |project, _| {
 634        assert!(project.remote_id().is_some());
 635        assert!(!project.is_online());
 636    });
 637    assert!(client_b
 638        .user_store
 639        .read_with(cx_b, |store, _| { store.contacts()[0].projects.is_empty() }));
 640
 641    // When the project is taken online, its metadata is sent to the server
 642    // and broadcasted to other users.
 643    project.update(cx_a, |p, cx| p.set_online(true, cx));
 644    deterministic.run_until_parked();
 645    let project_id = project.read_with(cx_a, |p, _| p.remote_id()).unwrap();
 646    client_b.user_store.read_with(cx_b, |store, _| {
 647        assert_eq!(
 648            store.contacts()[0].projects,
 649            &[ProjectMetadata {
 650                id: project_id,
 651                visible_worktree_root_names: vec!["crate1".into(), "crate2".into()],
 652                guests: Default::default(),
 653            }]
 654        );
 655    });
 656
 657    // The project is registered again when the host loses and regains connection.
 658    server.disconnect_client(user_a);
 659    server.forbid_connections();
 660    cx_a.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
 661    assert!(server
 662        .store
 663        .lock()
 664        .await
 665        .project_metadata_for_user(user_a)
 666        .is_empty());
 667    assert!(project.read_with(cx_a, |p, _| p.remote_id().is_none()));
 668    assert!(client_b
 669        .user_store
 670        .read_with(cx_b, |store, _| { store.contacts()[0].projects.is_empty() }));
 671
 672    server.allow_connections();
 673    cx_b.foreground().advance_clock(Duration::from_secs(10));
 674    let project_id = project.read_with(cx_a, |p, _| p.remote_id()).unwrap();
 675    client_b.user_store.read_with(cx_b, |store, _| {
 676        assert_eq!(
 677            store.contacts()[0].projects,
 678            &[ProjectMetadata {
 679                id: project_id,
 680                visible_worktree_root_names: vec!["crate1".into(), "crate2".into()],
 681                guests: Default::default(),
 682            }]
 683        );
 684    });
 685
 686    project
 687        .update(cx_a, |p, cx| {
 688            p.find_or_create_local_worktree("/code/crate3", true, cx)
 689        })
 690        .await
 691        .unwrap();
 692    deterministic.run_until_parked();
 693    client_b.user_store.read_with(cx_b, |store, _| {
 694        assert_eq!(
 695            store.contacts()[0].projects,
 696            &[ProjectMetadata {
 697                id: project_id,
 698                visible_worktree_root_names: vec![
 699                    "crate1".into(),
 700                    "crate2".into(),
 701                    "crate3".into()
 702                ],
 703                guests: Default::default(),
 704            }]
 705        );
 706    });
 707
 708    // Build another project using a directory which was previously part of
 709    // an online project. Restore the project's state from the host's database.
 710    let project2_a = cx_a.update(|cx| {
 711        Project::local(
 712            false,
 713            client_a.client.clone(),
 714            client_a.user_store.clone(),
 715            client_a.project_store.clone(),
 716            client_a.language_registry.clone(),
 717            client_a.fs.clone(),
 718            cx,
 719        )
 720    });
 721    project2_a
 722        .update(cx_a, |p, cx| {
 723            p.find_or_create_local_worktree("/code/crate3", true, cx)
 724        })
 725        .await
 726        .unwrap();
 727    project2_a
 728        .update(cx_a, |project, cx| project.restore_state(cx))
 729        .await
 730        .unwrap();
 731
 732    // This project is now online, because its directory was previously online.
 733    project2_a.read_with(cx_a, |project, _| assert!(project.is_online()));
 734    deterministic.run_until_parked();
 735    let project2_id = project2_a.read_with(cx_a, |p, _| p.remote_id()).unwrap();
 736    client_b.user_store.read_with(cx_b, |store, _| {
 737        assert_eq!(
 738            store.contacts()[0].projects,
 739            &[
 740                ProjectMetadata {
 741                    id: project_id,
 742                    visible_worktree_root_names: vec![
 743                        "crate1".into(),
 744                        "crate2".into(),
 745                        "crate3".into()
 746                    ],
 747                    guests: Default::default(),
 748                },
 749                ProjectMetadata {
 750                    id: project2_id,
 751                    visible_worktree_root_names: vec!["crate3".into()],
 752                    guests: Default::default(),
 753                }
 754            ]
 755        );
 756    });
 757
 758    let project2_b = client_b.build_remote_project(&project2_a, cx_a, cx_b).await;
 759    let project2_c = cx_c.foreground().spawn(Project::remote(
 760        project2_id,
 761        client_c.client.clone(),
 762        client_c.user_store.clone(),
 763        client_c.project_store.clone(),
 764        client_c.language_registry.clone(),
 765        FakeFs::new(cx_c.background()),
 766        cx_c.to_async(),
 767    ));
 768    deterministic.run_until_parked();
 769
 770    // Taking a project offline unshares the project, rejects any pending join request and
 771    // disconnects existing guests.
 772    project2_a.update(cx_a, |project, cx| project.set_online(false, cx));
 773    deterministic.run_until_parked();
 774    project2_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
 775    project2_b.read_with(cx_b, |project, _| assert!(project.is_read_only()));
 776    project2_c.await.unwrap_err();
 777
 778    client_b.user_store.read_with(cx_b, |store, _| {
 779        assert_eq!(
 780            store.contacts()[0].projects,
 781            &[ProjectMetadata {
 782                id: project_id,
 783                visible_worktree_root_names: vec![
 784                    "crate1".into(),
 785                    "crate2".into(),
 786                    "crate3".into()
 787                ],
 788                guests: Default::default(),
 789            },]
 790        );
 791    });
 792
 793    cx_a.update(|cx| {
 794        drop(subscriptions);
 795        drop(view);
 796        cx.remove_window(window_id);
 797    });
 798}
 799
 800#[gpui::test(iterations = 10)]
 801async fn test_propagate_saves_and_fs_changes(
 802    cx_a: &mut TestAppContext,
 803    cx_b: &mut TestAppContext,
 804    cx_c: &mut TestAppContext,
 805) {
 806    cx_a.foreground().forbid_parking();
 807    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 808    let client_a = server.create_client(cx_a, "user_a").await;
 809    let client_b = server.create_client(cx_b, "user_b").await;
 810    let client_c = server.create_client(cx_c, "user_c").await;
 811    server
 812        .make_contacts(vec![
 813            (&client_a, cx_a),
 814            (&client_b, cx_b),
 815            (&client_c, cx_c),
 816        ])
 817        .await;
 818
 819    client_a
 820        .fs
 821        .insert_tree(
 822            "/a",
 823            json!({
 824                "file1": "",
 825                "file2": ""
 826            }),
 827        )
 828        .await;
 829    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
 830    let worktree_a = project_a.read_with(cx_a, |p, cx| p.worktrees(cx).next().unwrap());
 831
 832    // Join that worktree as clients B and C.
 833    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 834    let project_c = client_c.build_remote_project(&project_a, cx_a, cx_c).await;
 835    let worktree_b = project_b.read_with(cx_b, |p, cx| p.worktrees(cx).next().unwrap());
 836    let worktree_c = project_c.read_with(cx_c, |p, cx| p.worktrees(cx).next().unwrap());
 837
 838    // Open and edit a buffer as both guests B and C.
 839    let buffer_b = project_b
 840        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
 841        .await
 842        .unwrap();
 843    let buffer_c = project_c
 844        .update(cx_c, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
 845        .await
 846        .unwrap();
 847    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "i-am-b, ")], cx));
 848    buffer_c.update(cx_c, |buf, cx| buf.edit([(0..0, "i-am-c, ")], cx));
 849
 850    // Open and edit that buffer as the host.
 851    let buffer_a = project_a
 852        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
 853        .await
 854        .unwrap();
 855
 856    buffer_a
 857        .condition(cx_a, |buf, _| buf.text() == "i-am-c, i-am-b, ")
 858        .await;
 859    buffer_a.update(cx_a, |buf, cx| {
 860        buf.edit([(buf.len()..buf.len(), "i-am-a")], cx)
 861    });
 862
 863    // Wait for edits to propagate
 864    buffer_a
 865        .condition(cx_a, |buf, _| buf.text() == "i-am-c, i-am-b, i-am-a")
 866        .await;
 867    buffer_b
 868        .condition(cx_b, |buf, _| buf.text() == "i-am-c, i-am-b, i-am-a")
 869        .await;
 870    buffer_c
 871        .condition(cx_c, |buf, _| buf.text() == "i-am-c, i-am-b, i-am-a")
 872        .await;
 873
 874    // Edit the buffer as the host and concurrently save as guest B.
 875    let save_b = buffer_b.update(cx_b, |buf, cx| buf.save(cx));
 876    buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "hi-a, ")], cx));
 877    save_b.await.unwrap();
 878    assert_eq!(
 879        client_a.fs.load("/a/file1".as_ref()).await.unwrap(),
 880        "hi-a, i-am-c, i-am-b, i-am-a"
 881    );
 882    buffer_a.read_with(cx_a, |buf, _| assert!(!buf.is_dirty()));
 883    buffer_b.read_with(cx_b, |buf, _| assert!(!buf.is_dirty()));
 884    buffer_c.condition(cx_c, |buf, _| !buf.is_dirty()).await;
 885
 886    worktree_a.flush_fs_events(cx_a).await;
 887
 888    // Make changes on host's file system, see those changes on guest worktrees.
 889    client_a
 890        .fs
 891        .rename(
 892            "/a/file1".as_ref(),
 893            "/a/file1-renamed".as_ref(),
 894            Default::default(),
 895        )
 896        .await
 897        .unwrap();
 898
 899    client_a
 900        .fs
 901        .rename("/a/file2".as_ref(), "/a/file3".as_ref(), Default::default())
 902        .await
 903        .unwrap();
 904    client_a.fs.insert_file("/a/file4", "4".into()).await;
 905
 906    worktree_a
 907        .condition(&cx_a, |tree, _| {
 908            tree.paths()
 909                .map(|p| p.to_string_lossy())
 910                .collect::<Vec<_>>()
 911                == ["file1-renamed", "file3", "file4"]
 912        })
 913        .await;
 914    worktree_b
 915        .condition(&cx_b, |tree, _| {
 916            tree.paths()
 917                .map(|p| p.to_string_lossy())
 918                .collect::<Vec<_>>()
 919                == ["file1-renamed", "file3", "file4"]
 920        })
 921        .await;
 922    worktree_c
 923        .condition(&cx_c, |tree, _| {
 924            tree.paths()
 925                .map(|p| p.to_string_lossy())
 926                .collect::<Vec<_>>()
 927                == ["file1-renamed", "file3", "file4"]
 928        })
 929        .await;
 930
 931    // Ensure buffer files are updated as well.
 932    buffer_a
 933        .condition(&cx_a, |buf, _| {
 934            buf.file().unwrap().path().to_str() == Some("file1-renamed")
 935        })
 936        .await;
 937    buffer_b
 938        .condition(&cx_b, |buf, _| {
 939            buf.file().unwrap().path().to_str() == Some("file1-renamed")
 940        })
 941        .await;
 942    buffer_c
 943        .condition(&cx_c, |buf, _| {
 944            buf.file().unwrap().path().to_str() == Some("file1-renamed")
 945        })
 946        .await;
 947}
 948
 949#[gpui::test(iterations = 10)]
 950async fn test_fs_operations(
 951    executor: Arc<Deterministic>,
 952    cx_a: &mut TestAppContext,
 953    cx_b: &mut TestAppContext,
 954) {
 955    executor.forbid_parking();
 956    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 957    let client_a = server.create_client(cx_a, "user_a").await;
 958    let client_b = server.create_client(cx_b, "user_b").await;
 959    server
 960        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
 961        .await;
 962
 963    client_a
 964        .fs
 965        .insert_tree(
 966            "/dir",
 967            json!({
 968                "a.txt": "a-contents",
 969                "b.txt": "b-contents",
 970            }),
 971        )
 972        .await;
 973    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
 974    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 975
 976    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
 977    let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
 978
 979    let entry = project_b
 980        .update(cx_b, |project, cx| {
 981            project
 982                .create_entry((worktree_id, "c.txt"), false, cx)
 983                .unwrap()
 984        })
 985        .await
 986        .unwrap();
 987    worktree_a.read_with(cx_a, |worktree, _| {
 988        assert_eq!(
 989            worktree
 990                .paths()
 991                .map(|p| p.to_string_lossy())
 992                .collect::<Vec<_>>(),
 993            ["a.txt", "b.txt", "c.txt"]
 994        );
 995    });
 996    worktree_b.read_with(cx_b, |worktree, _| {
 997        assert_eq!(
 998            worktree
 999                .paths()
1000                .map(|p| p.to_string_lossy())
1001                .collect::<Vec<_>>(),
1002            ["a.txt", "b.txt", "c.txt"]
1003        );
1004    });
1005
1006    project_b
1007        .update(cx_b, |project, cx| {
1008            project.rename_entry(entry.id, Path::new("d.txt"), cx)
1009        })
1010        .unwrap()
1011        .await
1012        .unwrap();
1013    worktree_a.read_with(cx_a, |worktree, _| {
1014        assert_eq!(
1015            worktree
1016                .paths()
1017                .map(|p| p.to_string_lossy())
1018                .collect::<Vec<_>>(),
1019            ["a.txt", "b.txt", "d.txt"]
1020        );
1021    });
1022    worktree_b.read_with(cx_b, |worktree, _| {
1023        assert_eq!(
1024            worktree
1025                .paths()
1026                .map(|p| p.to_string_lossy())
1027                .collect::<Vec<_>>(),
1028            ["a.txt", "b.txt", "d.txt"]
1029        );
1030    });
1031
1032    let dir_entry = project_b
1033        .update(cx_b, |project, cx| {
1034            project
1035                .create_entry((worktree_id, "DIR"), true, cx)
1036                .unwrap()
1037        })
1038        .await
1039        .unwrap();
1040    worktree_a.read_with(cx_a, |worktree, _| {
1041        assert_eq!(
1042            worktree
1043                .paths()
1044                .map(|p| p.to_string_lossy())
1045                .collect::<Vec<_>>(),
1046            ["DIR", "a.txt", "b.txt", "d.txt"]
1047        );
1048    });
1049    worktree_b.read_with(cx_b, |worktree, _| {
1050        assert_eq!(
1051            worktree
1052                .paths()
1053                .map(|p| p.to_string_lossy())
1054                .collect::<Vec<_>>(),
1055            ["DIR", "a.txt", "b.txt", "d.txt"]
1056        );
1057    });
1058
1059    project_b
1060        .update(cx_b, |project, cx| {
1061            project
1062                .create_entry((worktree_id, "DIR/e.txt"), false, cx)
1063                .unwrap()
1064        })
1065        .await
1066        .unwrap();
1067    project_b
1068        .update(cx_b, |project, cx| {
1069            project
1070                .create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
1071                .unwrap()
1072        })
1073        .await
1074        .unwrap();
1075    project_b
1076        .update(cx_b, |project, cx| {
1077            project
1078                .create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
1079                .unwrap()
1080        })
1081        .await
1082        .unwrap();
1083    worktree_a.read_with(cx_a, |worktree, _| {
1084        assert_eq!(
1085            worktree
1086                .paths()
1087                .map(|p| p.to_string_lossy())
1088                .collect::<Vec<_>>(),
1089            [
1090                "DIR",
1091                "DIR/SUBDIR",
1092                "DIR/SUBDIR/f.txt",
1093                "DIR/e.txt",
1094                "a.txt",
1095                "b.txt",
1096                "d.txt"
1097            ]
1098        );
1099    });
1100    worktree_b.read_with(cx_b, |worktree, _| {
1101        assert_eq!(
1102            worktree
1103                .paths()
1104                .map(|p| p.to_string_lossy())
1105                .collect::<Vec<_>>(),
1106            [
1107                "DIR",
1108                "DIR/SUBDIR",
1109                "DIR/SUBDIR/f.txt",
1110                "DIR/e.txt",
1111                "a.txt",
1112                "b.txt",
1113                "d.txt"
1114            ]
1115        );
1116    });
1117
1118    project_b
1119        .update(cx_b, |project, cx| {
1120            project
1121                .copy_entry(entry.id, Path::new("f.txt"), cx)
1122                .unwrap()
1123        })
1124        .await
1125        .unwrap();
1126    worktree_a.read_with(cx_a, |worktree, _| {
1127        assert_eq!(
1128            worktree
1129                .paths()
1130                .map(|p| p.to_string_lossy())
1131                .collect::<Vec<_>>(),
1132            [
1133                "DIR",
1134                "DIR/SUBDIR",
1135                "DIR/SUBDIR/f.txt",
1136                "DIR/e.txt",
1137                "a.txt",
1138                "b.txt",
1139                "d.txt",
1140                "f.txt"
1141            ]
1142        );
1143    });
1144    worktree_b.read_with(cx_b, |worktree, _| {
1145        assert_eq!(
1146            worktree
1147                .paths()
1148                .map(|p| p.to_string_lossy())
1149                .collect::<Vec<_>>(),
1150            [
1151                "DIR",
1152                "DIR/SUBDIR",
1153                "DIR/SUBDIR/f.txt",
1154                "DIR/e.txt",
1155                "a.txt",
1156                "b.txt",
1157                "d.txt",
1158                "f.txt"
1159            ]
1160        );
1161    });
1162
1163    project_b
1164        .update(cx_b, |project, cx| {
1165            project.delete_entry(dir_entry.id, cx).unwrap()
1166        })
1167        .await
1168        .unwrap();
1169    worktree_a.read_with(cx_a, |worktree, _| {
1170        assert_eq!(
1171            worktree
1172                .paths()
1173                .map(|p| p.to_string_lossy())
1174                .collect::<Vec<_>>(),
1175            ["a.txt", "b.txt", "d.txt", "f.txt"]
1176        );
1177    });
1178    worktree_b.read_with(cx_b, |worktree, _| {
1179        assert_eq!(
1180            worktree
1181                .paths()
1182                .map(|p| p.to_string_lossy())
1183                .collect::<Vec<_>>(),
1184            ["a.txt", "b.txt", "d.txt", "f.txt"]
1185        );
1186    });
1187
1188    project_b
1189        .update(cx_b, |project, cx| {
1190            project.delete_entry(entry.id, cx).unwrap()
1191        })
1192        .await
1193        .unwrap();
1194    worktree_a.read_with(cx_a, |worktree, _| {
1195        assert_eq!(
1196            worktree
1197                .paths()
1198                .map(|p| p.to_string_lossy())
1199                .collect::<Vec<_>>(),
1200            ["a.txt", "b.txt", "f.txt"]
1201        );
1202    });
1203    worktree_b.read_with(cx_b, |worktree, _| {
1204        assert_eq!(
1205            worktree
1206                .paths()
1207                .map(|p| p.to_string_lossy())
1208                .collect::<Vec<_>>(),
1209            ["a.txt", "b.txt", "f.txt"]
1210        );
1211    });
1212}
1213
1214#[gpui::test(iterations = 10)]
1215async fn test_buffer_conflict_after_save(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1216    cx_a.foreground().forbid_parking();
1217    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1218    let client_a = server.create_client(cx_a, "user_a").await;
1219    let client_b = server.create_client(cx_b, "user_b").await;
1220    server
1221        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1222        .await;
1223
1224    client_a
1225        .fs
1226        .insert_tree(
1227            "/dir",
1228            json!({
1229                "a.txt": "a-contents",
1230            }),
1231        )
1232        .await;
1233    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
1234    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1235
1236    // Open a buffer as client B
1237    let buffer_b = project_b
1238        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1239        .await
1240        .unwrap();
1241
1242    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "world ")], cx));
1243    buffer_b.read_with(cx_b, |buf, _| {
1244        assert!(buf.is_dirty());
1245        assert!(!buf.has_conflict());
1246    });
1247
1248    buffer_b.update(cx_b, |buf, cx| buf.save(cx)).await.unwrap();
1249    buffer_b
1250        .condition(&cx_b, |buffer_b, _| !buffer_b.is_dirty())
1251        .await;
1252    buffer_b.read_with(cx_b, |buf, _| {
1253        assert!(!buf.has_conflict());
1254    });
1255
1256    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "hello ")], cx));
1257    buffer_b.read_with(cx_b, |buf, _| {
1258        assert!(buf.is_dirty());
1259        assert!(!buf.has_conflict());
1260    });
1261}
1262
1263#[gpui::test(iterations = 10)]
1264async fn test_buffer_reloading(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1265    cx_a.foreground().forbid_parking();
1266    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1267    let client_a = server.create_client(cx_a, "user_a").await;
1268    let client_b = server.create_client(cx_b, "user_b").await;
1269    server
1270        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1271        .await;
1272
1273    client_a
1274        .fs
1275        .insert_tree(
1276            "/dir",
1277            json!({
1278                "a.txt": "a\nb\nc",
1279            }),
1280        )
1281        .await;
1282    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
1283    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1284
1285    // Open a buffer as client B
1286    let buffer_b = project_b
1287        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1288        .await
1289        .unwrap();
1290    buffer_b.read_with(cx_b, |buf, _| {
1291        assert!(!buf.is_dirty());
1292        assert!(!buf.has_conflict());
1293        assert_eq!(buf.line_ending(), LineEnding::Unix);
1294    });
1295
1296    let new_contents = Rope::from("d\ne\nf");
1297    client_a
1298        .fs
1299        .save("/dir/a.txt".as_ref(), &new_contents, LineEnding::Windows)
1300        .await
1301        .unwrap();
1302    buffer_b
1303        .condition(&cx_b, |buf, _| {
1304            buf.text() == new_contents.to_string() && !buf.is_dirty()
1305        })
1306        .await;
1307    buffer_b.read_with(cx_b, |buf, _| {
1308        assert!(!buf.is_dirty());
1309        assert!(!buf.has_conflict());
1310        assert_eq!(buf.line_ending(), LineEnding::Windows);
1311    });
1312}
1313
1314#[gpui::test(iterations = 10)]
1315async fn test_editing_while_guest_opens_buffer(
1316    cx_a: &mut TestAppContext,
1317    cx_b: &mut TestAppContext,
1318) {
1319    cx_a.foreground().forbid_parking();
1320    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1321    let client_a = server.create_client(cx_a, "user_a").await;
1322    let client_b = server.create_client(cx_b, "user_b").await;
1323    server
1324        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1325        .await;
1326
1327    client_a
1328        .fs
1329        .insert_tree("/dir", json!({ "a.txt": "a-contents" }))
1330        .await;
1331    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
1332    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1333
1334    // Open a buffer as client A
1335    let buffer_a = project_a
1336        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1337        .await
1338        .unwrap();
1339
1340    // Start opening the same buffer as client B
1341    let buffer_b = cx_b
1342        .background()
1343        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)));
1344
1345    // Edit the buffer as client A while client B is still opening it.
1346    cx_b.background().simulate_random_delay().await;
1347    buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "X")], cx));
1348    cx_b.background().simulate_random_delay().await;
1349    buffer_a.update(cx_a, |buf, cx| buf.edit([(1..1, "Y")], cx));
1350
1351    let text = buffer_a.read_with(cx_a, |buf, _| buf.text());
1352    let buffer_b = buffer_b.await.unwrap();
1353    buffer_b.condition(&cx_b, |buf, _| buf.text() == text).await;
1354}
1355
1356#[gpui::test(iterations = 10)]
1357async fn test_leaving_worktree_while_opening_buffer(
1358    cx_a: &mut TestAppContext,
1359    cx_b: &mut TestAppContext,
1360) {
1361    cx_a.foreground().forbid_parking();
1362    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1363    let client_a = server.create_client(cx_a, "user_a").await;
1364    let client_b = server.create_client(cx_b, "user_b").await;
1365    server
1366        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1367        .await;
1368
1369    client_a
1370        .fs
1371        .insert_tree("/dir", json!({ "a.txt": "a-contents" }))
1372        .await;
1373    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
1374    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1375
1376    // See that a guest has joined as client A.
1377    project_a
1378        .condition(&cx_a, |p, _| p.collaborators().len() == 1)
1379        .await;
1380
1381    // Begin opening a buffer as client B, but leave the project before the open completes.
1382    let buffer_b = cx_b
1383        .background()
1384        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)));
1385    cx_b.update(|_| drop(project_b));
1386    drop(buffer_b);
1387
1388    // See that the guest has left.
1389    project_a
1390        .condition(&cx_a, |p, _| p.collaborators().len() == 0)
1391        .await;
1392}
1393
1394#[gpui::test(iterations = 10)]
1395async fn test_leaving_project(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1396    cx_a.foreground().forbid_parking();
1397    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1398    let client_a = server.create_client(cx_a, "user_a").await;
1399    let client_b = server.create_client(cx_b, "user_b").await;
1400    server
1401        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1402        .await;
1403
1404    client_a
1405        .fs
1406        .insert_tree(
1407            "/a",
1408            json!({
1409                "a.txt": "a-contents",
1410                "b.txt": "b-contents",
1411            }),
1412        )
1413        .await;
1414    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
1415    let _project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1416
1417    // Client A sees that a guest has joined.
1418    project_a
1419        .condition(cx_a, |p, _| p.collaborators().len() == 1)
1420        .await;
1421
1422    // Drop client B's connection and ensure client A observes client B leaving the project.
1423    client_b.disconnect(&cx_b.to_async()).unwrap();
1424    project_a
1425        .condition(cx_a, |p, _| p.collaborators().len() == 0)
1426        .await;
1427
1428    // Rejoin the project as client B
1429    let _project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1430
1431    // Client A sees that a guest has re-joined.
1432    project_a
1433        .condition(cx_a, |p, _| p.collaborators().len() == 1)
1434        .await;
1435
1436    // Simulate connection loss for client B and ensure client A observes client B leaving the project.
1437    client_b.wait_for_current_user(cx_b).await;
1438    server.disconnect_client(client_b.current_user_id(cx_b));
1439    cx_a.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
1440    project_a
1441        .condition(cx_a, |p, _| p.collaborators().len() == 0)
1442        .await;
1443}
1444
1445#[gpui::test(iterations = 10)]
1446async fn test_collaborating_with_diagnostics(
1447    deterministic: Arc<Deterministic>,
1448    cx_a: &mut TestAppContext,
1449    cx_b: &mut TestAppContext,
1450    cx_c: &mut TestAppContext,
1451) {
1452    deterministic.forbid_parking();
1453    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1454    let client_a = server.create_client(cx_a, "user_a").await;
1455    let client_b = server.create_client(cx_b, "user_b").await;
1456    let client_c = server.create_client(cx_c, "user_c").await;
1457    server
1458        .make_contacts(vec![
1459            (&client_a, cx_a),
1460            (&client_b, cx_b),
1461            (&client_c, cx_c),
1462        ])
1463        .await;
1464
1465    // Set up a fake language server.
1466    let mut language = Language::new(
1467        LanguageConfig {
1468            name: "Rust".into(),
1469            path_suffixes: vec!["rs".to_string()],
1470            ..Default::default()
1471        },
1472        Some(tree_sitter_rust::language()),
1473    );
1474    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
1475    client_a.language_registry.add(Arc::new(language));
1476
1477    // Share a project as client A
1478    client_a
1479        .fs
1480        .insert_tree(
1481            "/a",
1482            json!({
1483                "a.rs": "let one = two",
1484                "other.rs": "",
1485            }),
1486        )
1487        .await;
1488    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1489    let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
1490
1491    // Cause the language server to start.
1492    let _buffer = cx_a
1493        .background()
1494        .spawn(project_a.update(cx_a, |project, cx| {
1495            project.open_buffer(
1496                ProjectPath {
1497                    worktree_id,
1498                    path: Path::new("other.rs").into(),
1499                },
1500                cx,
1501            )
1502        }))
1503        .await
1504        .unwrap();
1505
1506    // Join the worktree as client B.
1507    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1508
1509    // Simulate a language server reporting errors for a file.
1510    let mut fake_language_server = fake_language_servers.next().await.unwrap();
1511    fake_language_server
1512        .receive_notification::<lsp::notification::DidOpenTextDocument>()
1513        .await;
1514    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
1515        lsp::PublishDiagnosticsParams {
1516            uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
1517            version: None,
1518            diagnostics: vec![lsp::Diagnostic {
1519                severity: Some(lsp::DiagnosticSeverity::ERROR),
1520                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
1521                message: "message 1".to_string(),
1522                ..Default::default()
1523            }],
1524        },
1525    );
1526
1527    // Wait for server to see the diagnostics update.
1528    deterministic.run_until_parked();
1529    {
1530        let store = server.store.lock().await;
1531        let project = store.project(ProjectId::from_proto(project_id)).unwrap();
1532        let worktree = project.worktrees.get(&worktree_id.to_proto()).unwrap();
1533        assert!(!worktree.diagnostic_summaries.is_empty());
1534    }
1535
1536    // Ensure client B observes the new diagnostics.
1537    project_b.read_with(cx_b, |project, cx| {
1538        assert_eq!(
1539            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
1540            &[(
1541                ProjectPath {
1542                    worktree_id,
1543                    path: Arc::from(Path::new("a.rs")),
1544                },
1545                DiagnosticSummary {
1546                    error_count: 1,
1547                    warning_count: 0,
1548                    ..Default::default()
1549                },
1550            )]
1551        )
1552    });
1553
1554    // Join project as client C and observe the diagnostics.
1555    let project_c = client_c.build_remote_project(&project_a, cx_a, cx_c).await;
1556    deterministic.run_until_parked();
1557    project_c.read_with(cx_c, |project, cx| {
1558        assert_eq!(
1559            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
1560            &[(
1561                ProjectPath {
1562                    worktree_id,
1563                    path: Arc::from(Path::new("a.rs")),
1564                },
1565                DiagnosticSummary {
1566                    error_count: 1,
1567                    warning_count: 0,
1568                    ..Default::default()
1569                },
1570            )]
1571        )
1572    });
1573
1574    // Simulate a language server reporting more errors for a file.
1575    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
1576        lsp::PublishDiagnosticsParams {
1577            uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
1578            version: None,
1579            diagnostics: vec![
1580                lsp::Diagnostic {
1581                    severity: Some(lsp::DiagnosticSeverity::ERROR),
1582                    range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
1583                    message: "message 1".to_string(),
1584                    ..Default::default()
1585                },
1586                lsp::Diagnostic {
1587                    severity: Some(lsp::DiagnosticSeverity::WARNING),
1588                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 13)),
1589                    message: "message 2".to_string(),
1590                    ..Default::default()
1591                },
1592            ],
1593        },
1594    );
1595
1596    // Clients B and C get the updated summaries
1597    deterministic.run_until_parked();
1598    project_b.read_with(cx_b, |project, cx| {
1599        assert_eq!(
1600            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
1601            [(
1602                ProjectPath {
1603                    worktree_id,
1604                    path: Arc::from(Path::new("a.rs")),
1605                },
1606                DiagnosticSummary {
1607                    error_count: 1,
1608                    warning_count: 1,
1609                    ..Default::default()
1610                },
1611            )]
1612        );
1613    });
1614    project_c.read_with(cx_c, |project, cx| {
1615        assert_eq!(
1616            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
1617            [(
1618                ProjectPath {
1619                    worktree_id,
1620                    path: Arc::from(Path::new("a.rs")),
1621                },
1622                DiagnosticSummary {
1623                    error_count: 1,
1624                    warning_count: 1,
1625                    ..Default::default()
1626                },
1627            )]
1628        );
1629    });
1630
1631    // Open the file with the errors on client B. They should be present.
1632    let buffer_b = cx_b
1633        .background()
1634        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
1635        .await
1636        .unwrap();
1637
1638    buffer_b.read_with(cx_b, |buffer, _| {
1639        assert_eq!(
1640            buffer
1641                .snapshot()
1642                .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
1643                .map(|entry| entry)
1644                .collect::<Vec<_>>(),
1645            &[
1646                DiagnosticEntry {
1647                    range: Point::new(0, 4)..Point::new(0, 7),
1648                    diagnostic: Diagnostic {
1649                        group_id: 1,
1650                        message: "message 1".to_string(),
1651                        severity: lsp::DiagnosticSeverity::ERROR,
1652                        is_primary: true,
1653                        ..Default::default()
1654                    }
1655                },
1656                DiagnosticEntry {
1657                    range: Point::new(0, 10)..Point::new(0, 13),
1658                    diagnostic: Diagnostic {
1659                        group_id: 2,
1660                        severity: lsp::DiagnosticSeverity::WARNING,
1661                        message: "message 2".to_string(),
1662                        is_primary: true,
1663                        ..Default::default()
1664                    }
1665                }
1666            ]
1667        );
1668    });
1669
1670    // Simulate a language server reporting no errors for a file.
1671    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
1672        lsp::PublishDiagnosticsParams {
1673            uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
1674            version: None,
1675            diagnostics: vec![],
1676        },
1677    );
1678    deterministic.run_until_parked();
1679    project_a.read_with(cx_a, |project, cx| {
1680        assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
1681    });
1682    project_b.read_with(cx_b, |project, cx| {
1683        assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
1684    });
1685    project_c.read_with(cx_c, |project, cx| {
1686        assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
1687    });
1688}
1689
1690#[gpui::test(iterations = 10)]
1691async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1692    cx_a.foreground().forbid_parking();
1693    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1694    let client_a = server.create_client(cx_a, "user_a").await;
1695    let client_b = server.create_client(cx_b, "user_b").await;
1696    server
1697        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1698        .await;
1699
1700    // Set up a fake language server.
1701    let mut language = Language::new(
1702        LanguageConfig {
1703            name: "Rust".into(),
1704            path_suffixes: vec!["rs".to_string()],
1705            ..Default::default()
1706        },
1707        Some(tree_sitter_rust::language()),
1708    );
1709    let mut fake_language_servers = language.set_fake_lsp_adapter(FakeLspAdapter {
1710        capabilities: lsp::ServerCapabilities {
1711            completion_provider: Some(lsp::CompletionOptions {
1712                trigger_characters: Some(vec![".".to_string()]),
1713                ..Default::default()
1714            }),
1715            ..Default::default()
1716        },
1717        ..Default::default()
1718    });
1719    client_a.language_registry.add(Arc::new(language));
1720
1721    client_a
1722        .fs
1723        .insert_tree(
1724            "/a",
1725            json!({
1726                "main.rs": "fn main() { a }",
1727                "other.rs": "",
1728            }),
1729        )
1730        .await;
1731    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1732    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1733
1734    // Open a file in an editor as the guest.
1735    let buffer_b = project_b
1736        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1737        .await
1738        .unwrap();
1739    let (window_b, _) = cx_b.add_window(|_| EmptyView);
1740    let editor_b = cx_b.add_view(window_b, |cx| {
1741        Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
1742    });
1743
1744    let fake_language_server = fake_language_servers.next().await.unwrap();
1745    buffer_b
1746        .condition(&cx_b, |buffer, _| !buffer.completion_triggers().is_empty())
1747        .await;
1748
1749    // Type a completion trigger character as the guest.
1750    editor_b.update(cx_b, |editor, cx| {
1751        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1752        editor.handle_input(&Input(".".into()), cx);
1753        cx.focus(&editor_b);
1754    });
1755
1756    // Receive a completion request as the host's language server.
1757    // Return some completions from the host's language server.
1758    cx_a.foreground().start_waiting();
1759    fake_language_server
1760        .handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
1761            assert_eq!(
1762                params.text_document_position.text_document.uri,
1763                lsp::Url::from_file_path("/a/main.rs").unwrap(),
1764            );
1765            assert_eq!(
1766                params.text_document_position.position,
1767                lsp::Position::new(0, 14),
1768            );
1769
1770            Ok(Some(lsp::CompletionResponse::Array(vec![
1771                lsp::CompletionItem {
1772                    label: "first_method(…)".into(),
1773                    detail: Some("fn(&mut self, B) -> C".into()),
1774                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1775                        new_text: "first_method($1)".to_string(),
1776                        range: lsp::Range::new(
1777                            lsp::Position::new(0, 14),
1778                            lsp::Position::new(0, 14),
1779                        ),
1780                    })),
1781                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1782                    ..Default::default()
1783                },
1784                lsp::CompletionItem {
1785                    label: "second_method(…)".into(),
1786                    detail: Some("fn(&mut self, C) -> D<E>".into()),
1787                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1788                        new_text: "second_method()".to_string(),
1789                        range: lsp::Range::new(
1790                            lsp::Position::new(0, 14),
1791                            lsp::Position::new(0, 14),
1792                        ),
1793                    })),
1794                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1795                    ..Default::default()
1796                },
1797            ])))
1798        })
1799        .next()
1800        .await
1801        .unwrap();
1802    cx_a.foreground().finish_waiting();
1803
1804    // Open the buffer on the host.
1805    let buffer_a = project_a
1806        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1807        .await
1808        .unwrap();
1809    buffer_a
1810        .condition(&cx_a, |buffer, _| buffer.text() == "fn main() { a. }")
1811        .await;
1812
1813    // Confirm a completion on the guest.
1814    editor_b
1815        .condition(&cx_b, |editor, _| editor.context_menu_visible())
1816        .await;
1817    editor_b.update(cx_b, |editor, cx| {
1818        editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
1819        assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
1820    });
1821
1822    // Return a resolved completion from the host's language server.
1823    // The resolved completion has an additional text edit.
1824    fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
1825        |params, _| async move {
1826            assert_eq!(params.label, "first_method(…)");
1827            Ok(lsp::CompletionItem {
1828                label: "first_method(…)".into(),
1829                detail: Some("fn(&mut self, B) -> C".into()),
1830                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1831                    new_text: "first_method($1)".to_string(),
1832                    range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1833                })),
1834                additional_text_edits: Some(vec![lsp::TextEdit {
1835                    new_text: "use d::SomeTrait;\n".to_string(),
1836                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
1837                }]),
1838                insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1839                ..Default::default()
1840            })
1841        },
1842    );
1843
1844    // The additional edit is applied.
1845    buffer_a
1846        .condition(&cx_a, |buffer, _| {
1847            buffer.text() == "use d::SomeTrait;\nfn main() { a.first_method() }"
1848        })
1849        .await;
1850    buffer_b
1851        .condition(&cx_b, |buffer, _| {
1852            buffer.text() == "use d::SomeTrait;\nfn main() { a.first_method() }"
1853        })
1854        .await;
1855}
1856
1857#[gpui::test(iterations = 10)]
1858async fn test_reloading_buffer_manually(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1859    cx_a.foreground().forbid_parking();
1860    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1861    let client_a = server.create_client(cx_a, "user_a").await;
1862    let client_b = server.create_client(cx_b, "user_b").await;
1863    server
1864        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1865        .await;
1866
1867    client_a
1868        .fs
1869        .insert_tree("/a", json!({ "a.rs": "let one = 1;" }))
1870        .await;
1871    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1872    let buffer_a = project_a
1873        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
1874        .await
1875        .unwrap();
1876
1877    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1878
1879    let buffer_b = cx_b
1880        .background()
1881        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
1882        .await
1883        .unwrap();
1884    buffer_b.update(cx_b, |buffer, cx| {
1885        buffer.edit([(4..7, "six")], cx);
1886        buffer.edit([(10..11, "6")], cx);
1887        assert_eq!(buffer.text(), "let six = 6;");
1888        assert!(buffer.is_dirty());
1889        assert!(!buffer.has_conflict());
1890    });
1891    buffer_a
1892        .condition(cx_a, |buffer, _| buffer.text() == "let six = 6;")
1893        .await;
1894
1895    client_a
1896        .fs
1897        .save(
1898            "/a/a.rs".as_ref(),
1899            &Rope::from("let seven = 7;"),
1900            LineEnding::Unix,
1901        )
1902        .await
1903        .unwrap();
1904    buffer_a
1905        .condition(cx_a, |buffer, _| buffer.has_conflict())
1906        .await;
1907    buffer_b
1908        .condition(cx_b, |buffer, _| buffer.has_conflict())
1909        .await;
1910
1911    project_b
1912        .update(cx_b, |project, cx| {
1913            project.reload_buffers(HashSet::from_iter([buffer_b.clone()]), true, cx)
1914        })
1915        .await
1916        .unwrap();
1917    buffer_a.read_with(cx_a, |buffer, _| {
1918        assert_eq!(buffer.text(), "let seven = 7;");
1919        assert!(!buffer.is_dirty());
1920        assert!(!buffer.has_conflict());
1921    });
1922    buffer_b.read_with(cx_b, |buffer, _| {
1923        assert_eq!(buffer.text(), "let seven = 7;");
1924        assert!(!buffer.is_dirty());
1925        assert!(!buffer.has_conflict());
1926    });
1927
1928    buffer_a.update(cx_a, |buffer, cx| {
1929        // Undoing on the host is a no-op when the reload was initiated by the guest.
1930        buffer.undo(cx);
1931        assert_eq!(buffer.text(), "let seven = 7;");
1932        assert!(!buffer.is_dirty());
1933        assert!(!buffer.has_conflict());
1934    });
1935    buffer_b.update(cx_b, |buffer, cx| {
1936        // Undoing on the guest rolls back the buffer to before it was reloaded but the conflict gets cleared.
1937        buffer.undo(cx);
1938        assert_eq!(buffer.text(), "let six = 6;");
1939        assert!(buffer.is_dirty());
1940        assert!(!buffer.has_conflict());
1941    });
1942}
1943
1944#[gpui::test(iterations = 10)]
1945async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1946    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1947    let client_a = server.create_client(cx_a, "user_a").await;
1948    let client_b = server.create_client(cx_b, "user_b").await;
1949    server
1950        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1951        .await;
1952
1953    // Set up a fake language server.
1954    let mut language = Language::new(
1955        LanguageConfig {
1956            name: "Rust".into(),
1957            path_suffixes: vec!["rs".to_string()],
1958            ..Default::default()
1959        },
1960        Some(tree_sitter_rust::language()),
1961    );
1962    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
1963    client_a.language_registry.add(Arc::new(language));
1964
1965    // Here we insert a fake tree with a directory that exists on disk. This is needed
1966    // because later we'll invoke a command, which requires passing a working directory
1967    // that points to a valid location on disk.
1968    let directory = env::current_dir().unwrap();
1969    client_a
1970        .fs
1971        .insert_tree(&directory, json!({ "a.rs": "let one = \"two\"" }))
1972        .await;
1973    let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
1974    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1975
1976    let buffer_b = cx_b
1977        .background()
1978        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
1979        .await
1980        .unwrap();
1981
1982    let fake_language_server = fake_language_servers.next().await.unwrap();
1983    fake_language_server.handle_request::<lsp::request::Formatting, _, _>(|_, _| async move {
1984        Ok(Some(vec![
1985            lsp::TextEdit {
1986                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
1987                new_text: "h".to_string(),
1988            },
1989            lsp::TextEdit {
1990                range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
1991                new_text: "y".to_string(),
1992            },
1993        ]))
1994    });
1995
1996    project_b
1997        .update(cx_b, |project, cx| {
1998            project.format(HashSet::from_iter([buffer_b.clone()]), true, cx)
1999        })
2000        .await
2001        .unwrap();
2002    assert_eq!(
2003        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
2004        "let honey = \"two\""
2005    );
2006
2007    // Ensure buffer can be formatted using an external command. Notice how the
2008    // host's configuration is honored as opposed to using the guest's settings.
2009    cx_a.update(|cx| {
2010        cx.update_global(|settings: &mut Settings, _| {
2011            settings.language_settings.format_on_save = Some(FormatOnSave::External {
2012                command: "awk".to_string(),
2013                arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()],
2014            });
2015        });
2016    });
2017    project_b
2018        .update(cx_b, |project, cx| {
2019            project.format(HashSet::from_iter([buffer_b.clone()]), true, cx)
2020        })
2021        .await
2022        .unwrap();
2023    assert_eq!(
2024        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
2025        format!("let honey = \"{}/a.rs\"\n", directory.to_str().unwrap())
2026    );
2027}
2028
2029#[gpui::test(iterations = 10)]
2030async fn test_definition(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2031    cx_a.foreground().forbid_parking();
2032    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2033    let client_a = server.create_client(cx_a, "user_a").await;
2034    let client_b = server.create_client(cx_b, "user_b").await;
2035    server
2036        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2037        .await;
2038
2039    // Set up a fake language server.
2040    let mut language = Language::new(
2041        LanguageConfig {
2042            name: "Rust".into(),
2043            path_suffixes: vec!["rs".to_string()],
2044            ..Default::default()
2045        },
2046        Some(tree_sitter_rust::language()),
2047    );
2048    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
2049    client_a.language_registry.add(Arc::new(language));
2050
2051    client_a
2052        .fs
2053        .insert_tree(
2054            "/root",
2055            json!({
2056                "dir-1": {
2057                    "a.rs": "const ONE: usize = b::TWO + b::THREE;",
2058                },
2059                "dir-2": {
2060                    "b.rs": "const TWO: usize = 2;\nconst THREE: usize = 3;",
2061                }
2062            }),
2063        )
2064        .await;
2065    let (project_a, worktree_id) = client_a.build_local_project("/root/dir-1", cx_a).await;
2066    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2067
2068    // Open the file on client B.
2069    let buffer_b = cx_b
2070        .background()
2071        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
2072        .await
2073        .unwrap();
2074
2075    // Request the definition of a symbol as the guest.
2076    let fake_language_server = fake_language_servers.next().await.unwrap();
2077    fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
2078        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
2079            lsp::Location::new(
2080                lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
2081                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
2082            ),
2083        )))
2084    });
2085
2086    let definitions_1 = project_b
2087        .update(cx_b, |p, cx| p.definition(&buffer_b, 23, cx))
2088        .await
2089        .unwrap();
2090    cx_b.read(|cx| {
2091        assert_eq!(definitions_1.len(), 1);
2092        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
2093        let target_buffer = definitions_1[0].target.buffer.read(cx);
2094        assert_eq!(
2095            target_buffer.text(),
2096            "const TWO: usize = 2;\nconst THREE: usize = 3;"
2097        );
2098        assert_eq!(
2099            definitions_1[0].target.range.to_point(target_buffer),
2100            Point::new(0, 6)..Point::new(0, 9)
2101        );
2102    });
2103
2104    // Try getting more definitions for the same buffer, ensuring the buffer gets reused from
2105    // the previous call to `definition`.
2106    fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
2107        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
2108            lsp::Location::new(
2109                lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
2110                lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
2111            ),
2112        )))
2113    });
2114
2115    let definitions_2 = project_b
2116        .update(cx_b, |p, cx| p.definition(&buffer_b, 33, cx))
2117        .await
2118        .unwrap();
2119    cx_b.read(|cx| {
2120        assert_eq!(definitions_2.len(), 1);
2121        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
2122        let target_buffer = definitions_2[0].target.buffer.read(cx);
2123        assert_eq!(
2124            target_buffer.text(),
2125            "const TWO: usize = 2;\nconst THREE: usize = 3;"
2126        );
2127        assert_eq!(
2128            definitions_2[0].target.range.to_point(target_buffer),
2129            Point::new(1, 6)..Point::new(1, 11)
2130        );
2131    });
2132    assert_eq!(
2133        definitions_1[0].target.buffer,
2134        definitions_2[0].target.buffer
2135    );
2136}
2137
2138#[gpui::test(iterations = 10)]
2139async fn test_references(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2140    cx_a.foreground().forbid_parking();
2141    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2142    let client_a = server.create_client(cx_a, "user_a").await;
2143    let client_b = server.create_client(cx_b, "user_b").await;
2144    server
2145        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2146        .await;
2147
2148    // Set up a fake language server.
2149    let mut language = Language::new(
2150        LanguageConfig {
2151            name: "Rust".into(),
2152            path_suffixes: vec!["rs".to_string()],
2153            ..Default::default()
2154        },
2155        Some(tree_sitter_rust::language()),
2156    );
2157    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
2158    client_a.language_registry.add(Arc::new(language));
2159
2160    client_a
2161        .fs
2162        .insert_tree(
2163            "/root",
2164            json!({
2165                "dir-1": {
2166                    "one.rs": "const ONE: usize = 1;",
2167                    "two.rs": "const TWO: usize = one::ONE + one::ONE;",
2168                },
2169                "dir-2": {
2170                    "three.rs": "const THREE: usize = two::TWO + one::ONE;",
2171                }
2172            }),
2173        )
2174        .await;
2175    let (project_a, worktree_id) = client_a.build_local_project("/root/dir-1", cx_a).await;
2176    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2177
2178    // Open the file on client B.
2179    let buffer_b = cx_b
2180        .background()
2181        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx)))
2182        .await
2183        .unwrap();
2184
2185    // Request references to a symbol as the guest.
2186    let fake_language_server = fake_language_servers.next().await.unwrap();
2187    fake_language_server.handle_request::<lsp::request::References, _, _>(|params, _| async move {
2188        assert_eq!(
2189            params.text_document_position.text_document.uri.as_str(),
2190            "file:///root/dir-1/one.rs"
2191        );
2192        Ok(Some(vec![
2193            lsp::Location {
2194                uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
2195                range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
2196            },
2197            lsp::Location {
2198                uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
2199                range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
2200            },
2201            lsp::Location {
2202                uri: lsp::Url::from_file_path("/root/dir-2/three.rs").unwrap(),
2203                range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
2204            },
2205        ]))
2206    });
2207
2208    let references = project_b
2209        .update(cx_b, |p, cx| p.references(&buffer_b, 7, cx))
2210        .await
2211        .unwrap();
2212    cx_b.read(|cx| {
2213        assert_eq!(references.len(), 3);
2214        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
2215
2216        let two_buffer = references[0].buffer.read(cx);
2217        let three_buffer = references[2].buffer.read(cx);
2218        assert_eq!(
2219            two_buffer.file().unwrap().path().as_ref(),
2220            Path::new("two.rs")
2221        );
2222        assert_eq!(references[1].buffer, references[0].buffer);
2223        assert_eq!(
2224            three_buffer.file().unwrap().full_path(cx),
2225            Path::new("three.rs")
2226        );
2227
2228        assert_eq!(references[0].range.to_offset(&two_buffer), 24..27);
2229        assert_eq!(references[1].range.to_offset(&two_buffer), 35..38);
2230        assert_eq!(references[2].range.to_offset(&three_buffer), 37..40);
2231    });
2232}
2233
2234#[gpui::test(iterations = 10)]
2235async fn test_project_search(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2236    cx_a.foreground().forbid_parking();
2237    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2238    let client_a = server.create_client(cx_a, "user_a").await;
2239    let client_b = server.create_client(cx_b, "user_b").await;
2240    server
2241        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2242        .await;
2243
2244    client_a
2245        .fs
2246        .insert_tree(
2247            "/root",
2248            json!({
2249                "dir-1": {
2250                    "a": "hello world",
2251                    "b": "goodnight moon",
2252                    "c": "a world of goo",
2253                    "d": "world champion of clown world",
2254                },
2255                "dir-2": {
2256                    "e": "disney world is fun",
2257                }
2258            }),
2259        )
2260        .await;
2261    let (project_a, _) = client_a.build_local_project("/root/dir-1", cx_a).await;
2262    let (worktree_2, _) = project_a
2263        .update(cx_a, |p, cx| {
2264            p.find_or_create_local_worktree("/root/dir-2", true, cx)
2265        })
2266        .await
2267        .unwrap();
2268    worktree_2
2269        .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
2270        .await;
2271
2272    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2273
2274    // Perform a search as the guest.
2275    let results = project_b
2276        .update(cx_b, |project, cx| {
2277            project.search(SearchQuery::text("world", false, false), cx)
2278        })
2279        .await
2280        .unwrap();
2281
2282    let mut ranges_by_path = results
2283        .into_iter()
2284        .map(|(buffer, ranges)| {
2285            buffer.read_with(cx_b, |buffer, cx| {
2286                let path = buffer.file().unwrap().full_path(cx);
2287                let offset_ranges = ranges
2288                    .into_iter()
2289                    .map(|range| range.to_offset(buffer))
2290                    .collect::<Vec<_>>();
2291                (path, offset_ranges)
2292            })
2293        })
2294        .collect::<Vec<_>>();
2295    ranges_by_path.sort_by_key(|(path, _)| path.clone());
2296
2297    assert_eq!(
2298        ranges_by_path,
2299        &[
2300            (PathBuf::from("dir-1/a"), vec![6..11]),
2301            (PathBuf::from("dir-1/c"), vec![2..7]),
2302            (PathBuf::from("dir-1/d"), vec![0..5, 24..29]),
2303            (PathBuf::from("dir-2/e"), vec![7..12]),
2304        ]
2305    );
2306}
2307
2308#[gpui::test(iterations = 10)]
2309async fn test_document_highlights(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2310    cx_a.foreground().forbid_parking();
2311    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2312    let client_a = server.create_client(cx_a, "user_a").await;
2313    let client_b = server.create_client(cx_b, "user_b").await;
2314    server
2315        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2316        .await;
2317
2318    client_a
2319        .fs
2320        .insert_tree(
2321            "/root-1",
2322            json!({
2323                "main.rs": "fn double(number: i32) -> i32 { number + number }",
2324            }),
2325        )
2326        .await;
2327
2328    // Set up a fake language server.
2329    let mut language = Language::new(
2330        LanguageConfig {
2331            name: "Rust".into(),
2332            path_suffixes: vec!["rs".to_string()],
2333            ..Default::default()
2334        },
2335        Some(tree_sitter_rust::language()),
2336    );
2337    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
2338    client_a.language_registry.add(Arc::new(language));
2339
2340    let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
2341    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2342
2343    // Open the file on client B.
2344    let buffer_b = cx_b
2345        .background()
2346        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)))
2347        .await
2348        .unwrap();
2349
2350    // Request document highlights as the guest.
2351    let fake_language_server = fake_language_servers.next().await.unwrap();
2352    fake_language_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>(
2353        |params, _| async move {
2354            assert_eq!(
2355                params
2356                    .text_document_position_params
2357                    .text_document
2358                    .uri
2359                    .as_str(),
2360                "file:///root-1/main.rs"
2361            );
2362            assert_eq!(
2363                params.text_document_position_params.position,
2364                lsp::Position::new(0, 34)
2365            );
2366            Ok(Some(vec![
2367                lsp::DocumentHighlight {
2368                    kind: Some(lsp::DocumentHighlightKind::WRITE),
2369                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 16)),
2370                },
2371                lsp::DocumentHighlight {
2372                    kind: Some(lsp::DocumentHighlightKind::READ),
2373                    range: lsp::Range::new(lsp::Position::new(0, 32), lsp::Position::new(0, 38)),
2374                },
2375                lsp::DocumentHighlight {
2376                    kind: Some(lsp::DocumentHighlightKind::READ),
2377                    range: lsp::Range::new(lsp::Position::new(0, 41), lsp::Position::new(0, 47)),
2378                },
2379            ]))
2380        },
2381    );
2382
2383    let highlights = project_b
2384        .update(cx_b, |p, cx| p.document_highlights(&buffer_b, 34, cx))
2385        .await
2386        .unwrap();
2387    buffer_b.read_with(cx_b, |buffer, _| {
2388        let snapshot = buffer.snapshot();
2389
2390        let highlights = highlights
2391            .into_iter()
2392            .map(|highlight| (highlight.kind, highlight.range.to_offset(&snapshot)))
2393            .collect::<Vec<_>>();
2394        assert_eq!(
2395            highlights,
2396            &[
2397                (lsp::DocumentHighlightKind::WRITE, 10..16),
2398                (lsp::DocumentHighlightKind::READ, 32..38),
2399                (lsp::DocumentHighlightKind::READ, 41..47)
2400            ]
2401        )
2402    });
2403}
2404
2405#[gpui::test(iterations = 10)]
2406async fn test_lsp_hover(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2407    cx_a.foreground().forbid_parking();
2408    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2409    let client_a = server.create_client(cx_a, "user_a").await;
2410    let client_b = server.create_client(cx_b, "user_b").await;
2411    server
2412        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2413        .await;
2414
2415    client_a
2416        .fs
2417        .insert_tree(
2418            "/root-1",
2419            json!({
2420                "main.rs": "use std::collections::HashMap;",
2421            }),
2422        )
2423        .await;
2424
2425    // Set up a fake language server.
2426    let mut language = Language::new(
2427        LanguageConfig {
2428            name: "Rust".into(),
2429            path_suffixes: vec!["rs".to_string()],
2430            ..Default::default()
2431        },
2432        Some(tree_sitter_rust::language()),
2433    );
2434    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
2435    client_a.language_registry.add(Arc::new(language));
2436
2437    let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
2438    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2439
2440    // Open the file as the guest
2441    let buffer_b = cx_b
2442        .background()
2443        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)))
2444        .await
2445        .unwrap();
2446
2447    // Request hover information as the guest.
2448    let fake_language_server = fake_language_servers.next().await.unwrap();
2449    fake_language_server.handle_request::<lsp::request::HoverRequest, _, _>(
2450        |params, _| async move {
2451            assert_eq!(
2452                params
2453                    .text_document_position_params
2454                    .text_document
2455                    .uri
2456                    .as_str(),
2457                "file:///root-1/main.rs"
2458            );
2459            assert_eq!(
2460                params.text_document_position_params.position,
2461                lsp::Position::new(0, 22)
2462            );
2463            Ok(Some(lsp::Hover {
2464                contents: lsp::HoverContents::Array(vec![
2465                    lsp::MarkedString::String("Test hover content.".to_string()),
2466                    lsp::MarkedString::LanguageString(lsp::LanguageString {
2467                        language: "Rust".to_string(),
2468                        value: "let foo = 42;".to_string(),
2469                    }),
2470                ]),
2471                range: Some(lsp::Range::new(
2472                    lsp::Position::new(0, 22),
2473                    lsp::Position::new(0, 29),
2474                )),
2475            }))
2476        },
2477    );
2478
2479    let hover_info = project_b
2480        .update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
2481        .await
2482        .unwrap()
2483        .unwrap();
2484    buffer_b.read_with(cx_b, |buffer, _| {
2485        let snapshot = buffer.snapshot();
2486        assert_eq!(hover_info.range.unwrap().to_offset(&snapshot), 22..29);
2487        assert_eq!(
2488            hover_info.contents,
2489            vec![
2490                project::HoverBlock {
2491                    text: "Test hover content.".to_string(),
2492                    language: None,
2493                },
2494                project::HoverBlock {
2495                    text: "let foo = 42;".to_string(),
2496                    language: Some("Rust".to_string()),
2497                }
2498            ]
2499        );
2500    });
2501}
2502
2503#[gpui::test(iterations = 10)]
2504async fn test_project_symbols(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2505    cx_a.foreground().forbid_parking();
2506    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2507    let client_a = server.create_client(cx_a, "user_a").await;
2508    let client_b = server.create_client(cx_b, "user_b").await;
2509    server
2510        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2511        .await;
2512
2513    // Set up a fake language server.
2514    let mut language = Language::new(
2515        LanguageConfig {
2516            name: "Rust".into(),
2517            path_suffixes: vec!["rs".to_string()],
2518            ..Default::default()
2519        },
2520        Some(tree_sitter_rust::language()),
2521    );
2522    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
2523    client_a.language_registry.add(Arc::new(language));
2524
2525    client_a
2526        .fs
2527        .insert_tree(
2528            "/code",
2529            json!({
2530                "crate-1": {
2531                    "one.rs": "const ONE: usize = 1;",
2532                },
2533                "crate-2": {
2534                    "two.rs": "const TWO: usize = 2; const THREE: usize = 3;",
2535                },
2536                "private": {
2537                    "passwords.txt": "the-password",
2538                }
2539            }),
2540        )
2541        .await;
2542    let (project_a, worktree_id) = client_a.build_local_project("/code/crate-1", cx_a).await;
2543    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2544
2545    // Cause the language server to start.
2546    let _buffer = cx_b
2547        .background()
2548        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx)))
2549        .await
2550        .unwrap();
2551
2552    let fake_language_server = fake_language_servers.next().await.unwrap();
2553    fake_language_server.handle_request::<lsp::request::WorkspaceSymbol, _, _>(|_, _| async move {
2554        #[allow(deprecated)]
2555        Ok(Some(vec![lsp::SymbolInformation {
2556            name: "TWO".into(),
2557            location: lsp::Location {
2558                uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
2559                range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
2560            },
2561            kind: lsp::SymbolKind::CONSTANT,
2562            tags: None,
2563            container_name: None,
2564            deprecated: None,
2565        }]))
2566    });
2567
2568    // Request the definition of a symbol as the guest.
2569    let symbols = project_b
2570        .update(cx_b, |p, cx| p.symbols("two", cx))
2571        .await
2572        .unwrap();
2573    assert_eq!(symbols.len(), 1);
2574    assert_eq!(symbols[0].name, "TWO");
2575
2576    // Open one of the returned symbols.
2577    let buffer_b_2 = project_b
2578        .update(cx_b, |project, cx| {
2579            project.open_buffer_for_symbol(&symbols[0], cx)
2580        })
2581        .await
2582        .unwrap();
2583    buffer_b_2.read_with(cx_b, |buffer, _| {
2584        assert_eq!(
2585            buffer.file().unwrap().path().as_ref(),
2586            Path::new("../crate-2/two.rs")
2587        );
2588    });
2589
2590    // Attempt to craft a symbol and violate host's privacy by opening an arbitrary file.
2591    let mut fake_symbol = symbols[0].clone();
2592    fake_symbol.path = Path::new("/code/secrets").into();
2593    let error = project_b
2594        .update(cx_b, |project, cx| {
2595            project.open_buffer_for_symbol(&fake_symbol, cx)
2596        })
2597        .await
2598        .unwrap_err();
2599    assert!(error.to_string().contains("invalid symbol signature"));
2600}
2601
2602#[gpui::test(iterations = 10)]
2603async fn test_open_buffer_while_getting_definition_pointing_to_it(
2604    cx_a: &mut TestAppContext,
2605    cx_b: &mut TestAppContext,
2606    mut rng: StdRng,
2607) {
2608    cx_a.foreground().forbid_parking();
2609    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2610    let client_a = server.create_client(cx_a, "user_a").await;
2611    let client_b = server.create_client(cx_b, "user_b").await;
2612    server
2613        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2614        .await;
2615
2616    // Set up a fake language server.
2617    let mut language = Language::new(
2618        LanguageConfig {
2619            name: "Rust".into(),
2620            path_suffixes: vec!["rs".to_string()],
2621            ..Default::default()
2622        },
2623        Some(tree_sitter_rust::language()),
2624    );
2625    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
2626    client_a.language_registry.add(Arc::new(language));
2627
2628    client_a
2629        .fs
2630        .insert_tree(
2631            "/root",
2632            json!({
2633                "a.rs": "const ONE: usize = b::TWO;",
2634                "b.rs": "const TWO: usize = 2",
2635            }),
2636        )
2637        .await;
2638    let (project_a, worktree_id) = client_a.build_local_project("/root", cx_a).await;
2639    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2640
2641    let buffer_b1 = cx_b
2642        .background()
2643        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
2644        .await
2645        .unwrap();
2646
2647    let fake_language_server = fake_language_servers.next().await.unwrap();
2648    fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
2649        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
2650            lsp::Location::new(
2651                lsp::Url::from_file_path("/root/b.rs").unwrap(),
2652                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
2653            ),
2654        )))
2655    });
2656
2657    let definitions;
2658    let buffer_b2;
2659    if rng.gen() {
2660        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
2661        buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
2662    } else {
2663        buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
2664        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
2665    }
2666
2667    let buffer_b2 = buffer_b2.await.unwrap();
2668    let definitions = definitions.await.unwrap();
2669    assert_eq!(definitions.len(), 1);
2670    assert_eq!(definitions[0].target.buffer, buffer_b2);
2671}
2672
2673#[gpui::test(iterations = 10)]
2674async fn test_collaborating_with_code_actions(
2675    cx_a: &mut TestAppContext,
2676    cx_b: &mut TestAppContext,
2677) {
2678    cx_a.foreground().forbid_parking();
2679    cx_b.update(|cx| editor::init(cx));
2680    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2681    let client_a = server.create_client(cx_a, "user_a").await;
2682    let client_b = server.create_client(cx_b, "user_b").await;
2683    server
2684        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2685        .await;
2686
2687    // Set up a fake language server.
2688    let mut language = Language::new(
2689        LanguageConfig {
2690            name: "Rust".into(),
2691            path_suffixes: vec!["rs".to_string()],
2692            ..Default::default()
2693        },
2694        Some(tree_sitter_rust::language()),
2695    );
2696    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
2697    client_a.language_registry.add(Arc::new(language));
2698
2699    client_a
2700        .fs
2701        .insert_tree(
2702            "/a",
2703            json!({
2704                "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
2705                "other.rs": "pub fn foo() -> usize { 4 }",
2706            }),
2707        )
2708        .await;
2709    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
2710
2711    // Join the project as client B.
2712    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2713    let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(project_b.clone(), cx));
2714    let editor_b = workspace_b
2715        .update(cx_b, |workspace, cx| {
2716            workspace.open_path((worktree_id, "main.rs"), true, cx)
2717        })
2718        .await
2719        .unwrap()
2720        .downcast::<Editor>()
2721        .unwrap();
2722
2723    let mut fake_language_server = fake_language_servers.next().await.unwrap();
2724    fake_language_server
2725        .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
2726            assert_eq!(
2727                params.text_document.uri,
2728                lsp::Url::from_file_path("/a/main.rs").unwrap(),
2729            );
2730            assert_eq!(params.range.start, lsp::Position::new(0, 0));
2731            assert_eq!(params.range.end, lsp::Position::new(0, 0));
2732            Ok(None)
2733        })
2734        .next()
2735        .await;
2736
2737    // Move cursor to a location that contains code actions.
2738    editor_b.update(cx_b, |editor, cx| {
2739        editor.change_selections(None, cx, |s| {
2740            s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
2741        });
2742        cx.focus(&editor_b);
2743    });
2744
2745    fake_language_server
2746        .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
2747            assert_eq!(
2748                params.text_document.uri,
2749                lsp::Url::from_file_path("/a/main.rs").unwrap(),
2750            );
2751            assert_eq!(params.range.start, lsp::Position::new(1, 31));
2752            assert_eq!(params.range.end, lsp::Position::new(1, 31));
2753
2754            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
2755                lsp::CodeAction {
2756                    title: "Inline into all callers".to_string(),
2757                    edit: Some(lsp::WorkspaceEdit {
2758                        changes: Some(
2759                            [
2760                                (
2761                                    lsp::Url::from_file_path("/a/main.rs").unwrap(),
2762                                    vec![lsp::TextEdit::new(
2763                                        lsp::Range::new(
2764                                            lsp::Position::new(1, 22),
2765                                            lsp::Position::new(1, 34),
2766                                        ),
2767                                        "4".to_string(),
2768                                    )],
2769                                ),
2770                                (
2771                                    lsp::Url::from_file_path("/a/other.rs").unwrap(),
2772                                    vec![lsp::TextEdit::new(
2773                                        lsp::Range::new(
2774                                            lsp::Position::new(0, 0),
2775                                            lsp::Position::new(0, 27),
2776                                        ),
2777                                        "".to_string(),
2778                                    )],
2779                                ),
2780                            ]
2781                            .into_iter()
2782                            .collect(),
2783                        ),
2784                        ..Default::default()
2785                    }),
2786                    data: Some(json!({
2787                        "codeActionParams": {
2788                            "range": {
2789                                "start": {"line": 1, "column": 31},
2790                                "end": {"line": 1, "column": 31},
2791                            }
2792                        }
2793                    })),
2794                    ..Default::default()
2795                },
2796            )]))
2797        })
2798        .next()
2799        .await;
2800
2801    // Toggle code actions and wait for them to display.
2802    editor_b.update(cx_b, |editor, cx| {
2803        editor.toggle_code_actions(
2804            &ToggleCodeActions {
2805                deployed_from_indicator: false,
2806            },
2807            cx,
2808        );
2809    });
2810    editor_b
2811        .condition(&cx_b, |editor, _| editor.context_menu_visible())
2812        .await;
2813
2814    fake_language_server.remove_request_handler::<lsp::request::CodeActionRequest>();
2815
2816    // Confirming the code action will trigger a resolve request.
2817    let confirm_action = workspace_b
2818        .update(cx_b, |workspace, cx| {
2819            Editor::confirm_code_action(workspace, &ConfirmCodeAction { item_ix: Some(0) }, cx)
2820        })
2821        .unwrap();
2822    fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
2823        |_, _| async move {
2824            Ok(lsp::CodeAction {
2825                title: "Inline into all callers".to_string(),
2826                edit: Some(lsp::WorkspaceEdit {
2827                    changes: Some(
2828                        [
2829                            (
2830                                lsp::Url::from_file_path("/a/main.rs").unwrap(),
2831                                vec![lsp::TextEdit::new(
2832                                    lsp::Range::new(
2833                                        lsp::Position::new(1, 22),
2834                                        lsp::Position::new(1, 34),
2835                                    ),
2836                                    "4".to_string(),
2837                                )],
2838                            ),
2839                            (
2840                                lsp::Url::from_file_path("/a/other.rs").unwrap(),
2841                                vec![lsp::TextEdit::new(
2842                                    lsp::Range::new(
2843                                        lsp::Position::new(0, 0),
2844                                        lsp::Position::new(0, 27),
2845                                    ),
2846                                    "".to_string(),
2847                                )],
2848                            ),
2849                        ]
2850                        .into_iter()
2851                        .collect(),
2852                    ),
2853                    ..Default::default()
2854                }),
2855                ..Default::default()
2856            })
2857        },
2858    );
2859
2860    // After the action is confirmed, an editor containing both modified files is opened.
2861    confirm_action.await.unwrap();
2862    let code_action_editor = workspace_b.read_with(cx_b, |workspace, cx| {
2863        workspace
2864            .active_item(cx)
2865            .unwrap()
2866            .downcast::<Editor>()
2867            .unwrap()
2868    });
2869    code_action_editor.update(cx_b, |editor, cx| {
2870        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
2871        editor.undo(&Undo, cx);
2872        assert_eq!(
2873            editor.text(cx),
2874            "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
2875        );
2876        editor.redo(&Redo, cx);
2877        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
2878    });
2879}
2880
2881#[gpui::test(iterations = 10)]
2882async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2883    cx_a.foreground().forbid_parking();
2884    cx_b.update(|cx| editor::init(cx));
2885    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2886    let client_a = server.create_client(cx_a, "user_a").await;
2887    let client_b = server.create_client(cx_b, "user_b").await;
2888    server
2889        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2890        .await;
2891
2892    // Set up a fake language server.
2893    let mut language = Language::new(
2894        LanguageConfig {
2895            name: "Rust".into(),
2896            path_suffixes: vec!["rs".to_string()],
2897            ..Default::default()
2898        },
2899        Some(tree_sitter_rust::language()),
2900    );
2901    let mut fake_language_servers = language.set_fake_lsp_adapter(FakeLspAdapter {
2902        capabilities: lsp::ServerCapabilities {
2903            rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
2904                prepare_provider: Some(true),
2905                work_done_progress_options: Default::default(),
2906            })),
2907            ..Default::default()
2908        },
2909        ..Default::default()
2910    });
2911    client_a.language_registry.add(Arc::new(language));
2912
2913    client_a
2914        .fs
2915        .insert_tree(
2916            "/dir",
2917            json!({
2918                "one.rs": "const ONE: usize = 1;",
2919                "two.rs": "const TWO: usize = one::ONE + one::ONE;"
2920            }),
2921        )
2922        .await;
2923    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2924    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2925
2926    let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(project_b.clone(), cx));
2927    let editor_b = workspace_b
2928        .update(cx_b, |workspace, cx| {
2929            workspace.open_path((worktree_id, "one.rs"), true, cx)
2930        })
2931        .await
2932        .unwrap()
2933        .downcast::<Editor>()
2934        .unwrap();
2935    let fake_language_server = fake_language_servers.next().await.unwrap();
2936
2937    // Move cursor to a location that can be renamed.
2938    let prepare_rename = editor_b.update(cx_b, |editor, cx| {
2939        editor.change_selections(None, cx, |s| s.select_ranges([7..7]));
2940        editor.rename(&Rename, cx).unwrap()
2941    });
2942
2943    fake_language_server
2944        .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
2945            assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
2946            assert_eq!(params.position, lsp::Position::new(0, 7));
2947            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
2948                lsp::Position::new(0, 6),
2949                lsp::Position::new(0, 9),
2950            ))))
2951        })
2952        .next()
2953        .await
2954        .unwrap();
2955    prepare_rename.await.unwrap();
2956    editor_b.update(cx_b, |editor, cx| {
2957        let rename = editor.pending_rename().unwrap();
2958        let buffer = editor.buffer().read(cx).snapshot(cx);
2959        assert_eq!(
2960            rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
2961            6..9
2962        );
2963        rename.editor.update(cx, |rename_editor, cx| {
2964            rename_editor.buffer().update(cx, |rename_buffer, cx| {
2965                rename_buffer.edit([(0..3, "THREE")], cx);
2966            });
2967        });
2968    });
2969
2970    let confirm_rename = workspace_b.update(cx_b, |workspace, cx| {
2971        Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap()
2972    });
2973    fake_language_server
2974        .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
2975            assert_eq!(
2976                params.text_document_position.text_document.uri.as_str(),
2977                "file:///dir/one.rs"
2978            );
2979            assert_eq!(
2980                params.text_document_position.position,
2981                lsp::Position::new(0, 6)
2982            );
2983            assert_eq!(params.new_name, "THREE");
2984            Ok(Some(lsp::WorkspaceEdit {
2985                changes: Some(
2986                    [
2987                        (
2988                            lsp::Url::from_file_path("/dir/one.rs").unwrap(),
2989                            vec![lsp::TextEdit::new(
2990                                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
2991                                "THREE".to_string(),
2992                            )],
2993                        ),
2994                        (
2995                            lsp::Url::from_file_path("/dir/two.rs").unwrap(),
2996                            vec![
2997                                lsp::TextEdit::new(
2998                                    lsp::Range::new(
2999                                        lsp::Position::new(0, 24),
3000                                        lsp::Position::new(0, 27),
3001                                    ),
3002                                    "THREE".to_string(),
3003                                ),
3004                                lsp::TextEdit::new(
3005                                    lsp::Range::new(
3006                                        lsp::Position::new(0, 35),
3007                                        lsp::Position::new(0, 38),
3008                                    ),
3009                                    "THREE".to_string(),
3010                                ),
3011                            ],
3012                        ),
3013                    ]
3014                    .into_iter()
3015                    .collect(),
3016                ),
3017                ..Default::default()
3018            }))
3019        })
3020        .next()
3021        .await
3022        .unwrap();
3023    confirm_rename.await.unwrap();
3024
3025    let rename_editor = workspace_b.read_with(cx_b, |workspace, cx| {
3026        workspace
3027            .active_item(cx)
3028            .unwrap()
3029            .downcast::<Editor>()
3030            .unwrap()
3031    });
3032    rename_editor.update(cx_b, |editor, cx| {
3033        assert_eq!(
3034            editor.text(cx),
3035            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
3036        );
3037        editor.undo(&Undo, cx);
3038        assert_eq!(
3039            editor.text(cx),
3040            "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
3041        );
3042        editor.redo(&Redo, cx);
3043        assert_eq!(
3044            editor.text(cx),
3045            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
3046        );
3047    });
3048
3049    // Ensure temporary rename edits cannot be undone/redone.
3050    editor_b.update(cx_b, |editor, cx| {
3051        editor.undo(&Undo, cx);
3052        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
3053        editor.undo(&Undo, cx);
3054        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
3055        editor.redo(&Redo, cx);
3056        assert_eq!(editor.text(cx), "const THREE: usize = 1;");
3057    })
3058}
3059
3060#[gpui::test(iterations = 10)]
3061async fn test_language_server_statuses(
3062    deterministic: Arc<Deterministic>,
3063    cx_a: &mut TestAppContext,
3064    cx_b: &mut TestAppContext,
3065) {
3066    deterministic.forbid_parking();
3067
3068    cx_b.update(|cx| editor::init(cx));
3069    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3070    let client_a = server.create_client(cx_a, "user_a").await;
3071    let client_b = server.create_client(cx_b, "user_b").await;
3072    server
3073        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
3074        .await;
3075
3076    // Set up a fake language server.
3077    let mut language = Language::new(
3078        LanguageConfig {
3079            name: "Rust".into(),
3080            path_suffixes: vec!["rs".to_string()],
3081            ..Default::default()
3082        },
3083        Some(tree_sitter_rust::language()),
3084    );
3085    let mut fake_language_servers = language.set_fake_lsp_adapter(FakeLspAdapter {
3086        name: "the-language-server",
3087        ..Default::default()
3088    });
3089    client_a.language_registry.add(Arc::new(language));
3090
3091    client_a
3092        .fs
3093        .insert_tree(
3094            "/dir",
3095            json!({
3096                "main.rs": "const ONE: usize = 1;",
3097            }),
3098        )
3099        .await;
3100    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
3101
3102    let _buffer_a = project_a
3103        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
3104        .await
3105        .unwrap();
3106
3107    let fake_language_server = fake_language_servers.next().await.unwrap();
3108    fake_language_server.start_progress("the-token").await;
3109    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
3110        token: lsp::NumberOrString::String("the-token".to_string()),
3111        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
3112            lsp::WorkDoneProgressReport {
3113                message: Some("the-message".to_string()),
3114                ..Default::default()
3115            },
3116        )),
3117    });
3118    deterministic.run_until_parked();
3119    project_a.read_with(cx_a, |project, _| {
3120        let status = project.language_server_statuses().next().unwrap();
3121        assert_eq!(status.name, "the-language-server");
3122        assert_eq!(status.pending_work.len(), 1);
3123        assert_eq!(
3124            status.pending_work["the-token"].message.as_ref().unwrap(),
3125            "the-message"
3126        );
3127    });
3128
3129    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
3130    project_b.read_with(cx_b, |project, _| {
3131        let status = project.language_server_statuses().next().unwrap();
3132        assert_eq!(status.name, "the-language-server");
3133    });
3134
3135    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
3136        token: lsp::NumberOrString::String("the-token".to_string()),
3137        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
3138            lsp::WorkDoneProgressReport {
3139                message: Some("the-message-2".to_string()),
3140                ..Default::default()
3141            },
3142        )),
3143    });
3144    deterministic.run_until_parked();
3145    project_a.read_with(cx_a, |project, _| {
3146        let status = project.language_server_statuses().next().unwrap();
3147        assert_eq!(status.name, "the-language-server");
3148        assert_eq!(status.pending_work.len(), 1);
3149        assert_eq!(
3150            status.pending_work["the-token"].message.as_ref().unwrap(),
3151            "the-message-2"
3152        );
3153    });
3154    project_b.read_with(cx_b, |project, _| {
3155        let status = project.language_server_statuses().next().unwrap();
3156        assert_eq!(status.name, "the-language-server");
3157        assert_eq!(status.pending_work.len(), 1);
3158        assert_eq!(
3159            status.pending_work["the-token"].message.as_ref().unwrap(),
3160            "the-message-2"
3161        );
3162    });
3163}
3164
3165#[gpui::test(iterations = 10)]
3166async fn test_basic_chat(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3167    cx_a.foreground().forbid_parking();
3168    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3169    let client_a = server.create_client(cx_a, "user_a").await;
3170    let client_b = server.create_client(cx_b, "user_b").await;
3171
3172    // Create an org that includes these 2 users.
3173    let db = &server.app_state.db;
3174    let org_id = db.create_org("Test Org", "test-org").await.unwrap();
3175    db.add_org_member(org_id, client_a.current_user_id(&cx_a), false)
3176        .await
3177        .unwrap();
3178    db.add_org_member(org_id, client_b.current_user_id(&cx_b), false)
3179        .await
3180        .unwrap();
3181
3182    // Create a channel that includes all the users.
3183    let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap();
3184    db.add_channel_member(channel_id, client_a.current_user_id(&cx_a), false)
3185        .await
3186        .unwrap();
3187    db.add_channel_member(channel_id, client_b.current_user_id(&cx_b), false)
3188        .await
3189        .unwrap();
3190    db.create_channel_message(
3191        channel_id,
3192        client_b.current_user_id(&cx_b),
3193        "hello A, it's B.",
3194        OffsetDateTime::now_utc(),
3195        1,
3196    )
3197    .await
3198    .unwrap();
3199
3200    let channels_a =
3201        cx_a.add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx));
3202    channels_a
3203        .condition(cx_a, |list, _| list.available_channels().is_some())
3204        .await;
3205    channels_a.read_with(cx_a, |list, _| {
3206        assert_eq!(
3207            list.available_channels().unwrap(),
3208            &[ChannelDetails {
3209                id: channel_id.to_proto(),
3210                name: "test-channel".to_string()
3211            }]
3212        )
3213    });
3214    let channel_a = channels_a.update(cx_a, |this, cx| {
3215        this.get_channel(channel_id.to_proto(), cx).unwrap()
3216    });
3217    channel_a.read_with(cx_a, |channel, _| assert!(channel.messages().is_empty()));
3218    channel_a
3219        .condition(&cx_a, |channel, _| {
3220            channel_messages(channel)
3221                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
3222        })
3223        .await;
3224
3225    let channels_b =
3226        cx_b.add_model(|cx| ChannelList::new(client_b.user_store.clone(), client_b.clone(), cx));
3227    channels_b
3228        .condition(cx_b, |list, _| list.available_channels().is_some())
3229        .await;
3230    channels_b.read_with(cx_b, |list, _| {
3231        assert_eq!(
3232            list.available_channels().unwrap(),
3233            &[ChannelDetails {
3234                id: channel_id.to_proto(),
3235                name: "test-channel".to_string()
3236            }]
3237        )
3238    });
3239
3240    let channel_b = channels_b.update(cx_b, |this, cx| {
3241        this.get_channel(channel_id.to_proto(), cx).unwrap()
3242    });
3243    channel_b.read_with(cx_b, |channel, _| assert!(channel.messages().is_empty()));
3244    channel_b
3245        .condition(&cx_b, |channel, _| {
3246            channel_messages(channel)
3247                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
3248        })
3249        .await;
3250
3251    channel_a
3252        .update(cx_a, |channel, cx| {
3253            channel
3254                .send_message("oh, hi B.".to_string(), cx)
3255                .unwrap()
3256                .detach();
3257            let task = channel.send_message("sup".to_string(), cx).unwrap();
3258            assert_eq!(
3259                channel_messages(channel),
3260                &[
3261                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3262                    ("user_a".to_string(), "oh, hi B.".to_string(), true),
3263                    ("user_a".to_string(), "sup".to_string(), true)
3264                ]
3265            );
3266            task
3267        })
3268        .await
3269        .unwrap();
3270
3271    channel_b
3272        .condition(&cx_b, |channel, _| {
3273            channel_messages(channel)
3274                == [
3275                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3276                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
3277                    ("user_a".to_string(), "sup".to_string(), false),
3278                ]
3279        })
3280        .await;
3281
3282    assert_eq!(
3283        server
3284            .store()
3285            .await
3286            .channel(channel_id)
3287            .unwrap()
3288            .connection_ids
3289            .len(),
3290        2
3291    );
3292    cx_b.update(|_| drop(channel_b));
3293    server
3294        .condition(|state| state.channel(channel_id).unwrap().connection_ids.len() == 1)
3295        .await;
3296
3297    cx_a.update(|_| drop(channel_a));
3298    server
3299        .condition(|state| state.channel(channel_id).is_none())
3300        .await;
3301}
3302
3303#[gpui::test(iterations = 10)]
3304async fn test_chat_message_validation(cx_a: &mut TestAppContext) {
3305    cx_a.foreground().forbid_parking();
3306    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3307    let client_a = server.create_client(cx_a, "user_a").await;
3308
3309    let db = &server.app_state.db;
3310    let org_id = db.create_org("Test Org", "test-org").await.unwrap();
3311    let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap();
3312    db.add_org_member(org_id, client_a.current_user_id(&cx_a), false)
3313        .await
3314        .unwrap();
3315    db.add_channel_member(channel_id, client_a.current_user_id(&cx_a), false)
3316        .await
3317        .unwrap();
3318
3319    let channels_a =
3320        cx_a.add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx));
3321    channels_a
3322        .condition(cx_a, |list, _| list.available_channels().is_some())
3323        .await;
3324    let channel_a = channels_a.update(cx_a, |this, cx| {
3325        this.get_channel(channel_id.to_proto(), cx).unwrap()
3326    });
3327
3328    // Messages aren't allowed to be too long.
3329    channel_a
3330        .update(cx_a, |channel, cx| {
3331            let long_body = "this is long.\n".repeat(1024);
3332            channel.send_message(long_body, cx).unwrap()
3333        })
3334        .await
3335        .unwrap_err();
3336
3337    // Messages aren't allowed to be blank.
3338    channel_a.update(cx_a, |channel, cx| {
3339        channel.send_message(String::new(), cx).unwrap_err()
3340    });
3341
3342    // Leading and trailing whitespace are trimmed.
3343    channel_a
3344        .update(cx_a, |channel, cx| {
3345            channel
3346                .send_message("\n surrounded by whitespace  \n".to_string(), cx)
3347                .unwrap()
3348        })
3349        .await
3350        .unwrap();
3351    assert_eq!(
3352        db.get_channel_messages(channel_id, 10, None)
3353            .await
3354            .unwrap()
3355            .iter()
3356            .map(|m| &m.body)
3357            .collect::<Vec<_>>(),
3358        &["surrounded by whitespace"]
3359    );
3360}
3361
3362#[gpui::test(iterations = 10)]
3363async fn test_chat_reconnection(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3364    cx_a.foreground().forbid_parking();
3365    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3366    let client_a = server.create_client(cx_a, "user_a").await;
3367    let client_b = server.create_client(cx_b, "user_b").await;
3368
3369    let mut status_b = client_b.status();
3370
3371    // Create an org that includes these 2 users.
3372    let db = &server.app_state.db;
3373    let org_id = db.create_org("Test Org", "test-org").await.unwrap();
3374    db.add_org_member(org_id, client_a.current_user_id(&cx_a), false)
3375        .await
3376        .unwrap();
3377    db.add_org_member(org_id, client_b.current_user_id(&cx_b), false)
3378        .await
3379        .unwrap();
3380
3381    // Create a channel that includes all the users.
3382    let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap();
3383    db.add_channel_member(channel_id, client_a.current_user_id(&cx_a), false)
3384        .await
3385        .unwrap();
3386    db.add_channel_member(channel_id, client_b.current_user_id(&cx_b), false)
3387        .await
3388        .unwrap();
3389    db.create_channel_message(
3390        channel_id,
3391        client_b.current_user_id(&cx_b),
3392        "hello A, it's B.",
3393        OffsetDateTime::now_utc(),
3394        2,
3395    )
3396    .await
3397    .unwrap();
3398
3399    let channels_a =
3400        cx_a.add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx));
3401    channels_a
3402        .condition(cx_a, |list, _| list.available_channels().is_some())
3403        .await;
3404
3405    channels_a.read_with(cx_a, |list, _| {
3406        assert_eq!(
3407            list.available_channels().unwrap(),
3408            &[ChannelDetails {
3409                id: channel_id.to_proto(),
3410                name: "test-channel".to_string()
3411            }]
3412        )
3413    });
3414    let channel_a = channels_a.update(cx_a, |this, cx| {
3415        this.get_channel(channel_id.to_proto(), cx).unwrap()
3416    });
3417    channel_a.read_with(cx_a, |channel, _| assert!(channel.messages().is_empty()));
3418    channel_a
3419        .condition(&cx_a, |channel, _| {
3420            channel_messages(channel)
3421                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
3422        })
3423        .await;
3424
3425    let channels_b =
3426        cx_b.add_model(|cx| ChannelList::new(client_b.user_store.clone(), client_b.clone(), cx));
3427    channels_b
3428        .condition(cx_b, |list, _| list.available_channels().is_some())
3429        .await;
3430    channels_b.read_with(cx_b, |list, _| {
3431        assert_eq!(
3432            list.available_channels().unwrap(),
3433            &[ChannelDetails {
3434                id: channel_id.to_proto(),
3435                name: "test-channel".to_string()
3436            }]
3437        )
3438    });
3439
3440    let channel_b = channels_b.update(cx_b, |this, cx| {
3441        this.get_channel(channel_id.to_proto(), cx).unwrap()
3442    });
3443    channel_b.read_with(cx_b, |channel, _| assert!(channel.messages().is_empty()));
3444    channel_b
3445        .condition(&cx_b, |channel, _| {
3446            channel_messages(channel)
3447                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
3448        })
3449        .await;
3450
3451    // Disconnect client B, ensuring we can still access its cached channel data.
3452    server.forbid_connections();
3453    server.disconnect_client(client_b.current_user_id(&cx_b));
3454    cx_b.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
3455    while !matches!(
3456        status_b.next().await,
3457        Some(client::Status::ReconnectionError { .. })
3458    ) {}
3459
3460    channels_b.read_with(cx_b, |channels, _| {
3461        assert_eq!(
3462            channels.available_channels().unwrap(),
3463            [ChannelDetails {
3464                id: channel_id.to_proto(),
3465                name: "test-channel".to_string()
3466            }]
3467        )
3468    });
3469    channel_b.read_with(cx_b, |channel, _| {
3470        assert_eq!(
3471            channel_messages(channel),
3472            [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
3473        )
3474    });
3475
3476    // Send a message from client B while it is disconnected.
3477    channel_b
3478        .update(cx_b, |channel, cx| {
3479            let task = channel
3480                .send_message("can you see this?".to_string(), cx)
3481                .unwrap();
3482            assert_eq!(
3483                channel_messages(channel),
3484                &[
3485                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3486                    ("user_b".to_string(), "can you see this?".to_string(), true)
3487                ]
3488            );
3489            task
3490        })
3491        .await
3492        .unwrap_err();
3493
3494    // Send a message from client A while B is disconnected.
3495    channel_a
3496        .update(cx_a, |channel, cx| {
3497            channel
3498                .send_message("oh, hi B.".to_string(), cx)
3499                .unwrap()
3500                .detach();
3501            let task = channel.send_message("sup".to_string(), cx).unwrap();
3502            assert_eq!(
3503                channel_messages(channel),
3504                &[
3505                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3506                    ("user_a".to_string(), "oh, hi B.".to_string(), true),
3507                    ("user_a".to_string(), "sup".to_string(), true)
3508                ]
3509            );
3510            task
3511        })
3512        .await
3513        .unwrap();
3514
3515    // Give client B a chance to reconnect.
3516    server.allow_connections();
3517    cx_b.foreground().advance_clock(Duration::from_secs(10));
3518
3519    // Verify that B sees the new messages upon reconnection, as well as the message client B
3520    // sent while offline.
3521    channel_b
3522        .condition(&cx_b, |channel, _| {
3523            channel_messages(channel)
3524                == [
3525                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3526                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
3527                    ("user_a".to_string(), "sup".to_string(), false),
3528                    ("user_b".to_string(), "can you see this?".to_string(), false),
3529                ]
3530        })
3531        .await;
3532
3533    // Ensure client A and B can communicate normally after reconnection.
3534    channel_a
3535        .update(cx_a, |channel, cx| {
3536            channel.send_message("you online?".to_string(), cx).unwrap()
3537        })
3538        .await
3539        .unwrap();
3540    channel_b
3541        .condition(&cx_b, |channel, _| {
3542            channel_messages(channel)
3543                == [
3544                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3545                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
3546                    ("user_a".to_string(), "sup".to_string(), false),
3547                    ("user_b".to_string(), "can you see this?".to_string(), false),
3548                    ("user_a".to_string(), "you online?".to_string(), false),
3549                ]
3550        })
3551        .await;
3552
3553    channel_b
3554        .update(cx_b, |channel, cx| {
3555            channel.send_message("yep".to_string(), cx).unwrap()
3556        })
3557        .await
3558        .unwrap();
3559    channel_a
3560        .condition(&cx_a, |channel, _| {
3561            channel_messages(channel)
3562                == [
3563                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3564                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
3565                    ("user_a".to_string(), "sup".to_string(), false),
3566                    ("user_b".to_string(), "can you see this?".to_string(), false),
3567                    ("user_a".to_string(), "you online?".to_string(), false),
3568                    ("user_b".to_string(), "yep".to_string(), false),
3569                ]
3570        })
3571        .await;
3572}
3573
3574#[gpui::test(iterations = 10)]
3575async fn test_contacts(
3576    deterministic: Arc<Deterministic>,
3577    cx_a: &mut TestAppContext,
3578    cx_b: &mut TestAppContext,
3579    cx_c: &mut TestAppContext,
3580) {
3581    cx_a.foreground().forbid_parking();
3582    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3583    let client_a = server.create_client(cx_a, "user_a").await;
3584    let client_b = server.create_client(cx_b, "user_b").await;
3585    let client_c = server.create_client(cx_c, "user_c").await;
3586    server
3587        .make_contacts(vec![
3588            (&client_a, cx_a),
3589            (&client_b, cx_b),
3590            (&client_c, cx_c),
3591        ])
3592        .await;
3593
3594    deterministic.run_until_parked();
3595    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b), (&client_c, &cx_c)] {
3596        client.user_store.read_with(*cx, |store, _| {
3597            assert_eq!(
3598                contacts(store),
3599                [
3600                    ("user_a", true, vec![]),
3601                    ("user_b", true, vec![]),
3602                    ("user_c", true, vec![])
3603                ],
3604                "{} has the wrong contacts",
3605                client.username
3606            )
3607        });
3608    }
3609
3610    // Share a project as client A.
3611    client_a.fs.create_dir(Path::new("/a")).await.unwrap();
3612    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
3613
3614    deterministic.run_until_parked();
3615    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b), (&client_c, &cx_c)] {
3616        client.user_store.read_with(*cx, |store, _| {
3617            assert_eq!(
3618                contacts(store),
3619                [
3620                    ("user_a", true, vec![("a", vec![])]),
3621                    ("user_b", true, vec![]),
3622                    ("user_c", true, vec![])
3623                ],
3624                "{} has the wrong contacts",
3625                client.username
3626            )
3627        });
3628    }
3629
3630    let _project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
3631
3632    deterministic.run_until_parked();
3633    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b), (&client_c, &cx_c)] {
3634        client.user_store.read_with(*cx, |store, _| {
3635            assert_eq!(
3636                contacts(store),
3637                [
3638                    ("user_a", true, vec![("a", vec!["user_b"])]),
3639                    ("user_b", true, vec![]),
3640                    ("user_c", true, vec![])
3641                ],
3642                "{} has the wrong contacts",
3643                client.username
3644            )
3645        });
3646    }
3647
3648    // Add a local project as client B
3649    client_a.fs.create_dir("/b".as_ref()).await.unwrap();
3650    let (_project_b, _) = client_b.build_local_project("/b", cx_b).await;
3651
3652    deterministic.run_until_parked();
3653    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b), (&client_c, &cx_c)] {
3654        client.user_store.read_with(*cx, |store, _| {
3655            assert_eq!(
3656                contacts(store),
3657                [
3658                    ("user_a", true, vec![("a", vec!["user_b"])]),
3659                    ("user_b", true, vec![("b", vec![])]),
3660                    ("user_c", true, vec![])
3661                ],
3662                "{} has the wrong contacts",
3663                client.username
3664            )
3665        });
3666    }
3667
3668    project_a
3669        .condition(&cx_a, |project, _| {
3670            project.collaborators().contains_key(&client_b.peer_id)
3671        })
3672        .await;
3673
3674    cx_a.update(move |_| drop(project_a));
3675    deterministic.run_until_parked();
3676    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b), (&client_c, &cx_c)] {
3677        client.user_store.read_with(*cx, |store, _| {
3678            assert_eq!(
3679                contacts(store),
3680                [
3681                    ("user_a", true, vec![]),
3682                    ("user_b", true, vec![("b", vec![])]),
3683                    ("user_c", true, vec![])
3684                ],
3685                "{} has the wrong contacts",
3686                client.username
3687            )
3688        });
3689    }
3690
3691    server.disconnect_client(client_c.current_user_id(cx_c));
3692    server.forbid_connections();
3693    deterministic.advance_clock(rpc::RECEIVE_TIMEOUT);
3694    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b)] {
3695        client.user_store.read_with(*cx, |store, _| {
3696            assert_eq!(
3697                contacts(store),
3698                [
3699                    ("user_a", true, vec![]),
3700                    ("user_b", true, vec![("b", vec![])]),
3701                    ("user_c", false, vec![])
3702                ],
3703                "{} has the wrong contacts",
3704                client.username
3705            )
3706        });
3707    }
3708    client_c
3709        .user_store
3710        .read_with(cx_c, |store, _| assert_eq!(contacts(store), []));
3711
3712    server.allow_connections();
3713    client_c
3714        .authenticate_and_connect(false, &cx_c.to_async())
3715        .await
3716        .unwrap();
3717
3718    deterministic.run_until_parked();
3719    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b), (&client_c, &cx_c)] {
3720        client.user_store.read_with(*cx, |store, _| {
3721            assert_eq!(
3722                contacts(store),
3723                [
3724                    ("user_a", true, vec![]),
3725                    ("user_b", true, vec![("b", vec![])]),
3726                    ("user_c", true, vec![])
3727                ],
3728                "{} has the wrong contacts",
3729                client.username
3730            )
3731        });
3732    }
3733
3734    fn contacts(user_store: &UserStore) -> Vec<(&str, bool, Vec<(&str, Vec<&str>)>)> {
3735        user_store
3736            .contacts()
3737            .iter()
3738            .map(|contact| {
3739                let projects = contact
3740                    .projects
3741                    .iter()
3742                    .map(|p| {
3743                        (
3744                            p.visible_worktree_root_names[0].as_str(),
3745                            p.guests.iter().map(|p| p.github_login.as_str()).collect(),
3746                        )
3747                    })
3748                    .collect();
3749                (contact.user.github_login.as_str(), contact.online, projects)
3750            })
3751            .collect()
3752    }
3753}
3754
3755#[gpui::test(iterations = 10)]
3756async fn test_contact_requests(
3757    executor: Arc<Deterministic>,
3758    cx_a: &mut TestAppContext,
3759    cx_a2: &mut TestAppContext,
3760    cx_b: &mut TestAppContext,
3761    cx_b2: &mut TestAppContext,
3762    cx_c: &mut TestAppContext,
3763    cx_c2: &mut TestAppContext,
3764) {
3765    cx_a.foreground().forbid_parking();
3766
3767    // Connect to a server as 3 clients.
3768    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3769    let client_a = server.create_client(cx_a, "user_a").await;
3770    let client_a2 = server.create_client(cx_a2, "user_a").await;
3771    let client_b = server.create_client(cx_b, "user_b").await;
3772    let client_b2 = server.create_client(cx_b2, "user_b").await;
3773    let client_c = server.create_client(cx_c, "user_c").await;
3774    let client_c2 = server.create_client(cx_c2, "user_c").await;
3775
3776    assert_eq!(client_a.user_id().unwrap(), client_a2.user_id().unwrap());
3777    assert_eq!(client_b.user_id().unwrap(), client_b2.user_id().unwrap());
3778    assert_eq!(client_c.user_id().unwrap(), client_c2.user_id().unwrap());
3779
3780    // User A and User C request that user B become their contact.
3781    client_a
3782        .user_store
3783        .update(cx_a, |store, cx| {
3784            store.request_contact(client_b.user_id().unwrap(), cx)
3785        })
3786        .await
3787        .unwrap();
3788    client_c
3789        .user_store
3790        .update(cx_c, |store, cx| {
3791            store.request_contact(client_b.user_id().unwrap(), cx)
3792        })
3793        .await
3794        .unwrap();
3795    executor.run_until_parked();
3796
3797    // All users see the pending request appear in all their clients.
3798    assert_eq!(
3799        client_a.summarize_contacts(&cx_a).outgoing_requests,
3800        &["user_b"]
3801    );
3802    assert_eq!(
3803        client_a2.summarize_contacts(&cx_a2).outgoing_requests,
3804        &["user_b"]
3805    );
3806    assert_eq!(
3807        client_b.summarize_contacts(&cx_b).incoming_requests,
3808        &["user_a", "user_c"]
3809    );
3810    assert_eq!(
3811        client_b2.summarize_contacts(&cx_b2).incoming_requests,
3812        &["user_a", "user_c"]
3813    );
3814    assert_eq!(
3815        client_c.summarize_contacts(&cx_c).outgoing_requests,
3816        &["user_b"]
3817    );
3818    assert_eq!(
3819        client_c2.summarize_contacts(&cx_c2).outgoing_requests,
3820        &["user_b"]
3821    );
3822
3823    // Contact requests are present upon connecting (tested here via disconnect/reconnect)
3824    disconnect_and_reconnect(&client_a, cx_a).await;
3825    disconnect_and_reconnect(&client_b, cx_b).await;
3826    disconnect_and_reconnect(&client_c, cx_c).await;
3827    executor.run_until_parked();
3828    assert_eq!(
3829        client_a.summarize_contacts(&cx_a).outgoing_requests,
3830        &["user_b"]
3831    );
3832    assert_eq!(
3833        client_b.summarize_contacts(&cx_b).incoming_requests,
3834        &["user_a", "user_c"]
3835    );
3836    assert_eq!(
3837        client_c.summarize_contacts(&cx_c).outgoing_requests,
3838        &["user_b"]
3839    );
3840
3841    // User B accepts the request from user A.
3842    client_b
3843        .user_store
3844        .update(cx_b, |store, cx| {
3845            store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
3846        })
3847        .await
3848        .unwrap();
3849
3850    executor.run_until_parked();
3851
3852    // User B sees user A as their contact now in all client, and the incoming request from them is removed.
3853    let contacts_b = client_b.summarize_contacts(&cx_b);
3854    assert_eq!(contacts_b.current, &["user_a", "user_b"]);
3855    assert_eq!(contacts_b.incoming_requests, &["user_c"]);
3856    let contacts_b2 = client_b2.summarize_contacts(&cx_b2);
3857    assert_eq!(contacts_b2.current, &["user_a", "user_b"]);
3858    assert_eq!(contacts_b2.incoming_requests, &["user_c"]);
3859
3860    // User A sees user B as their contact now in all clients, and the outgoing request to them is removed.
3861    let contacts_a = client_a.summarize_contacts(&cx_a);
3862    assert_eq!(contacts_a.current, &["user_a", "user_b"]);
3863    assert!(contacts_a.outgoing_requests.is_empty());
3864    let contacts_a2 = client_a2.summarize_contacts(&cx_a2);
3865    assert_eq!(contacts_a2.current, &["user_a", "user_b"]);
3866    assert!(contacts_a2.outgoing_requests.is_empty());
3867
3868    // Contacts are present upon connecting (tested here via disconnect/reconnect)
3869    disconnect_and_reconnect(&client_a, cx_a).await;
3870    disconnect_and_reconnect(&client_b, cx_b).await;
3871    disconnect_and_reconnect(&client_c, cx_c).await;
3872    executor.run_until_parked();
3873    assert_eq!(
3874        client_a.summarize_contacts(&cx_a).current,
3875        &["user_a", "user_b"]
3876    );
3877    assert_eq!(
3878        client_b.summarize_contacts(&cx_b).current,
3879        &["user_a", "user_b"]
3880    );
3881    assert_eq!(
3882        client_b.summarize_contacts(&cx_b).incoming_requests,
3883        &["user_c"]
3884    );
3885    assert_eq!(client_c.summarize_contacts(&cx_c).current, &["user_c"]);
3886    assert_eq!(
3887        client_c.summarize_contacts(&cx_c).outgoing_requests,
3888        &["user_b"]
3889    );
3890
3891    // User B rejects the request from user C.
3892    client_b
3893        .user_store
3894        .update(cx_b, |store, cx| {
3895            store.respond_to_contact_request(client_c.user_id().unwrap(), false, cx)
3896        })
3897        .await
3898        .unwrap();
3899
3900    executor.run_until_parked();
3901
3902    // User B doesn't see user C as their contact, and the incoming request from them is removed.
3903    let contacts_b = client_b.summarize_contacts(&cx_b);
3904    assert_eq!(contacts_b.current, &["user_a", "user_b"]);
3905    assert!(contacts_b.incoming_requests.is_empty());
3906    let contacts_b2 = client_b2.summarize_contacts(&cx_b2);
3907    assert_eq!(contacts_b2.current, &["user_a", "user_b"]);
3908    assert!(contacts_b2.incoming_requests.is_empty());
3909
3910    // User C doesn't see user B as their contact, and the outgoing request to them is removed.
3911    let contacts_c = client_c.summarize_contacts(&cx_c);
3912    assert_eq!(contacts_c.current, &["user_c"]);
3913    assert!(contacts_c.outgoing_requests.is_empty());
3914    let contacts_c2 = client_c2.summarize_contacts(&cx_c2);
3915    assert_eq!(contacts_c2.current, &["user_c"]);
3916    assert!(contacts_c2.outgoing_requests.is_empty());
3917
3918    // Incoming/outgoing requests are not present upon connecting (tested here via disconnect/reconnect)
3919    disconnect_and_reconnect(&client_a, cx_a).await;
3920    disconnect_and_reconnect(&client_b, cx_b).await;
3921    disconnect_and_reconnect(&client_c, cx_c).await;
3922    executor.run_until_parked();
3923    assert_eq!(
3924        client_a.summarize_contacts(&cx_a).current,
3925        &["user_a", "user_b"]
3926    );
3927    assert_eq!(
3928        client_b.summarize_contacts(&cx_b).current,
3929        &["user_a", "user_b"]
3930    );
3931    assert!(client_b
3932        .summarize_contacts(&cx_b)
3933        .incoming_requests
3934        .is_empty());
3935    assert_eq!(client_c.summarize_contacts(&cx_c).current, &["user_c"]);
3936    assert!(client_c
3937        .summarize_contacts(&cx_c)
3938        .outgoing_requests
3939        .is_empty());
3940
3941    async fn disconnect_and_reconnect(client: &TestClient, cx: &mut TestAppContext) {
3942        client.disconnect(&cx.to_async()).unwrap();
3943        client.clear_contacts(cx).await;
3944        client
3945            .authenticate_and_connect(false, &cx.to_async())
3946            .await
3947            .unwrap();
3948    }
3949}
3950
3951#[gpui::test(iterations = 10)]
3952async fn test_following(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3953    cx_a.foreground().forbid_parking();
3954    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3955    let client_a = server.create_client(cx_a, "user_a").await;
3956    let client_b = server.create_client(cx_b, "user_b").await;
3957    server
3958        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
3959        .await;
3960    cx_a.update(editor::init);
3961    cx_b.update(editor::init);
3962
3963    client_a
3964        .fs
3965        .insert_tree(
3966            "/a",
3967            json!({
3968                "1.txt": "one",
3969                "2.txt": "two",
3970                "3.txt": "three",
3971            }),
3972        )
3973        .await;
3974    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
3975
3976    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
3977
3978    // Client A opens some editors.
3979    let workspace_a = client_a.build_workspace(&project_a, cx_a);
3980    let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
3981    let editor_a1 = workspace_a
3982        .update(cx_a, |workspace, cx| {
3983            workspace.open_path((worktree_id, "1.txt"), true, cx)
3984        })
3985        .await
3986        .unwrap()
3987        .downcast::<Editor>()
3988        .unwrap();
3989    let editor_a2 = workspace_a
3990        .update(cx_a, |workspace, cx| {
3991            workspace.open_path((worktree_id, "2.txt"), true, cx)
3992        })
3993        .await
3994        .unwrap()
3995        .downcast::<Editor>()
3996        .unwrap();
3997
3998    // Client B opens an editor.
3999    let workspace_b = client_b.build_workspace(&project_b, cx_b);
4000    let editor_b1 = workspace_b
4001        .update(cx_b, |workspace, cx| {
4002            workspace.open_path((worktree_id, "1.txt"), true, cx)
4003        })
4004        .await
4005        .unwrap()
4006        .downcast::<Editor>()
4007        .unwrap();
4008
4009    let client_a_id = project_b.read_with(cx_b, |project, _| {
4010        project.collaborators().values().next().unwrap().peer_id
4011    });
4012    let client_b_id = project_a.read_with(cx_a, |project, _| {
4013        project.collaborators().values().next().unwrap().peer_id
4014    });
4015
4016    // When client B starts following client A, all visible view states are replicated to client B.
4017    editor_a1.update(cx_a, |editor, cx| {
4018        editor.change_selections(None, cx, |s| s.select_ranges([0..1]))
4019    });
4020    editor_a2.update(cx_a, |editor, cx| {
4021        editor.change_selections(None, cx, |s| s.select_ranges([2..3]))
4022    });
4023    workspace_b
4024        .update(cx_b, |workspace, cx| {
4025            workspace
4026                .toggle_follow(&ToggleFollow(client_a_id), cx)
4027                .unwrap()
4028        })
4029        .await
4030        .unwrap();
4031
4032    let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
4033        workspace
4034            .active_item(cx)
4035            .unwrap()
4036            .downcast::<Editor>()
4037            .unwrap()
4038    });
4039    assert!(cx_b.read(|cx| editor_b2.is_focused(cx)));
4040    assert_eq!(
4041        editor_b2.read_with(cx_b, |editor, cx| editor.project_path(cx)),
4042        Some((worktree_id, "2.txt").into())
4043    );
4044    assert_eq!(
4045        editor_b2.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
4046        vec![2..3]
4047    );
4048    assert_eq!(
4049        editor_b1.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
4050        vec![0..1]
4051    );
4052
4053    // When client A activates a different editor, client B does so as well.
4054    workspace_a.update(cx_a, |workspace, cx| {
4055        workspace.activate_item(&editor_a1, cx)
4056    });
4057    workspace_b
4058        .condition(cx_b, |workspace, cx| {
4059            workspace.active_item(cx).unwrap().id() == editor_b1.id()
4060        })
4061        .await;
4062
4063    // When client A navigates back and forth, client B does so as well.
4064    workspace_a
4065        .update(cx_a, |workspace, cx| {
4066            workspace::Pane::go_back(workspace, None, cx)
4067        })
4068        .await;
4069    workspace_b
4070        .condition(cx_b, |workspace, cx| {
4071            workspace.active_item(cx).unwrap().id() == editor_b2.id()
4072        })
4073        .await;
4074
4075    workspace_a
4076        .update(cx_a, |workspace, cx| {
4077            workspace::Pane::go_forward(workspace, None, cx)
4078        })
4079        .await;
4080    workspace_b
4081        .condition(cx_b, |workspace, cx| {
4082            workspace.active_item(cx).unwrap().id() == editor_b1.id()
4083        })
4084        .await;
4085
4086    // Changes to client A's editor are reflected on client B.
4087    editor_a1.update(cx_a, |editor, cx| {
4088        editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
4089    });
4090    editor_b1
4091        .condition(cx_b, |editor, cx| {
4092            editor.selections.ranges(cx) == vec![1..1, 2..2]
4093        })
4094        .await;
4095
4096    editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
4097    editor_b1
4098        .condition(cx_b, |editor, cx| editor.text(cx) == "TWO")
4099        .await;
4100
4101    editor_a1.update(cx_a, |editor, cx| {
4102        editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4103        editor.set_scroll_position(vec2f(0., 100.), cx);
4104    });
4105    editor_b1
4106        .condition(cx_b, |editor, cx| {
4107            editor.selections.ranges(cx) == vec![3..3]
4108        })
4109        .await;
4110
4111    // After unfollowing, client B stops receiving updates from client A.
4112    workspace_b.update(cx_b, |workspace, cx| {
4113        workspace.unfollow(&workspace.active_pane().clone(), cx)
4114    });
4115    workspace_a.update(cx_a, |workspace, cx| {
4116        workspace.activate_item(&editor_a2, cx)
4117    });
4118    cx_a.foreground().run_until_parked();
4119    assert_eq!(
4120        workspace_b.read_with(cx_b, |workspace, cx| workspace
4121            .active_item(cx)
4122            .unwrap()
4123            .id()),
4124        editor_b1.id()
4125    );
4126
4127    // Client A starts following client B.
4128    workspace_a
4129        .update(cx_a, |workspace, cx| {
4130            workspace
4131                .toggle_follow(&ToggleFollow(client_b_id), cx)
4132                .unwrap()
4133        })
4134        .await
4135        .unwrap();
4136    assert_eq!(
4137        workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
4138        Some(client_b_id)
4139    );
4140    assert_eq!(
4141        workspace_a.read_with(cx_a, |workspace, cx| workspace
4142            .active_item(cx)
4143            .unwrap()
4144            .id()),
4145        editor_a1.id()
4146    );
4147
4148    // Following interrupts when client B disconnects.
4149    client_b.disconnect(&cx_b.to_async()).unwrap();
4150    cx_a.foreground().run_until_parked();
4151    assert_eq!(
4152        workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
4153        None
4154    );
4155}
4156
4157#[gpui::test(iterations = 10)]
4158async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4159    cx_a.foreground().forbid_parking();
4160    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
4161    let client_a = server.create_client(cx_a, "user_a").await;
4162    let client_b = server.create_client(cx_b, "user_b").await;
4163    server
4164        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
4165        .await;
4166    cx_a.update(editor::init);
4167    cx_b.update(editor::init);
4168
4169    // Client A shares a project.
4170    client_a
4171        .fs
4172        .insert_tree(
4173            "/a",
4174            json!({
4175                "1.txt": "one",
4176                "2.txt": "two",
4177                "3.txt": "three",
4178                "4.txt": "four",
4179            }),
4180        )
4181        .await;
4182    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
4183
4184    // Client B joins the project.
4185    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
4186
4187    // Client A opens some editors.
4188    let workspace_a = client_a.build_workspace(&project_a, cx_a);
4189    let pane_a1 = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
4190    let _editor_a1 = workspace_a
4191        .update(cx_a, |workspace, cx| {
4192            workspace.open_path((worktree_id, "1.txt"), true, cx)
4193        })
4194        .await
4195        .unwrap()
4196        .downcast::<Editor>()
4197        .unwrap();
4198
4199    // Client B opens an editor.
4200    let workspace_b = client_b.build_workspace(&project_b, cx_b);
4201    let pane_b1 = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
4202    let _editor_b1 = workspace_b
4203        .update(cx_b, |workspace, cx| {
4204            workspace.open_path((worktree_id, "2.txt"), true, cx)
4205        })
4206        .await
4207        .unwrap()
4208        .downcast::<Editor>()
4209        .unwrap();
4210
4211    // Clients A and B follow each other in split panes
4212    workspace_a.update(cx_a, |workspace, cx| {
4213        workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
4214        assert_ne!(*workspace.active_pane(), pane_a1);
4215    });
4216    workspace_a
4217        .update(cx_a, |workspace, cx| {
4218            let leader_id = *project_a.read(cx).collaborators().keys().next().unwrap();
4219            workspace
4220                .toggle_follow(&workspace::ToggleFollow(leader_id), cx)
4221                .unwrap()
4222        })
4223        .await
4224        .unwrap();
4225    workspace_b.update(cx_b, |workspace, cx| {
4226        workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
4227        assert_ne!(*workspace.active_pane(), pane_b1);
4228    });
4229    workspace_b
4230        .update(cx_b, |workspace, cx| {
4231            let leader_id = *project_b.read(cx).collaborators().keys().next().unwrap();
4232            workspace
4233                .toggle_follow(&workspace::ToggleFollow(leader_id), cx)
4234                .unwrap()
4235        })
4236        .await
4237        .unwrap();
4238
4239    workspace_a
4240        .update(cx_a, |workspace, cx| {
4241            workspace.activate_next_pane(cx);
4242            assert_eq!(*workspace.active_pane(), pane_a1);
4243            workspace.open_path((worktree_id, "3.txt"), true, cx)
4244        })
4245        .await
4246        .unwrap();
4247    workspace_b
4248        .update(cx_b, |workspace, cx| {
4249            workspace.activate_next_pane(cx);
4250            assert_eq!(*workspace.active_pane(), pane_b1);
4251            workspace.open_path((worktree_id, "4.txt"), true, cx)
4252        })
4253        .await
4254        .unwrap();
4255    cx_a.foreground().run_until_parked();
4256
4257    // Ensure leader updates don't change the active pane of followers
4258    workspace_a.read_with(cx_a, |workspace, _| {
4259        assert_eq!(*workspace.active_pane(), pane_a1);
4260    });
4261    workspace_b.read_with(cx_b, |workspace, _| {
4262        assert_eq!(*workspace.active_pane(), pane_b1);
4263    });
4264
4265    // Ensure peers following each other doesn't cause an infinite loop.
4266    assert_eq!(
4267        workspace_a.read_with(cx_a, |workspace, cx| workspace
4268            .active_item(cx)
4269            .unwrap()
4270            .project_path(cx)),
4271        Some((worktree_id, "3.txt").into())
4272    );
4273    workspace_a.update(cx_a, |workspace, cx| {
4274        assert_eq!(
4275            workspace.active_item(cx).unwrap().project_path(cx),
4276            Some((worktree_id, "3.txt").into())
4277        );
4278        workspace.activate_next_pane(cx);
4279        assert_eq!(
4280            workspace.active_item(cx).unwrap().project_path(cx),
4281            Some((worktree_id, "4.txt").into())
4282        );
4283    });
4284    workspace_b.update(cx_b, |workspace, cx| {
4285        assert_eq!(
4286            workspace.active_item(cx).unwrap().project_path(cx),
4287            Some((worktree_id, "4.txt").into())
4288        );
4289        workspace.activate_next_pane(cx);
4290        assert_eq!(
4291            workspace.active_item(cx).unwrap().project_path(cx),
4292            Some((worktree_id, "3.txt").into())
4293        );
4294    });
4295}
4296
4297#[gpui::test(iterations = 10)]
4298async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4299    cx_a.foreground().forbid_parking();
4300
4301    // 2 clients connect to a server.
4302    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
4303    let client_a = server.create_client(cx_a, "user_a").await;
4304    let client_b = server.create_client(cx_b, "user_b").await;
4305    server
4306        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
4307        .await;
4308    cx_a.update(editor::init);
4309    cx_b.update(editor::init);
4310
4311    // Client A shares a project.
4312    client_a
4313        .fs
4314        .insert_tree(
4315            "/a",
4316            json!({
4317                "1.txt": "one",
4318                "2.txt": "two",
4319                "3.txt": "three",
4320            }),
4321        )
4322        .await;
4323    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
4324    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
4325
4326    // Client A opens some editors.
4327    let workspace_a = client_a.build_workspace(&project_a, cx_a);
4328    let _editor_a1 = workspace_a
4329        .update(cx_a, |workspace, cx| {
4330            workspace.open_path((worktree_id, "1.txt"), true, cx)
4331        })
4332        .await
4333        .unwrap()
4334        .downcast::<Editor>()
4335        .unwrap();
4336
4337    // Client B starts following client A.
4338    let workspace_b = client_b.build_workspace(&project_b, cx_b);
4339    let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
4340    let leader_id = project_b.read_with(cx_b, |project, _| {
4341        project.collaborators().values().next().unwrap().peer_id
4342    });
4343    workspace_b
4344        .update(cx_b, |workspace, cx| {
4345            workspace
4346                .toggle_follow(&ToggleFollow(leader_id), cx)
4347                .unwrap()
4348        })
4349        .await
4350        .unwrap();
4351    assert_eq!(
4352        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4353        Some(leader_id)
4354    );
4355    let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
4356        workspace
4357            .active_item(cx)
4358            .unwrap()
4359            .downcast::<Editor>()
4360            .unwrap()
4361    });
4362
4363    // When client B moves, it automatically stops following client A.
4364    editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
4365    assert_eq!(
4366        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4367        None
4368    );
4369
4370    workspace_b
4371        .update(cx_b, |workspace, cx| {
4372            workspace
4373                .toggle_follow(&ToggleFollow(leader_id), cx)
4374                .unwrap()
4375        })
4376        .await
4377        .unwrap();
4378    assert_eq!(
4379        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4380        Some(leader_id)
4381    );
4382
4383    // When client B edits, it automatically stops following client A.
4384    editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
4385    assert_eq!(
4386        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4387        None
4388    );
4389
4390    workspace_b
4391        .update(cx_b, |workspace, cx| {
4392            workspace
4393                .toggle_follow(&ToggleFollow(leader_id), cx)
4394                .unwrap()
4395        })
4396        .await
4397        .unwrap();
4398    assert_eq!(
4399        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4400        Some(leader_id)
4401    );
4402
4403    // When client B scrolls, it automatically stops following client A.
4404    editor_b2.update(cx_b, |editor, cx| {
4405        editor.set_scroll_position(vec2f(0., 3.), cx)
4406    });
4407    assert_eq!(
4408        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4409        None
4410    );
4411
4412    workspace_b
4413        .update(cx_b, |workspace, cx| {
4414            workspace
4415                .toggle_follow(&ToggleFollow(leader_id), cx)
4416                .unwrap()
4417        })
4418        .await
4419        .unwrap();
4420    assert_eq!(
4421        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4422        Some(leader_id)
4423    );
4424
4425    // When client B activates a different pane, it continues following client A in the original pane.
4426    workspace_b.update(cx_b, |workspace, cx| {
4427        workspace.split_pane(pane_b.clone(), SplitDirection::Right, cx)
4428    });
4429    assert_eq!(
4430        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4431        Some(leader_id)
4432    );
4433
4434    workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
4435    assert_eq!(
4436        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4437        Some(leader_id)
4438    );
4439
4440    // When client B activates a different item in the original pane, it automatically stops following client A.
4441    workspace_b
4442        .update(cx_b, |workspace, cx| {
4443            workspace.open_path((worktree_id, "2.txt"), true, cx)
4444        })
4445        .await
4446        .unwrap();
4447    assert_eq!(
4448        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4449        None
4450    );
4451}
4452
4453#[gpui::test(iterations = 10)]
4454async fn test_peers_simultaneously_following_each_other(
4455    deterministic: Arc<Deterministic>,
4456    cx_a: &mut TestAppContext,
4457    cx_b: &mut TestAppContext,
4458) {
4459    deterministic.forbid_parking();
4460
4461    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
4462    let client_a = server.create_client(cx_a, "user_a").await;
4463    let client_b = server.create_client(cx_b, "user_b").await;
4464    server
4465        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
4466        .await;
4467    cx_a.update(editor::init);
4468    cx_b.update(editor::init);
4469
4470    client_a.fs.insert_tree("/a", json!({})).await;
4471    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
4472    let workspace_a = client_a.build_workspace(&project_a, cx_a);
4473
4474    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
4475    let workspace_b = client_b.build_workspace(&project_b, cx_b);
4476
4477    deterministic.run_until_parked();
4478    let client_a_id = project_b.read_with(cx_b, |project, _| {
4479        project.collaborators().values().next().unwrap().peer_id
4480    });
4481    let client_b_id = project_a.read_with(cx_a, |project, _| {
4482        project.collaborators().values().next().unwrap().peer_id
4483    });
4484
4485    let a_follow_b = workspace_a.update(cx_a, |workspace, cx| {
4486        workspace
4487            .toggle_follow(&ToggleFollow(client_b_id), cx)
4488            .unwrap()
4489    });
4490    let b_follow_a = workspace_b.update(cx_b, |workspace, cx| {
4491        workspace
4492            .toggle_follow(&ToggleFollow(client_a_id), cx)
4493            .unwrap()
4494    });
4495
4496    futures::try_join!(a_follow_b, b_follow_a).unwrap();
4497    workspace_a.read_with(cx_a, |workspace, _| {
4498        assert_eq!(
4499            workspace.leader_for_pane(&workspace.active_pane()),
4500            Some(client_b_id)
4501        );
4502    });
4503    workspace_b.read_with(cx_b, |workspace, _| {
4504        assert_eq!(
4505            workspace.leader_for_pane(&workspace.active_pane()),
4506            Some(client_a_id)
4507        );
4508    });
4509}
4510
4511#[gpui::test(iterations = 100)]
4512async fn test_random_collaboration(
4513    cx: &mut TestAppContext,
4514    deterministic: Arc<Deterministic>,
4515    rng: StdRng,
4516) {
4517    deterministic.forbid_parking();
4518    let max_peers = env::var("MAX_PEERS")
4519        .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
4520        .unwrap_or(5);
4521    assert!(max_peers <= 5);
4522
4523    let max_operations = env::var("OPERATIONS")
4524        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
4525        .unwrap_or(10);
4526
4527    let rng = Arc::new(Mutex::new(rng));
4528
4529    let guest_lang_registry = Arc::new(LanguageRegistry::test());
4530    let host_language_registry = Arc::new(LanguageRegistry::test());
4531
4532    let fs = FakeFs::new(cx.background());
4533    fs.insert_tree("/_collab", json!({"init": ""})).await;
4534
4535    let mut server = TestServer::start(cx.foreground(), cx.background()).await;
4536    let db = server.app_state.db.clone();
4537    let host_user_id = db.create_user("host", None, false).await.unwrap();
4538    let mut available_guests = vec![
4539        "guest-1".to_string(),
4540        "guest-2".to_string(),
4541        "guest-3".to_string(),
4542        "guest-4".to_string(),
4543    ];
4544
4545    for username in &available_guests {
4546        let guest_user_id = db.create_user(username, None, false).await.unwrap();
4547        assert_eq!(*username, format!("guest-{}", guest_user_id));
4548        server
4549            .app_state
4550            .db
4551            .send_contact_request(guest_user_id, host_user_id)
4552            .await
4553            .unwrap();
4554        server
4555            .app_state
4556            .db
4557            .respond_to_contact_request(host_user_id, guest_user_id, true)
4558            .await
4559            .unwrap();
4560    }
4561
4562    let mut clients = Vec::new();
4563    let mut user_ids = Vec::new();
4564    let mut op_start_signals = Vec::new();
4565
4566    let mut next_entity_id = 100000;
4567    let mut host_cx = TestAppContext::new(
4568        cx.foreground_platform(),
4569        cx.platform(),
4570        deterministic.build_foreground(next_entity_id),
4571        deterministic.build_background(),
4572        cx.font_cache(),
4573        cx.leak_detector(),
4574        next_entity_id,
4575    );
4576    let host = server.create_client(&mut host_cx, "host").await;
4577    let host_project = host_cx.update(|cx| {
4578        Project::local(
4579            true,
4580            host.client.clone(),
4581            host.user_store.clone(),
4582            host.project_store.clone(),
4583            host_language_registry.clone(),
4584            fs.clone(),
4585            cx,
4586        )
4587    });
4588    let host_project_id = host_project
4589        .update(&mut host_cx, |p, _| p.next_remote_id())
4590        .await;
4591
4592    let (collab_worktree, _) = host_project
4593        .update(&mut host_cx, |project, cx| {
4594            project.find_or_create_local_worktree("/_collab", true, cx)
4595        })
4596        .await
4597        .unwrap();
4598    collab_worktree
4599        .read_with(&host_cx, |tree, _| tree.as_local().unwrap().scan_complete())
4600        .await;
4601
4602    // Set up fake language servers.
4603    let mut language = Language::new(
4604        LanguageConfig {
4605            name: "Rust".into(),
4606            path_suffixes: vec!["rs".to_string()],
4607            ..Default::default()
4608        },
4609        None,
4610    );
4611    let _fake_servers = language.set_fake_lsp_adapter(FakeLspAdapter {
4612        name: "the-fake-language-server",
4613        capabilities: lsp::LanguageServer::full_capabilities(),
4614        initializer: Some(Box::new({
4615            let rng = rng.clone();
4616            let fs = fs.clone();
4617            let project = host_project.downgrade();
4618            move |fake_server: &mut FakeLanguageServer| {
4619                fake_server.handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
4620                    Ok(Some(lsp::CompletionResponse::Array(vec![
4621                        lsp::CompletionItem {
4622                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
4623                                range: lsp::Range::new(
4624                                    lsp::Position::new(0, 0),
4625                                    lsp::Position::new(0, 0),
4626                                ),
4627                                new_text: "the-new-text".to_string(),
4628                            })),
4629                            ..Default::default()
4630                        },
4631                    ])))
4632                });
4633
4634                fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
4635                    |_, _| async move {
4636                        Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
4637                            lsp::CodeAction {
4638                                title: "the-code-action".to_string(),
4639                                ..Default::default()
4640                            },
4641                        )]))
4642                    },
4643                );
4644
4645                fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
4646                    |params, _| async move {
4647                        Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
4648                            params.position,
4649                            params.position,
4650                        ))))
4651                    },
4652                );
4653
4654                fake_server.handle_request::<lsp::request::GotoDefinition, _, _>({
4655                    let fs = fs.clone();
4656                    let rng = rng.clone();
4657                    move |_, _| {
4658                        let fs = fs.clone();
4659                        let rng = rng.clone();
4660                        async move {
4661                            let files = fs.files().await;
4662                            let mut rng = rng.lock();
4663                            let count = rng.gen_range::<usize, _>(1..3);
4664                            let files = (0..count)
4665                                .map(|_| files.choose(&mut *rng).unwrap())
4666                                .collect::<Vec<_>>();
4667                            log::info!("LSP: Returning definitions in files {:?}", &files);
4668                            Ok(Some(lsp::GotoDefinitionResponse::Array(
4669                                files
4670                                    .into_iter()
4671                                    .map(|file| lsp::Location {
4672                                        uri: lsp::Url::from_file_path(file).unwrap(),
4673                                        range: Default::default(),
4674                                    })
4675                                    .collect(),
4676                            )))
4677                        }
4678                    }
4679                });
4680
4681                fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>({
4682                    let rng = rng.clone();
4683                    let project = project.clone();
4684                    move |params, mut cx| {
4685                        let highlights = if let Some(project) = project.upgrade(&cx) {
4686                            project.update(&mut cx, |project, cx| {
4687                                let path = params
4688                                    .text_document_position_params
4689                                    .text_document
4690                                    .uri
4691                                    .to_file_path()
4692                                    .unwrap();
4693                                let (worktree, relative_path) =
4694                                    project.find_local_worktree(&path, cx)?;
4695                                let project_path =
4696                                    ProjectPath::from((worktree.read(cx).id(), relative_path));
4697                                let buffer = project.get_open_buffer(&project_path, cx)?.read(cx);
4698
4699                                let mut highlights = Vec::new();
4700                                let highlight_count = rng.lock().gen_range(1..=5);
4701                                let mut prev_end = 0;
4702                                for _ in 0..highlight_count {
4703                                    let range =
4704                                        buffer.random_byte_range(prev_end, &mut *rng.lock());
4705
4706                                    highlights.push(lsp::DocumentHighlight {
4707                                        range: range_to_lsp(range.to_point_utf16(buffer)),
4708                                        kind: Some(lsp::DocumentHighlightKind::READ),
4709                                    });
4710                                    prev_end = range.end;
4711                                }
4712                                Some(highlights)
4713                            })
4714                        } else {
4715                            None
4716                        };
4717                        async move { Ok(highlights) }
4718                    }
4719                });
4720            }
4721        })),
4722        ..Default::default()
4723    });
4724    host_language_registry.add(Arc::new(language));
4725
4726    let op_start_signal = futures::channel::mpsc::unbounded();
4727    user_ids.push(host.current_user_id(&host_cx));
4728    op_start_signals.push(op_start_signal.0);
4729    clients.push(host_cx.foreground().spawn(host.simulate_host(
4730        host_project,
4731        op_start_signal.1,
4732        rng.clone(),
4733        host_cx,
4734    )));
4735
4736    let disconnect_host_at = if rng.lock().gen_bool(0.2) {
4737        rng.lock().gen_range(0..max_operations)
4738    } else {
4739        max_operations
4740    };
4741
4742    let mut operations = 0;
4743    while operations < max_operations {
4744        if operations == disconnect_host_at {
4745            server.disconnect_client(user_ids[0]);
4746            deterministic.advance_clock(RECEIVE_TIMEOUT);
4747            drop(op_start_signals);
4748
4749            deterministic.start_waiting();
4750            let mut clients = futures::future::join_all(clients).await;
4751            deterministic.finish_waiting();
4752            deterministic.run_until_parked();
4753
4754            let (host, host_project, mut host_cx, host_err) = clients.remove(0);
4755            if let Some(host_err) = host_err {
4756                log::error!("host error - {:?}", host_err);
4757            }
4758            host_project.read_with(&host_cx, |project, _| assert!(!project.is_shared()));
4759            for (guest, guest_project, mut guest_cx, guest_err) in clients {
4760                if let Some(guest_err) = guest_err {
4761                    log::error!("{} error - {:?}", guest.username, guest_err);
4762                }
4763
4764                let contacts = server
4765                    .app_state
4766                    .db
4767                    .get_contacts(guest.current_user_id(&guest_cx))
4768                    .await
4769                    .unwrap();
4770                let contacts = server
4771                    .store
4772                    .lock()
4773                    .await
4774                    .build_initial_contacts_update(contacts)
4775                    .contacts;
4776                assert!(!contacts
4777                    .iter()
4778                    .flat_map(|contact| &contact.projects)
4779                    .any(|project| project.id == host_project_id));
4780                guest_project.read_with(&guest_cx, |project, _| assert!(project.is_read_only()));
4781                guest_cx.update(|_| drop((guest, guest_project)));
4782            }
4783            host_cx.update(|_| drop((host, host_project)));
4784
4785            return;
4786        }
4787
4788        let distribution = rng.lock().gen_range(0..100);
4789        match distribution {
4790            0..=19 if !available_guests.is_empty() => {
4791                let guest_ix = rng.lock().gen_range(0..available_guests.len());
4792                let guest_username = available_guests.remove(guest_ix);
4793                log::info!("Adding new connection for {}", guest_username);
4794                next_entity_id += 100000;
4795                let mut guest_cx = TestAppContext::new(
4796                    cx.foreground_platform(),
4797                    cx.platform(),
4798                    deterministic.build_foreground(next_entity_id),
4799                    deterministic.build_background(),
4800                    cx.font_cache(),
4801                    cx.leak_detector(),
4802                    next_entity_id,
4803                );
4804
4805                deterministic.start_waiting();
4806                let guest = server.create_client(&mut guest_cx, &guest_username).await;
4807                let guest_project = Project::remote(
4808                    host_project_id,
4809                    guest.client.clone(),
4810                    guest.user_store.clone(),
4811                    guest.project_store.clone(),
4812                    guest_lang_registry.clone(),
4813                    FakeFs::new(cx.background()),
4814                    guest_cx.to_async(),
4815                )
4816                .await
4817                .unwrap();
4818                deterministic.finish_waiting();
4819
4820                let op_start_signal = futures::channel::mpsc::unbounded();
4821                user_ids.push(guest.current_user_id(&guest_cx));
4822                op_start_signals.push(op_start_signal.0);
4823                clients.push(guest_cx.foreground().spawn(guest.simulate_guest(
4824                    guest_username.clone(),
4825                    guest_project,
4826                    op_start_signal.1,
4827                    rng.clone(),
4828                    guest_cx,
4829                )));
4830
4831                log::info!("Added connection for {}", guest_username);
4832                operations += 1;
4833            }
4834            20..=29 if clients.len() > 1 => {
4835                let guest_ix = rng.lock().gen_range(1..clients.len());
4836                log::info!("Removing guest {}", user_ids[guest_ix]);
4837                let removed_guest_id = user_ids.remove(guest_ix);
4838                let guest = clients.remove(guest_ix);
4839                op_start_signals.remove(guest_ix);
4840                server.forbid_connections();
4841                server.disconnect_client(removed_guest_id);
4842                deterministic.advance_clock(RECEIVE_TIMEOUT);
4843                deterministic.start_waiting();
4844                log::info!("Waiting for guest {} to exit...", removed_guest_id);
4845                let (guest, guest_project, mut guest_cx, guest_err) = guest.await;
4846                deterministic.finish_waiting();
4847                server.allow_connections();
4848
4849                if let Some(guest_err) = guest_err {
4850                    log::error!("{} error - {:?}", guest.username, guest_err);
4851                }
4852                guest_project.read_with(&guest_cx, |project, _| assert!(project.is_read_only()));
4853                for user_id in &user_ids {
4854                    let contacts = server.app_state.db.get_contacts(*user_id).await.unwrap();
4855                    let contacts = server
4856                        .store
4857                        .lock()
4858                        .await
4859                        .build_initial_contacts_update(contacts)
4860                        .contacts;
4861                    for contact in contacts {
4862                        if contact.online {
4863                            assert_ne!(
4864                                contact.user_id, removed_guest_id.0 as u64,
4865                                "removed guest is still a contact of another peer"
4866                            );
4867                        }
4868                        for project in contact.projects {
4869                            for project_guest_id in project.guests {
4870                                assert_ne!(
4871                                    project_guest_id, removed_guest_id.0 as u64,
4872                                    "removed guest appears as still participating on a project"
4873                                );
4874                            }
4875                        }
4876                    }
4877                }
4878
4879                log::info!("{} removed", guest.username);
4880                available_guests.push(guest.username.clone());
4881                guest_cx.update(|_| drop((guest, guest_project)));
4882
4883                operations += 1;
4884            }
4885            _ => {
4886                while operations < max_operations && rng.lock().gen_bool(0.7) {
4887                    op_start_signals
4888                        .choose(&mut *rng.lock())
4889                        .unwrap()
4890                        .unbounded_send(())
4891                        .unwrap();
4892                    operations += 1;
4893                }
4894
4895                if rng.lock().gen_bool(0.8) {
4896                    deterministic.run_until_parked();
4897                }
4898            }
4899        }
4900    }
4901
4902    drop(op_start_signals);
4903    deterministic.start_waiting();
4904    let mut clients = futures::future::join_all(clients).await;
4905    deterministic.finish_waiting();
4906    deterministic.run_until_parked();
4907
4908    let (host_client, host_project, mut host_cx, host_err) = clients.remove(0);
4909    if let Some(host_err) = host_err {
4910        panic!("host error - {:?}", host_err);
4911    }
4912    let host_worktree_snapshots = host_project.read_with(&host_cx, |project, cx| {
4913        project
4914            .worktrees(cx)
4915            .map(|worktree| {
4916                let snapshot = worktree.read(cx).snapshot();
4917                (snapshot.id(), snapshot)
4918            })
4919            .collect::<BTreeMap<_, _>>()
4920    });
4921
4922    host_project.read_with(&host_cx, |project, cx| project.check_invariants(cx));
4923
4924    for (guest_client, guest_project, mut guest_cx, guest_err) in clients.into_iter() {
4925        if let Some(guest_err) = guest_err {
4926            panic!("{} error - {:?}", guest_client.username, guest_err);
4927        }
4928        let worktree_snapshots = guest_project.read_with(&guest_cx, |project, cx| {
4929            project
4930                .worktrees(cx)
4931                .map(|worktree| {
4932                    let worktree = worktree.read(cx);
4933                    (worktree.id(), worktree.snapshot())
4934                })
4935                .collect::<BTreeMap<_, _>>()
4936        });
4937
4938        assert_eq!(
4939            worktree_snapshots.keys().collect::<Vec<_>>(),
4940            host_worktree_snapshots.keys().collect::<Vec<_>>(),
4941            "{} has different worktrees than the host",
4942            guest_client.username
4943        );
4944        for (id, host_snapshot) in &host_worktree_snapshots {
4945            let guest_snapshot = &worktree_snapshots[id];
4946            assert_eq!(
4947                guest_snapshot.root_name(),
4948                host_snapshot.root_name(),
4949                "{} has different root name than the host for worktree {}",
4950                guest_client.username,
4951                id
4952            );
4953            assert_eq!(
4954                guest_snapshot.entries(false).collect::<Vec<_>>(),
4955                host_snapshot.entries(false).collect::<Vec<_>>(),
4956                "{} has different snapshot than the host for worktree {}",
4957                guest_client.username,
4958                id
4959            );
4960            assert_eq!(guest_snapshot.scan_id(), host_snapshot.scan_id());
4961        }
4962
4963        guest_project.read_with(&guest_cx, |project, cx| project.check_invariants(cx));
4964
4965        for guest_buffer in &guest_client.buffers {
4966            let buffer_id = guest_buffer.read_with(&guest_cx, |buffer, _| buffer.remote_id());
4967            let host_buffer = host_project.read_with(&host_cx, |project, cx| {
4968                project.buffer_for_id(buffer_id, cx).expect(&format!(
4969                    "host does not have buffer for guest:{}, peer:{}, id:{}",
4970                    guest_client.username, guest_client.peer_id, buffer_id
4971                ))
4972            });
4973            let path =
4974                host_buffer.read_with(&host_cx, |buffer, cx| buffer.file().unwrap().full_path(cx));
4975
4976            assert_eq!(
4977                guest_buffer.read_with(&guest_cx, |buffer, _| buffer.deferred_ops_len()),
4978                0,
4979                "{}, buffer {}, path {:?} has deferred operations",
4980                guest_client.username,
4981                buffer_id,
4982                path,
4983            );
4984            assert_eq!(
4985                guest_buffer.read_with(&guest_cx, |buffer, _| buffer.text()),
4986                host_buffer.read_with(&host_cx, |buffer, _| buffer.text()),
4987                "{}, buffer {}, path {:?}, differs from the host's buffer",
4988                guest_client.username,
4989                buffer_id,
4990                path
4991            );
4992        }
4993
4994        guest_cx.update(|_| drop((guest_project, guest_client)));
4995    }
4996
4997    host_cx.update(|_| drop((host_client, host_project)));
4998}
4999
5000struct TestServer {
5001    peer: Arc<Peer>,
5002    app_state: Arc<AppState>,
5003    server: Arc<Server>,
5004    foreground: Rc<executor::Foreground>,
5005    notifications: mpsc::UnboundedReceiver<()>,
5006    connection_killers: Arc<Mutex<HashMap<UserId, Arc<AtomicBool>>>>,
5007    forbid_connections: Arc<AtomicBool>,
5008    _test_db: TestDb,
5009}
5010
5011impl TestServer {
5012    async fn start(
5013        foreground: Rc<executor::Foreground>,
5014        background: Arc<executor::Background>,
5015    ) -> Self {
5016        let test_db = TestDb::fake(background.clone());
5017        let app_state = Self::build_app_state(&test_db).await;
5018        let peer = Peer::new();
5019        let notifications = mpsc::unbounded();
5020        let server = Server::new(app_state.clone(), Some(notifications.0));
5021        Self {
5022            peer,
5023            app_state,
5024            server,
5025            foreground,
5026            notifications: notifications.1,
5027            connection_killers: Default::default(),
5028            forbid_connections: Default::default(),
5029            _test_db: test_db,
5030        }
5031    }
5032
5033    async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient {
5034        cx.update(|cx| {
5035            let mut settings = Settings::test(cx);
5036            settings.projects_online_by_default = false;
5037            cx.set_global(settings);
5038        });
5039
5040        let http = FakeHttpClient::with_404_response();
5041        let user_id = if let Ok(Some(user)) = self.app_state.db.get_user_by_github_login(name).await
5042        {
5043            user.id
5044        } else {
5045            self.app_state
5046                .db
5047                .create_user(name, None, false)
5048                .await
5049                .unwrap()
5050        };
5051        let client_name = name.to_string();
5052        let mut client = Client::new(http.clone());
5053        let server = self.server.clone();
5054        let db = self.app_state.db.clone();
5055        let connection_killers = self.connection_killers.clone();
5056        let forbid_connections = self.forbid_connections.clone();
5057        let (connection_id_tx, mut connection_id_rx) = mpsc::channel(16);
5058
5059        Arc::get_mut(&mut client)
5060            .unwrap()
5061            .set_id(user_id.0 as usize)
5062            .override_authenticate(move |cx| {
5063                cx.spawn(|_| async move {
5064                    let access_token = "the-token".to_string();
5065                    Ok(Credentials {
5066                        user_id: user_id.0 as u64,
5067                        access_token,
5068                    })
5069                })
5070            })
5071            .override_establish_connection(move |credentials, cx| {
5072                assert_eq!(credentials.user_id, user_id.0 as u64);
5073                assert_eq!(credentials.access_token, "the-token");
5074
5075                let server = server.clone();
5076                let db = db.clone();
5077                let connection_killers = connection_killers.clone();
5078                let forbid_connections = forbid_connections.clone();
5079                let client_name = client_name.clone();
5080                let connection_id_tx = connection_id_tx.clone();
5081                cx.spawn(move |cx| async move {
5082                    if forbid_connections.load(SeqCst) {
5083                        Err(EstablishConnectionError::other(anyhow!(
5084                            "server is forbidding connections"
5085                        )))
5086                    } else {
5087                        let (client_conn, server_conn, killed) =
5088                            Connection::in_memory(cx.background());
5089                        connection_killers.lock().insert(user_id, killed);
5090                        let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
5091                        cx.background()
5092                            .spawn(server.handle_connection(
5093                                server_conn,
5094                                client_name,
5095                                user,
5096                                Some(connection_id_tx),
5097                                cx.background(),
5098                            ))
5099                            .detach();
5100                        Ok(client_conn)
5101                    }
5102                })
5103            });
5104
5105        let fs = FakeFs::new(cx.background());
5106        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
5107        let project_store = cx.add_model(|_| ProjectStore::new(project::Db::open_fake()));
5108        let app_state = Arc::new(workspace::AppState {
5109            client: client.clone(),
5110            user_store: user_store.clone(),
5111            project_store: project_store.clone(),
5112            languages: Arc::new(LanguageRegistry::new(Task::ready(()))),
5113            themes: ThemeRegistry::new((), cx.font_cache()),
5114            fs: fs.clone(),
5115            build_window_options: || Default::default(),
5116            initialize_workspace: |_, _, _| unimplemented!(),
5117        });
5118
5119        Channel::init(&client);
5120        Project::init(&client);
5121        cx.update(|cx| workspace::init(app_state.clone(), cx));
5122
5123        client
5124            .authenticate_and_connect(false, &cx.to_async())
5125            .await
5126            .unwrap();
5127        let peer_id = PeerId(connection_id_rx.next().await.unwrap().0);
5128
5129        let client = TestClient {
5130            client,
5131            peer_id,
5132            username: name.to_string(),
5133            user_store,
5134            project_store,
5135            fs,
5136            language_registry: Arc::new(LanguageRegistry::test()),
5137            buffers: Default::default(),
5138        };
5139        client.wait_for_current_user(cx).await;
5140        client
5141    }
5142
5143    fn disconnect_client(&self, user_id: UserId) {
5144        self.connection_killers
5145            .lock()
5146            .remove(&user_id)
5147            .unwrap()
5148            .store(true, SeqCst);
5149    }
5150
5151    fn forbid_connections(&self) {
5152        self.forbid_connections.store(true, SeqCst);
5153    }
5154
5155    fn allow_connections(&self) {
5156        self.forbid_connections.store(false, SeqCst);
5157    }
5158
5159    async fn make_contacts(&self, mut clients: Vec<(&TestClient, &mut TestAppContext)>) {
5160        while let Some((client_a, cx_a)) = clients.pop() {
5161            for (client_b, cx_b) in &mut clients {
5162                client_a
5163                    .user_store
5164                    .update(cx_a, |store, cx| {
5165                        store.request_contact(client_b.user_id().unwrap(), cx)
5166                    })
5167                    .await
5168                    .unwrap();
5169                cx_a.foreground().run_until_parked();
5170                client_b
5171                    .user_store
5172                    .update(*cx_b, |store, cx| {
5173                        store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
5174                    })
5175                    .await
5176                    .unwrap();
5177            }
5178        }
5179    }
5180
5181    async fn build_app_state(test_db: &TestDb) -> Arc<AppState> {
5182        Arc::new(AppState {
5183            db: test_db.db().clone(),
5184            api_token: Default::default(),
5185            invite_link_prefix: Default::default(),
5186        })
5187    }
5188
5189    async fn condition<F>(&mut self, mut predicate: F)
5190    where
5191        F: FnMut(&Store) -> bool,
5192    {
5193        assert!(
5194            self.foreground.parking_forbidden(),
5195            "you must call forbid_parking to use server conditions so we don't block indefinitely"
5196        );
5197        while !(predicate)(&*self.server.store.lock().await) {
5198            self.foreground.start_waiting();
5199            self.notifications.next().await;
5200            self.foreground.finish_waiting();
5201        }
5202    }
5203}
5204
5205impl Deref for TestServer {
5206    type Target = Server;
5207
5208    fn deref(&self) -> &Self::Target {
5209        &self.server
5210    }
5211}
5212
5213impl Drop for TestServer {
5214    fn drop(&mut self) {
5215        self.peer.reset();
5216    }
5217}
5218
5219struct TestClient {
5220    client: Arc<Client>,
5221    username: String,
5222    pub peer_id: PeerId,
5223    pub user_store: ModelHandle<UserStore>,
5224    pub project_store: ModelHandle<ProjectStore>,
5225    language_registry: Arc<LanguageRegistry>,
5226    fs: Arc<FakeFs>,
5227    buffers: HashSet<ModelHandle<language::Buffer>>,
5228}
5229
5230impl Deref for TestClient {
5231    type Target = Arc<Client>;
5232
5233    fn deref(&self) -> &Self::Target {
5234        &self.client
5235    }
5236}
5237
5238struct ContactsSummary {
5239    pub current: Vec<String>,
5240    pub outgoing_requests: Vec<String>,
5241    pub incoming_requests: Vec<String>,
5242}
5243
5244impl TestClient {
5245    pub fn current_user_id(&self, cx: &TestAppContext) -> UserId {
5246        UserId::from_proto(
5247            self.user_store
5248                .read_with(cx, |user_store, _| user_store.current_user().unwrap().id),
5249        )
5250    }
5251
5252    async fn wait_for_current_user(&self, cx: &TestAppContext) {
5253        let mut authed_user = self
5254            .user_store
5255            .read_with(cx, |user_store, _| user_store.watch_current_user());
5256        while authed_user.next().await.unwrap().is_none() {}
5257    }
5258
5259    async fn clear_contacts(&self, cx: &mut TestAppContext) {
5260        self.user_store
5261            .update(cx, |store, _| store.clear_contacts())
5262            .await;
5263    }
5264
5265    fn summarize_contacts(&self, cx: &TestAppContext) -> ContactsSummary {
5266        self.user_store.read_with(cx, |store, _| ContactsSummary {
5267            current: store
5268                .contacts()
5269                .iter()
5270                .map(|contact| contact.user.github_login.clone())
5271                .collect(),
5272            outgoing_requests: store
5273                .outgoing_contact_requests()
5274                .iter()
5275                .map(|user| user.github_login.clone())
5276                .collect(),
5277            incoming_requests: store
5278                .incoming_contact_requests()
5279                .iter()
5280                .map(|user| user.github_login.clone())
5281                .collect(),
5282        })
5283    }
5284
5285    async fn build_local_project(
5286        &self,
5287        root_path: impl AsRef<Path>,
5288        cx: &mut TestAppContext,
5289    ) -> (ModelHandle<Project>, WorktreeId) {
5290        let project = cx.update(|cx| {
5291            Project::local(
5292                true,
5293                self.client.clone(),
5294                self.user_store.clone(),
5295                self.project_store.clone(),
5296                self.language_registry.clone(),
5297                self.fs.clone(),
5298                cx,
5299            )
5300        });
5301        let (worktree, _) = project
5302            .update(cx, |p, cx| {
5303                p.find_or_create_local_worktree(root_path, true, cx)
5304            })
5305            .await
5306            .unwrap();
5307        worktree
5308            .read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
5309            .await;
5310        project
5311            .update(cx, |project, _| project.next_remote_id())
5312            .await;
5313        (project, worktree.read_with(cx, |tree, _| tree.id()))
5314    }
5315
5316    async fn build_remote_project(
5317        &self,
5318        host_project: &ModelHandle<Project>,
5319        host_cx: &mut TestAppContext,
5320        guest_cx: &mut TestAppContext,
5321    ) -> ModelHandle<Project> {
5322        let host_project_id = host_project
5323            .read_with(host_cx, |project, _| project.next_remote_id())
5324            .await;
5325        let guest_user_id = self.user_id().unwrap();
5326        let languages = host_project.read_with(host_cx, |project, _| project.languages().clone());
5327        let project_b = guest_cx.spawn(|cx| {
5328            Project::remote(
5329                host_project_id,
5330                self.client.clone(),
5331                self.user_store.clone(),
5332                self.project_store.clone(),
5333                languages,
5334                FakeFs::new(cx.background()),
5335                cx,
5336            )
5337        });
5338        host_cx.foreground().run_until_parked();
5339        host_project.update(host_cx, |project, cx| {
5340            project.respond_to_join_request(guest_user_id, true, cx)
5341        });
5342        let project = project_b.await.unwrap();
5343        project
5344    }
5345
5346    fn build_workspace(
5347        &self,
5348        project: &ModelHandle<Project>,
5349        cx: &mut TestAppContext,
5350    ) -> ViewHandle<Workspace> {
5351        let (window_id, _) = cx.add_window(|_| EmptyView);
5352        cx.add_view(window_id, |cx| Workspace::new(project.clone(), cx))
5353    }
5354
5355    async fn simulate_host(
5356        mut self,
5357        project: ModelHandle<Project>,
5358        op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
5359        rng: Arc<Mutex<StdRng>>,
5360        mut cx: TestAppContext,
5361    ) -> (
5362        Self,
5363        ModelHandle<Project>,
5364        TestAppContext,
5365        Option<anyhow::Error>,
5366    ) {
5367        async fn simulate_host_internal(
5368            client: &mut TestClient,
5369            project: ModelHandle<Project>,
5370            mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
5371            rng: Arc<Mutex<StdRng>>,
5372            cx: &mut TestAppContext,
5373        ) -> anyhow::Result<()> {
5374            let fs = project.read_with(cx, |project, _| project.fs().clone());
5375
5376            cx.update(|cx| {
5377                cx.subscribe(&project, move |project, event, cx| {
5378                    if let project::Event::ContactRequestedJoin(user) = event {
5379                        log::info!("Host: accepting join request from {}", user.github_login);
5380                        project.update(cx, |project, cx| {
5381                            project.respond_to_join_request(user.id, true, cx)
5382                        });
5383                    }
5384                })
5385                .detach();
5386            });
5387
5388            while op_start_signal.next().await.is_some() {
5389                let distribution = rng.lock().gen_range::<usize, _>(0..100);
5390                let files = fs.as_fake().files().await;
5391                match distribution {
5392                    0..=19 if !files.is_empty() => {
5393                        let path = files.choose(&mut *rng.lock()).unwrap();
5394                        let mut path = path.as_path();
5395                        while let Some(parent_path) = path.parent() {
5396                            path = parent_path;
5397                            if rng.lock().gen() {
5398                                break;
5399                            }
5400                        }
5401
5402                        log::info!("Host: find/create local worktree {:?}", path);
5403                        let find_or_create_worktree = project.update(cx, |project, cx| {
5404                            project.find_or_create_local_worktree(path, true, cx)
5405                        });
5406                        if rng.lock().gen() {
5407                            cx.background().spawn(find_or_create_worktree).detach();
5408                        } else {
5409                            find_or_create_worktree.await?;
5410                        }
5411                    }
5412                    20..=79 if !files.is_empty() => {
5413                        let buffer = if client.buffers.is_empty() || rng.lock().gen() {
5414                            let file = files.choose(&mut *rng.lock()).unwrap();
5415                            let (worktree, path) = project
5416                                .update(cx, |project, cx| {
5417                                    project.find_or_create_local_worktree(file.clone(), true, cx)
5418                                })
5419                                .await?;
5420                            let project_path =
5421                                worktree.read_with(cx, |worktree, _| (worktree.id(), path));
5422                            log::info!(
5423                                "Host: opening path {:?}, worktree {}, relative_path {:?}",
5424                                file,
5425                                project_path.0,
5426                                project_path.1
5427                            );
5428                            let buffer = project
5429                                .update(cx, |project, cx| project.open_buffer(project_path, cx))
5430                                .await
5431                                .unwrap();
5432                            client.buffers.insert(buffer.clone());
5433                            buffer
5434                        } else {
5435                            client
5436                                .buffers
5437                                .iter()
5438                                .choose(&mut *rng.lock())
5439                                .unwrap()
5440                                .clone()
5441                        };
5442
5443                        if rng.lock().gen_bool(0.1) {
5444                            cx.update(|cx| {
5445                                log::info!(
5446                                    "Host: dropping buffer {:?}",
5447                                    buffer.read(cx).file().unwrap().full_path(cx)
5448                                );
5449                                client.buffers.remove(&buffer);
5450                                drop(buffer);
5451                            });
5452                        } else {
5453                            buffer.update(cx, |buffer, cx| {
5454                                log::info!(
5455                                    "Host: updating buffer {:?} ({})",
5456                                    buffer.file().unwrap().full_path(cx),
5457                                    buffer.remote_id()
5458                                );
5459
5460                                if rng.lock().gen_bool(0.7) {
5461                                    buffer.randomly_edit(&mut *rng.lock(), 5, cx);
5462                                } else {
5463                                    buffer.randomly_undo_redo(&mut *rng.lock(), cx);
5464                                }
5465                            });
5466                        }
5467                    }
5468                    _ => loop {
5469                        let path_component_count = rng.lock().gen_range::<usize, _>(1..=5);
5470                        let mut path = PathBuf::new();
5471                        path.push("/");
5472                        for _ in 0..path_component_count {
5473                            let letter = rng.lock().gen_range(b'a'..=b'z');
5474                            path.push(std::str::from_utf8(&[letter]).unwrap());
5475                        }
5476                        path.set_extension("rs");
5477                        let parent_path = path.parent().unwrap();
5478
5479                        log::info!("Host: creating file {:?}", path,);
5480
5481                        if fs.create_dir(&parent_path).await.is_ok()
5482                            && fs.create_file(&path, Default::default()).await.is_ok()
5483                        {
5484                            break;
5485                        } else {
5486                            log::info!("Host: cannot create file");
5487                        }
5488                    },
5489                }
5490
5491                cx.background().simulate_random_delay().await;
5492            }
5493
5494            Ok(())
5495        }
5496
5497        let result =
5498            simulate_host_internal(&mut self, project.clone(), op_start_signal, rng, &mut cx).await;
5499        log::info!("Host done");
5500        (self, project, cx, result.err())
5501    }
5502
5503    pub async fn simulate_guest(
5504        mut self,
5505        guest_username: String,
5506        project: ModelHandle<Project>,
5507        op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
5508        rng: Arc<Mutex<StdRng>>,
5509        mut cx: TestAppContext,
5510    ) -> (
5511        Self,
5512        ModelHandle<Project>,
5513        TestAppContext,
5514        Option<anyhow::Error>,
5515    ) {
5516        async fn simulate_guest_internal(
5517            client: &mut TestClient,
5518            guest_username: &str,
5519            project: ModelHandle<Project>,
5520            mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
5521            rng: Arc<Mutex<StdRng>>,
5522            cx: &mut TestAppContext,
5523        ) -> anyhow::Result<()> {
5524            while op_start_signal.next().await.is_some() {
5525                let buffer = if client.buffers.is_empty() || rng.lock().gen() {
5526                    let worktree = if let Some(worktree) = project.read_with(cx, |project, cx| {
5527                        project
5528                            .worktrees(&cx)
5529                            .filter(|worktree| {
5530                                let worktree = worktree.read(cx);
5531                                worktree.is_visible()
5532                                    && worktree.entries(false).any(|e| e.is_file())
5533                            })
5534                            .choose(&mut *rng.lock())
5535                    }) {
5536                        worktree
5537                    } else {
5538                        cx.background().simulate_random_delay().await;
5539                        continue;
5540                    };
5541
5542                    let (worktree_root_name, project_path) =
5543                        worktree.read_with(cx, |worktree, _| {
5544                            let entry = worktree
5545                                .entries(false)
5546                                .filter(|e| e.is_file())
5547                                .choose(&mut *rng.lock())
5548                                .unwrap();
5549                            (
5550                                worktree.root_name().to_string(),
5551                                (worktree.id(), entry.path.clone()),
5552                            )
5553                        });
5554                    log::info!(
5555                        "{}: opening path {:?} in worktree {} ({})",
5556                        guest_username,
5557                        project_path.1,
5558                        project_path.0,
5559                        worktree_root_name,
5560                    );
5561                    let buffer = project
5562                        .update(cx, |project, cx| {
5563                            project.open_buffer(project_path.clone(), cx)
5564                        })
5565                        .await?;
5566                    log::info!(
5567                        "{}: opened path {:?} in worktree {} ({}) with buffer id {}",
5568                        guest_username,
5569                        project_path.1,
5570                        project_path.0,
5571                        worktree_root_name,
5572                        buffer.read_with(cx, |buffer, _| buffer.remote_id())
5573                    );
5574                    client.buffers.insert(buffer.clone());
5575                    buffer
5576                } else {
5577                    client
5578                        .buffers
5579                        .iter()
5580                        .choose(&mut *rng.lock())
5581                        .unwrap()
5582                        .clone()
5583                };
5584
5585                let choice = rng.lock().gen_range(0..100);
5586                match choice {
5587                    0..=9 => {
5588                        cx.update(|cx| {
5589                            log::info!(
5590                                "{}: dropping buffer {:?}",
5591                                guest_username,
5592                                buffer.read(cx).file().unwrap().full_path(cx)
5593                            );
5594                            client.buffers.remove(&buffer);
5595                            drop(buffer);
5596                        });
5597                    }
5598                    10..=19 => {
5599                        let completions = project.update(cx, |project, cx| {
5600                            log::info!(
5601                                "{}: requesting completions for buffer {} ({:?})",
5602                                guest_username,
5603                                buffer.read(cx).remote_id(),
5604                                buffer.read(cx).file().unwrap().full_path(cx)
5605                            );
5606                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
5607                            project.completions(&buffer, offset, cx)
5608                        });
5609                        let completions = cx.background().spawn(async move {
5610                            completions
5611                                .await
5612                                .map_err(|err| anyhow!("completions request failed: {:?}", err))
5613                        });
5614                        if rng.lock().gen_bool(0.3) {
5615                            log::info!("{}: detaching completions request", guest_username);
5616                            cx.update(|cx| completions.detach_and_log_err(cx));
5617                        } else {
5618                            completions.await?;
5619                        }
5620                    }
5621                    20..=29 => {
5622                        let code_actions = project.update(cx, |project, cx| {
5623                            log::info!(
5624                                "{}: requesting code actions for buffer {} ({:?})",
5625                                guest_username,
5626                                buffer.read(cx).remote_id(),
5627                                buffer.read(cx).file().unwrap().full_path(cx)
5628                            );
5629                            let range = buffer.read(cx).random_byte_range(0, &mut *rng.lock());
5630                            project.code_actions(&buffer, range, cx)
5631                        });
5632                        let code_actions = cx.background().spawn(async move {
5633                            code_actions
5634                                .await
5635                                .map_err(|err| anyhow!("code actions request failed: {:?}", err))
5636                        });
5637                        if rng.lock().gen_bool(0.3) {
5638                            log::info!("{}: detaching code actions request", guest_username);
5639                            cx.update(|cx| code_actions.detach_and_log_err(cx));
5640                        } else {
5641                            code_actions.await?;
5642                        }
5643                    }
5644                    30..=39 if buffer.read_with(cx, |buffer, _| buffer.is_dirty()) => {
5645                        let (requested_version, save) = buffer.update(cx, |buffer, cx| {
5646                            log::info!(
5647                                "{}: saving buffer {} ({:?})",
5648                                guest_username,
5649                                buffer.remote_id(),
5650                                buffer.file().unwrap().full_path(cx)
5651                            );
5652                            (buffer.version(), buffer.save(cx))
5653                        });
5654                        let save = cx.background().spawn(async move {
5655                            let (saved_version, _, _) = save
5656                                .await
5657                                .map_err(|err| anyhow!("save request failed: {:?}", err))?;
5658                            assert!(saved_version.observed_all(&requested_version));
5659                            Ok::<_, anyhow::Error>(())
5660                        });
5661                        if rng.lock().gen_bool(0.3) {
5662                            log::info!("{}: detaching save request", guest_username);
5663                            cx.update(|cx| save.detach_and_log_err(cx));
5664                        } else {
5665                            save.await?;
5666                        }
5667                    }
5668                    40..=44 => {
5669                        let prepare_rename = project.update(cx, |project, cx| {
5670                            log::info!(
5671                                "{}: preparing rename for buffer {} ({:?})",
5672                                guest_username,
5673                                buffer.read(cx).remote_id(),
5674                                buffer.read(cx).file().unwrap().full_path(cx)
5675                            );
5676                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
5677                            project.prepare_rename(buffer, offset, cx)
5678                        });
5679                        let prepare_rename = cx.background().spawn(async move {
5680                            prepare_rename
5681                                .await
5682                                .map_err(|err| anyhow!("prepare rename request failed: {:?}", err))
5683                        });
5684                        if rng.lock().gen_bool(0.3) {
5685                            log::info!("{}: detaching prepare rename request", guest_username);
5686                            cx.update(|cx| prepare_rename.detach_and_log_err(cx));
5687                        } else {
5688                            prepare_rename.await?;
5689                        }
5690                    }
5691                    45..=49 => {
5692                        let definitions = project.update(cx, |project, cx| {
5693                            log::info!(
5694                                "{}: requesting definitions for buffer {} ({:?})",
5695                                guest_username,
5696                                buffer.read(cx).remote_id(),
5697                                buffer.read(cx).file().unwrap().full_path(cx)
5698                            );
5699                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
5700                            project.definition(&buffer, offset, cx)
5701                        });
5702                        let definitions = cx.background().spawn(async move {
5703                            definitions
5704                                .await
5705                                .map_err(|err| anyhow!("definitions request failed: {:?}", err))
5706                        });
5707                        if rng.lock().gen_bool(0.3) {
5708                            log::info!("{}: detaching definitions request", guest_username);
5709                            cx.update(|cx| definitions.detach_and_log_err(cx));
5710                        } else {
5711                            client.buffers.extend(
5712                                definitions.await?.into_iter().map(|loc| loc.target.buffer),
5713                            );
5714                        }
5715                    }
5716                    50..=54 => {
5717                        let highlights = project.update(cx, |project, cx| {
5718                            log::info!(
5719                                "{}: requesting highlights for buffer {} ({:?})",
5720                                guest_username,
5721                                buffer.read(cx).remote_id(),
5722                                buffer.read(cx).file().unwrap().full_path(cx)
5723                            );
5724                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
5725                            project.document_highlights(&buffer, offset, cx)
5726                        });
5727                        let highlights = cx.background().spawn(async move {
5728                            highlights
5729                                .await
5730                                .map_err(|err| anyhow!("highlights request failed: {:?}", err))
5731                        });
5732                        if rng.lock().gen_bool(0.3) {
5733                            log::info!("{}: detaching highlights request", guest_username);
5734                            cx.update(|cx| highlights.detach_and_log_err(cx));
5735                        } else {
5736                            highlights.await?;
5737                        }
5738                    }
5739                    55..=59 => {
5740                        let search = project.update(cx, |project, cx| {
5741                            let query = rng.lock().gen_range('a'..='z');
5742                            log::info!("{}: project-wide search {:?}", guest_username, query);
5743                            project.search(SearchQuery::text(query, false, false), cx)
5744                        });
5745                        let search = cx.background().spawn(async move {
5746                            search
5747                                .await
5748                                .map_err(|err| anyhow!("search request failed: {:?}", err))
5749                        });
5750                        if rng.lock().gen_bool(0.3) {
5751                            log::info!("{}: detaching search request", guest_username);
5752                            cx.update(|cx| search.detach_and_log_err(cx));
5753                        } else {
5754                            client.buffers.extend(search.await?.into_keys());
5755                        }
5756                    }
5757                    60..=69 => {
5758                        let worktree = project
5759                            .read_with(cx, |project, cx| {
5760                                project
5761                                    .worktrees(&cx)
5762                                    .filter(|worktree| {
5763                                        let worktree = worktree.read(cx);
5764                                        worktree.is_visible()
5765                                            && worktree.entries(false).any(|e| e.is_file())
5766                                            && worktree.root_entry().map_or(false, |e| e.is_dir())
5767                                    })
5768                                    .choose(&mut *rng.lock())
5769                            })
5770                            .unwrap();
5771                        let (worktree_id, worktree_root_name) = worktree
5772                            .read_with(cx, |worktree, _| {
5773                                (worktree.id(), worktree.root_name().to_string())
5774                            });
5775
5776                        let mut new_name = String::new();
5777                        for _ in 0..10 {
5778                            let letter = rng.lock().gen_range('a'..='z');
5779                            new_name.push(letter);
5780                        }
5781                        let mut new_path = PathBuf::new();
5782                        new_path.push(new_name);
5783                        new_path.set_extension("rs");
5784                        log::info!(
5785                            "{}: creating {:?} in worktree {} ({})",
5786                            guest_username,
5787                            new_path,
5788                            worktree_id,
5789                            worktree_root_name,
5790                        );
5791                        project
5792                            .update(cx, |project, cx| {
5793                                project.create_entry((worktree_id, new_path), false, cx)
5794                            })
5795                            .unwrap()
5796                            .await?;
5797                    }
5798                    _ => {
5799                        buffer.update(cx, |buffer, cx| {
5800                            log::info!(
5801                                "{}: updating buffer {} ({:?})",
5802                                guest_username,
5803                                buffer.remote_id(),
5804                                buffer.file().unwrap().full_path(cx)
5805                            );
5806                            if rng.lock().gen_bool(0.7) {
5807                                buffer.randomly_edit(&mut *rng.lock(), 5, cx);
5808                            } else {
5809                                buffer.randomly_undo_redo(&mut *rng.lock(), cx);
5810                            }
5811                        });
5812                    }
5813                }
5814                cx.background().simulate_random_delay().await;
5815            }
5816            Ok(())
5817        }
5818
5819        let result = simulate_guest_internal(
5820            &mut self,
5821            &guest_username,
5822            project.clone(),
5823            op_start_signal,
5824            rng,
5825            &mut cx,
5826        )
5827        .await;
5828        log::info!("{}: done", guest_username);
5829
5830        (self, project, cx, result.err())
5831    }
5832}
5833
5834impl Drop for TestClient {
5835    fn drop(&mut self) {
5836        self.client.tear_down();
5837    }
5838}
5839
5840impl Executor for Arc<gpui::executor::Background> {
5841    type Sleep = gpui::executor::Timer;
5842
5843    fn spawn_detached<F: 'static + Send + Future<Output = ()>>(&self, future: F) {
5844        self.spawn(future).detach();
5845    }
5846
5847    fn sleep(&self, duration: Duration) -> Self::Sleep {
5848        self.as_ref().timer(duration)
5849    }
5850}
5851
5852fn channel_messages(channel: &Channel) -> Vec<(String, String, bool)> {
5853    channel
5854        .messages()
5855        .cursor::<()>()
5856        .map(|m| {
5857            (
5858                m.sender.github_login.clone(),
5859                m.body.clone(),
5860                m.is_pending(),
5861            )
5862        })
5863        .collect()
5864}
5865
5866struct EmptyView;
5867
5868impl gpui::Entity for EmptyView {
5869    type Event = ();
5870}
5871
5872impl gpui::View for EmptyView {
5873    fn ui_name() -> &'static str {
5874        "empty view"
5875    }
5876
5877    fn render(&mut self, _: &mut gpui::RenderContext<Self>) -> gpui::ElementBox {
5878        gpui::Element::boxed(gpui::elements::Empty::new())
5879    }
5880}