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