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()).await;
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
1710        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1711            capabilities: lsp::ServerCapabilities {
1712                completion_provider: Some(lsp::CompletionOptions {
1713                    trigger_characters: Some(vec![".".to_string()]),
1714                    ..Default::default()
1715                }),
1716                ..Default::default()
1717            },
1718            ..Default::default()
1719        }))
1720        .await;
1721    client_a.language_registry.add(Arc::new(language));
1722
1723    client_a
1724        .fs
1725        .insert_tree(
1726            "/a",
1727            json!({
1728                "main.rs": "fn main() { a }",
1729                "other.rs": "",
1730            }),
1731        )
1732        .await;
1733    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1734    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1735
1736    // Open a file in an editor as the guest.
1737    let buffer_b = project_b
1738        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1739        .await
1740        .unwrap();
1741    let (window_b, _) = cx_b.add_window(|_| EmptyView);
1742    let editor_b = cx_b.add_view(window_b, |cx| {
1743        Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
1744    });
1745
1746    let fake_language_server = fake_language_servers.next().await.unwrap();
1747    buffer_b
1748        .condition(&cx_b, |buffer, _| !buffer.completion_triggers().is_empty())
1749        .await;
1750
1751    // Type a completion trigger character as the guest.
1752    editor_b.update(cx_b, |editor, cx| {
1753        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1754        editor.handle_input(&Input(".".into()), cx);
1755        cx.focus(&editor_b);
1756    });
1757
1758    // Receive a completion request as the host's language server.
1759    // Return some completions from the host's language server.
1760    cx_a.foreground().start_waiting();
1761    fake_language_server
1762        .handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
1763            assert_eq!(
1764                params.text_document_position.text_document.uri,
1765                lsp::Url::from_file_path("/a/main.rs").unwrap(),
1766            );
1767            assert_eq!(
1768                params.text_document_position.position,
1769                lsp::Position::new(0, 14),
1770            );
1771
1772            Ok(Some(lsp::CompletionResponse::Array(vec![
1773                lsp::CompletionItem {
1774                    label: "first_method(…)".into(),
1775                    detail: Some("fn(&mut self, B) -> C".into()),
1776                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1777                        new_text: "first_method($1)".to_string(),
1778                        range: lsp::Range::new(
1779                            lsp::Position::new(0, 14),
1780                            lsp::Position::new(0, 14),
1781                        ),
1782                    })),
1783                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1784                    ..Default::default()
1785                },
1786                lsp::CompletionItem {
1787                    label: "second_method(…)".into(),
1788                    detail: Some("fn(&mut self, C) -> D<E>".into()),
1789                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1790                        new_text: "second_method()".to_string(),
1791                        range: lsp::Range::new(
1792                            lsp::Position::new(0, 14),
1793                            lsp::Position::new(0, 14),
1794                        ),
1795                    })),
1796                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1797                    ..Default::default()
1798                },
1799            ])))
1800        })
1801        .next()
1802        .await
1803        .unwrap();
1804    cx_a.foreground().finish_waiting();
1805
1806    // Open the buffer on the host.
1807    let buffer_a = project_a
1808        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1809        .await
1810        .unwrap();
1811    buffer_a
1812        .condition(&cx_a, |buffer, _| buffer.text() == "fn main() { a. }")
1813        .await;
1814
1815    // Confirm a completion on the guest.
1816    editor_b
1817        .condition(&cx_b, |editor, _| editor.context_menu_visible())
1818        .await;
1819    editor_b.update(cx_b, |editor, cx| {
1820        editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
1821        assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
1822    });
1823
1824    // Return a resolved completion from the host's language server.
1825    // The resolved completion has an additional text edit.
1826    fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
1827        |params, _| async move {
1828            assert_eq!(params.label, "first_method(…)");
1829            Ok(lsp::CompletionItem {
1830                label: "first_method(…)".into(),
1831                detail: Some("fn(&mut self, B) -> C".into()),
1832                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1833                    new_text: "first_method($1)".to_string(),
1834                    range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1835                })),
1836                additional_text_edits: Some(vec![lsp::TextEdit {
1837                    new_text: "use d::SomeTrait;\n".to_string(),
1838                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
1839                }]),
1840                insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1841                ..Default::default()
1842            })
1843        },
1844    );
1845
1846    // The additional edit is applied.
1847    buffer_a
1848        .condition(&cx_a, |buffer, _| {
1849            buffer.text() == "use d::SomeTrait;\nfn main() { a.first_method() }"
1850        })
1851        .await;
1852    buffer_b
1853        .condition(&cx_b, |buffer, _| {
1854            buffer.text() == "use d::SomeTrait;\nfn main() { a.first_method() }"
1855        })
1856        .await;
1857}
1858
1859#[gpui::test(iterations = 10)]
1860async fn test_reloading_buffer_manually(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1861    cx_a.foreground().forbid_parking();
1862    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1863    let client_a = server.create_client(cx_a, "user_a").await;
1864    let client_b = server.create_client(cx_b, "user_b").await;
1865    server
1866        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1867        .await;
1868
1869    client_a
1870        .fs
1871        .insert_tree("/a", json!({ "a.rs": "let one = 1;" }))
1872        .await;
1873    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1874    let buffer_a = project_a
1875        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
1876        .await
1877        .unwrap();
1878
1879    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1880
1881    let buffer_b = cx_b
1882        .background()
1883        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
1884        .await
1885        .unwrap();
1886    buffer_b.update(cx_b, |buffer, cx| {
1887        buffer.edit([(4..7, "six")], cx);
1888        buffer.edit([(10..11, "6")], cx);
1889        assert_eq!(buffer.text(), "let six = 6;");
1890        assert!(buffer.is_dirty());
1891        assert!(!buffer.has_conflict());
1892    });
1893    buffer_a
1894        .condition(cx_a, |buffer, _| buffer.text() == "let six = 6;")
1895        .await;
1896
1897    client_a
1898        .fs
1899        .save(
1900            "/a/a.rs".as_ref(),
1901            &Rope::from("let seven = 7;"),
1902            LineEnding::Unix,
1903        )
1904        .await
1905        .unwrap();
1906    buffer_a
1907        .condition(cx_a, |buffer, _| buffer.has_conflict())
1908        .await;
1909    buffer_b
1910        .condition(cx_b, |buffer, _| buffer.has_conflict())
1911        .await;
1912
1913    project_b
1914        .update(cx_b, |project, cx| {
1915            project.reload_buffers(HashSet::from_iter([buffer_b.clone()]), true, cx)
1916        })
1917        .await
1918        .unwrap();
1919    buffer_a.read_with(cx_a, |buffer, _| {
1920        assert_eq!(buffer.text(), "let seven = 7;");
1921        assert!(!buffer.is_dirty());
1922        assert!(!buffer.has_conflict());
1923    });
1924    buffer_b.read_with(cx_b, |buffer, _| {
1925        assert_eq!(buffer.text(), "let seven = 7;");
1926        assert!(!buffer.is_dirty());
1927        assert!(!buffer.has_conflict());
1928    });
1929
1930    buffer_a.update(cx_a, |buffer, cx| {
1931        // Undoing on the host is a no-op when the reload was initiated by the guest.
1932        buffer.undo(cx);
1933        assert_eq!(buffer.text(), "let seven = 7;");
1934        assert!(!buffer.is_dirty());
1935        assert!(!buffer.has_conflict());
1936    });
1937    buffer_b.update(cx_b, |buffer, cx| {
1938        // Undoing on the guest rolls back the buffer to before it was reloaded but the conflict gets cleared.
1939        buffer.undo(cx);
1940        assert_eq!(buffer.text(), "let six = 6;");
1941        assert!(buffer.is_dirty());
1942        assert!(!buffer.has_conflict());
1943    });
1944}
1945
1946#[gpui::test(iterations = 10)]
1947async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1948    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1949    let client_a = server.create_client(cx_a, "user_a").await;
1950    let client_b = server.create_client(cx_b, "user_b").await;
1951    server
1952        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1953        .await;
1954
1955    // Set up a fake language server.
1956    let mut language = Language::new(
1957        LanguageConfig {
1958            name: "Rust".into(),
1959            path_suffixes: vec!["rs".to_string()],
1960            ..Default::default()
1961        },
1962        Some(tree_sitter_rust::language()),
1963    );
1964    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
1965    client_a.language_registry.add(Arc::new(language));
1966
1967    // Here we insert a fake tree with a directory that exists on disk. This is needed
1968    // because later we'll invoke a command, which requires passing a working directory
1969    // that points to a valid location on disk.
1970    let directory = env::current_dir().unwrap();
1971    client_a
1972        .fs
1973        .insert_tree(&directory, json!({ "a.rs": "let one = \"two\"" }))
1974        .await;
1975    let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
1976    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1977
1978    let buffer_b = cx_b
1979        .background()
1980        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
1981        .await
1982        .unwrap();
1983
1984    let fake_language_server = fake_language_servers.next().await.unwrap();
1985    fake_language_server.handle_request::<lsp::request::Formatting, _, _>(|_, _| async move {
1986        Ok(Some(vec![
1987            lsp::TextEdit {
1988                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
1989                new_text: "h".to_string(),
1990            },
1991            lsp::TextEdit {
1992                range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
1993                new_text: "y".to_string(),
1994            },
1995        ]))
1996    });
1997
1998    project_b
1999        .update(cx_b, |project, cx| {
2000            project.format(HashSet::from_iter([buffer_b.clone()]), true, cx)
2001        })
2002        .await
2003        .unwrap();
2004    assert_eq!(
2005        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
2006        "let honey = \"two\""
2007    );
2008
2009    // Ensure buffer can be formatted using an external command. Notice how the
2010    // host's configuration is honored as opposed to using the guest's settings.
2011    cx_a.update(|cx| {
2012        cx.update_global(|settings: &mut Settings, _| {
2013            settings.editor_defaults.format_on_save = Some(FormatOnSave::External {
2014                command: "awk".to_string(),
2015                arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()],
2016            });
2017        });
2018    });
2019    project_b
2020        .update(cx_b, |project, cx| {
2021            project.format(HashSet::from_iter([buffer_b.clone()]), true, cx)
2022        })
2023        .await
2024        .unwrap();
2025    assert_eq!(
2026        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
2027        format!("let honey = \"{}/a.rs\"\n", directory.to_str().unwrap())
2028    );
2029}
2030
2031#[gpui::test(iterations = 10)]
2032async fn test_definition(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2033    cx_a.foreground().forbid_parking();
2034    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2035    let client_a = server.create_client(cx_a, "user_a").await;
2036    let client_b = server.create_client(cx_b, "user_b").await;
2037    server
2038        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2039        .await;
2040
2041    // Set up a fake language server.
2042    let mut language = Language::new(
2043        LanguageConfig {
2044            name: "Rust".into(),
2045            path_suffixes: vec!["rs".to_string()],
2046            ..Default::default()
2047        },
2048        Some(tree_sitter_rust::language()),
2049    );
2050    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2051    client_a.language_registry.add(Arc::new(language));
2052
2053    client_a
2054        .fs
2055        .insert_tree(
2056            "/root",
2057            json!({
2058                "dir-1": {
2059                    "a.rs": "const ONE: usize = b::TWO + b::THREE;",
2060                },
2061                "dir-2": {
2062                    "b.rs": "const TWO: usize = 2;\nconst THREE: usize = 3;",
2063                }
2064            }),
2065        )
2066        .await;
2067    let (project_a, worktree_id) = client_a.build_local_project("/root/dir-1", cx_a).await;
2068    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2069
2070    // Open the file on client B.
2071    let buffer_b = cx_b
2072        .background()
2073        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
2074        .await
2075        .unwrap();
2076
2077    // Request the definition of a symbol as the guest.
2078    let fake_language_server = fake_language_servers.next().await.unwrap();
2079    fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
2080        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
2081            lsp::Location::new(
2082                lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
2083                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
2084            ),
2085        )))
2086    });
2087
2088    let definitions_1 = project_b
2089        .update(cx_b, |p, cx| p.definition(&buffer_b, 23, cx))
2090        .await
2091        .unwrap();
2092    cx_b.read(|cx| {
2093        assert_eq!(definitions_1.len(), 1);
2094        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
2095        let target_buffer = definitions_1[0].target.buffer.read(cx);
2096        assert_eq!(
2097            target_buffer.text(),
2098            "const TWO: usize = 2;\nconst THREE: usize = 3;"
2099        );
2100        assert_eq!(
2101            definitions_1[0].target.range.to_point(target_buffer),
2102            Point::new(0, 6)..Point::new(0, 9)
2103        );
2104    });
2105
2106    // Try getting more definitions for the same buffer, ensuring the buffer gets reused from
2107    // the previous call to `definition`.
2108    fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
2109        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
2110            lsp::Location::new(
2111                lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
2112                lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
2113            ),
2114        )))
2115    });
2116
2117    let definitions_2 = project_b
2118        .update(cx_b, |p, cx| p.definition(&buffer_b, 33, cx))
2119        .await
2120        .unwrap();
2121    cx_b.read(|cx| {
2122        assert_eq!(definitions_2.len(), 1);
2123        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
2124        let target_buffer = definitions_2[0].target.buffer.read(cx);
2125        assert_eq!(
2126            target_buffer.text(),
2127            "const TWO: usize = 2;\nconst THREE: usize = 3;"
2128        );
2129        assert_eq!(
2130            definitions_2[0].target.range.to_point(target_buffer),
2131            Point::new(1, 6)..Point::new(1, 11)
2132        );
2133    });
2134    assert_eq!(
2135        definitions_1[0].target.buffer,
2136        definitions_2[0].target.buffer
2137    );
2138}
2139
2140#[gpui::test(iterations = 10)]
2141async fn test_references(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2142    cx_a.foreground().forbid_parking();
2143    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2144    let client_a = server.create_client(cx_a, "user_a").await;
2145    let client_b = server.create_client(cx_b, "user_b").await;
2146    server
2147        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2148        .await;
2149
2150    // Set up a fake language server.
2151    let mut language = Language::new(
2152        LanguageConfig {
2153            name: "Rust".into(),
2154            path_suffixes: vec!["rs".to_string()],
2155            ..Default::default()
2156        },
2157        Some(tree_sitter_rust::language()),
2158    );
2159    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2160    client_a.language_registry.add(Arc::new(language));
2161
2162    client_a
2163        .fs
2164        .insert_tree(
2165            "/root",
2166            json!({
2167                "dir-1": {
2168                    "one.rs": "const ONE: usize = 1;",
2169                    "two.rs": "const TWO: usize = one::ONE + one::ONE;",
2170                },
2171                "dir-2": {
2172                    "three.rs": "const THREE: usize = two::TWO + one::ONE;",
2173                }
2174            }),
2175        )
2176        .await;
2177    let (project_a, worktree_id) = client_a.build_local_project("/root/dir-1", cx_a).await;
2178    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2179
2180    // Open the file on client B.
2181    let buffer_b = cx_b
2182        .background()
2183        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx)))
2184        .await
2185        .unwrap();
2186
2187    // Request references to a symbol as the guest.
2188    let fake_language_server = fake_language_servers.next().await.unwrap();
2189    fake_language_server.handle_request::<lsp::request::References, _, _>(|params, _| async move {
2190        assert_eq!(
2191            params.text_document_position.text_document.uri.as_str(),
2192            "file:///root/dir-1/one.rs"
2193        );
2194        Ok(Some(vec![
2195            lsp::Location {
2196                uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
2197                range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
2198            },
2199            lsp::Location {
2200                uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
2201                range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
2202            },
2203            lsp::Location {
2204                uri: lsp::Url::from_file_path("/root/dir-2/three.rs").unwrap(),
2205                range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
2206            },
2207        ]))
2208    });
2209
2210    let references = project_b
2211        .update(cx_b, |p, cx| p.references(&buffer_b, 7, cx))
2212        .await
2213        .unwrap();
2214    cx_b.read(|cx| {
2215        assert_eq!(references.len(), 3);
2216        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
2217
2218        let two_buffer = references[0].buffer.read(cx);
2219        let three_buffer = references[2].buffer.read(cx);
2220        assert_eq!(
2221            two_buffer.file().unwrap().path().as_ref(),
2222            Path::new("two.rs")
2223        );
2224        assert_eq!(references[1].buffer, references[0].buffer);
2225        assert_eq!(
2226            three_buffer.file().unwrap().full_path(cx),
2227            Path::new("three.rs")
2228        );
2229
2230        assert_eq!(references[0].range.to_offset(&two_buffer), 24..27);
2231        assert_eq!(references[1].range.to_offset(&two_buffer), 35..38);
2232        assert_eq!(references[2].range.to_offset(&three_buffer), 37..40);
2233    });
2234}
2235
2236#[gpui::test(iterations = 10)]
2237async fn test_project_search(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2238    cx_a.foreground().forbid_parking();
2239    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2240    let client_a = server.create_client(cx_a, "user_a").await;
2241    let client_b = server.create_client(cx_b, "user_b").await;
2242    server
2243        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2244        .await;
2245
2246    client_a
2247        .fs
2248        .insert_tree(
2249            "/root",
2250            json!({
2251                "dir-1": {
2252                    "a": "hello world",
2253                    "b": "goodnight moon",
2254                    "c": "a world of goo",
2255                    "d": "world champion of clown world",
2256                },
2257                "dir-2": {
2258                    "e": "disney world is fun",
2259                }
2260            }),
2261        )
2262        .await;
2263    let (project_a, _) = client_a.build_local_project("/root/dir-1", cx_a).await;
2264    let (worktree_2, _) = project_a
2265        .update(cx_a, |p, cx| {
2266            p.find_or_create_local_worktree("/root/dir-2", true, cx)
2267        })
2268        .await
2269        .unwrap();
2270    worktree_2
2271        .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
2272        .await;
2273
2274    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2275
2276    // Perform a search as the guest.
2277    let results = project_b
2278        .update(cx_b, |project, cx| {
2279            project.search(SearchQuery::text("world", false, false), cx)
2280        })
2281        .await
2282        .unwrap();
2283
2284    let mut ranges_by_path = results
2285        .into_iter()
2286        .map(|(buffer, ranges)| {
2287            buffer.read_with(cx_b, |buffer, cx| {
2288                let path = buffer.file().unwrap().full_path(cx);
2289                let offset_ranges = ranges
2290                    .into_iter()
2291                    .map(|range| range.to_offset(buffer))
2292                    .collect::<Vec<_>>();
2293                (path, offset_ranges)
2294            })
2295        })
2296        .collect::<Vec<_>>();
2297    ranges_by_path.sort_by_key(|(path, _)| path.clone());
2298
2299    assert_eq!(
2300        ranges_by_path,
2301        &[
2302            (PathBuf::from("dir-1/a"), vec![6..11]),
2303            (PathBuf::from("dir-1/c"), vec![2..7]),
2304            (PathBuf::from("dir-1/d"), vec![0..5, 24..29]),
2305            (PathBuf::from("dir-2/e"), vec![7..12]),
2306        ]
2307    );
2308}
2309
2310#[gpui::test(iterations = 10)]
2311async fn test_document_highlights(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2312    cx_a.foreground().forbid_parking();
2313    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2314    let client_a = server.create_client(cx_a, "user_a").await;
2315    let client_b = server.create_client(cx_b, "user_b").await;
2316    server
2317        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2318        .await;
2319
2320    client_a
2321        .fs
2322        .insert_tree(
2323            "/root-1",
2324            json!({
2325                "main.rs": "fn double(number: i32) -> i32 { number + number }",
2326            }),
2327        )
2328        .await;
2329
2330    // Set up a fake language server.
2331    let mut language = Language::new(
2332        LanguageConfig {
2333            name: "Rust".into(),
2334            path_suffixes: vec!["rs".to_string()],
2335            ..Default::default()
2336        },
2337        Some(tree_sitter_rust::language()),
2338    );
2339    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2340    client_a.language_registry.add(Arc::new(language));
2341
2342    let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
2343    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2344
2345    // Open the file on client B.
2346    let buffer_b = cx_b
2347        .background()
2348        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)))
2349        .await
2350        .unwrap();
2351
2352    // Request document highlights as the guest.
2353    let fake_language_server = fake_language_servers.next().await.unwrap();
2354    fake_language_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>(
2355        |params, _| async move {
2356            assert_eq!(
2357                params
2358                    .text_document_position_params
2359                    .text_document
2360                    .uri
2361                    .as_str(),
2362                "file:///root-1/main.rs"
2363            );
2364            assert_eq!(
2365                params.text_document_position_params.position,
2366                lsp::Position::new(0, 34)
2367            );
2368            Ok(Some(vec![
2369                lsp::DocumentHighlight {
2370                    kind: Some(lsp::DocumentHighlightKind::WRITE),
2371                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 16)),
2372                },
2373                lsp::DocumentHighlight {
2374                    kind: Some(lsp::DocumentHighlightKind::READ),
2375                    range: lsp::Range::new(lsp::Position::new(0, 32), lsp::Position::new(0, 38)),
2376                },
2377                lsp::DocumentHighlight {
2378                    kind: Some(lsp::DocumentHighlightKind::READ),
2379                    range: lsp::Range::new(lsp::Position::new(0, 41), lsp::Position::new(0, 47)),
2380                },
2381            ]))
2382        },
2383    );
2384
2385    let highlights = project_b
2386        .update(cx_b, |p, cx| p.document_highlights(&buffer_b, 34, cx))
2387        .await
2388        .unwrap();
2389    buffer_b.read_with(cx_b, |buffer, _| {
2390        let snapshot = buffer.snapshot();
2391
2392        let highlights = highlights
2393            .into_iter()
2394            .map(|highlight| (highlight.kind, highlight.range.to_offset(&snapshot)))
2395            .collect::<Vec<_>>();
2396        assert_eq!(
2397            highlights,
2398            &[
2399                (lsp::DocumentHighlightKind::WRITE, 10..16),
2400                (lsp::DocumentHighlightKind::READ, 32..38),
2401                (lsp::DocumentHighlightKind::READ, 41..47)
2402            ]
2403        )
2404    });
2405}
2406
2407#[gpui::test(iterations = 10)]
2408async fn test_lsp_hover(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2409    cx_a.foreground().forbid_parking();
2410    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2411    let client_a = server.create_client(cx_a, "user_a").await;
2412    let client_b = server.create_client(cx_b, "user_b").await;
2413    server
2414        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2415        .await;
2416
2417    client_a
2418        .fs
2419        .insert_tree(
2420            "/root-1",
2421            json!({
2422                "main.rs": "use std::collections::HashMap;",
2423            }),
2424        )
2425        .await;
2426
2427    // Set up a fake language server.
2428    let mut language = Language::new(
2429        LanguageConfig {
2430            name: "Rust".into(),
2431            path_suffixes: vec!["rs".to_string()],
2432            ..Default::default()
2433        },
2434        Some(tree_sitter_rust::language()),
2435    );
2436    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2437    client_a.language_registry.add(Arc::new(language));
2438
2439    let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
2440    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2441
2442    // Open the file as the guest
2443    let buffer_b = cx_b
2444        .background()
2445        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)))
2446        .await
2447        .unwrap();
2448
2449    // Request hover information as the guest.
2450    let fake_language_server = fake_language_servers.next().await.unwrap();
2451    fake_language_server.handle_request::<lsp::request::HoverRequest, _, _>(
2452        |params, _| async move {
2453            assert_eq!(
2454                params
2455                    .text_document_position_params
2456                    .text_document
2457                    .uri
2458                    .as_str(),
2459                "file:///root-1/main.rs"
2460            );
2461            assert_eq!(
2462                params.text_document_position_params.position,
2463                lsp::Position::new(0, 22)
2464            );
2465            Ok(Some(lsp::Hover {
2466                contents: lsp::HoverContents::Array(vec![
2467                    lsp::MarkedString::String("Test hover content.".to_string()),
2468                    lsp::MarkedString::LanguageString(lsp::LanguageString {
2469                        language: "Rust".to_string(),
2470                        value: "let foo = 42;".to_string(),
2471                    }),
2472                ]),
2473                range: Some(lsp::Range::new(
2474                    lsp::Position::new(0, 22),
2475                    lsp::Position::new(0, 29),
2476                )),
2477            }))
2478        },
2479    );
2480
2481    let hover_info = project_b
2482        .update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
2483        .await
2484        .unwrap()
2485        .unwrap();
2486    buffer_b.read_with(cx_b, |buffer, _| {
2487        let snapshot = buffer.snapshot();
2488        assert_eq!(hover_info.range.unwrap().to_offset(&snapshot), 22..29);
2489        assert_eq!(
2490            hover_info.contents,
2491            vec![
2492                project::HoverBlock {
2493                    text: "Test hover content.".to_string(),
2494                    language: None,
2495                },
2496                project::HoverBlock {
2497                    text: "let foo = 42;".to_string(),
2498                    language: Some("Rust".to_string()),
2499                }
2500            ]
2501        );
2502    });
2503}
2504
2505#[gpui::test(iterations = 10)]
2506async fn test_project_symbols(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2507    cx_a.foreground().forbid_parking();
2508    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2509    let client_a = server.create_client(cx_a, "user_a").await;
2510    let client_b = server.create_client(cx_b, "user_b").await;
2511    server
2512        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2513        .await;
2514
2515    // Set up a fake language server.
2516    let mut language = Language::new(
2517        LanguageConfig {
2518            name: "Rust".into(),
2519            path_suffixes: vec!["rs".to_string()],
2520            ..Default::default()
2521        },
2522        Some(tree_sitter_rust::language()),
2523    );
2524    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2525    client_a.language_registry.add(Arc::new(language));
2526
2527    client_a
2528        .fs
2529        .insert_tree(
2530            "/code",
2531            json!({
2532                "crate-1": {
2533                    "one.rs": "const ONE: usize = 1;",
2534                },
2535                "crate-2": {
2536                    "two.rs": "const TWO: usize = 2; const THREE: usize = 3;",
2537                },
2538                "private": {
2539                    "passwords.txt": "the-password",
2540                }
2541            }),
2542        )
2543        .await;
2544    let (project_a, worktree_id) = client_a.build_local_project("/code/crate-1", cx_a).await;
2545    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2546
2547    // Cause the language server to start.
2548    let _buffer = cx_b
2549        .background()
2550        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx)))
2551        .await
2552        .unwrap();
2553
2554    let fake_language_server = fake_language_servers.next().await.unwrap();
2555    fake_language_server.handle_request::<lsp::request::WorkspaceSymbol, _, _>(|_, _| async move {
2556        #[allow(deprecated)]
2557        Ok(Some(vec![lsp::SymbolInformation {
2558            name: "TWO".into(),
2559            location: lsp::Location {
2560                uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
2561                range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
2562            },
2563            kind: lsp::SymbolKind::CONSTANT,
2564            tags: None,
2565            container_name: None,
2566            deprecated: None,
2567        }]))
2568    });
2569
2570    // Request the definition of a symbol as the guest.
2571    let symbols = project_b
2572        .update(cx_b, |p, cx| p.symbols("two", cx))
2573        .await
2574        .unwrap();
2575    assert_eq!(symbols.len(), 1);
2576    assert_eq!(symbols[0].name, "TWO");
2577
2578    // Open one of the returned symbols.
2579    let buffer_b_2 = project_b
2580        .update(cx_b, |project, cx| {
2581            project.open_buffer_for_symbol(&symbols[0], cx)
2582        })
2583        .await
2584        .unwrap();
2585    buffer_b_2.read_with(cx_b, |buffer, _| {
2586        assert_eq!(
2587            buffer.file().unwrap().path().as_ref(),
2588            Path::new("../crate-2/two.rs")
2589        );
2590    });
2591
2592    // Attempt to craft a symbol and violate host's privacy by opening an arbitrary file.
2593    let mut fake_symbol = symbols[0].clone();
2594    fake_symbol.path = Path::new("/code/secrets").into();
2595    let error = project_b
2596        .update(cx_b, |project, cx| {
2597            project.open_buffer_for_symbol(&fake_symbol, cx)
2598        })
2599        .await
2600        .unwrap_err();
2601    assert!(error.to_string().contains("invalid symbol signature"));
2602}
2603
2604#[gpui::test(iterations = 10)]
2605async fn test_open_buffer_while_getting_definition_pointing_to_it(
2606    cx_a: &mut TestAppContext,
2607    cx_b: &mut TestAppContext,
2608    mut rng: StdRng,
2609) {
2610    cx_a.foreground().forbid_parking();
2611    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2612    let client_a = server.create_client(cx_a, "user_a").await;
2613    let client_b = server.create_client(cx_b, "user_b").await;
2614    server
2615        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2616        .await;
2617
2618    // Set up a fake language server.
2619    let mut language = Language::new(
2620        LanguageConfig {
2621            name: "Rust".into(),
2622            path_suffixes: vec!["rs".to_string()],
2623            ..Default::default()
2624        },
2625        Some(tree_sitter_rust::language()),
2626    );
2627    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2628    client_a.language_registry.add(Arc::new(language));
2629
2630    client_a
2631        .fs
2632        .insert_tree(
2633            "/root",
2634            json!({
2635                "a.rs": "const ONE: usize = b::TWO;",
2636                "b.rs": "const TWO: usize = 2",
2637            }),
2638        )
2639        .await;
2640    let (project_a, worktree_id) = client_a.build_local_project("/root", cx_a).await;
2641    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2642
2643    let buffer_b1 = cx_b
2644        .background()
2645        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
2646        .await
2647        .unwrap();
2648
2649    let fake_language_server = fake_language_servers.next().await.unwrap();
2650    fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
2651        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
2652            lsp::Location::new(
2653                lsp::Url::from_file_path("/root/b.rs").unwrap(),
2654                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
2655            ),
2656        )))
2657    });
2658
2659    let definitions;
2660    let buffer_b2;
2661    if rng.gen() {
2662        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
2663        buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
2664    } else {
2665        buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
2666        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
2667    }
2668
2669    let buffer_b2 = buffer_b2.await.unwrap();
2670    let definitions = definitions.await.unwrap();
2671    assert_eq!(definitions.len(), 1);
2672    assert_eq!(definitions[0].target.buffer, buffer_b2);
2673}
2674
2675#[gpui::test(iterations = 10)]
2676async fn test_collaborating_with_code_actions(
2677    cx_a: &mut TestAppContext,
2678    cx_b: &mut TestAppContext,
2679) {
2680    cx_a.foreground().forbid_parking();
2681    cx_b.update(|cx| editor::init(cx));
2682    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2683    let client_a = server.create_client(cx_a, "user_a").await;
2684    let client_b = server.create_client(cx_b, "user_b").await;
2685    server
2686        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2687        .await;
2688
2689    // Set up a fake language server.
2690    let mut language = Language::new(
2691        LanguageConfig {
2692            name: "Rust".into(),
2693            path_suffixes: vec!["rs".to_string()],
2694            ..Default::default()
2695        },
2696        Some(tree_sitter_rust::language()),
2697    );
2698    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2699    client_a.language_registry.add(Arc::new(language));
2700
2701    client_a
2702        .fs
2703        .insert_tree(
2704            "/a",
2705            json!({
2706                "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
2707                "other.rs": "pub fn foo() -> usize { 4 }",
2708            }),
2709        )
2710        .await;
2711    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
2712
2713    // Join the project as client B.
2714    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2715    let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(project_b.clone(), cx));
2716    let editor_b = workspace_b
2717        .update(cx_b, |workspace, cx| {
2718            workspace.open_path((worktree_id, "main.rs"), true, cx)
2719        })
2720        .await
2721        .unwrap()
2722        .downcast::<Editor>()
2723        .unwrap();
2724
2725    let mut fake_language_server = fake_language_servers.next().await.unwrap();
2726    fake_language_server
2727        .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
2728            assert_eq!(
2729                params.text_document.uri,
2730                lsp::Url::from_file_path("/a/main.rs").unwrap(),
2731            );
2732            assert_eq!(params.range.start, lsp::Position::new(0, 0));
2733            assert_eq!(params.range.end, lsp::Position::new(0, 0));
2734            Ok(None)
2735        })
2736        .next()
2737        .await;
2738
2739    // Move cursor to a location that contains code actions.
2740    editor_b.update(cx_b, |editor, cx| {
2741        editor.change_selections(None, cx, |s| {
2742            s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
2743        });
2744        cx.focus(&editor_b);
2745    });
2746
2747    fake_language_server
2748        .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
2749            assert_eq!(
2750                params.text_document.uri,
2751                lsp::Url::from_file_path("/a/main.rs").unwrap(),
2752            );
2753            assert_eq!(params.range.start, lsp::Position::new(1, 31));
2754            assert_eq!(params.range.end, lsp::Position::new(1, 31));
2755
2756            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
2757                lsp::CodeAction {
2758                    title: "Inline into all callers".to_string(),
2759                    edit: Some(lsp::WorkspaceEdit {
2760                        changes: Some(
2761                            [
2762                                (
2763                                    lsp::Url::from_file_path("/a/main.rs").unwrap(),
2764                                    vec![lsp::TextEdit::new(
2765                                        lsp::Range::new(
2766                                            lsp::Position::new(1, 22),
2767                                            lsp::Position::new(1, 34),
2768                                        ),
2769                                        "4".to_string(),
2770                                    )],
2771                                ),
2772                                (
2773                                    lsp::Url::from_file_path("/a/other.rs").unwrap(),
2774                                    vec![lsp::TextEdit::new(
2775                                        lsp::Range::new(
2776                                            lsp::Position::new(0, 0),
2777                                            lsp::Position::new(0, 27),
2778                                        ),
2779                                        "".to_string(),
2780                                    )],
2781                                ),
2782                            ]
2783                            .into_iter()
2784                            .collect(),
2785                        ),
2786                        ..Default::default()
2787                    }),
2788                    data: Some(json!({
2789                        "codeActionParams": {
2790                            "range": {
2791                                "start": {"line": 1, "column": 31},
2792                                "end": {"line": 1, "column": 31},
2793                            }
2794                        }
2795                    })),
2796                    ..Default::default()
2797                },
2798            )]))
2799        })
2800        .next()
2801        .await;
2802
2803    // Toggle code actions and wait for them to display.
2804    editor_b.update(cx_b, |editor, cx| {
2805        editor.toggle_code_actions(
2806            &ToggleCodeActions {
2807                deployed_from_indicator: false,
2808            },
2809            cx,
2810        );
2811    });
2812    editor_b
2813        .condition(&cx_b, |editor, _| editor.context_menu_visible())
2814        .await;
2815
2816    fake_language_server.remove_request_handler::<lsp::request::CodeActionRequest>();
2817
2818    // Confirming the code action will trigger a resolve request.
2819    let confirm_action = workspace_b
2820        .update(cx_b, |workspace, cx| {
2821            Editor::confirm_code_action(workspace, &ConfirmCodeAction { item_ix: Some(0) }, cx)
2822        })
2823        .unwrap();
2824    fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
2825        |_, _| async move {
2826            Ok(lsp::CodeAction {
2827                title: "Inline into all callers".to_string(),
2828                edit: Some(lsp::WorkspaceEdit {
2829                    changes: Some(
2830                        [
2831                            (
2832                                lsp::Url::from_file_path("/a/main.rs").unwrap(),
2833                                vec![lsp::TextEdit::new(
2834                                    lsp::Range::new(
2835                                        lsp::Position::new(1, 22),
2836                                        lsp::Position::new(1, 34),
2837                                    ),
2838                                    "4".to_string(),
2839                                )],
2840                            ),
2841                            (
2842                                lsp::Url::from_file_path("/a/other.rs").unwrap(),
2843                                vec![lsp::TextEdit::new(
2844                                    lsp::Range::new(
2845                                        lsp::Position::new(0, 0),
2846                                        lsp::Position::new(0, 27),
2847                                    ),
2848                                    "".to_string(),
2849                                )],
2850                            ),
2851                        ]
2852                        .into_iter()
2853                        .collect(),
2854                    ),
2855                    ..Default::default()
2856                }),
2857                ..Default::default()
2858            })
2859        },
2860    );
2861
2862    // After the action is confirmed, an editor containing both modified files is opened.
2863    confirm_action.await.unwrap();
2864    let code_action_editor = workspace_b.read_with(cx_b, |workspace, cx| {
2865        workspace
2866            .active_item(cx)
2867            .unwrap()
2868            .downcast::<Editor>()
2869            .unwrap()
2870    });
2871    code_action_editor.update(cx_b, |editor, cx| {
2872        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
2873        editor.undo(&Undo, cx);
2874        assert_eq!(
2875            editor.text(cx),
2876            "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
2877        );
2878        editor.redo(&Redo, cx);
2879        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
2880    });
2881}
2882
2883#[gpui::test(iterations = 10)]
2884async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2885    cx_a.foreground().forbid_parking();
2886    cx_b.update(|cx| editor::init(cx));
2887    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2888    let client_a = server.create_client(cx_a, "user_a").await;
2889    let client_b = server.create_client(cx_b, "user_b").await;
2890    server
2891        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2892        .await;
2893
2894    // Set up a fake language server.
2895    let mut language = Language::new(
2896        LanguageConfig {
2897            name: "Rust".into(),
2898            path_suffixes: vec!["rs".to_string()],
2899            ..Default::default()
2900        },
2901        Some(tree_sitter_rust::language()),
2902    );
2903    let mut fake_language_servers = language
2904        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2905            capabilities: lsp::ServerCapabilities {
2906                rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
2907                    prepare_provider: Some(true),
2908                    work_done_progress_options: Default::default(),
2909                })),
2910                ..Default::default()
2911            },
2912            ..Default::default()
2913        }))
2914        .await;
2915    client_a.language_registry.add(Arc::new(language));
2916
2917    client_a
2918        .fs
2919        .insert_tree(
2920            "/dir",
2921            json!({
2922                "one.rs": "const ONE: usize = 1;",
2923                "two.rs": "const TWO: usize = one::ONE + one::ONE;"
2924            }),
2925        )
2926        .await;
2927    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2928    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2929
2930    let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(project_b.clone(), cx));
2931    let editor_b = workspace_b
2932        .update(cx_b, |workspace, cx| {
2933            workspace.open_path((worktree_id, "one.rs"), true, cx)
2934        })
2935        .await
2936        .unwrap()
2937        .downcast::<Editor>()
2938        .unwrap();
2939    let fake_language_server = fake_language_servers.next().await.unwrap();
2940
2941    // Move cursor to a location that can be renamed.
2942    let prepare_rename = editor_b.update(cx_b, |editor, cx| {
2943        editor.change_selections(None, cx, |s| s.select_ranges([7..7]));
2944        editor.rename(&Rename, cx).unwrap()
2945    });
2946
2947    fake_language_server
2948        .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
2949            assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
2950            assert_eq!(params.position, lsp::Position::new(0, 7));
2951            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
2952                lsp::Position::new(0, 6),
2953                lsp::Position::new(0, 9),
2954            ))))
2955        })
2956        .next()
2957        .await
2958        .unwrap();
2959    prepare_rename.await.unwrap();
2960    editor_b.update(cx_b, |editor, cx| {
2961        let rename = editor.pending_rename().unwrap();
2962        let buffer = editor.buffer().read(cx).snapshot(cx);
2963        assert_eq!(
2964            rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
2965            6..9
2966        );
2967        rename.editor.update(cx, |rename_editor, cx| {
2968            rename_editor.buffer().update(cx, |rename_buffer, cx| {
2969                rename_buffer.edit([(0..3, "THREE")], cx);
2970            });
2971        });
2972    });
2973
2974    let confirm_rename = workspace_b.update(cx_b, |workspace, cx| {
2975        Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap()
2976    });
2977    fake_language_server
2978        .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
2979            assert_eq!(
2980                params.text_document_position.text_document.uri.as_str(),
2981                "file:///dir/one.rs"
2982            );
2983            assert_eq!(
2984                params.text_document_position.position,
2985                lsp::Position::new(0, 6)
2986            );
2987            assert_eq!(params.new_name, "THREE");
2988            Ok(Some(lsp::WorkspaceEdit {
2989                changes: Some(
2990                    [
2991                        (
2992                            lsp::Url::from_file_path("/dir/one.rs").unwrap(),
2993                            vec![lsp::TextEdit::new(
2994                                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
2995                                "THREE".to_string(),
2996                            )],
2997                        ),
2998                        (
2999                            lsp::Url::from_file_path("/dir/two.rs").unwrap(),
3000                            vec![
3001                                lsp::TextEdit::new(
3002                                    lsp::Range::new(
3003                                        lsp::Position::new(0, 24),
3004                                        lsp::Position::new(0, 27),
3005                                    ),
3006                                    "THREE".to_string(),
3007                                ),
3008                                lsp::TextEdit::new(
3009                                    lsp::Range::new(
3010                                        lsp::Position::new(0, 35),
3011                                        lsp::Position::new(0, 38),
3012                                    ),
3013                                    "THREE".to_string(),
3014                                ),
3015                            ],
3016                        ),
3017                    ]
3018                    .into_iter()
3019                    .collect(),
3020                ),
3021                ..Default::default()
3022            }))
3023        })
3024        .next()
3025        .await
3026        .unwrap();
3027    confirm_rename.await.unwrap();
3028
3029    let rename_editor = workspace_b.read_with(cx_b, |workspace, cx| {
3030        workspace
3031            .active_item(cx)
3032            .unwrap()
3033            .downcast::<Editor>()
3034            .unwrap()
3035    });
3036    rename_editor.update(cx_b, |editor, cx| {
3037        assert_eq!(
3038            editor.text(cx),
3039            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
3040        );
3041        editor.undo(&Undo, cx);
3042        assert_eq!(
3043            editor.text(cx),
3044            "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
3045        );
3046        editor.redo(&Redo, cx);
3047        assert_eq!(
3048            editor.text(cx),
3049            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
3050        );
3051    });
3052
3053    // Ensure temporary rename edits cannot be undone/redone.
3054    editor_b.update(cx_b, |editor, cx| {
3055        editor.undo(&Undo, cx);
3056        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
3057        editor.undo(&Undo, cx);
3058        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
3059        editor.redo(&Redo, cx);
3060        assert_eq!(editor.text(cx), "const THREE: usize = 1;");
3061    })
3062}
3063
3064#[gpui::test(iterations = 10)]
3065async fn test_language_server_statuses(
3066    deterministic: Arc<Deterministic>,
3067    cx_a: &mut TestAppContext,
3068    cx_b: &mut TestAppContext,
3069) {
3070    deterministic.forbid_parking();
3071
3072    cx_b.update(|cx| editor::init(cx));
3073    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3074    let client_a = server.create_client(cx_a, "user_a").await;
3075    let client_b = server.create_client(cx_b, "user_b").await;
3076    server
3077        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
3078        .await;
3079
3080    // Set up a fake language server.
3081    let mut language = Language::new(
3082        LanguageConfig {
3083            name: "Rust".into(),
3084            path_suffixes: vec!["rs".to_string()],
3085            ..Default::default()
3086        },
3087        Some(tree_sitter_rust::language()),
3088    );
3089    let mut fake_language_servers = language
3090        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3091            name: "the-language-server",
3092            ..Default::default()
3093        }))
3094        .await;
3095    client_a.language_registry.add(Arc::new(language));
3096
3097    client_a
3098        .fs
3099        .insert_tree(
3100            "/dir",
3101            json!({
3102                "main.rs": "const ONE: usize = 1;",
3103            }),
3104        )
3105        .await;
3106    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
3107
3108    let _buffer_a = project_a
3109        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
3110        .await
3111        .unwrap();
3112
3113    let fake_language_server = fake_language_servers.next().await.unwrap();
3114    fake_language_server.start_progress("the-token").await;
3115    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
3116        token: lsp::NumberOrString::String("the-token".to_string()),
3117        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
3118            lsp::WorkDoneProgressReport {
3119                message: Some("the-message".to_string()),
3120                ..Default::default()
3121            },
3122        )),
3123    });
3124    deterministic.run_until_parked();
3125    project_a.read_with(cx_a, |project, _| {
3126        let status = project.language_server_statuses().next().unwrap();
3127        assert_eq!(status.name, "the-language-server");
3128        assert_eq!(status.pending_work.len(), 1);
3129        assert_eq!(
3130            status.pending_work["the-token"].message.as_ref().unwrap(),
3131            "the-message"
3132        );
3133    });
3134
3135    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
3136    project_b.read_with(cx_b, |project, _| {
3137        let status = project.language_server_statuses().next().unwrap();
3138        assert_eq!(status.name, "the-language-server");
3139    });
3140
3141    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
3142        token: lsp::NumberOrString::String("the-token".to_string()),
3143        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
3144            lsp::WorkDoneProgressReport {
3145                message: Some("the-message-2".to_string()),
3146                ..Default::default()
3147            },
3148        )),
3149    });
3150    deterministic.run_until_parked();
3151    project_a.read_with(cx_a, |project, _| {
3152        let status = project.language_server_statuses().next().unwrap();
3153        assert_eq!(status.name, "the-language-server");
3154        assert_eq!(status.pending_work.len(), 1);
3155        assert_eq!(
3156            status.pending_work["the-token"].message.as_ref().unwrap(),
3157            "the-message-2"
3158        );
3159    });
3160    project_b.read_with(cx_b, |project, _| {
3161        let status = project.language_server_statuses().next().unwrap();
3162        assert_eq!(status.name, "the-language-server");
3163        assert_eq!(status.pending_work.len(), 1);
3164        assert_eq!(
3165            status.pending_work["the-token"].message.as_ref().unwrap(),
3166            "the-message-2"
3167        );
3168    });
3169}
3170
3171#[gpui::test(iterations = 10)]
3172async fn test_basic_chat(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3173    cx_a.foreground().forbid_parking();
3174    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3175    let client_a = server.create_client(cx_a, "user_a").await;
3176    let client_b = server.create_client(cx_b, "user_b").await;
3177
3178    // Create an org that includes these 2 users.
3179    let db = &server.app_state.db;
3180    let org_id = db.create_org("Test Org", "test-org").await.unwrap();
3181    db.add_org_member(org_id, client_a.current_user_id(&cx_a), false)
3182        .await
3183        .unwrap();
3184    db.add_org_member(org_id, client_b.current_user_id(&cx_b), false)
3185        .await
3186        .unwrap();
3187
3188    // Create a channel that includes all the users.
3189    let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap();
3190    db.add_channel_member(channel_id, client_a.current_user_id(&cx_a), false)
3191        .await
3192        .unwrap();
3193    db.add_channel_member(channel_id, client_b.current_user_id(&cx_b), false)
3194        .await
3195        .unwrap();
3196    db.create_channel_message(
3197        channel_id,
3198        client_b.current_user_id(&cx_b),
3199        "hello A, it's B.",
3200        OffsetDateTime::now_utc(),
3201        1,
3202    )
3203    .await
3204    .unwrap();
3205
3206    let channels_a =
3207        cx_a.add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx));
3208    channels_a
3209        .condition(cx_a, |list, _| list.available_channels().is_some())
3210        .await;
3211    channels_a.read_with(cx_a, |list, _| {
3212        assert_eq!(
3213            list.available_channels().unwrap(),
3214            &[ChannelDetails {
3215                id: channel_id.to_proto(),
3216                name: "test-channel".to_string()
3217            }]
3218        )
3219    });
3220    let channel_a = channels_a.update(cx_a, |this, cx| {
3221        this.get_channel(channel_id.to_proto(), cx).unwrap()
3222    });
3223    channel_a.read_with(cx_a, |channel, _| assert!(channel.messages().is_empty()));
3224    channel_a
3225        .condition(&cx_a, |channel, _| {
3226            channel_messages(channel)
3227                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
3228        })
3229        .await;
3230
3231    let channels_b =
3232        cx_b.add_model(|cx| ChannelList::new(client_b.user_store.clone(), client_b.clone(), cx));
3233    channels_b
3234        .condition(cx_b, |list, _| list.available_channels().is_some())
3235        .await;
3236    channels_b.read_with(cx_b, |list, _| {
3237        assert_eq!(
3238            list.available_channels().unwrap(),
3239            &[ChannelDetails {
3240                id: channel_id.to_proto(),
3241                name: "test-channel".to_string()
3242            }]
3243        )
3244    });
3245
3246    let channel_b = channels_b.update(cx_b, |this, cx| {
3247        this.get_channel(channel_id.to_proto(), cx).unwrap()
3248    });
3249    channel_b.read_with(cx_b, |channel, _| assert!(channel.messages().is_empty()));
3250    channel_b
3251        .condition(&cx_b, |channel, _| {
3252            channel_messages(channel)
3253                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
3254        })
3255        .await;
3256
3257    channel_a
3258        .update(cx_a, |channel, cx| {
3259            channel
3260                .send_message("oh, hi B.".to_string(), cx)
3261                .unwrap()
3262                .detach();
3263            let task = channel.send_message("sup".to_string(), cx).unwrap();
3264            assert_eq!(
3265                channel_messages(channel),
3266                &[
3267                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3268                    ("user_a".to_string(), "oh, hi B.".to_string(), true),
3269                    ("user_a".to_string(), "sup".to_string(), true)
3270                ]
3271            );
3272            task
3273        })
3274        .await
3275        .unwrap();
3276
3277    channel_b
3278        .condition(&cx_b, |channel, _| {
3279            channel_messages(channel)
3280                == [
3281                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3282                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
3283                    ("user_a".to_string(), "sup".to_string(), false),
3284                ]
3285        })
3286        .await;
3287
3288    assert_eq!(
3289        server
3290            .store()
3291            .await
3292            .channel(channel_id)
3293            .unwrap()
3294            .connection_ids
3295            .len(),
3296        2
3297    );
3298    cx_b.update(|_| drop(channel_b));
3299    server
3300        .condition(|state| state.channel(channel_id).unwrap().connection_ids.len() == 1)
3301        .await;
3302
3303    cx_a.update(|_| drop(channel_a));
3304    server
3305        .condition(|state| state.channel(channel_id).is_none())
3306        .await;
3307}
3308
3309#[gpui::test(iterations = 10)]
3310async fn test_chat_message_validation(cx_a: &mut TestAppContext) {
3311    cx_a.foreground().forbid_parking();
3312    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3313    let client_a = server.create_client(cx_a, "user_a").await;
3314
3315    let db = &server.app_state.db;
3316    let org_id = db.create_org("Test Org", "test-org").await.unwrap();
3317    let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap();
3318    db.add_org_member(org_id, client_a.current_user_id(&cx_a), false)
3319        .await
3320        .unwrap();
3321    db.add_channel_member(channel_id, client_a.current_user_id(&cx_a), false)
3322        .await
3323        .unwrap();
3324
3325    let channels_a =
3326        cx_a.add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx));
3327    channels_a
3328        .condition(cx_a, |list, _| list.available_channels().is_some())
3329        .await;
3330    let channel_a = channels_a.update(cx_a, |this, cx| {
3331        this.get_channel(channel_id.to_proto(), cx).unwrap()
3332    });
3333
3334    // Messages aren't allowed to be too long.
3335    channel_a
3336        .update(cx_a, |channel, cx| {
3337            let long_body = "this is long.\n".repeat(1024);
3338            channel.send_message(long_body, cx).unwrap()
3339        })
3340        .await
3341        .unwrap_err();
3342
3343    // Messages aren't allowed to be blank.
3344    channel_a.update(cx_a, |channel, cx| {
3345        channel.send_message(String::new(), cx).unwrap_err()
3346    });
3347
3348    // Leading and trailing whitespace are trimmed.
3349    channel_a
3350        .update(cx_a, |channel, cx| {
3351            channel
3352                .send_message("\n surrounded by whitespace  \n".to_string(), cx)
3353                .unwrap()
3354        })
3355        .await
3356        .unwrap();
3357    assert_eq!(
3358        db.get_channel_messages(channel_id, 10, None)
3359            .await
3360            .unwrap()
3361            .iter()
3362            .map(|m| &m.body)
3363            .collect::<Vec<_>>(),
3364        &["surrounded by whitespace"]
3365    );
3366}
3367
3368#[gpui::test(iterations = 10)]
3369async fn test_chat_reconnection(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3370    cx_a.foreground().forbid_parking();
3371    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3372    let client_a = server.create_client(cx_a, "user_a").await;
3373    let client_b = server.create_client(cx_b, "user_b").await;
3374
3375    let mut status_b = client_b.status();
3376
3377    // Create an org that includes these 2 users.
3378    let db = &server.app_state.db;
3379    let org_id = db.create_org("Test Org", "test-org").await.unwrap();
3380    db.add_org_member(org_id, client_a.current_user_id(&cx_a), false)
3381        .await
3382        .unwrap();
3383    db.add_org_member(org_id, client_b.current_user_id(&cx_b), false)
3384        .await
3385        .unwrap();
3386
3387    // Create a channel that includes all the users.
3388    let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap();
3389    db.add_channel_member(channel_id, client_a.current_user_id(&cx_a), false)
3390        .await
3391        .unwrap();
3392    db.add_channel_member(channel_id, client_b.current_user_id(&cx_b), false)
3393        .await
3394        .unwrap();
3395    db.create_channel_message(
3396        channel_id,
3397        client_b.current_user_id(&cx_b),
3398        "hello A, it's B.",
3399        OffsetDateTime::now_utc(),
3400        2,
3401    )
3402    .await
3403    .unwrap();
3404
3405    let channels_a =
3406        cx_a.add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx));
3407    channels_a
3408        .condition(cx_a, |list, _| list.available_channels().is_some())
3409        .await;
3410
3411    channels_a.read_with(cx_a, |list, _| {
3412        assert_eq!(
3413            list.available_channels().unwrap(),
3414            &[ChannelDetails {
3415                id: channel_id.to_proto(),
3416                name: "test-channel".to_string()
3417            }]
3418        )
3419    });
3420    let channel_a = channels_a.update(cx_a, |this, cx| {
3421        this.get_channel(channel_id.to_proto(), cx).unwrap()
3422    });
3423    channel_a.read_with(cx_a, |channel, _| assert!(channel.messages().is_empty()));
3424    channel_a
3425        .condition(&cx_a, |channel, _| {
3426            channel_messages(channel)
3427                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
3428        })
3429        .await;
3430
3431    let channels_b =
3432        cx_b.add_model(|cx| ChannelList::new(client_b.user_store.clone(), client_b.clone(), cx));
3433    channels_b
3434        .condition(cx_b, |list, _| list.available_channels().is_some())
3435        .await;
3436    channels_b.read_with(cx_b, |list, _| {
3437        assert_eq!(
3438            list.available_channels().unwrap(),
3439            &[ChannelDetails {
3440                id: channel_id.to_proto(),
3441                name: "test-channel".to_string()
3442            }]
3443        )
3444    });
3445
3446    let channel_b = channels_b.update(cx_b, |this, cx| {
3447        this.get_channel(channel_id.to_proto(), cx).unwrap()
3448    });
3449    channel_b.read_with(cx_b, |channel, _| assert!(channel.messages().is_empty()));
3450    channel_b
3451        .condition(&cx_b, |channel, _| {
3452            channel_messages(channel)
3453                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
3454        })
3455        .await;
3456
3457    // Disconnect client B, ensuring we can still access its cached channel data.
3458    server.forbid_connections();
3459    server.disconnect_client(client_b.current_user_id(&cx_b));
3460    cx_b.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
3461    while !matches!(
3462        status_b.next().await,
3463        Some(client::Status::ReconnectionError { .. })
3464    ) {}
3465
3466    channels_b.read_with(cx_b, |channels, _| {
3467        assert_eq!(
3468            channels.available_channels().unwrap(),
3469            [ChannelDetails {
3470                id: channel_id.to_proto(),
3471                name: "test-channel".to_string()
3472            }]
3473        )
3474    });
3475    channel_b.read_with(cx_b, |channel, _| {
3476        assert_eq!(
3477            channel_messages(channel),
3478            [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
3479        )
3480    });
3481
3482    // Send a message from client B while it is disconnected.
3483    channel_b
3484        .update(cx_b, |channel, cx| {
3485            let task = channel
3486                .send_message("can you see this?".to_string(), cx)
3487                .unwrap();
3488            assert_eq!(
3489                channel_messages(channel),
3490                &[
3491                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3492                    ("user_b".to_string(), "can you see this?".to_string(), true)
3493                ]
3494            );
3495            task
3496        })
3497        .await
3498        .unwrap_err();
3499
3500    // Send a message from client A while B is disconnected.
3501    channel_a
3502        .update(cx_a, |channel, cx| {
3503            channel
3504                .send_message("oh, hi B.".to_string(), cx)
3505                .unwrap()
3506                .detach();
3507            let task = channel.send_message("sup".to_string(), cx).unwrap();
3508            assert_eq!(
3509                channel_messages(channel),
3510                &[
3511                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3512                    ("user_a".to_string(), "oh, hi B.".to_string(), true),
3513                    ("user_a".to_string(), "sup".to_string(), true)
3514                ]
3515            );
3516            task
3517        })
3518        .await
3519        .unwrap();
3520
3521    // Give client B a chance to reconnect.
3522    server.allow_connections();
3523    cx_b.foreground().advance_clock(Duration::from_secs(10));
3524
3525    // Verify that B sees the new messages upon reconnection, as well as the message client B
3526    // sent while offline.
3527    channel_b
3528        .condition(&cx_b, |channel, _| {
3529            channel_messages(channel)
3530                == [
3531                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3532                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
3533                    ("user_a".to_string(), "sup".to_string(), false),
3534                    ("user_b".to_string(), "can you see this?".to_string(), false),
3535                ]
3536        })
3537        .await;
3538
3539    // Ensure client A and B can communicate normally after reconnection.
3540    channel_a
3541        .update(cx_a, |channel, cx| {
3542            channel.send_message("you online?".to_string(), cx).unwrap()
3543        })
3544        .await
3545        .unwrap();
3546    channel_b
3547        .condition(&cx_b, |channel, _| {
3548            channel_messages(channel)
3549                == [
3550                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3551                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
3552                    ("user_a".to_string(), "sup".to_string(), false),
3553                    ("user_b".to_string(), "can you see this?".to_string(), false),
3554                    ("user_a".to_string(), "you online?".to_string(), false),
3555                ]
3556        })
3557        .await;
3558
3559    channel_b
3560        .update(cx_b, |channel, cx| {
3561            channel.send_message("yep".to_string(), cx).unwrap()
3562        })
3563        .await
3564        .unwrap();
3565    channel_a
3566        .condition(&cx_a, |channel, _| {
3567            channel_messages(channel)
3568                == [
3569                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3570                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
3571                    ("user_a".to_string(), "sup".to_string(), false),
3572                    ("user_b".to_string(), "can you see this?".to_string(), false),
3573                    ("user_a".to_string(), "you online?".to_string(), false),
3574                    ("user_b".to_string(), "yep".to_string(), false),
3575                ]
3576        })
3577        .await;
3578}
3579
3580#[gpui::test(iterations = 10)]
3581async fn test_contacts(
3582    deterministic: Arc<Deterministic>,
3583    cx_a: &mut TestAppContext,
3584    cx_b: &mut TestAppContext,
3585    cx_c: &mut TestAppContext,
3586) {
3587    cx_a.foreground().forbid_parking();
3588    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3589    let client_a = server.create_client(cx_a, "user_a").await;
3590    let client_b = server.create_client(cx_b, "user_b").await;
3591    let client_c = server.create_client(cx_c, "user_c").await;
3592    server
3593        .make_contacts(vec![
3594            (&client_a, cx_a),
3595            (&client_b, cx_b),
3596            (&client_c, cx_c),
3597        ])
3598        .await;
3599
3600    deterministic.run_until_parked();
3601    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b), (&client_c, &cx_c)] {
3602        client.user_store.read_with(*cx, |store, _| {
3603            assert_eq!(
3604                contacts(store),
3605                [
3606                    ("user_a", true, vec![]),
3607                    ("user_b", true, vec![]),
3608                    ("user_c", true, vec![])
3609                ],
3610                "{} has the wrong contacts",
3611                client.username
3612            )
3613        });
3614    }
3615
3616    // Share a project as client A.
3617    client_a.fs.create_dir(Path::new("/a")).await.unwrap();
3618    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
3619
3620    deterministic.run_until_parked();
3621    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b), (&client_c, &cx_c)] {
3622        client.user_store.read_with(*cx, |store, _| {
3623            assert_eq!(
3624                contacts(store),
3625                [
3626                    ("user_a", true, vec![("a", vec![])]),
3627                    ("user_b", true, vec![]),
3628                    ("user_c", true, vec![])
3629                ],
3630                "{} has the wrong contacts",
3631                client.username
3632            )
3633        });
3634    }
3635
3636    let _project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
3637
3638    deterministic.run_until_parked();
3639    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b), (&client_c, &cx_c)] {
3640        client.user_store.read_with(*cx, |store, _| {
3641            assert_eq!(
3642                contacts(store),
3643                [
3644                    ("user_a", true, vec![("a", vec!["user_b"])]),
3645                    ("user_b", true, vec![]),
3646                    ("user_c", true, vec![])
3647                ],
3648                "{} has the wrong contacts",
3649                client.username
3650            )
3651        });
3652    }
3653
3654    // Add a local project as client B
3655    client_a.fs.create_dir("/b".as_ref()).await.unwrap();
3656    let (_project_b, _) = client_b.build_local_project("/b", cx_b).await;
3657
3658    deterministic.run_until_parked();
3659    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b), (&client_c, &cx_c)] {
3660        client.user_store.read_with(*cx, |store, _| {
3661            assert_eq!(
3662                contacts(store),
3663                [
3664                    ("user_a", true, vec![("a", vec!["user_b"])]),
3665                    ("user_b", true, vec![("b", vec![])]),
3666                    ("user_c", true, vec![])
3667                ],
3668                "{} has the wrong contacts",
3669                client.username
3670            )
3671        });
3672    }
3673
3674    project_a
3675        .condition(&cx_a, |project, _| {
3676            project.collaborators().contains_key(&client_b.peer_id)
3677        })
3678        .await;
3679
3680    cx_a.update(move |_| drop(project_a));
3681    deterministic.run_until_parked();
3682    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b), (&client_c, &cx_c)] {
3683        client.user_store.read_with(*cx, |store, _| {
3684            assert_eq!(
3685                contacts(store),
3686                [
3687                    ("user_a", true, vec![]),
3688                    ("user_b", true, vec![("b", vec![])]),
3689                    ("user_c", true, vec![])
3690                ],
3691                "{} has the wrong contacts",
3692                client.username
3693            )
3694        });
3695    }
3696
3697    server.disconnect_client(client_c.current_user_id(cx_c));
3698    server.forbid_connections();
3699    deterministic.advance_clock(rpc::RECEIVE_TIMEOUT);
3700    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b)] {
3701        client.user_store.read_with(*cx, |store, _| {
3702            assert_eq!(
3703                contacts(store),
3704                [
3705                    ("user_a", true, vec![]),
3706                    ("user_b", true, vec![("b", vec![])]),
3707                    ("user_c", false, vec![])
3708                ],
3709                "{} has the wrong contacts",
3710                client.username
3711            )
3712        });
3713    }
3714    client_c
3715        .user_store
3716        .read_with(cx_c, |store, _| assert_eq!(contacts(store), []));
3717
3718    server.allow_connections();
3719    client_c
3720        .authenticate_and_connect(false, &cx_c.to_async())
3721        .await
3722        .unwrap();
3723
3724    deterministic.run_until_parked();
3725    for (client, cx) in [(&client_a, &cx_a), (&client_b, &cx_b), (&client_c, &cx_c)] {
3726        client.user_store.read_with(*cx, |store, _| {
3727            assert_eq!(
3728                contacts(store),
3729                [
3730                    ("user_a", true, vec![]),
3731                    ("user_b", true, vec![("b", vec![])]),
3732                    ("user_c", true, vec![])
3733                ],
3734                "{} has the wrong contacts",
3735                client.username
3736            )
3737        });
3738    }
3739
3740    fn contacts(user_store: &UserStore) -> Vec<(&str, bool, Vec<(&str, Vec<&str>)>)> {
3741        user_store
3742            .contacts()
3743            .iter()
3744            .map(|contact| {
3745                let projects = contact
3746                    .projects
3747                    .iter()
3748                    .map(|p| {
3749                        (
3750                            p.visible_worktree_root_names[0].as_str(),
3751                            p.guests.iter().map(|p| p.github_login.as_str()).collect(),
3752                        )
3753                    })
3754                    .collect();
3755                (contact.user.github_login.as_str(), contact.online, projects)
3756            })
3757            .collect()
3758    }
3759}
3760
3761#[gpui::test(iterations = 10)]
3762async fn test_contact_requests(
3763    executor: Arc<Deterministic>,
3764    cx_a: &mut TestAppContext,
3765    cx_a2: &mut TestAppContext,
3766    cx_b: &mut TestAppContext,
3767    cx_b2: &mut TestAppContext,
3768    cx_c: &mut TestAppContext,
3769    cx_c2: &mut TestAppContext,
3770) {
3771    cx_a.foreground().forbid_parking();
3772
3773    // Connect to a server as 3 clients.
3774    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3775    let client_a = server.create_client(cx_a, "user_a").await;
3776    let client_a2 = server.create_client(cx_a2, "user_a").await;
3777    let client_b = server.create_client(cx_b, "user_b").await;
3778    let client_b2 = server.create_client(cx_b2, "user_b").await;
3779    let client_c = server.create_client(cx_c, "user_c").await;
3780    let client_c2 = server.create_client(cx_c2, "user_c").await;
3781
3782    assert_eq!(client_a.user_id().unwrap(), client_a2.user_id().unwrap());
3783    assert_eq!(client_b.user_id().unwrap(), client_b2.user_id().unwrap());
3784    assert_eq!(client_c.user_id().unwrap(), client_c2.user_id().unwrap());
3785
3786    // User A and User C request that user B become their contact.
3787    client_a
3788        .user_store
3789        .update(cx_a, |store, cx| {
3790            store.request_contact(client_b.user_id().unwrap(), cx)
3791        })
3792        .await
3793        .unwrap();
3794    client_c
3795        .user_store
3796        .update(cx_c, |store, cx| {
3797            store.request_contact(client_b.user_id().unwrap(), cx)
3798        })
3799        .await
3800        .unwrap();
3801    executor.run_until_parked();
3802
3803    // All users see the pending request appear in all their clients.
3804    assert_eq!(
3805        client_a.summarize_contacts(&cx_a).outgoing_requests,
3806        &["user_b"]
3807    );
3808    assert_eq!(
3809        client_a2.summarize_contacts(&cx_a2).outgoing_requests,
3810        &["user_b"]
3811    );
3812    assert_eq!(
3813        client_b.summarize_contacts(&cx_b).incoming_requests,
3814        &["user_a", "user_c"]
3815    );
3816    assert_eq!(
3817        client_b2.summarize_contacts(&cx_b2).incoming_requests,
3818        &["user_a", "user_c"]
3819    );
3820    assert_eq!(
3821        client_c.summarize_contacts(&cx_c).outgoing_requests,
3822        &["user_b"]
3823    );
3824    assert_eq!(
3825        client_c2.summarize_contacts(&cx_c2).outgoing_requests,
3826        &["user_b"]
3827    );
3828
3829    // Contact requests are present upon connecting (tested here via disconnect/reconnect)
3830    disconnect_and_reconnect(&client_a, cx_a).await;
3831    disconnect_and_reconnect(&client_b, cx_b).await;
3832    disconnect_and_reconnect(&client_c, cx_c).await;
3833    executor.run_until_parked();
3834    assert_eq!(
3835        client_a.summarize_contacts(&cx_a).outgoing_requests,
3836        &["user_b"]
3837    );
3838    assert_eq!(
3839        client_b.summarize_contacts(&cx_b).incoming_requests,
3840        &["user_a", "user_c"]
3841    );
3842    assert_eq!(
3843        client_c.summarize_contacts(&cx_c).outgoing_requests,
3844        &["user_b"]
3845    );
3846
3847    // User B accepts the request from user A.
3848    client_b
3849        .user_store
3850        .update(cx_b, |store, cx| {
3851            store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
3852        })
3853        .await
3854        .unwrap();
3855
3856    executor.run_until_parked();
3857
3858    // User B sees user A as their contact now in all client, and the incoming request from them is removed.
3859    let contacts_b = client_b.summarize_contacts(&cx_b);
3860    assert_eq!(contacts_b.current, &["user_a", "user_b"]);
3861    assert_eq!(contacts_b.incoming_requests, &["user_c"]);
3862    let contacts_b2 = client_b2.summarize_contacts(&cx_b2);
3863    assert_eq!(contacts_b2.current, &["user_a", "user_b"]);
3864    assert_eq!(contacts_b2.incoming_requests, &["user_c"]);
3865
3866    // User A sees user B as their contact now in all clients, and the outgoing request to them is removed.
3867    let contacts_a = client_a.summarize_contacts(&cx_a);
3868    assert_eq!(contacts_a.current, &["user_a", "user_b"]);
3869    assert!(contacts_a.outgoing_requests.is_empty());
3870    let contacts_a2 = client_a2.summarize_contacts(&cx_a2);
3871    assert_eq!(contacts_a2.current, &["user_a", "user_b"]);
3872    assert!(contacts_a2.outgoing_requests.is_empty());
3873
3874    // Contacts are present upon connecting (tested here via disconnect/reconnect)
3875    disconnect_and_reconnect(&client_a, cx_a).await;
3876    disconnect_and_reconnect(&client_b, cx_b).await;
3877    disconnect_and_reconnect(&client_c, cx_c).await;
3878    executor.run_until_parked();
3879    assert_eq!(
3880        client_a.summarize_contacts(&cx_a).current,
3881        &["user_a", "user_b"]
3882    );
3883    assert_eq!(
3884        client_b.summarize_contacts(&cx_b).current,
3885        &["user_a", "user_b"]
3886    );
3887    assert_eq!(
3888        client_b.summarize_contacts(&cx_b).incoming_requests,
3889        &["user_c"]
3890    );
3891    assert_eq!(client_c.summarize_contacts(&cx_c).current, &["user_c"]);
3892    assert_eq!(
3893        client_c.summarize_contacts(&cx_c).outgoing_requests,
3894        &["user_b"]
3895    );
3896
3897    // User B rejects the request from user C.
3898    client_b
3899        .user_store
3900        .update(cx_b, |store, cx| {
3901            store.respond_to_contact_request(client_c.user_id().unwrap(), false, cx)
3902        })
3903        .await
3904        .unwrap();
3905
3906    executor.run_until_parked();
3907
3908    // User B doesn't see user C as their contact, and the incoming request from them is removed.
3909    let contacts_b = client_b.summarize_contacts(&cx_b);
3910    assert_eq!(contacts_b.current, &["user_a", "user_b"]);
3911    assert!(contacts_b.incoming_requests.is_empty());
3912    let contacts_b2 = client_b2.summarize_contacts(&cx_b2);
3913    assert_eq!(contacts_b2.current, &["user_a", "user_b"]);
3914    assert!(contacts_b2.incoming_requests.is_empty());
3915
3916    // User C doesn't see user B as their contact, and the outgoing request to them is removed.
3917    let contacts_c = client_c.summarize_contacts(&cx_c);
3918    assert_eq!(contacts_c.current, &["user_c"]);
3919    assert!(contacts_c.outgoing_requests.is_empty());
3920    let contacts_c2 = client_c2.summarize_contacts(&cx_c2);
3921    assert_eq!(contacts_c2.current, &["user_c"]);
3922    assert!(contacts_c2.outgoing_requests.is_empty());
3923
3924    // Incoming/outgoing requests are not present upon connecting (tested here via disconnect/reconnect)
3925    disconnect_and_reconnect(&client_a, cx_a).await;
3926    disconnect_and_reconnect(&client_b, cx_b).await;
3927    disconnect_and_reconnect(&client_c, cx_c).await;
3928    executor.run_until_parked();
3929    assert_eq!(
3930        client_a.summarize_contacts(&cx_a).current,
3931        &["user_a", "user_b"]
3932    );
3933    assert_eq!(
3934        client_b.summarize_contacts(&cx_b).current,
3935        &["user_a", "user_b"]
3936    );
3937    assert!(client_b
3938        .summarize_contacts(&cx_b)
3939        .incoming_requests
3940        .is_empty());
3941    assert_eq!(client_c.summarize_contacts(&cx_c).current, &["user_c"]);
3942    assert!(client_c
3943        .summarize_contacts(&cx_c)
3944        .outgoing_requests
3945        .is_empty());
3946
3947    async fn disconnect_and_reconnect(client: &TestClient, cx: &mut TestAppContext) {
3948        client.disconnect(&cx.to_async()).unwrap();
3949        client.clear_contacts(cx).await;
3950        client
3951            .authenticate_and_connect(false, &cx.to_async())
3952            .await
3953            .unwrap();
3954    }
3955}
3956
3957#[gpui::test(iterations = 10)]
3958async fn test_following(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3959    cx_a.foreground().forbid_parking();
3960    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3961    let client_a = server.create_client(cx_a, "user_a").await;
3962    let client_b = server.create_client(cx_b, "user_b").await;
3963    server
3964        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
3965        .await;
3966    cx_a.update(editor::init);
3967    cx_b.update(editor::init);
3968
3969    client_a
3970        .fs
3971        .insert_tree(
3972            "/a",
3973            json!({
3974                "1.txt": "one",
3975                "2.txt": "two",
3976                "3.txt": "three",
3977            }),
3978        )
3979        .await;
3980    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
3981
3982    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
3983
3984    // Client A opens some editors.
3985    let workspace_a = client_a.build_workspace(&project_a, cx_a);
3986    let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
3987    let editor_a1 = workspace_a
3988        .update(cx_a, |workspace, cx| {
3989            workspace.open_path((worktree_id, "1.txt"), true, cx)
3990        })
3991        .await
3992        .unwrap()
3993        .downcast::<Editor>()
3994        .unwrap();
3995    let editor_a2 = workspace_a
3996        .update(cx_a, |workspace, cx| {
3997            workspace.open_path((worktree_id, "2.txt"), true, cx)
3998        })
3999        .await
4000        .unwrap()
4001        .downcast::<Editor>()
4002        .unwrap();
4003
4004    // Client B opens an editor.
4005    let workspace_b = client_b.build_workspace(&project_b, cx_b);
4006    let editor_b1 = workspace_b
4007        .update(cx_b, |workspace, cx| {
4008            workspace.open_path((worktree_id, "1.txt"), true, cx)
4009        })
4010        .await
4011        .unwrap()
4012        .downcast::<Editor>()
4013        .unwrap();
4014
4015    let client_a_id = project_b.read_with(cx_b, |project, _| {
4016        project.collaborators().values().next().unwrap().peer_id
4017    });
4018    let client_b_id = project_a.read_with(cx_a, |project, _| {
4019        project.collaborators().values().next().unwrap().peer_id
4020    });
4021
4022    // When client B starts following client A, all visible view states are replicated to client B.
4023    editor_a1.update(cx_a, |editor, cx| {
4024        editor.change_selections(None, cx, |s| s.select_ranges([0..1]))
4025    });
4026    editor_a2.update(cx_a, |editor, cx| {
4027        editor.change_selections(None, cx, |s| s.select_ranges([2..3]))
4028    });
4029    workspace_b
4030        .update(cx_b, |workspace, cx| {
4031            workspace
4032                .toggle_follow(&ToggleFollow(client_a_id), cx)
4033                .unwrap()
4034        })
4035        .await
4036        .unwrap();
4037
4038    let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
4039        workspace
4040            .active_item(cx)
4041            .unwrap()
4042            .downcast::<Editor>()
4043            .unwrap()
4044    });
4045    assert!(cx_b.read(|cx| editor_b2.is_focused(cx)));
4046    assert_eq!(
4047        editor_b2.read_with(cx_b, |editor, cx| editor.project_path(cx)),
4048        Some((worktree_id, "2.txt").into())
4049    );
4050    assert_eq!(
4051        editor_b2.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
4052        vec![2..3]
4053    );
4054    assert_eq!(
4055        editor_b1.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
4056        vec![0..1]
4057    );
4058
4059    // When client A activates a different editor, client B does so as well.
4060    workspace_a.update(cx_a, |workspace, cx| {
4061        workspace.activate_item(&editor_a1, cx)
4062    });
4063    workspace_b
4064        .condition(cx_b, |workspace, cx| {
4065            workspace.active_item(cx).unwrap().id() == editor_b1.id()
4066        })
4067        .await;
4068
4069    // When client A navigates back and forth, client B does so as well.
4070    workspace_a
4071        .update(cx_a, |workspace, cx| {
4072            workspace::Pane::go_back(workspace, None, cx)
4073        })
4074        .await;
4075    workspace_b
4076        .condition(cx_b, |workspace, cx| {
4077            workspace.active_item(cx).unwrap().id() == editor_b2.id()
4078        })
4079        .await;
4080
4081    workspace_a
4082        .update(cx_a, |workspace, cx| {
4083            workspace::Pane::go_forward(workspace, None, cx)
4084        })
4085        .await;
4086    workspace_b
4087        .condition(cx_b, |workspace, cx| {
4088            workspace.active_item(cx).unwrap().id() == editor_b1.id()
4089        })
4090        .await;
4091
4092    // Changes to client A's editor are reflected on client B.
4093    editor_a1.update(cx_a, |editor, cx| {
4094        editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
4095    });
4096    editor_b1
4097        .condition(cx_b, |editor, cx| {
4098            editor.selections.ranges(cx) == vec![1..1, 2..2]
4099        })
4100        .await;
4101
4102    editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
4103    editor_b1
4104        .condition(cx_b, |editor, cx| editor.text(cx) == "TWO")
4105        .await;
4106
4107    editor_a1.update(cx_a, |editor, cx| {
4108        editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4109        editor.set_scroll_position(vec2f(0., 100.), cx);
4110    });
4111    editor_b1
4112        .condition(cx_b, |editor, cx| {
4113            editor.selections.ranges(cx) == vec![3..3]
4114        })
4115        .await;
4116
4117    // After unfollowing, client B stops receiving updates from client A.
4118    workspace_b.update(cx_b, |workspace, cx| {
4119        workspace.unfollow(&workspace.active_pane().clone(), cx)
4120    });
4121    workspace_a.update(cx_a, |workspace, cx| {
4122        workspace.activate_item(&editor_a2, cx)
4123    });
4124    cx_a.foreground().run_until_parked();
4125    assert_eq!(
4126        workspace_b.read_with(cx_b, |workspace, cx| workspace
4127            .active_item(cx)
4128            .unwrap()
4129            .id()),
4130        editor_b1.id()
4131    );
4132
4133    // Client A starts following client B.
4134    workspace_a
4135        .update(cx_a, |workspace, cx| {
4136            workspace
4137                .toggle_follow(&ToggleFollow(client_b_id), cx)
4138                .unwrap()
4139        })
4140        .await
4141        .unwrap();
4142    assert_eq!(
4143        workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
4144        Some(client_b_id)
4145    );
4146    assert_eq!(
4147        workspace_a.read_with(cx_a, |workspace, cx| workspace
4148            .active_item(cx)
4149            .unwrap()
4150            .id()),
4151        editor_a1.id()
4152    );
4153
4154    // Following interrupts when client B disconnects.
4155    client_b.disconnect(&cx_b.to_async()).unwrap();
4156    cx_a.foreground().run_until_parked();
4157    assert_eq!(
4158        workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
4159        None
4160    );
4161}
4162
4163#[gpui::test(iterations = 10)]
4164async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4165    cx_a.foreground().forbid_parking();
4166    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
4167    let client_a = server.create_client(cx_a, "user_a").await;
4168    let client_b = server.create_client(cx_b, "user_b").await;
4169    server
4170        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
4171        .await;
4172    cx_a.update(editor::init);
4173    cx_b.update(editor::init);
4174
4175    // Client A shares a project.
4176    client_a
4177        .fs
4178        .insert_tree(
4179            "/a",
4180            json!({
4181                "1.txt": "one",
4182                "2.txt": "two",
4183                "3.txt": "three",
4184                "4.txt": "four",
4185            }),
4186        )
4187        .await;
4188    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
4189
4190    // Client B joins the project.
4191    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
4192
4193    // Client A opens some editors.
4194    let workspace_a = client_a.build_workspace(&project_a, cx_a);
4195    let pane_a1 = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
4196    let _editor_a1 = workspace_a
4197        .update(cx_a, |workspace, cx| {
4198            workspace.open_path((worktree_id, "1.txt"), true, cx)
4199        })
4200        .await
4201        .unwrap()
4202        .downcast::<Editor>()
4203        .unwrap();
4204
4205    // Client B opens an editor.
4206    let workspace_b = client_b.build_workspace(&project_b, cx_b);
4207    let pane_b1 = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
4208    let _editor_b1 = workspace_b
4209        .update(cx_b, |workspace, cx| {
4210            workspace.open_path((worktree_id, "2.txt"), true, cx)
4211        })
4212        .await
4213        .unwrap()
4214        .downcast::<Editor>()
4215        .unwrap();
4216
4217    // Clients A and B follow each other in split panes
4218    workspace_a.update(cx_a, |workspace, cx| {
4219        workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
4220        assert_ne!(*workspace.active_pane(), pane_a1);
4221    });
4222    workspace_a
4223        .update(cx_a, |workspace, cx| {
4224            let leader_id = *project_a.read(cx).collaborators().keys().next().unwrap();
4225            workspace
4226                .toggle_follow(&workspace::ToggleFollow(leader_id), cx)
4227                .unwrap()
4228        })
4229        .await
4230        .unwrap();
4231    workspace_b.update(cx_b, |workspace, cx| {
4232        workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
4233        assert_ne!(*workspace.active_pane(), pane_b1);
4234    });
4235    workspace_b
4236        .update(cx_b, |workspace, cx| {
4237            let leader_id = *project_b.read(cx).collaborators().keys().next().unwrap();
4238            workspace
4239                .toggle_follow(&workspace::ToggleFollow(leader_id), cx)
4240                .unwrap()
4241        })
4242        .await
4243        .unwrap();
4244
4245    workspace_a
4246        .update(cx_a, |workspace, cx| {
4247            workspace.activate_next_pane(cx);
4248            assert_eq!(*workspace.active_pane(), pane_a1);
4249            workspace.open_path((worktree_id, "3.txt"), true, cx)
4250        })
4251        .await
4252        .unwrap();
4253    workspace_b
4254        .update(cx_b, |workspace, cx| {
4255            workspace.activate_next_pane(cx);
4256            assert_eq!(*workspace.active_pane(), pane_b1);
4257            workspace.open_path((worktree_id, "4.txt"), true, cx)
4258        })
4259        .await
4260        .unwrap();
4261    cx_a.foreground().run_until_parked();
4262
4263    // Ensure leader updates don't change the active pane of followers
4264    workspace_a.read_with(cx_a, |workspace, _| {
4265        assert_eq!(*workspace.active_pane(), pane_a1);
4266    });
4267    workspace_b.read_with(cx_b, |workspace, _| {
4268        assert_eq!(*workspace.active_pane(), pane_b1);
4269    });
4270
4271    // Ensure peers following each other doesn't cause an infinite loop.
4272    assert_eq!(
4273        workspace_a.read_with(cx_a, |workspace, cx| workspace
4274            .active_item(cx)
4275            .unwrap()
4276            .project_path(cx)),
4277        Some((worktree_id, "3.txt").into())
4278    );
4279    workspace_a.update(cx_a, |workspace, cx| {
4280        assert_eq!(
4281            workspace.active_item(cx).unwrap().project_path(cx),
4282            Some((worktree_id, "3.txt").into())
4283        );
4284        workspace.activate_next_pane(cx);
4285        assert_eq!(
4286            workspace.active_item(cx).unwrap().project_path(cx),
4287            Some((worktree_id, "4.txt").into())
4288        );
4289    });
4290    workspace_b.update(cx_b, |workspace, cx| {
4291        assert_eq!(
4292            workspace.active_item(cx).unwrap().project_path(cx),
4293            Some((worktree_id, "4.txt").into())
4294        );
4295        workspace.activate_next_pane(cx);
4296        assert_eq!(
4297            workspace.active_item(cx).unwrap().project_path(cx),
4298            Some((worktree_id, "3.txt").into())
4299        );
4300    });
4301}
4302
4303#[gpui::test(iterations = 10)]
4304async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4305    cx_a.foreground().forbid_parking();
4306
4307    // 2 clients connect to a server.
4308    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
4309    let client_a = server.create_client(cx_a, "user_a").await;
4310    let client_b = server.create_client(cx_b, "user_b").await;
4311    server
4312        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
4313        .await;
4314    cx_a.update(editor::init);
4315    cx_b.update(editor::init);
4316
4317    // Client A shares a project.
4318    client_a
4319        .fs
4320        .insert_tree(
4321            "/a",
4322            json!({
4323                "1.txt": "one",
4324                "2.txt": "two",
4325                "3.txt": "three",
4326            }),
4327        )
4328        .await;
4329    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
4330    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
4331
4332    // Client A opens some editors.
4333    let workspace_a = client_a.build_workspace(&project_a, cx_a);
4334    let _editor_a1 = workspace_a
4335        .update(cx_a, |workspace, cx| {
4336            workspace.open_path((worktree_id, "1.txt"), true, cx)
4337        })
4338        .await
4339        .unwrap()
4340        .downcast::<Editor>()
4341        .unwrap();
4342
4343    // Client B starts following client A.
4344    let workspace_b = client_b.build_workspace(&project_b, cx_b);
4345    let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
4346    let leader_id = project_b.read_with(cx_b, |project, _| {
4347        project.collaborators().values().next().unwrap().peer_id
4348    });
4349    workspace_b
4350        .update(cx_b, |workspace, cx| {
4351            workspace
4352                .toggle_follow(&ToggleFollow(leader_id), cx)
4353                .unwrap()
4354        })
4355        .await
4356        .unwrap();
4357    assert_eq!(
4358        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4359        Some(leader_id)
4360    );
4361    let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
4362        workspace
4363            .active_item(cx)
4364            .unwrap()
4365            .downcast::<Editor>()
4366            .unwrap()
4367    });
4368
4369    // When client B moves, it automatically stops following client A.
4370    editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
4371    assert_eq!(
4372        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4373        None
4374    );
4375
4376    workspace_b
4377        .update(cx_b, |workspace, cx| {
4378            workspace
4379                .toggle_follow(&ToggleFollow(leader_id), cx)
4380                .unwrap()
4381        })
4382        .await
4383        .unwrap();
4384    assert_eq!(
4385        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4386        Some(leader_id)
4387    );
4388
4389    // When client B edits, it automatically stops following client A.
4390    editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
4391    assert_eq!(
4392        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4393        None
4394    );
4395
4396    workspace_b
4397        .update(cx_b, |workspace, cx| {
4398            workspace
4399                .toggle_follow(&ToggleFollow(leader_id), cx)
4400                .unwrap()
4401        })
4402        .await
4403        .unwrap();
4404    assert_eq!(
4405        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4406        Some(leader_id)
4407    );
4408
4409    // When client B scrolls, it automatically stops following client A.
4410    editor_b2.update(cx_b, |editor, cx| {
4411        editor.set_scroll_position(vec2f(0., 3.), cx)
4412    });
4413    assert_eq!(
4414        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4415        None
4416    );
4417
4418    workspace_b
4419        .update(cx_b, |workspace, cx| {
4420            workspace
4421                .toggle_follow(&ToggleFollow(leader_id), cx)
4422                .unwrap()
4423        })
4424        .await
4425        .unwrap();
4426    assert_eq!(
4427        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4428        Some(leader_id)
4429    );
4430
4431    // When client B activates a different pane, it continues following client A in the original pane.
4432    workspace_b.update(cx_b, |workspace, cx| {
4433        workspace.split_pane(pane_b.clone(), SplitDirection::Right, cx)
4434    });
4435    assert_eq!(
4436        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4437        Some(leader_id)
4438    );
4439
4440    workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
4441    assert_eq!(
4442        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4443        Some(leader_id)
4444    );
4445
4446    // When client B activates a different item in the original pane, it automatically stops following client A.
4447    workspace_b
4448        .update(cx_b, |workspace, cx| {
4449            workspace.open_path((worktree_id, "2.txt"), true, cx)
4450        })
4451        .await
4452        .unwrap();
4453    assert_eq!(
4454        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4455        None
4456    );
4457}
4458
4459#[gpui::test(iterations = 10)]
4460async fn test_peers_simultaneously_following_each_other(
4461    deterministic: Arc<Deterministic>,
4462    cx_a: &mut TestAppContext,
4463    cx_b: &mut TestAppContext,
4464) {
4465    deterministic.forbid_parking();
4466
4467    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
4468    let client_a = server.create_client(cx_a, "user_a").await;
4469    let client_b = server.create_client(cx_b, "user_b").await;
4470    server
4471        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
4472        .await;
4473    cx_a.update(editor::init);
4474    cx_b.update(editor::init);
4475
4476    client_a.fs.insert_tree("/a", json!({})).await;
4477    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
4478    let workspace_a = client_a.build_workspace(&project_a, cx_a);
4479
4480    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
4481    let workspace_b = client_b.build_workspace(&project_b, cx_b);
4482
4483    deterministic.run_until_parked();
4484    let client_a_id = project_b.read_with(cx_b, |project, _| {
4485        project.collaborators().values().next().unwrap().peer_id
4486    });
4487    let client_b_id = project_a.read_with(cx_a, |project, _| {
4488        project.collaborators().values().next().unwrap().peer_id
4489    });
4490
4491    let a_follow_b = workspace_a.update(cx_a, |workspace, cx| {
4492        workspace
4493            .toggle_follow(&ToggleFollow(client_b_id), cx)
4494            .unwrap()
4495    });
4496    let b_follow_a = workspace_b.update(cx_b, |workspace, cx| {
4497        workspace
4498            .toggle_follow(&ToggleFollow(client_a_id), cx)
4499            .unwrap()
4500    });
4501
4502    futures::try_join!(a_follow_b, b_follow_a).unwrap();
4503    workspace_a.read_with(cx_a, |workspace, _| {
4504        assert_eq!(
4505            workspace.leader_for_pane(&workspace.active_pane()),
4506            Some(client_b_id)
4507        );
4508    });
4509    workspace_b.read_with(cx_b, |workspace, _| {
4510        assert_eq!(
4511            workspace.leader_for_pane(&workspace.active_pane()),
4512            Some(client_a_id)
4513        );
4514    });
4515}
4516
4517#[gpui::test(iterations = 100)]
4518async fn test_random_collaboration(
4519    cx: &mut TestAppContext,
4520    deterministic: Arc<Deterministic>,
4521    rng: StdRng,
4522) {
4523    deterministic.forbid_parking();
4524    let max_peers = env::var("MAX_PEERS")
4525        .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
4526        .unwrap_or(5);
4527    assert!(max_peers <= 5);
4528
4529    let max_operations = env::var("OPERATIONS")
4530        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
4531        .unwrap_or(10);
4532
4533    let rng = Arc::new(Mutex::new(rng));
4534
4535    let guest_lang_registry = Arc::new(LanguageRegistry::test());
4536    let host_language_registry = Arc::new(LanguageRegistry::test());
4537
4538    let fs = FakeFs::new(cx.background());
4539    fs.insert_tree("/_collab", json!({"init": ""})).await;
4540
4541    let mut server = TestServer::start(cx.foreground(), cx.background()).await;
4542    let db = server.app_state.db.clone();
4543    let host_user_id = db.create_user("host", None, false).await.unwrap();
4544    let mut available_guests = vec![
4545        "guest-1".to_string(),
4546        "guest-2".to_string(),
4547        "guest-3".to_string(),
4548        "guest-4".to_string(),
4549    ];
4550
4551    for username in &available_guests {
4552        let guest_user_id = db.create_user(username, None, false).await.unwrap();
4553        assert_eq!(*username, format!("guest-{}", guest_user_id));
4554        server
4555            .app_state
4556            .db
4557            .send_contact_request(guest_user_id, host_user_id)
4558            .await
4559            .unwrap();
4560        server
4561            .app_state
4562            .db
4563            .respond_to_contact_request(host_user_id, guest_user_id, true)
4564            .await
4565            .unwrap();
4566    }
4567
4568    let mut clients = Vec::new();
4569    let mut user_ids = Vec::new();
4570    let mut op_start_signals = Vec::new();
4571
4572    let mut next_entity_id = 100000;
4573    let mut host_cx = TestAppContext::new(
4574        cx.foreground_platform(),
4575        cx.platform(),
4576        deterministic.build_foreground(next_entity_id),
4577        deterministic.build_background(),
4578        cx.font_cache(),
4579        cx.leak_detector(),
4580        next_entity_id,
4581    );
4582    let host = server.create_client(&mut host_cx, "host").await;
4583    let host_project = host_cx.update(|cx| {
4584        Project::local(
4585            true,
4586            host.client.clone(),
4587            host.user_store.clone(),
4588            host.project_store.clone(),
4589            host_language_registry.clone(),
4590            fs.clone(),
4591            cx,
4592        )
4593    });
4594    let host_project_id = host_project
4595        .update(&mut host_cx, |p, _| p.next_remote_id())
4596        .await;
4597
4598    let (collab_worktree, _) = host_project
4599        .update(&mut host_cx, |project, cx| {
4600            project.find_or_create_local_worktree("/_collab", true, cx)
4601        })
4602        .await
4603        .unwrap();
4604    collab_worktree
4605        .read_with(&host_cx, |tree, _| tree.as_local().unwrap().scan_complete())
4606        .await;
4607
4608    // Set up fake language servers.
4609    let mut language = Language::new(
4610        LanguageConfig {
4611            name: "Rust".into(),
4612            path_suffixes: vec!["rs".to_string()],
4613            ..Default::default()
4614        },
4615        None,
4616    );
4617    let _fake_servers = language
4618        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4619            name: "the-fake-language-server",
4620            capabilities: lsp::LanguageServer::full_capabilities(),
4621            initializer: Some(Box::new({
4622                let rng = rng.clone();
4623                let fs = fs.clone();
4624                let project = host_project.downgrade();
4625                move |fake_server: &mut FakeLanguageServer| {
4626                    fake_server.handle_request::<lsp::request::Completion, _, _>(
4627                        |_, _| async move {
4628                            Ok(Some(lsp::CompletionResponse::Array(vec![
4629                                lsp::CompletionItem {
4630                                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
4631                                        range: lsp::Range::new(
4632                                            lsp::Position::new(0, 0),
4633                                            lsp::Position::new(0, 0),
4634                                        ),
4635                                        new_text: "the-new-text".to_string(),
4636                                    })),
4637                                    ..Default::default()
4638                                },
4639                            ])))
4640                        },
4641                    );
4642
4643                    fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
4644                        |_, _| async move {
4645                            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
4646                                lsp::CodeAction {
4647                                    title: "the-code-action".to_string(),
4648                                    ..Default::default()
4649                                },
4650                            )]))
4651                        },
4652                    );
4653
4654                    fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
4655                        |params, _| async move {
4656                            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
4657                                params.position,
4658                                params.position,
4659                            ))))
4660                        },
4661                    );
4662
4663                    fake_server.handle_request::<lsp::request::GotoDefinition, _, _>({
4664                        let fs = fs.clone();
4665                        let rng = rng.clone();
4666                        move |_, _| {
4667                            let fs = fs.clone();
4668                            let rng = rng.clone();
4669                            async move {
4670                                let files = fs.files().await;
4671                                let mut rng = rng.lock();
4672                                let count = rng.gen_range::<usize, _>(1..3);
4673                                let files = (0..count)
4674                                    .map(|_| files.choose(&mut *rng).unwrap())
4675                                    .collect::<Vec<_>>();
4676                                log::info!("LSP: Returning definitions in files {:?}", &files);
4677                                Ok(Some(lsp::GotoDefinitionResponse::Array(
4678                                    files
4679                                        .into_iter()
4680                                        .map(|file| lsp::Location {
4681                                            uri: lsp::Url::from_file_path(file).unwrap(),
4682                                            range: Default::default(),
4683                                        })
4684                                        .collect(),
4685                                )))
4686                            }
4687                        }
4688                    });
4689
4690                    fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>({
4691                        let rng = rng.clone();
4692                        let project = project.clone();
4693                        move |params, mut cx| {
4694                            let highlights = if let Some(project) = project.upgrade(&cx) {
4695                                project.update(&mut cx, |project, cx| {
4696                                    let path = params
4697                                        .text_document_position_params
4698                                        .text_document
4699                                        .uri
4700                                        .to_file_path()
4701                                        .unwrap();
4702                                    let (worktree, relative_path) =
4703                                        project.find_local_worktree(&path, cx)?;
4704                                    let project_path =
4705                                        ProjectPath::from((worktree.read(cx).id(), relative_path));
4706                                    let buffer =
4707                                        project.get_open_buffer(&project_path, cx)?.read(cx);
4708
4709                                    let mut highlights = Vec::new();
4710                                    let highlight_count = rng.lock().gen_range(1..=5);
4711                                    let mut prev_end = 0;
4712                                    for _ in 0..highlight_count {
4713                                        let range =
4714                                            buffer.random_byte_range(prev_end, &mut *rng.lock());
4715
4716                                        highlights.push(lsp::DocumentHighlight {
4717                                            range: range_to_lsp(range.to_point_utf16(buffer)),
4718                                            kind: Some(lsp::DocumentHighlightKind::READ),
4719                                        });
4720                                        prev_end = range.end;
4721                                    }
4722                                    Some(highlights)
4723                                })
4724                            } else {
4725                                None
4726                            };
4727                            async move { Ok(highlights) }
4728                        }
4729                    });
4730                }
4731            })),
4732            ..Default::default()
4733        }))
4734        .await;
4735    host_language_registry.add(Arc::new(language));
4736
4737    let op_start_signal = futures::channel::mpsc::unbounded();
4738    user_ids.push(host.current_user_id(&host_cx));
4739    op_start_signals.push(op_start_signal.0);
4740    clients.push(host_cx.foreground().spawn(host.simulate_host(
4741        host_project,
4742        op_start_signal.1,
4743        rng.clone(),
4744        host_cx,
4745    )));
4746
4747    let disconnect_host_at = if rng.lock().gen_bool(0.2) {
4748        rng.lock().gen_range(0..max_operations)
4749    } else {
4750        max_operations
4751    };
4752
4753    let mut operations = 0;
4754    while operations < max_operations {
4755        if operations == disconnect_host_at {
4756            server.disconnect_client(user_ids[0]);
4757            deterministic.advance_clock(RECEIVE_TIMEOUT);
4758            drop(op_start_signals);
4759
4760            deterministic.start_waiting();
4761            let mut clients = futures::future::join_all(clients).await;
4762            deterministic.finish_waiting();
4763            deterministic.run_until_parked();
4764
4765            let (host, host_project, mut host_cx, host_err) = clients.remove(0);
4766            if let Some(host_err) = host_err {
4767                log::error!("host error - {:?}", host_err);
4768            }
4769            host_project.read_with(&host_cx, |project, _| assert!(!project.is_shared()));
4770            for (guest, guest_project, mut guest_cx, guest_err) in clients {
4771                if let Some(guest_err) = guest_err {
4772                    log::error!("{} error - {:?}", guest.username, guest_err);
4773                }
4774
4775                let contacts = server
4776                    .app_state
4777                    .db
4778                    .get_contacts(guest.current_user_id(&guest_cx))
4779                    .await
4780                    .unwrap();
4781                let contacts = server
4782                    .store
4783                    .lock()
4784                    .await
4785                    .build_initial_contacts_update(contacts)
4786                    .contacts;
4787                assert!(!contacts
4788                    .iter()
4789                    .flat_map(|contact| &contact.projects)
4790                    .any(|project| project.id == host_project_id));
4791                guest_project.read_with(&guest_cx, |project, _| assert!(project.is_read_only()));
4792                guest_cx.update(|_| drop((guest, guest_project)));
4793            }
4794            host_cx.update(|_| drop((host, host_project)));
4795
4796            return;
4797        }
4798
4799        let distribution = rng.lock().gen_range(0..100);
4800        match distribution {
4801            0..=19 if !available_guests.is_empty() => {
4802                let guest_ix = rng.lock().gen_range(0..available_guests.len());
4803                let guest_username = available_guests.remove(guest_ix);
4804                log::info!("Adding new connection for {}", guest_username);
4805                next_entity_id += 100000;
4806                let mut guest_cx = TestAppContext::new(
4807                    cx.foreground_platform(),
4808                    cx.platform(),
4809                    deterministic.build_foreground(next_entity_id),
4810                    deterministic.build_background(),
4811                    cx.font_cache(),
4812                    cx.leak_detector(),
4813                    next_entity_id,
4814                );
4815
4816                deterministic.start_waiting();
4817                let guest = server.create_client(&mut guest_cx, &guest_username).await;
4818                let guest_project = Project::remote(
4819                    host_project_id,
4820                    guest.client.clone(),
4821                    guest.user_store.clone(),
4822                    guest.project_store.clone(),
4823                    guest_lang_registry.clone(),
4824                    FakeFs::new(cx.background()),
4825                    guest_cx.to_async(),
4826                )
4827                .await
4828                .unwrap();
4829                deterministic.finish_waiting();
4830
4831                let op_start_signal = futures::channel::mpsc::unbounded();
4832                user_ids.push(guest.current_user_id(&guest_cx));
4833                op_start_signals.push(op_start_signal.0);
4834                clients.push(guest_cx.foreground().spawn(guest.simulate_guest(
4835                    guest_username.clone(),
4836                    guest_project,
4837                    op_start_signal.1,
4838                    rng.clone(),
4839                    guest_cx,
4840                )));
4841
4842                log::info!("Added connection for {}", guest_username);
4843                operations += 1;
4844            }
4845            20..=29 if clients.len() > 1 => {
4846                let guest_ix = rng.lock().gen_range(1..clients.len());
4847                log::info!("Removing guest {}", user_ids[guest_ix]);
4848                let removed_guest_id = user_ids.remove(guest_ix);
4849                let guest = clients.remove(guest_ix);
4850                op_start_signals.remove(guest_ix);
4851                server.forbid_connections();
4852                server.disconnect_client(removed_guest_id);
4853                deterministic.advance_clock(RECEIVE_TIMEOUT);
4854                deterministic.start_waiting();
4855                log::info!("Waiting for guest {} to exit...", removed_guest_id);
4856                let (guest, guest_project, mut guest_cx, guest_err) = guest.await;
4857                deterministic.finish_waiting();
4858                server.allow_connections();
4859
4860                if let Some(guest_err) = guest_err {
4861                    log::error!("{} error - {:?}", guest.username, guest_err);
4862                }
4863                guest_project.read_with(&guest_cx, |project, _| assert!(project.is_read_only()));
4864                for user_id in &user_ids {
4865                    let contacts = server.app_state.db.get_contacts(*user_id).await.unwrap();
4866                    let contacts = server
4867                        .store
4868                        .lock()
4869                        .await
4870                        .build_initial_contacts_update(contacts)
4871                        .contacts;
4872                    for contact in contacts {
4873                        if contact.online {
4874                            assert_ne!(
4875                                contact.user_id, removed_guest_id.0 as u64,
4876                                "removed guest is still a contact of another peer"
4877                            );
4878                        }
4879                        for project in contact.projects {
4880                            for project_guest_id in project.guests {
4881                                assert_ne!(
4882                                    project_guest_id, removed_guest_id.0 as u64,
4883                                    "removed guest appears as still participating on a project"
4884                                );
4885                            }
4886                        }
4887                    }
4888                }
4889
4890                log::info!("{} removed", guest.username);
4891                available_guests.push(guest.username.clone());
4892                guest_cx.update(|_| drop((guest, guest_project)));
4893
4894                operations += 1;
4895            }
4896            _ => {
4897                while operations < max_operations && rng.lock().gen_bool(0.7) {
4898                    op_start_signals
4899                        .choose(&mut *rng.lock())
4900                        .unwrap()
4901                        .unbounded_send(())
4902                        .unwrap();
4903                    operations += 1;
4904                }
4905
4906                if rng.lock().gen_bool(0.8) {
4907                    deterministic.run_until_parked();
4908                }
4909            }
4910        }
4911    }
4912
4913    drop(op_start_signals);
4914    deterministic.start_waiting();
4915    let mut clients = futures::future::join_all(clients).await;
4916    deterministic.finish_waiting();
4917    deterministic.run_until_parked();
4918
4919    let (host_client, host_project, mut host_cx, host_err) = clients.remove(0);
4920    if let Some(host_err) = host_err {
4921        panic!("host error - {:?}", host_err);
4922    }
4923    let host_worktree_snapshots = host_project.read_with(&host_cx, |project, cx| {
4924        project
4925            .worktrees(cx)
4926            .map(|worktree| {
4927                let snapshot = worktree.read(cx).snapshot();
4928                (snapshot.id(), snapshot)
4929            })
4930            .collect::<BTreeMap<_, _>>()
4931    });
4932
4933    host_project.read_with(&host_cx, |project, cx| project.check_invariants(cx));
4934
4935    for (guest_client, guest_project, mut guest_cx, guest_err) in clients.into_iter() {
4936        if let Some(guest_err) = guest_err {
4937            panic!("{} error - {:?}", guest_client.username, guest_err);
4938        }
4939        let worktree_snapshots = guest_project.read_with(&guest_cx, |project, cx| {
4940            project
4941                .worktrees(cx)
4942                .map(|worktree| {
4943                    let worktree = worktree.read(cx);
4944                    (worktree.id(), worktree.snapshot())
4945                })
4946                .collect::<BTreeMap<_, _>>()
4947        });
4948
4949        assert_eq!(
4950            worktree_snapshots.keys().collect::<Vec<_>>(),
4951            host_worktree_snapshots.keys().collect::<Vec<_>>(),
4952            "{} has different worktrees than the host",
4953            guest_client.username
4954        );
4955        for (id, host_snapshot) in &host_worktree_snapshots {
4956            let guest_snapshot = &worktree_snapshots[id];
4957            assert_eq!(
4958                guest_snapshot.root_name(),
4959                host_snapshot.root_name(),
4960                "{} has different root name than the host for worktree {}",
4961                guest_client.username,
4962                id
4963            );
4964            assert_eq!(
4965                guest_snapshot.entries(false).collect::<Vec<_>>(),
4966                host_snapshot.entries(false).collect::<Vec<_>>(),
4967                "{} has different snapshot than the host for worktree {}",
4968                guest_client.username,
4969                id
4970            );
4971            assert_eq!(guest_snapshot.scan_id(), host_snapshot.scan_id());
4972        }
4973
4974        guest_project.read_with(&guest_cx, |project, cx| project.check_invariants(cx));
4975
4976        for guest_buffer in &guest_client.buffers {
4977            let buffer_id = guest_buffer.read_with(&guest_cx, |buffer, _| buffer.remote_id());
4978            let host_buffer = host_project.read_with(&host_cx, |project, cx| {
4979                project.buffer_for_id(buffer_id, cx).expect(&format!(
4980                    "host does not have buffer for guest:{}, peer:{}, id:{}",
4981                    guest_client.username, guest_client.peer_id, buffer_id
4982                ))
4983            });
4984            let path =
4985                host_buffer.read_with(&host_cx, |buffer, cx| buffer.file().unwrap().full_path(cx));
4986
4987            assert_eq!(
4988                guest_buffer.read_with(&guest_cx, |buffer, _| buffer.deferred_ops_len()),
4989                0,
4990                "{}, buffer {}, path {:?} has deferred operations",
4991                guest_client.username,
4992                buffer_id,
4993                path,
4994            );
4995            assert_eq!(
4996                guest_buffer.read_with(&guest_cx, |buffer, _| buffer.text()),
4997                host_buffer.read_with(&host_cx, |buffer, _| buffer.text()),
4998                "{}, buffer {}, path {:?}, differs from the host's buffer",
4999                guest_client.username,
5000                buffer_id,
5001                path
5002            );
5003        }
5004
5005        guest_cx.update(|_| drop((guest_project, guest_client)));
5006    }
5007
5008    host_cx.update(|_| drop((host_client, host_project)));
5009}
5010
5011struct TestServer {
5012    peer: Arc<Peer>,
5013    app_state: Arc<AppState>,
5014    server: Arc<Server>,
5015    foreground: Rc<executor::Foreground>,
5016    notifications: mpsc::UnboundedReceiver<()>,
5017    connection_killers: Arc<Mutex<HashMap<UserId, Arc<AtomicBool>>>>,
5018    forbid_connections: Arc<AtomicBool>,
5019    _test_db: TestDb,
5020}
5021
5022impl TestServer {
5023    async fn start(
5024        foreground: Rc<executor::Foreground>,
5025        background: Arc<executor::Background>,
5026    ) -> Self {
5027        let test_db = TestDb::fake(background.clone());
5028        let app_state = Self::build_app_state(&test_db).await;
5029        let peer = Peer::new();
5030        let notifications = mpsc::unbounded();
5031        let server = Server::new(app_state.clone(), Some(notifications.0));
5032        Self {
5033            peer,
5034            app_state,
5035            server,
5036            foreground,
5037            notifications: notifications.1,
5038            connection_killers: Default::default(),
5039            forbid_connections: Default::default(),
5040            _test_db: test_db,
5041        }
5042    }
5043
5044    async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient {
5045        cx.update(|cx| {
5046            let mut settings = Settings::test(cx);
5047            settings.projects_online_by_default = false;
5048            cx.set_global(settings);
5049        });
5050
5051        let http = FakeHttpClient::with_404_response();
5052        let user_id = if let Ok(Some(user)) = self.app_state.db.get_user_by_github_login(name).await
5053        {
5054            user.id
5055        } else {
5056            self.app_state
5057                .db
5058                .create_user(name, None, false)
5059                .await
5060                .unwrap()
5061        };
5062        let client_name = name.to_string();
5063        let mut client = Client::new(http.clone());
5064        let server = self.server.clone();
5065        let db = self.app_state.db.clone();
5066        let connection_killers = self.connection_killers.clone();
5067        let forbid_connections = self.forbid_connections.clone();
5068        let (connection_id_tx, mut connection_id_rx) = mpsc::channel(16);
5069
5070        Arc::get_mut(&mut client)
5071            .unwrap()
5072            .set_id(user_id.0 as usize)
5073            .override_authenticate(move |cx| {
5074                cx.spawn(|_| async move {
5075                    let access_token = "the-token".to_string();
5076                    Ok(Credentials {
5077                        user_id: user_id.0 as u64,
5078                        access_token,
5079                    })
5080                })
5081            })
5082            .override_establish_connection(move |credentials, cx| {
5083                assert_eq!(credentials.user_id, user_id.0 as u64);
5084                assert_eq!(credentials.access_token, "the-token");
5085
5086                let server = server.clone();
5087                let db = db.clone();
5088                let connection_killers = connection_killers.clone();
5089                let forbid_connections = forbid_connections.clone();
5090                let client_name = client_name.clone();
5091                let connection_id_tx = connection_id_tx.clone();
5092                cx.spawn(move |cx| async move {
5093                    if forbid_connections.load(SeqCst) {
5094                        Err(EstablishConnectionError::other(anyhow!(
5095                            "server is forbidding connections"
5096                        )))
5097                    } else {
5098                        let (client_conn, server_conn, killed) =
5099                            Connection::in_memory(cx.background());
5100                        connection_killers.lock().insert(user_id, killed);
5101                        let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
5102                        cx.background()
5103                            .spawn(server.handle_connection(
5104                                server_conn,
5105                                client_name,
5106                                user,
5107                                Some(connection_id_tx),
5108                                cx.background(),
5109                            ))
5110                            .detach();
5111                        Ok(client_conn)
5112                    }
5113                })
5114            });
5115
5116        let fs = FakeFs::new(cx.background());
5117        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
5118        let project_store = cx.add_model(|_| ProjectStore::new(project::Db::open_fake()));
5119        let app_state = Arc::new(workspace::AppState {
5120            client: client.clone(),
5121            user_store: user_store.clone(),
5122            project_store: project_store.clone(),
5123            languages: Arc::new(LanguageRegistry::new(Task::ready(()))),
5124            themes: ThemeRegistry::new((), cx.font_cache()),
5125            fs: fs.clone(),
5126            build_window_options: || Default::default(),
5127            initialize_workspace: |_, _, _| unimplemented!(),
5128        });
5129
5130        Channel::init(&client);
5131        Project::init(&client);
5132        cx.update(|cx| workspace::init(app_state.clone(), cx));
5133
5134        client
5135            .authenticate_and_connect(false, &cx.to_async())
5136            .await
5137            .unwrap();
5138        let peer_id = PeerId(connection_id_rx.next().await.unwrap().0);
5139
5140        let client = TestClient {
5141            client,
5142            peer_id,
5143            username: name.to_string(),
5144            user_store,
5145            project_store,
5146            fs,
5147            language_registry: Arc::new(LanguageRegistry::test()),
5148            buffers: Default::default(),
5149        };
5150        client.wait_for_current_user(cx).await;
5151        client
5152    }
5153
5154    fn disconnect_client(&self, user_id: UserId) {
5155        self.connection_killers
5156            .lock()
5157            .remove(&user_id)
5158            .unwrap()
5159            .store(true, SeqCst);
5160    }
5161
5162    fn forbid_connections(&self) {
5163        self.forbid_connections.store(true, SeqCst);
5164    }
5165
5166    fn allow_connections(&self) {
5167        self.forbid_connections.store(false, SeqCst);
5168    }
5169
5170    async fn make_contacts(&self, mut clients: Vec<(&TestClient, &mut TestAppContext)>) {
5171        while let Some((client_a, cx_a)) = clients.pop() {
5172            for (client_b, cx_b) in &mut clients {
5173                client_a
5174                    .user_store
5175                    .update(cx_a, |store, cx| {
5176                        store.request_contact(client_b.user_id().unwrap(), cx)
5177                    })
5178                    .await
5179                    .unwrap();
5180                cx_a.foreground().run_until_parked();
5181                client_b
5182                    .user_store
5183                    .update(*cx_b, |store, cx| {
5184                        store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
5185                    })
5186                    .await
5187                    .unwrap();
5188            }
5189        }
5190    }
5191
5192    async fn build_app_state(test_db: &TestDb) -> Arc<AppState> {
5193        Arc::new(AppState {
5194            db: test_db.db().clone(),
5195            api_token: Default::default(),
5196            invite_link_prefix: Default::default(),
5197        })
5198    }
5199
5200    async fn condition<F>(&mut self, mut predicate: F)
5201    where
5202        F: FnMut(&Store) -> bool,
5203    {
5204        assert!(
5205            self.foreground.parking_forbidden(),
5206            "you must call forbid_parking to use server conditions so we don't block indefinitely"
5207        );
5208        while !(predicate)(&*self.server.store.lock().await) {
5209            self.foreground.start_waiting();
5210            self.notifications.next().await;
5211            self.foreground.finish_waiting();
5212        }
5213    }
5214}
5215
5216impl Deref for TestServer {
5217    type Target = Server;
5218
5219    fn deref(&self) -> &Self::Target {
5220        &self.server
5221    }
5222}
5223
5224impl Drop for TestServer {
5225    fn drop(&mut self) {
5226        self.peer.reset();
5227    }
5228}
5229
5230struct TestClient {
5231    client: Arc<Client>,
5232    username: String,
5233    pub peer_id: PeerId,
5234    pub user_store: ModelHandle<UserStore>,
5235    pub project_store: ModelHandle<ProjectStore>,
5236    language_registry: Arc<LanguageRegistry>,
5237    fs: Arc<FakeFs>,
5238    buffers: HashSet<ModelHandle<language::Buffer>>,
5239}
5240
5241impl Deref for TestClient {
5242    type Target = Arc<Client>;
5243
5244    fn deref(&self) -> &Self::Target {
5245        &self.client
5246    }
5247}
5248
5249struct ContactsSummary {
5250    pub current: Vec<String>,
5251    pub outgoing_requests: Vec<String>,
5252    pub incoming_requests: Vec<String>,
5253}
5254
5255impl TestClient {
5256    pub fn current_user_id(&self, cx: &TestAppContext) -> UserId {
5257        UserId::from_proto(
5258            self.user_store
5259                .read_with(cx, |user_store, _| user_store.current_user().unwrap().id),
5260        )
5261    }
5262
5263    async fn wait_for_current_user(&self, cx: &TestAppContext) {
5264        let mut authed_user = self
5265            .user_store
5266            .read_with(cx, |user_store, _| user_store.watch_current_user());
5267        while authed_user.next().await.unwrap().is_none() {}
5268    }
5269
5270    async fn clear_contacts(&self, cx: &mut TestAppContext) {
5271        self.user_store
5272            .update(cx, |store, _| store.clear_contacts())
5273            .await;
5274    }
5275
5276    fn summarize_contacts(&self, cx: &TestAppContext) -> ContactsSummary {
5277        self.user_store.read_with(cx, |store, _| ContactsSummary {
5278            current: store
5279                .contacts()
5280                .iter()
5281                .map(|contact| contact.user.github_login.clone())
5282                .collect(),
5283            outgoing_requests: store
5284                .outgoing_contact_requests()
5285                .iter()
5286                .map(|user| user.github_login.clone())
5287                .collect(),
5288            incoming_requests: store
5289                .incoming_contact_requests()
5290                .iter()
5291                .map(|user| user.github_login.clone())
5292                .collect(),
5293        })
5294    }
5295
5296    async fn build_local_project(
5297        &self,
5298        root_path: impl AsRef<Path>,
5299        cx: &mut TestAppContext,
5300    ) -> (ModelHandle<Project>, WorktreeId) {
5301        let project = cx.update(|cx| {
5302            Project::local(
5303                true,
5304                self.client.clone(),
5305                self.user_store.clone(),
5306                self.project_store.clone(),
5307                self.language_registry.clone(),
5308                self.fs.clone(),
5309                cx,
5310            )
5311        });
5312        let (worktree, _) = project
5313            .update(cx, |p, cx| {
5314                p.find_or_create_local_worktree(root_path, true, cx)
5315            })
5316            .await
5317            .unwrap();
5318        worktree
5319            .read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
5320            .await;
5321        project
5322            .update(cx, |project, _| project.next_remote_id())
5323            .await;
5324        (project, worktree.read_with(cx, |tree, _| tree.id()))
5325    }
5326
5327    async fn build_remote_project(
5328        &self,
5329        host_project: &ModelHandle<Project>,
5330        host_cx: &mut TestAppContext,
5331        guest_cx: &mut TestAppContext,
5332    ) -> ModelHandle<Project> {
5333        let host_project_id = host_project
5334            .read_with(host_cx, |project, _| project.next_remote_id())
5335            .await;
5336        let guest_user_id = self.user_id().unwrap();
5337        let languages = host_project.read_with(host_cx, |project, _| project.languages().clone());
5338        let project_b = guest_cx.spawn(|cx| {
5339            Project::remote(
5340                host_project_id,
5341                self.client.clone(),
5342                self.user_store.clone(),
5343                self.project_store.clone(),
5344                languages,
5345                FakeFs::new(cx.background()),
5346                cx,
5347            )
5348        });
5349        host_cx.foreground().run_until_parked();
5350        host_project.update(host_cx, |project, cx| {
5351            project.respond_to_join_request(guest_user_id, true, cx)
5352        });
5353        let project = project_b.await.unwrap();
5354        project
5355    }
5356
5357    fn build_workspace(
5358        &self,
5359        project: &ModelHandle<Project>,
5360        cx: &mut TestAppContext,
5361    ) -> ViewHandle<Workspace> {
5362        let (window_id, _) = cx.add_window(|_| EmptyView);
5363        cx.add_view(window_id, |cx| Workspace::new(project.clone(), cx))
5364    }
5365
5366    async fn simulate_host(
5367        mut self,
5368        project: ModelHandle<Project>,
5369        op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
5370        rng: Arc<Mutex<StdRng>>,
5371        mut cx: TestAppContext,
5372    ) -> (
5373        Self,
5374        ModelHandle<Project>,
5375        TestAppContext,
5376        Option<anyhow::Error>,
5377    ) {
5378        async fn simulate_host_internal(
5379            client: &mut TestClient,
5380            project: ModelHandle<Project>,
5381            mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
5382            rng: Arc<Mutex<StdRng>>,
5383            cx: &mut TestAppContext,
5384        ) -> anyhow::Result<()> {
5385            let fs = project.read_with(cx, |project, _| project.fs().clone());
5386
5387            cx.update(|cx| {
5388                cx.subscribe(&project, move |project, event, cx| {
5389                    if let project::Event::ContactRequestedJoin(user) = event {
5390                        log::info!("Host: accepting join request from {}", user.github_login);
5391                        project.update(cx, |project, cx| {
5392                            project.respond_to_join_request(user.id, true, cx)
5393                        });
5394                    }
5395                })
5396                .detach();
5397            });
5398
5399            while op_start_signal.next().await.is_some() {
5400                let distribution = rng.lock().gen_range::<usize, _>(0..100);
5401                let files = fs.as_fake().files().await;
5402                match distribution {
5403                    0..=19 if !files.is_empty() => {
5404                        let path = files.choose(&mut *rng.lock()).unwrap();
5405                        let mut path = path.as_path();
5406                        while let Some(parent_path) = path.parent() {
5407                            path = parent_path;
5408                            if rng.lock().gen() {
5409                                break;
5410                            }
5411                        }
5412
5413                        log::info!("Host: find/create local worktree {:?}", path);
5414                        let find_or_create_worktree = project.update(cx, |project, cx| {
5415                            project.find_or_create_local_worktree(path, true, cx)
5416                        });
5417                        if rng.lock().gen() {
5418                            cx.background().spawn(find_or_create_worktree).detach();
5419                        } else {
5420                            find_or_create_worktree.await?;
5421                        }
5422                    }
5423                    20..=79 if !files.is_empty() => {
5424                        let buffer = if client.buffers.is_empty() || rng.lock().gen() {
5425                            let file = files.choose(&mut *rng.lock()).unwrap();
5426                            let (worktree, path) = project
5427                                .update(cx, |project, cx| {
5428                                    project.find_or_create_local_worktree(file.clone(), true, cx)
5429                                })
5430                                .await?;
5431                            let project_path =
5432                                worktree.read_with(cx, |worktree, _| (worktree.id(), path));
5433                            log::info!(
5434                                "Host: opening path {:?}, worktree {}, relative_path {:?}",
5435                                file,
5436                                project_path.0,
5437                                project_path.1
5438                            );
5439                            let buffer = project
5440                                .update(cx, |project, cx| project.open_buffer(project_path, cx))
5441                                .await
5442                                .unwrap();
5443                            client.buffers.insert(buffer.clone());
5444                            buffer
5445                        } else {
5446                            client
5447                                .buffers
5448                                .iter()
5449                                .choose(&mut *rng.lock())
5450                                .unwrap()
5451                                .clone()
5452                        };
5453
5454                        if rng.lock().gen_bool(0.1) {
5455                            cx.update(|cx| {
5456                                log::info!(
5457                                    "Host: dropping buffer {:?}",
5458                                    buffer.read(cx).file().unwrap().full_path(cx)
5459                                );
5460                                client.buffers.remove(&buffer);
5461                                drop(buffer);
5462                            });
5463                        } else {
5464                            buffer.update(cx, |buffer, cx| {
5465                                log::info!(
5466                                    "Host: updating buffer {:?} ({})",
5467                                    buffer.file().unwrap().full_path(cx),
5468                                    buffer.remote_id()
5469                                );
5470
5471                                if rng.lock().gen_bool(0.7) {
5472                                    buffer.randomly_edit(&mut *rng.lock(), 5, cx);
5473                                } else {
5474                                    buffer.randomly_undo_redo(&mut *rng.lock(), cx);
5475                                }
5476                            });
5477                        }
5478                    }
5479                    _ => loop {
5480                        let path_component_count = rng.lock().gen_range::<usize, _>(1..=5);
5481                        let mut path = PathBuf::new();
5482                        path.push("/");
5483                        for _ in 0..path_component_count {
5484                            let letter = rng.lock().gen_range(b'a'..=b'z');
5485                            path.push(std::str::from_utf8(&[letter]).unwrap());
5486                        }
5487                        path.set_extension("rs");
5488                        let parent_path = path.parent().unwrap();
5489
5490                        log::info!("Host: creating file {:?}", path,);
5491
5492                        if fs.create_dir(&parent_path).await.is_ok()
5493                            && fs.create_file(&path, Default::default()).await.is_ok()
5494                        {
5495                            break;
5496                        } else {
5497                            log::info!("Host: cannot create file");
5498                        }
5499                    },
5500                }
5501
5502                cx.background().simulate_random_delay().await;
5503            }
5504
5505            Ok(())
5506        }
5507
5508        let result =
5509            simulate_host_internal(&mut self, project.clone(), op_start_signal, rng, &mut cx).await;
5510        log::info!("Host done");
5511        (self, project, cx, result.err())
5512    }
5513
5514    pub async fn simulate_guest(
5515        mut self,
5516        guest_username: String,
5517        project: ModelHandle<Project>,
5518        op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
5519        rng: Arc<Mutex<StdRng>>,
5520        mut cx: TestAppContext,
5521    ) -> (
5522        Self,
5523        ModelHandle<Project>,
5524        TestAppContext,
5525        Option<anyhow::Error>,
5526    ) {
5527        async fn simulate_guest_internal(
5528            client: &mut TestClient,
5529            guest_username: &str,
5530            project: ModelHandle<Project>,
5531            mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
5532            rng: Arc<Mutex<StdRng>>,
5533            cx: &mut TestAppContext,
5534        ) -> anyhow::Result<()> {
5535            while op_start_signal.next().await.is_some() {
5536                let buffer = if client.buffers.is_empty() || rng.lock().gen() {
5537                    let worktree = if let Some(worktree) = project.read_with(cx, |project, cx| {
5538                        project
5539                            .worktrees(&cx)
5540                            .filter(|worktree| {
5541                                let worktree = worktree.read(cx);
5542                                worktree.is_visible()
5543                                    && worktree.entries(false).any(|e| e.is_file())
5544                            })
5545                            .choose(&mut *rng.lock())
5546                    }) {
5547                        worktree
5548                    } else {
5549                        cx.background().simulate_random_delay().await;
5550                        continue;
5551                    };
5552
5553                    let (worktree_root_name, project_path) =
5554                        worktree.read_with(cx, |worktree, _| {
5555                            let entry = worktree
5556                                .entries(false)
5557                                .filter(|e| e.is_file())
5558                                .choose(&mut *rng.lock())
5559                                .unwrap();
5560                            (
5561                                worktree.root_name().to_string(),
5562                                (worktree.id(), entry.path.clone()),
5563                            )
5564                        });
5565                    log::info!(
5566                        "{}: opening path {:?} in worktree {} ({})",
5567                        guest_username,
5568                        project_path.1,
5569                        project_path.0,
5570                        worktree_root_name,
5571                    );
5572                    let buffer = project
5573                        .update(cx, |project, cx| {
5574                            project.open_buffer(project_path.clone(), cx)
5575                        })
5576                        .await?;
5577                    log::info!(
5578                        "{}: opened path {:?} in worktree {} ({}) with buffer id {}",
5579                        guest_username,
5580                        project_path.1,
5581                        project_path.0,
5582                        worktree_root_name,
5583                        buffer.read_with(cx, |buffer, _| buffer.remote_id())
5584                    );
5585                    client.buffers.insert(buffer.clone());
5586                    buffer
5587                } else {
5588                    client
5589                        .buffers
5590                        .iter()
5591                        .choose(&mut *rng.lock())
5592                        .unwrap()
5593                        .clone()
5594                };
5595
5596                let choice = rng.lock().gen_range(0..100);
5597                match choice {
5598                    0..=9 => {
5599                        cx.update(|cx| {
5600                            log::info!(
5601                                "{}: dropping buffer {:?}",
5602                                guest_username,
5603                                buffer.read(cx).file().unwrap().full_path(cx)
5604                            );
5605                            client.buffers.remove(&buffer);
5606                            drop(buffer);
5607                        });
5608                    }
5609                    10..=19 => {
5610                        let completions = project.update(cx, |project, cx| {
5611                            log::info!(
5612                                "{}: requesting completions for buffer {} ({:?})",
5613                                guest_username,
5614                                buffer.read(cx).remote_id(),
5615                                buffer.read(cx).file().unwrap().full_path(cx)
5616                            );
5617                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
5618                            project.completions(&buffer, offset, cx)
5619                        });
5620                        let completions = cx.background().spawn(async move {
5621                            completions
5622                                .await
5623                                .map_err(|err| anyhow!("completions request failed: {:?}", err))
5624                        });
5625                        if rng.lock().gen_bool(0.3) {
5626                            log::info!("{}: detaching completions request", guest_username);
5627                            cx.update(|cx| completions.detach_and_log_err(cx));
5628                        } else {
5629                            completions.await?;
5630                        }
5631                    }
5632                    20..=29 => {
5633                        let code_actions = project.update(cx, |project, cx| {
5634                            log::info!(
5635                                "{}: requesting code actions for buffer {} ({:?})",
5636                                guest_username,
5637                                buffer.read(cx).remote_id(),
5638                                buffer.read(cx).file().unwrap().full_path(cx)
5639                            );
5640                            let range = buffer.read(cx).random_byte_range(0, &mut *rng.lock());
5641                            project.code_actions(&buffer, range, cx)
5642                        });
5643                        let code_actions = cx.background().spawn(async move {
5644                            code_actions
5645                                .await
5646                                .map_err(|err| anyhow!("code actions request failed: {:?}", err))
5647                        });
5648                        if rng.lock().gen_bool(0.3) {
5649                            log::info!("{}: detaching code actions request", guest_username);
5650                            cx.update(|cx| code_actions.detach_and_log_err(cx));
5651                        } else {
5652                            code_actions.await?;
5653                        }
5654                    }
5655                    30..=39 if buffer.read_with(cx, |buffer, _| buffer.is_dirty()) => {
5656                        let (requested_version, save) = buffer.update(cx, |buffer, cx| {
5657                            log::info!(
5658                                "{}: saving buffer {} ({:?})",
5659                                guest_username,
5660                                buffer.remote_id(),
5661                                buffer.file().unwrap().full_path(cx)
5662                            );
5663                            (buffer.version(), buffer.save(cx))
5664                        });
5665                        let save = cx.background().spawn(async move {
5666                            let (saved_version, _, _) = save
5667                                .await
5668                                .map_err(|err| anyhow!("save request failed: {:?}", err))?;
5669                            assert!(saved_version.observed_all(&requested_version));
5670                            Ok::<_, anyhow::Error>(())
5671                        });
5672                        if rng.lock().gen_bool(0.3) {
5673                            log::info!("{}: detaching save request", guest_username);
5674                            cx.update(|cx| save.detach_and_log_err(cx));
5675                        } else {
5676                            save.await?;
5677                        }
5678                    }
5679                    40..=44 => {
5680                        let prepare_rename = project.update(cx, |project, cx| {
5681                            log::info!(
5682                                "{}: preparing rename for buffer {} ({:?})",
5683                                guest_username,
5684                                buffer.read(cx).remote_id(),
5685                                buffer.read(cx).file().unwrap().full_path(cx)
5686                            );
5687                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
5688                            project.prepare_rename(buffer, offset, cx)
5689                        });
5690                        let prepare_rename = cx.background().spawn(async move {
5691                            prepare_rename
5692                                .await
5693                                .map_err(|err| anyhow!("prepare rename request failed: {:?}", err))
5694                        });
5695                        if rng.lock().gen_bool(0.3) {
5696                            log::info!("{}: detaching prepare rename request", guest_username);
5697                            cx.update(|cx| prepare_rename.detach_and_log_err(cx));
5698                        } else {
5699                            prepare_rename.await?;
5700                        }
5701                    }
5702                    45..=49 => {
5703                        let definitions = project.update(cx, |project, cx| {
5704                            log::info!(
5705                                "{}: requesting definitions for buffer {} ({:?})",
5706                                guest_username,
5707                                buffer.read(cx).remote_id(),
5708                                buffer.read(cx).file().unwrap().full_path(cx)
5709                            );
5710                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
5711                            project.definition(&buffer, offset, cx)
5712                        });
5713                        let definitions = cx.background().spawn(async move {
5714                            definitions
5715                                .await
5716                                .map_err(|err| anyhow!("definitions request failed: {:?}", err))
5717                        });
5718                        if rng.lock().gen_bool(0.3) {
5719                            log::info!("{}: detaching definitions request", guest_username);
5720                            cx.update(|cx| definitions.detach_and_log_err(cx));
5721                        } else {
5722                            client.buffers.extend(
5723                                definitions.await?.into_iter().map(|loc| loc.target.buffer),
5724                            );
5725                        }
5726                    }
5727                    50..=54 => {
5728                        let highlights = project.update(cx, |project, cx| {
5729                            log::info!(
5730                                "{}: requesting highlights for buffer {} ({:?})",
5731                                guest_username,
5732                                buffer.read(cx).remote_id(),
5733                                buffer.read(cx).file().unwrap().full_path(cx)
5734                            );
5735                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
5736                            project.document_highlights(&buffer, offset, cx)
5737                        });
5738                        let highlights = cx.background().spawn(async move {
5739                            highlights
5740                                .await
5741                                .map_err(|err| anyhow!("highlights request failed: {:?}", err))
5742                        });
5743                        if rng.lock().gen_bool(0.3) {
5744                            log::info!("{}: detaching highlights request", guest_username);
5745                            cx.update(|cx| highlights.detach_and_log_err(cx));
5746                        } else {
5747                            highlights.await?;
5748                        }
5749                    }
5750                    55..=59 => {
5751                        let search = project.update(cx, |project, cx| {
5752                            let query = rng.lock().gen_range('a'..='z');
5753                            log::info!("{}: project-wide search {:?}", guest_username, query);
5754                            project.search(SearchQuery::text(query, false, false), cx)
5755                        });
5756                        let search = cx.background().spawn(async move {
5757                            search
5758                                .await
5759                                .map_err(|err| anyhow!("search request failed: {:?}", err))
5760                        });
5761                        if rng.lock().gen_bool(0.3) {
5762                            log::info!("{}: detaching search request", guest_username);
5763                            cx.update(|cx| search.detach_and_log_err(cx));
5764                        } else {
5765                            client.buffers.extend(search.await?.into_keys());
5766                        }
5767                    }
5768                    60..=69 => {
5769                        let worktree = project
5770                            .read_with(cx, |project, cx| {
5771                                project
5772                                    .worktrees(&cx)
5773                                    .filter(|worktree| {
5774                                        let worktree = worktree.read(cx);
5775                                        worktree.is_visible()
5776                                            && worktree.entries(false).any(|e| e.is_file())
5777                                            && worktree.root_entry().map_or(false, |e| e.is_dir())
5778                                    })
5779                                    .choose(&mut *rng.lock())
5780                            })
5781                            .unwrap();
5782                        let (worktree_id, worktree_root_name) = worktree
5783                            .read_with(cx, |worktree, _| {
5784                                (worktree.id(), worktree.root_name().to_string())
5785                            });
5786
5787                        let mut new_name = String::new();
5788                        for _ in 0..10 {
5789                            let letter = rng.lock().gen_range('a'..='z');
5790                            new_name.push(letter);
5791                        }
5792                        let mut new_path = PathBuf::new();
5793                        new_path.push(new_name);
5794                        new_path.set_extension("rs");
5795                        log::info!(
5796                            "{}: creating {:?} in worktree {} ({})",
5797                            guest_username,
5798                            new_path,
5799                            worktree_id,
5800                            worktree_root_name,
5801                        );
5802                        project
5803                            .update(cx, |project, cx| {
5804                                project.create_entry((worktree_id, new_path), false, cx)
5805                            })
5806                            .unwrap()
5807                            .await?;
5808                    }
5809                    _ => {
5810                        buffer.update(cx, |buffer, cx| {
5811                            log::info!(
5812                                "{}: updating buffer {} ({:?})",
5813                                guest_username,
5814                                buffer.remote_id(),
5815                                buffer.file().unwrap().full_path(cx)
5816                            );
5817                            if rng.lock().gen_bool(0.7) {
5818                                buffer.randomly_edit(&mut *rng.lock(), 5, cx);
5819                            } else {
5820                                buffer.randomly_undo_redo(&mut *rng.lock(), cx);
5821                            }
5822                        });
5823                    }
5824                }
5825                cx.background().simulate_random_delay().await;
5826            }
5827            Ok(())
5828        }
5829
5830        let result = simulate_guest_internal(
5831            &mut self,
5832            &guest_username,
5833            project.clone(),
5834            op_start_signal,
5835            rng,
5836            &mut cx,
5837        )
5838        .await;
5839        log::info!("{}: done", guest_username);
5840
5841        (self, project, cx, result.err())
5842    }
5843}
5844
5845impl Drop for TestClient {
5846    fn drop(&mut self) {
5847        self.client.tear_down();
5848    }
5849}
5850
5851impl Executor for Arc<gpui::executor::Background> {
5852    type Sleep = gpui::executor::Timer;
5853
5854    fn spawn_detached<F: 'static + Send + Future<Output = ()>>(&self, future: F) {
5855        self.spawn(future).detach();
5856    }
5857
5858    fn sleep(&self, duration: Duration) -> Self::Sleep {
5859        self.as_ref().timer(duration)
5860    }
5861}
5862
5863fn channel_messages(channel: &Channel) -> Vec<(String, String, bool)> {
5864    channel
5865        .messages()
5866        .cursor::<()>()
5867        .map(|m| {
5868            (
5869                m.sender.github_login.clone(),
5870                m.body.clone(),
5871                m.is_pending(),
5872            )
5873        })
5874        .collect()
5875}
5876
5877struct EmptyView;
5878
5879impl gpui::Entity for EmptyView {
5880    type Event = ();
5881}
5882
5883impl gpui::View for EmptyView {
5884    fn ui_name() -> &'static str {
5885        "empty view"
5886    }
5887
5888    fn render(&mut self, _: &mut gpui::RenderContext<Self>) -> gpui::ElementBox {
5889        gpui::Element::boxed(gpui::elements::Empty::new())
5890    }
5891}