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