remote_editing_tests.rs

   1/// todo(windows)
   2/// The tests in this file assume that server_cx is running on Windows too.
   3/// We neead to find a way to test Windows-Non-Windows interactions.
   4use crate::headless_project::HeadlessProject;
   5use client::{Client, UserStore};
   6use clock::FakeSystemClock;
   7use dap::DapRegistry;
   8use extension::ExtensionHostProxy;
   9use fs::{FakeFs, Fs};
  10use gpui::{AppContext as _, Entity, SemanticVersion, TestAppContext};
  11use http_client::{BlockedHttpClient, FakeHttpClient};
  12use language::{
  13    Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LineEnding,
  14    language_settings::{AllLanguageSettings, language_settings},
  15};
  16use lsp::{CompletionContext, CompletionResponse, CompletionTriggerKind, LanguageServerName};
  17use node_runtime::NodeRuntime;
  18use project::{
  19    Project, ProjectPath,
  20    search::{SearchQuery, SearchResult},
  21};
  22use remote::SshRemoteClient;
  23use serde_json::json;
  24use settings::{Settings, SettingsLocation, SettingsStore, initial_server_settings_content};
  25use smol::stream::StreamExt;
  26use std::{
  27    collections::HashSet,
  28    path::{Path, PathBuf},
  29    sync::Arc,
  30};
  31#[cfg(not(windows))]
  32use unindent::Unindent as _;
  33use util::{path, separator};
  34
  35#[gpui::test]
  36async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
  37    let fs = FakeFs::new(server_cx.executor());
  38    fs.insert_tree(
  39        path!("/code"),
  40        json!({
  41            "project1": {
  42                ".git": {},
  43                "README.md": "# project 1",
  44                "src": {
  45                    "lib.rs": "fn one() -> usize { 1 }"
  46                }
  47            },
  48            "project2": {
  49                "README.md": "# project 2",
  50            },
  51        }),
  52    )
  53    .await;
  54    fs.set_index_for_repo(
  55        Path::new(path!("/code/project1/.git")),
  56        &[("src/lib.rs".into(), "fn one() -> usize { 0 }".into())],
  57    );
  58
  59    let (project, _headless) = init_test(&fs, cx, server_cx).await;
  60    let (worktree, _) = project
  61        .update(cx, |project, cx| {
  62            project.find_or_create_worktree(path!("/code/project1"), true, cx)
  63        })
  64        .await
  65        .unwrap();
  66
  67    // The client sees the worktree's contents.
  68    cx.executor().run_until_parked();
  69    let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
  70    worktree.update(cx, |worktree, _cx| {
  71        assert_eq!(
  72            worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
  73            vec![
  74                Path::new("README.md"),
  75                Path::new("src"),
  76                Path::new("src/lib.rs"),
  77            ]
  78        );
  79    });
  80
  81    // The user opens a buffer in the remote worktree. The buffer's
  82    // contents are loaded from the remote filesystem.
  83    let buffer = project
  84        .update(cx, |project, cx| {
  85            project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
  86        })
  87        .await
  88        .unwrap();
  89    let diff = project
  90        .update(cx, |project, cx| {
  91            project.open_unstaged_diff(buffer.clone(), cx)
  92        })
  93        .await
  94        .unwrap();
  95
  96    diff.update(cx, |diff, _| {
  97        assert_eq!(diff.base_text_string().unwrap(), "fn one() -> usize { 0 }");
  98    });
  99
 100    buffer.update(cx, |buffer, cx| {
 101        assert_eq!(buffer.text(), "fn one() -> usize { 1 }");
 102        let ix = buffer.text().find('1').unwrap();
 103        buffer.edit([(ix..ix + 1, "100")], None, cx);
 104    });
 105
 106    // The user saves the buffer. The new contents are written to the
 107    // remote filesystem.
 108    project
 109        .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
 110        .await
 111        .unwrap();
 112    assert_eq!(
 113        fs.load("/code/project1/src/lib.rs".as_ref()).await.unwrap(),
 114        "fn one() -> usize { 100 }"
 115    );
 116
 117    // A new file is created in the remote filesystem. The user
 118    // sees the new file.
 119    fs.save(
 120        path!("/code/project1/src/main.rs").as_ref(),
 121        &"fn main() {}".into(),
 122        Default::default(),
 123    )
 124    .await
 125    .unwrap();
 126    cx.executor().run_until_parked();
 127    worktree.update(cx, |worktree, _cx| {
 128        assert_eq!(
 129            worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
 130            vec![
 131                Path::new("README.md"),
 132                Path::new("src"),
 133                Path::new("src/lib.rs"),
 134                Path::new("src/main.rs"),
 135            ]
 136        );
 137    });
 138
 139    // A file that is currently open in a buffer is renamed.
 140    fs.rename(
 141        path!("/code/project1/src/lib.rs").as_ref(),
 142        path!("/code/project1/src/lib2.rs").as_ref(),
 143        Default::default(),
 144    )
 145    .await
 146    .unwrap();
 147    cx.executor().run_until_parked();
 148    buffer.update(cx, |buffer, _| {
 149        assert_eq!(&**buffer.file().unwrap().path(), Path::new("src/lib2.rs"));
 150    });
 151
 152    fs.set_index_for_repo(
 153        Path::new(path!("/code/project1/.git")),
 154        &[("src/lib2.rs".into(), "fn one() -> usize { 100 }".into())],
 155    );
 156    cx.executor().run_until_parked();
 157    diff.update(cx, |diff, _| {
 158        assert_eq!(
 159            diff.base_text_string().unwrap(),
 160            "fn one() -> usize { 100 }"
 161        );
 162    });
 163}
 164
 165#[gpui::test]
 166async fn test_remote_project_search(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
 167    let fs = FakeFs::new(server_cx.executor());
 168    fs.insert_tree(
 169        path!("/code"),
 170        json!({
 171            "project1": {
 172                ".git": {},
 173                "README.md": "# project 1",
 174                "src": {
 175                    "lib.rs": "fn one() -> usize { 1 }"
 176                }
 177            },
 178        }),
 179    )
 180    .await;
 181
 182    let (project, headless) = init_test(&fs, cx, server_cx).await;
 183
 184    project
 185        .update(cx, |project, cx| {
 186            project.find_or_create_worktree(path!("/code/project1"), true, cx)
 187        })
 188        .await
 189        .unwrap();
 190
 191    cx.run_until_parked();
 192
 193    async fn do_search(project: &Entity<Project>, mut cx: TestAppContext) -> Entity<Buffer> {
 194        let receiver = project.update(&mut cx, |project, cx| {
 195            project.search(
 196                SearchQuery::text(
 197                    "project",
 198                    false,
 199                    true,
 200                    false,
 201                    Default::default(),
 202                    Default::default(),
 203                    None,
 204                )
 205                .unwrap(),
 206                cx,
 207            )
 208        });
 209
 210        let first_response = receiver.recv().await.unwrap();
 211        let SearchResult::Buffer { buffer, .. } = first_response else {
 212            panic!("incorrect result");
 213        };
 214        buffer.update(&mut cx, |buffer, cx| {
 215            assert_eq!(
 216                buffer.file().unwrap().full_path(cx).to_string_lossy(),
 217                separator!("project1/README.md")
 218            )
 219        });
 220
 221        assert!(receiver.recv().await.is_err());
 222        buffer
 223    }
 224
 225    let buffer = do_search(&project, cx.clone()).await;
 226
 227    // test that the headless server is tracking which buffers we have open correctly.
 228    cx.run_until_parked();
 229    headless.update(server_cx, |headless, cx| {
 230        assert!(headless.buffer_store.read(cx).has_shared_buffers())
 231    });
 232    do_search(&project, cx.clone()).await;
 233
 234    cx.update(|_| {
 235        drop(buffer);
 236    });
 237    cx.run_until_parked();
 238    headless.update(server_cx, |headless, cx| {
 239        assert!(!headless.buffer_store.read(cx).has_shared_buffers())
 240    });
 241
 242    do_search(&project, cx.clone()).await;
 243}
 244
 245#[gpui::test]
 246async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
 247    let fs = FakeFs::new(server_cx.executor());
 248    fs.insert_tree(
 249        "/code",
 250        json!({
 251            "project1": {
 252                ".git": {},
 253                "README.md": "# project 1",
 254                "src": {
 255                    "lib.rs": "fn one() -> usize { 1 }"
 256                }
 257            },
 258        }),
 259    )
 260    .await;
 261
 262    let (project, headless) = init_test(&fs, cx, server_cx).await;
 263
 264    cx.update_global(|settings_store: &mut SettingsStore, cx| {
 265        settings_store.set_user_settings(
 266            r#"{"languages":{"Rust":{"language_servers":["from-local-settings"]}}}"#,
 267            cx,
 268        )
 269    })
 270    .unwrap();
 271
 272    cx.run_until_parked();
 273
 274    server_cx.read(|cx| {
 275        assert_eq!(
 276            AllLanguageSettings::get_global(cx)
 277                .language(None, Some(&"Rust".into()), cx)
 278                .language_servers,
 279            ["..."] // local settings are ignored
 280        )
 281    });
 282
 283    server_cx
 284        .update_global(|settings_store: &mut SettingsStore, cx| {
 285            settings_store.set_server_settings(
 286                r#"{"languages":{"Rust":{"language_servers":["from-server-settings"]}}}"#,
 287                cx,
 288            )
 289        })
 290        .unwrap();
 291
 292    cx.run_until_parked();
 293
 294    server_cx.read(|cx| {
 295        assert_eq!(
 296            AllLanguageSettings::get_global(cx)
 297                .language(None, Some(&"Rust".into()), cx)
 298                .language_servers,
 299            ["from-server-settings".to_string()]
 300        )
 301    });
 302
 303    fs.insert_tree(
 304        "/code/project1/.zed",
 305        json!({
 306            "settings.json": r#"
 307                  {
 308                    "languages": {"Rust":{"language_servers":["override-rust-analyzer"]}},
 309                    "lsp": {
 310                      "override-rust-analyzer": {
 311                        "binary": {
 312                          "path": "~/.cargo/bin/rust-analyzer"
 313                        }
 314                      }
 315                    }
 316                  }"#
 317        }),
 318    )
 319    .await;
 320
 321    let worktree_id = project
 322        .update(cx, |project, cx| {
 323            project.find_or_create_worktree("/code/project1", true, cx)
 324        })
 325        .await
 326        .unwrap()
 327        .0
 328        .read_with(cx, |worktree, _| worktree.id());
 329
 330    let buffer = project
 331        .update(cx, |project, cx| {
 332            project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
 333        })
 334        .await
 335        .unwrap();
 336    cx.run_until_parked();
 337
 338    server_cx.read(|cx| {
 339        let worktree_id = headless
 340            .read(cx)
 341            .worktree_store
 342            .read(cx)
 343            .worktrees()
 344            .next()
 345            .unwrap()
 346            .read(cx)
 347            .id();
 348        assert_eq!(
 349            AllLanguageSettings::get(
 350                Some(SettingsLocation {
 351                    worktree_id,
 352                    path: Path::new("src/lib.rs")
 353                }),
 354                cx
 355            )
 356            .language(None, Some(&"Rust".into()), cx)
 357            .language_servers,
 358            ["override-rust-analyzer".to_string()]
 359        )
 360    });
 361
 362    cx.read(|cx| {
 363        let file = buffer.read(cx).file();
 364        assert_eq!(
 365            language_settings(Some("Rust".into()), file, cx).language_servers,
 366            ["override-rust-analyzer".to_string()]
 367        )
 368    });
 369}
 370
 371#[gpui::test]
 372async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
 373    let fs = FakeFs::new(server_cx.executor());
 374    fs.insert_tree(
 375        path!("/code"),
 376        json!({
 377            "project1": {
 378                ".git": {},
 379                "README.md": "# project 1",
 380                "src": {
 381                    "lib.rs": "fn one() -> usize { 1 }"
 382                }
 383            },
 384        }),
 385    )
 386    .await;
 387
 388    let (project, headless) = init_test(&fs, cx, server_cx).await;
 389
 390    fs.insert_tree(
 391        path!("/code/project1/.zed"),
 392        json!({
 393            "settings.json": r#"
 394          {
 395            "languages": {"Rust":{"language_servers":["rust-analyzer"]}},
 396            "lsp": {
 397              "rust-analyzer": {
 398                "binary": {
 399                  "path": "~/.cargo/bin/rust-analyzer"
 400                }
 401              }
 402            }
 403          }"#
 404        }),
 405    )
 406    .await;
 407
 408    cx.update_entity(&project, |project, _| {
 409        project.languages().register_test_language(LanguageConfig {
 410            name: "Rust".into(),
 411            matcher: LanguageMatcher {
 412                path_suffixes: vec!["rs".into()],
 413                ..Default::default()
 414            },
 415            ..Default::default()
 416        });
 417        project.languages().register_fake_lsp_adapter(
 418            "Rust",
 419            FakeLspAdapter {
 420                name: "rust-analyzer",
 421                ..Default::default()
 422            },
 423        )
 424    });
 425
 426    let mut fake_lsp = server_cx.update(|cx| {
 427        headless.read(cx).languages.register_fake_language_server(
 428            LanguageServerName("rust-analyzer".into()),
 429            Default::default(),
 430            None,
 431        )
 432    });
 433
 434    cx.run_until_parked();
 435
 436    let worktree_id = project
 437        .update(cx, |project, cx| {
 438            project.find_or_create_worktree(path!("/code/project1"), true, cx)
 439        })
 440        .await
 441        .unwrap()
 442        .0
 443        .read_with(cx, |worktree, _| worktree.id());
 444
 445    // Wait for the settings to synchronize
 446    cx.run_until_parked();
 447
 448    let (buffer, _handle) = project
 449        .update(cx, |project, cx| {
 450            project.open_buffer_with_lsp((worktree_id, Path::new("src/lib.rs")), cx)
 451        })
 452        .await
 453        .unwrap();
 454    cx.run_until_parked();
 455
 456    let fake_lsp = fake_lsp.next().await.unwrap();
 457
 458    cx.read(|cx| {
 459        let file = buffer.read(cx).file();
 460        assert_eq!(
 461            language_settings(Some("Rust".into()), file, cx).language_servers,
 462            ["rust-analyzer".to_string()]
 463        )
 464    });
 465
 466    let buffer_id = cx.read(|cx| {
 467        let buffer = buffer.read(cx);
 468        assert_eq!(buffer.language().unwrap().name(), "Rust".into());
 469        buffer.remote_id()
 470    });
 471
 472    server_cx.read(|cx| {
 473        let buffer = headless
 474            .read(cx)
 475            .buffer_store
 476            .read(cx)
 477            .get(buffer_id)
 478            .unwrap();
 479
 480        assert_eq!(buffer.read(cx).language().unwrap().name(), "Rust".into());
 481    });
 482
 483    server_cx.read(|cx| {
 484        let lsp_store = headless.read(cx).lsp_store.read(cx);
 485        assert_eq!(lsp_store.as_local().unwrap().language_servers.len(), 1);
 486    });
 487
 488    fake_lsp.set_request_handler::<lsp::request::Completion, _, _>(|_, _| async move {
 489        Ok(Some(CompletionResponse::Array(vec![lsp::CompletionItem {
 490            label: "boop".to_string(),
 491            ..Default::default()
 492        }])))
 493    });
 494
 495    let result = project
 496        .update(cx, |project, cx| {
 497            project.completions(
 498                &buffer,
 499                0,
 500                CompletionContext {
 501                    trigger_kind: CompletionTriggerKind::INVOKED,
 502                    trigger_character: None,
 503                },
 504                cx,
 505            )
 506        })
 507        .await
 508        .unwrap();
 509
 510    assert_eq!(
 511        result
 512            .unwrap()
 513            .into_iter()
 514            .map(|c| c.label.text)
 515            .collect::<Vec<_>>(),
 516        vec!["boop".to_string()]
 517    );
 518
 519    fake_lsp.set_request_handler::<lsp::request::Rename, _, _>(|_, _| async move {
 520        Ok(Some(lsp::WorkspaceEdit {
 521            changes: Some(
 522                [(
 523                    lsp::Url::from_file_path(path!("/code/project1/src/lib.rs")).unwrap(),
 524                    vec![lsp::TextEdit::new(
 525                        lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 6)),
 526                        "two".to_string(),
 527                    )],
 528                )]
 529                .into_iter()
 530                .collect(),
 531            ),
 532            ..Default::default()
 533        }))
 534    });
 535
 536    project
 537        .update(cx, |project, cx| {
 538            project.perform_rename(buffer.clone(), 3, "two".to_string(), cx)
 539        })
 540        .await
 541        .unwrap();
 542
 543    cx.run_until_parked();
 544    buffer.update(cx, |buffer, _| {
 545        assert_eq!(buffer.text(), "fn two() -> usize { 1 }")
 546    })
 547}
 548
 549#[gpui::test]
 550async fn test_remote_cancel_language_server_work(
 551    cx: &mut TestAppContext,
 552    server_cx: &mut TestAppContext,
 553) {
 554    let fs = FakeFs::new(server_cx.executor());
 555    fs.insert_tree(
 556        path!("/code"),
 557        json!({
 558            "project1": {
 559                ".git": {},
 560                "README.md": "# project 1",
 561                "src": {
 562                    "lib.rs": "fn one() -> usize { 1 }"
 563                }
 564            },
 565        }),
 566    )
 567    .await;
 568
 569    let (project, headless) = init_test(&fs, cx, server_cx).await;
 570
 571    fs.insert_tree(
 572        path!("/code/project1/.zed"),
 573        json!({
 574            "settings.json": r#"
 575          {
 576            "languages": {"Rust":{"language_servers":["rust-analyzer"]}},
 577            "lsp": {
 578              "rust-analyzer": {
 579                "binary": {
 580                  "path": "~/.cargo/bin/rust-analyzer"
 581                }
 582              }
 583            }
 584          }"#
 585        }),
 586    )
 587    .await;
 588
 589    cx.update_entity(&project, |project, _| {
 590        project.languages().register_test_language(LanguageConfig {
 591            name: "Rust".into(),
 592            matcher: LanguageMatcher {
 593                path_suffixes: vec!["rs".into()],
 594                ..Default::default()
 595            },
 596            ..Default::default()
 597        });
 598        project.languages().register_fake_lsp_adapter(
 599            "Rust",
 600            FakeLspAdapter {
 601                name: "rust-analyzer",
 602                ..Default::default()
 603            },
 604        )
 605    });
 606
 607    let mut fake_lsp = server_cx.update(|cx| {
 608        headless.read(cx).languages.register_fake_language_server(
 609            LanguageServerName("rust-analyzer".into()),
 610            Default::default(),
 611            None,
 612        )
 613    });
 614
 615    cx.run_until_parked();
 616
 617    let worktree_id = project
 618        .update(cx, |project, cx| {
 619            project.find_or_create_worktree(path!("/code/project1"), true, cx)
 620        })
 621        .await
 622        .unwrap()
 623        .0
 624        .read_with(cx, |worktree, _| worktree.id());
 625
 626    cx.run_until_parked();
 627
 628    let (buffer, _handle) = project
 629        .update(cx, |project, cx| {
 630            project.open_buffer_with_lsp((worktree_id, Path::new("src/lib.rs")), cx)
 631        })
 632        .await
 633        .unwrap();
 634
 635    cx.run_until_parked();
 636
 637    let mut fake_lsp = fake_lsp.next().await.unwrap();
 638
 639    // Cancelling all language server work for a given buffer
 640    {
 641        // Two operations, one cancellable and one not.
 642        fake_lsp
 643            .start_progress_with(
 644                "another-token",
 645                lsp::WorkDoneProgressBegin {
 646                    cancellable: Some(false),
 647                    ..Default::default()
 648                },
 649            )
 650            .await;
 651
 652        let progress_token = "the-progress-token";
 653        fake_lsp
 654            .start_progress_with(
 655                progress_token,
 656                lsp::WorkDoneProgressBegin {
 657                    cancellable: Some(true),
 658                    ..Default::default()
 659                },
 660            )
 661            .await;
 662
 663        cx.executor().run_until_parked();
 664
 665        project.update(cx, |project, cx| {
 666            project.cancel_language_server_work_for_buffers([buffer.clone()], cx)
 667        });
 668
 669        cx.executor().run_until_parked();
 670
 671        // Verify the cancellation was received on the server side
 672        let cancel_notification = fake_lsp
 673            .receive_notification::<lsp::notification::WorkDoneProgressCancel>()
 674            .await;
 675        assert_eq!(
 676            cancel_notification.token,
 677            lsp::NumberOrString::String(progress_token.into())
 678        );
 679    }
 680
 681    // Cancelling work by server_id and token
 682    {
 683        let server_id = fake_lsp.server.server_id();
 684        let progress_token = "the-progress-token";
 685
 686        fake_lsp
 687            .start_progress_with(
 688                progress_token,
 689                lsp::WorkDoneProgressBegin {
 690                    cancellable: Some(true),
 691                    ..Default::default()
 692                },
 693            )
 694            .await;
 695
 696        cx.executor().run_until_parked();
 697
 698        project.update(cx, |project, cx| {
 699            project.cancel_language_server_work(server_id, Some(progress_token.into()), cx)
 700        });
 701
 702        cx.executor().run_until_parked();
 703
 704        // Verify the cancellation was received on the server side
 705        let cancel_notification = fake_lsp
 706            .receive_notification::<lsp::notification::WorkDoneProgressCancel>()
 707            .await;
 708        assert_eq!(
 709            cancel_notification.token,
 710            lsp::NumberOrString::String(progress_token.into())
 711        );
 712    }
 713}
 714
 715#[gpui::test]
 716async fn test_remote_reload(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
 717    let fs = FakeFs::new(server_cx.executor());
 718    fs.insert_tree(
 719        path!("/code"),
 720        json!({
 721            "project1": {
 722                ".git": {},
 723                "README.md": "# project 1",
 724                "src": {
 725                    "lib.rs": "fn one() -> usize { 1 }"
 726                }
 727            },
 728        }),
 729    )
 730    .await;
 731
 732    let (project, _headless) = init_test(&fs, cx, server_cx).await;
 733    let (worktree, _) = project
 734        .update(cx, |project, cx| {
 735            project.find_or_create_worktree(path!("/code/project1"), true, cx)
 736        })
 737        .await
 738        .unwrap();
 739
 740    let worktree_id = cx.update(|cx| worktree.read(cx).id());
 741
 742    let buffer = project
 743        .update(cx, |project, cx| {
 744            project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
 745        })
 746        .await
 747        .unwrap();
 748
 749    fs.save(
 750        &PathBuf::from(path!("/code/project1/src/lib.rs")),
 751        &("bangles".to_string().into()),
 752        LineEnding::Unix,
 753    )
 754    .await
 755    .unwrap();
 756
 757    cx.run_until_parked();
 758
 759    buffer.update(cx, |buffer, cx| {
 760        assert_eq!(buffer.text(), "bangles");
 761        buffer.edit([(0..0, "a")], None, cx);
 762    });
 763
 764    fs.save(
 765        &PathBuf::from(path!("/code/project1/src/lib.rs")),
 766        &("bloop".to_string().into()),
 767        LineEnding::Unix,
 768    )
 769    .await
 770    .unwrap();
 771
 772    cx.run_until_parked();
 773    cx.update(|cx| {
 774        assert!(buffer.read(cx).has_conflict());
 775    });
 776
 777    project
 778        .update(cx, |project, cx| {
 779            project.reload_buffers([buffer.clone()].into_iter().collect(), false, cx)
 780        })
 781        .await
 782        .unwrap();
 783    cx.run_until_parked();
 784
 785    cx.update(|cx| {
 786        assert!(!buffer.read(cx).has_conflict());
 787    });
 788}
 789
 790#[gpui::test]
 791async fn test_remote_resolve_path_in_buffer(
 792    cx: &mut TestAppContext,
 793    server_cx: &mut TestAppContext,
 794) {
 795    let fs = FakeFs::new(server_cx.executor());
 796    // Even though we are not testing anything from project1, it is necessary to test if project2 is picking up correct worktree
 797    fs.insert_tree(
 798        path!("/code"),
 799        json!({
 800            "project1": {
 801                ".git": {},
 802                "README.md": "# project 1",
 803                "src": {
 804                    "lib.rs": "fn one() -> usize { 1 }"
 805                }
 806            },
 807            "project2": {
 808                ".git": {},
 809                "README.md": "# project 2",
 810                "src": {
 811                    "lib.rs": "fn two() -> usize { 2 }"
 812                }
 813            }
 814        }),
 815    )
 816    .await;
 817
 818    let (project, _headless) = init_test(&fs, cx, server_cx).await;
 819
 820    let _ = project
 821        .update(cx, |project, cx| {
 822            project.find_or_create_worktree(path!("/code/project1"), true, cx)
 823        })
 824        .await
 825        .unwrap();
 826
 827    let (worktree2, _) = project
 828        .update(cx, |project, cx| {
 829            project.find_or_create_worktree(path!("/code/project2"), true, cx)
 830        })
 831        .await
 832        .unwrap();
 833
 834    let worktree2_id = cx.update(|cx| worktree2.read(cx).id());
 835
 836    let buffer2 = project
 837        .update(cx, |project, cx| {
 838            project.open_buffer((worktree2_id, Path::new("src/lib.rs")), cx)
 839        })
 840        .await
 841        .unwrap();
 842
 843    let path = project
 844        .update(cx, |project, cx| {
 845            project.resolve_path_in_buffer(path!("/code/project2/README.md"), &buffer2, cx)
 846        })
 847        .await
 848        .unwrap();
 849    assert!(path.is_file());
 850    assert_eq!(
 851        path.abs_path().unwrap().to_string_lossy(),
 852        path!("/code/project2/README.md")
 853    );
 854
 855    let path = project
 856        .update(cx, |project, cx| {
 857            project.resolve_path_in_buffer("../README.md", &buffer2, cx)
 858        })
 859        .await
 860        .unwrap();
 861    assert!(path.is_file());
 862    assert_eq!(
 863        path.project_path().unwrap().clone(),
 864        ProjectPath::from((worktree2_id, "README.md"))
 865    );
 866
 867    let path = project
 868        .update(cx, |project, cx| {
 869            project.resolve_path_in_buffer("../src", &buffer2, cx)
 870        })
 871        .await
 872        .unwrap();
 873    assert_eq!(
 874        path.project_path().unwrap().clone(),
 875        ProjectPath::from((worktree2_id, "src"))
 876    );
 877    assert!(path.is_dir());
 878}
 879
 880#[gpui::test]
 881async fn test_remote_resolve_abs_path(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
 882    let fs = FakeFs::new(server_cx.executor());
 883    fs.insert_tree(
 884        path!("/code"),
 885        json!({
 886            "project1": {
 887                ".git": {},
 888                "README.md": "# project 1",
 889                "src": {
 890                    "lib.rs": "fn one() -> usize { 1 }"
 891                }
 892            },
 893        }),
 894    )
 895    .await;
 896
 897    let (project, _headless) = init_test(&fs, cx, server_cx).await;
 898
 899    let path = project
 900        .update(cx, |project, cx| {
 901            project.resolve_abs_path(path!("/code/project1/README.md"), cx)
 902        })
 903        .await
 904        .unwrap();
 905
 906    assert!(path.is_file());
 907    assert_eq!(
 908        path.abs_path().unwrap().to_string_lossy(),
 909        path!("/code/project1/README.md")
 910    );
 911
 912    let path = project
 913        .update(cx, |project, cx| {
 914            project.resolve_abs_path(path!("/code/project1/src"), cx)
 915        })
 916        .await
 917        .unwrap();
 918
 919    assert!(path.is_dir());
 920    assert_eq!(
 921        path.abs_path().unwrap().to_string_lossy(),
 922        path!("/code/project1/src")
 923    );
 924
 925    let path = project
 926        .update(cx, |project, cx| {
 927            project.resolve_abs_path(path!("/code/project1/DOESNOTEXIST"), cx)
 928        })
 929        .await;
 930    assert!(path.is_none());
 931}
 932
 933#[gpui::test(iterations = 10)]
 934async fn test_canceling_buffer_opening(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
 935    let fs = FakeFs::new(server_cx.executor());
 936    fs.insert_tree(
 937        "/code",
 938        json!({
 939            "project1": {
 940                ".git": {},
 941                "README.md": "# project 1",
 942                "src": {
 943                    "lib.rs": "fn one() -> usize { 1 }"
 944                }
 945            },
 946        }),
 947    )
 948    .await;
 949
 950    let (project, _headless) = init_test(&fs, cx, server_cx).await;
 951    let (worktree, _) = project
 952        .update(cx, |project, cx| {
 953            project.find_or_create_worktree("/code/project1", true, cx)
 954        })
 955        .await
 956        .unwrap();
 957    let worktree_id = worktree.read_with(cx, |tree, _| tree.id());
 958
 959    // Open a buffer on the client but cancel after a random amount of time.
 960    let buffer = project.update(cx, |p, cx| p.open_buffer((worktree_id, "src/lib.rs"), cx));
 961    cx.executor().simulate_random_delay().await;
 962    drop(buffer);
 963
 964    // Try opening the same buffer again as the client, and ensure we can
 965    // still do it despite the cancellation above.
 966    let buffer = project
 967        .update(cx, |p, cx| p.open_buffer((worktree_id, "src/lib.rs"), cx))
 968        .await
 969        .unwrap();
 970
 971    buffer.read_with(cx, |buf, _| {
 972        assert_eq!(buf.text(), "fn one() -> usize { 1 }")
 973    });
 974}
 975
 976#[gpui::test]
 977async fn test_adding_then_removing_then_adding_worktrees(
 978    cx: &mut TestAppContext,
 979    server_cx: &mut TestAppContext,
 980) {
 981    let fs = FakeFs::new(server_cx.executor());
 982    fs.insert_tree(
 983        path!("/code"),
 984        json!({
 985            "project1": {
 986                ".git": {},
 987                "README.md": "# project 1",
 988                "src": {
 989                    "lib.rs": "fn one() -> usize { 1 }"
 990                }
 991            },
 992            "project2": {
 993                "README.md": "# project 2",
 994            },
 995        }),
 996    )
 997    .await;
 998
 999    let (project, _headless) = init_test(&fs, cx, server_cx).await;
1000    let (_worktree, _) = project
1001        .update(cx, |project, cx| {
1002            project.find_or_create_worktree(path!("/code/project1"), true, cx)
1003        })
1004        .await
1005        .unwrap();
1006
1007    let (worktree_2, _) = project
1008        .update(cx, |project, cx| {
1009            project.find_or_create_worktree(path!("/code/project2"), true, cx)
1010        })
1011        .await
1012        .unwrap();
1013    let worktree_id_2 = worktree_2.read_with(cx, |tree, _| tree.id());
1014
1015    project.update(cx, |project, cx| project.remove_worktree(worktree_id_2, cx));
1016
1017    let (worktree_2, _) = project
1018        .update(cx, |project, cx| {
1019            project.find_or_create_worktree(path!("/code/project2"), true, cx)
1020        })
1021        .await
1022        .unwrap();
1023
1024    cx.run_until_parked();
1025    worktree_2.update(cx, |worktree, _cx| {
1026        assert!(worktree.is_visible());
1027        let entries = worktree.entries(true, 0).collect::<Vec<_>>();
1028        assert_eq!(entries.len(), 2);
1029        assert_eq!(
1030            entries[1].path.to_string_lossy().to_string(),
1031            "README.md".to_string()
1032        )
1033    })
1034}
1035
1036#[gpui::test]
1037async fn test_open_server_settings(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
1038    let fs = FakeFs::new(server_cx.executor());
1039    fs.insert_tree(
1040        path!("/code"),
1041        json!({
1042            "project1": {
1043                ".git": {},
1044                "README.md": "# project 1",
1045                "src": {
1046                    "lib.rs": "fn one() -> usize { 1 }"
1047                }
1048            },
1049        }),
1050    )
1051    .await;
1052
1053    let (project, _headless) = init_test(&fs, cx, server_cx).await;
1054    let buffer = project.update(cx, |project, cx| project.open_server_settings(cx));
1055    cx.executor().run_until_parked();
1056
1057    let buffer = buffer.await.unwrap();
1058
1059    cx.update(|cx| {
1060        assert_eq!(
1061            buffer.read(cx).text(),
1062            initial_server_settings_content()
1063                .to_string()
1064                .replace("\r\n", "\n")
1065        )
1066    })
1067}
1068
1069#[gpui::test(iterations = 20)]
1070async fn test_reconnect(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
1071    let fs = FakeFs::new(server_cx.executor());
1072    fs.insert_tree(
1073        path!("/code"),
1074        json!({
1075            "project1": {
1076                ".git": {},
1077                "README.md": "# project 1",
1078                "src": {
1079                    "lib.rs": "fn one() -> usize { 1 }"
1080                }
1081            },
1082        }),
1083    )
1084    .await;
1085
1086    let (project, _headless) = init_test(&fs, cx, server_cx).await;
1087
1088    let (worktree, _) = project
1089        .update(cx, |project, cx| {
1090            project.find_or_create_worktree(path!("/code/project1"), true, cx)
1091        })
1092        .await
1093        .unwrap();
1094
1095    let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
1096    let buffer = project
1097        .update(cx, |project, cx| {
1098            project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
1099        })
1100        .await
1101        .unwrap();
1102
1103    buffer.update(cx, |buffer, cx| {
1104        assert_eq!(buffer.text(), "fn one() -> usize { 1 }");
1105        let ix = buffer.text().find('1').unwrap();
1106        buffer.edit([(ix..ix + 1, "100")], None, cx);
1107    });
1108
1109    let client = cx.read(|cx| project.read(cx).ssh_client().unwrap());
1110    client
1111        .update(cx, |client, cx| client.simulate_disconnect(cx))
1112        .detach();
1113
1114    project
1115        .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
1116        .await
1117        .unwrap();
1118
1119    assert_eq!(
1120        fs.load(path!("/code/project1/src/lib.rs").as_ref())
1121            .await
1122            .unwrap(),
1123        "fn one() -> usize { 100 }"
1124    );
1125}
1126
1127#[gpui::test]
1128async fn test_remote_root_rename(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
1129    let fs = FakeFs::new(server_cx.executor());
1130    fs.insert_tree(
1131        "/code",
1132        json!({
1133            "project1": {
1134                ".git": {},
1135                "README.md": "# project 1",
1136            },
1137        }),
1138    )
1139    .await;
1140
1141    let (project, _) = init_test(&fs, cx, server_cx).await;
1142
1143    let (worktree, _) = project
1144        .update(cx, |project, cx| {
1145            project.find_or_create_worktree("/code/project1", true, cx)
1146        })
1147        .await
1148        .unwrap();
1149
1150    cx.run_until_parked();
1151
1152    fs.rename(
1153        &PathBuf::from("/code/project1"),
1154        &PathBuf::from("/code/project2"),
1155        Default::default(),
1156    )
1157    .await
1158    .unwrap();
1159
1160    cx.run_until_parked();
1161    worktree.update(cx, |worktree, _| {
1162        assert_eq!(worktree.root_name(), "project2")
1163    })
1164}
1165
1166#[gpui::test]
1167async fn test_remote_rename_entry(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
1168    let fs = FakeFs::new(server_cx.executor());
1169    fs.insert_tree(
1170        "/code",
1171        json!({
1172            "project1": {
1173                ".git": {},
1174                "README.md": "# project 1",
1175            },
1176        }),
1177    )
1178    .await;
1179
1180    let (project, _) = init_test(&fs, cx, server_cx).await;
1181    let (worktree, _) = project
1182        .update(cx, |project, cx| {
1183            project.find_or_create_worktree("/code/project1", true, cx)
1184        })
1185        .await
1186        .unwrap();
1187
1188    cx.run_until_parked();
1189
1190    let entry = worktree
1191        .update(cx, |worktree, cx| {
1192            let entry = worktree.entry_for_path("README.md").unwrap();
1193            worktree.rename_entry(entry.id, Path::new("README.rst"), cx)
1194        })
1195        .await
1196        .unwrap()
1197        .to_included()
1198        .unwrap();
1199
1200    cx.run_until_parked();
1201
1202    worktree.update(cx, |worktree, _| {
1203        assert_eq!(worktree.entry_for_path("README.rst").unwrap().id, entry.id)
1204    });
1205}
1206
1207#[gpui::test]
1208async fn test_copy_file_into_remote_project(
1209    cx: &mut TestAppContext,
1210    server_cx: &mut TestAppContext,
1211) {
1212    let remote_fs = FakeFs::new(server_cx.executor());
1213    remote_fs
1214        .insert_tree(
1215            path!("/code"),
1216            json!({
1217                "project1": {
1218                    ".git": {},
1219                    "README.md": "# project 1",
1220                    "src": {
1221                        "main.rs": ""
1222                    }
1223                },
1224            }),
1225        )
1226        .await;
1227
1228    let (project, _) = init_test(&remote_fs, cx, server_cx).await;
1229    let (worktree, _) = project
1230        .update(cx, |project, cx| {
1231            project.find_or_create_worktree(path!("/code/project1"), true, cx)
1232        })
1233        .await
1234        .unwrap();
1235
1236    cx.run_until_parked();
1237
1238    let local_fs = project
1239        .read_with(cx, |project, _| project.fs().clone())
1240        .as_fake();
1241    local_fs
1242        .insert_tree(
1243            path!("/local-code"),
1244            json!({
1245                "dir1": {
1246                    "file1": "file 1 content",
1247                    "dir2": {
1248                        "file2": "file 2 content",
1249                        "dir3": {
1250                            "file3": ""
1251                        },
1252                        "dir4": {}
1253                    },
1254                    "dir5": {}
1255                },
1256                "file4": "file 4 content"
1257            }),
1258        )
1259        .await;
1260
1261    worktree
1262        .update(cx, |worktree, cx| {
1263            worktree.copy_external_entries(
1264                Path::new("src").into(),
1265                vec![
1266                    Path::new(path!("/local-code/dir1/file1")).into(),
1267                    Path::new(path!("/local-code/dir1/dir2")).into(),
1268                ],
1269                local_fs.clone(),
1270                cx,
1271            )
1272        })
1273        .await
1274        .unwrap();
1275
1276    assert_eq!(
1277        remote_fs.paths(true),
1278        vec![
1279            PathBuf::from(path!("/")),
1280            PathBuf::from(path!("/code")),
1281            PathBuf::from(path!("/code/project1")),
1282            PathBuf::from(path!("/code/project1/.git")),
1283            PathBuf::from(path!("/code/project1/README.md")),
1284            PathBuf::from(path!("/code/project1/src")),
1285            PathBuf::from(path!("/code/project1/src/dir2")),
1286            PathBuf::from(path!("/code/project1/src/file1")),
1287            PathBuf::from(path!("/code/project1/src/main.rs")),
1288            PathBuf::from(path!("/code/project1/src/dir2/dir3")),
1289            PathBuf::from(path!("/code/project1/src/dir2/dir4")),
1290            PathBuf::from(path!("/code/project1/src/dir2/file2")),
1291            PathBuf::from(path!("/code/project1/src/dir2/dir3/file3")),
1292        ]
1293    );
1294    assert_eq!(
1295        remote_fs
1296            .load(path!("/code/project1/src/file1").as_ref())
1297            .await
1298            .unwrap(),
1299        "file 1 content"
1300    );
1301    assert_eq!(
1302        remote_fs
1303            .load(path!("/code/project1/src/dir2/file2").as_ref())
1304            .await
1305            .unwrap(),
1306        "file 2 content"
1307    );
1308    assert_eq!(
1309        remote_fs
1310            .load(path!("/code/project1/src/dir2/dir3/file3").as_ref())
1311            .await
1312            .unwrap(),
1313        ""
1314    );
1315}
1316
1317// TODO: this test fails on Windows.
1318#[cfg(not(windows))]
1319#[gpui::test]
1320async fn test_remote_git_diffs(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
1321    let text_2 = "
1322        fn one() -> usize {
1323            1
1324        }
1325    "
1326    .unindent();
1327    let text_1 = "
1328        fn one() -> usize {
1329            0
1330        }
1331    "
1332    .unindent();
1333
1334    let fs = FakeFs::new(server_cx.executor());
1335    fs.insert_tree(
1336        "/code",
1337        json!({
1338            "project1": {
1339                ".git": {},
1340                "src": {
1341                    "lib.rs": text_2
1342                },
1343                "README.md": "# project 1",
1344            },
1345        }),
1346    )
1347    .await;
1348    fs.set_index_for_repo(
1349        Path::new("/code/project1/.git"),
1350        &[("src/lib.rs".into(), text_1.clone())],
1351    );
1352    fs.set_head_for_repo(
1353        Path::new("/code/project1/.git"),
1354        &[("src/lib.rs".into(), text_1.clone())],
1355    );
1356
1357    let (project, _headless) = init_test(&fs, cx, server_cx).await;
1358    let (worktree, _) = project
1359        .update(cx, |project, cx| {
1360            project.find_or_create_worktree("/code/project1", true, cx)
1361        })
1362        .await
1363        .unwrap();
1364    let worktree_id = cx.update(|cx| worktree.read(cx).id());
1365    cx.executor().run_until_parked();
1366
1367    let buffer = project
1368        .update(cx, |project, cx| {
1369            project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
1370        })
1371        .await
1372        .unwrap();
1373    let diff = project
1374        .update(cx, |project, cx| {
1375            project.open_uncommitted_diff(buffer.clone(), cx)
1376        })
1377        .await
1378        .unwrap();
1379
1380    diff.read_with(cx, |diff, cx| {
1381        assert_eq!(diff.base_text_string().unwrap(), text_1);
1382        assert_eq!(
1383            diff.secondary_diff()
1384                .unwrap()
1385                .read(cx)
1386                .base_text_string()
1387                .unwrap(),
1388            text_1
1389        );
1390    });
1391
1392    // stage the current buffer's contents
1393    fs.set_index_for_repo(
1394        Path::new("/code/project1/.git"),
1395        &[("src/lib.rs".into(), text_2.clone())],
1396    );
1397
1398    cx.executor().run_until_parked();
1399    diff.read_with(cx, |diff, cx| {
1400        assert_eq!(diff.base_text_string().unwrap(), text_1);
1401        assert_eq!(
1402            diff.secondary_diff()
1403                .unwrap()
1404                .read(cx)
1405                .base_text_string()
1406                .unwrap(),
1407            text_2
1408        );
1409    });
1410
1411    // commit the current buffer's contents
1412    fs.set_head_for_repo(
1413        Path::new("/code/project1/.git"),
1414        &[("src/lib.rs".into(), text_2.clone())],
1415    );
1416
1417    cx.executor().run_until_parked();
1418    diff.read_with(cx, |diff, cx| {
1419        assert_eq!(diff.base_text_string().unwrap(), text_2);
1420        assert_eq!(
1421            diff.secondary_diff()
1422                .unwrap()
1423                .read(cx)
1424                .base_text_string()
1425                .unwrap(),
1426            text_2
1427        );
1428    });
1429}
1430
1431#[gpui::test]
1432async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
1433    let fs = FakeFs::new(server_cx.executor());
1434    fs.insert_tree(
1435        path!("/code"),
1436        json!({
1437            "project1": {
1438                ".git": {},
1439                "README.md": "# project 1",
1440            },
1441        }),
1442    )
1443    .await;
1444
1445    let (project, headless_project) = init_test(&fs, cx, server_cx).await;
1446    let branches = ["main", "dev", "feature-1"];
1447    let branches_set = branches
1448        .iter()
1449        .map(ToString::to_string)
1450        .collect::<HashSet<_>>();
1451    fs.insert_branches(Path::new(path!("/code/project1/.git")), &branches);
1452
1453    let (_worktree, _) = project
1454        .update(cx, |project, cx| {
1455            project.find_or_create_worktree(path!("/code/project1"), true, cx)
1456        })
1457        .await
1458        .unwrap();
1459    // Give the worktree a bit of time to index the file system
1460    cx.run_until_parked();
1461
1462    let repository = project.update(cx, |project, cx| project.active_repository(cx).unwrap());
1463
1464    let remote_branches = repository
1465        .update(cx, |repository, _| repository.branches())
1466        .await
1467        .unwrap()
1468        .unwrap();
1469
1470    let new_branch = branches[2];
1471
1472    let remote_branches = remote_branches
1473        .into_iter()
1474        .map(|branch| branch.name.to_string())
1475        .collect::<HashSet<_>>();
1476
1477    assert_eq!(&remote_branches, &branches_set);
1478
1479    cx.update(|cx| {
1480        repository.update(cx, |repository, _cx| {
1481            repository.change_branch(new_branch.to_string())
1482        })
1483    })
1484    .await
1485    .unwrap()
1486    .unwrap();
1487
1488    cx.run_until_parked();
1489
1490    let server_branch = server_cx.update(|cx| {
1491        headless_project.update(cx, |headless_project, cx| {
1492            headless_project.git_store.update(cx, |git_store, cx| {
1493                git_store
1494                    .repositories()
1495                    .values()
1496                    .next()
1497                    .unwrap()
1498                    .read(cx)
1499                    .branch
1500                    .as_ref()
1501                    .unwrap()
1502                    .clone()
1503            })
1504        })
1505    });
1506
1507    assert_eq!(server_branch.name, branches[2]);
1508
1509    // Also try creating a new branch
1510    cx.update(|cx| {
1511        repository.update(cx, |repo, _cx| {
1512            repo.create_branch("totally-new-branch".to_string())
1513        })
1514    })
1515    .await
1516    .unwrap()
1517    .unwrap();
1518
1519    cx.update(|cx| {
1520        repository.update(cx, |repo, _cx| {
1521            repo.change_branch("totally-new-branch".to_string())
1522        })
1523    })
1524    .await
1525    .unwrap()
1526    .unwrap();
1527
1528    cx.run_until_parked();
1529
1530    let server_branch = server_cx.update(|cx| {
1531        headless_project.update(cx, |headless_project, cx| {
1532            headless_project.git_store.update(cx, |git_store, cx| {
1533                git_store
1534                    .repositories()
1535                    .values()
1536                    .next()
1537                    .unwrap()
1538                    .read(cx)
1539                    .branch
1540                    .as_ref()
1541                    .unwrap()
1542                    .clone()
1543            })
1544        })
1545    });
1546
1547    assert_eq!(server_branch.name, "totally-new-branch");
1548}
1549
1550pub async fn init_test(
1551    server_fs: &Arc<FakeFs>,
1552    cx: &mut TestAppContext,
1553    server_cx: &mut TestAppContext,
1554) -> (Entity<Project>, Entity<HeadlessProject>) {
1555    let server_fs = server_fs.clone();
1556    cx.update(|cx| {
1557        release_channel::init(SemanticVersion::default(), cx);
1558    });
1559    server_cx.update(|cx| {
1560        release_channel::init(SemanticVersion::default(), cx);
1561    });
1562    init_logger();
1563
1564    let (opts, ssh_server_client) = SshRemoteClient::fake_server(cx, server_cx);
1565    let http_client = Arc::new(BlockedHttpClient);
1566    let node_runtime = NodeRuntime::unavailable();
1567    let languages = Arc::new(LanguageRegistry::new(cx.executor()));
1568    let debug_adapters = DapRegistry::default().into();
1569    let proxy = Arc::new(ExtensionHostProxy::new());
1570    server_cx.update(HeadlessProject::init);
1571    let headless = server_cx.new(|cx| {
1572        client::init_settings(cx);
1573
1574        HeadlessProject::new(
1575            crate::HeadlessAppState {
1576                session: ssh_server_client,
1577                fs: server_fs.clone(),
1578                http_client,
1579                node_runtime,
1580                languages,
1581                debug_adapters,
1582                extension_host_proxy: proxy,
1583            },
1584            cx,
1585        )
1586    });
1587
1588    let ssh = SshRemoteClient::fake_client(opts, cx).await;
1589    let project = build_project(ssh, cx);
1590    project
1591        .update(cx, {
1592            let headless = headless.clone();
1593            |_, cx| cx.on_release(|_, _| drop(headless))
1594        })
1595        .detach();
1596    (project, headless)
1597}
1598
1599fn init_logger() {
1600    if std::env::var("RUST_LOG").is_ok() {
1601        env_logger::try_init().ok();
1602    }
1603}
1604
1605fn build_project(ssh: Entity<SshRemoteClient>, cx: &mut TestAppContext) -> Entity<Project> {
1606    cx.update(|cx| {
1607        if !cx.has_global::<SettingsStore>() {
1608            let settings_store = SettingsStore::test(cx);
1609            cx.set_global(settings_store);
1610        }
1611    });
1612
1613    let client = cx.update(|cx| {
1614        Client::new(
1615            Arc::new(FakeSystemClock::new()),
1616            FakeHttpClient::with_404_response(),
1617            cx,
1618        )
1619    });
1620
1621    let node = NodeRuntime::unavailable();
1622    let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
1623    let languages = Arc::new(LanguageRegistry::test(cx.executor()));
1624    let fs = FakeFs::new(cx.executor());
1625
1626    cx.update(|cx| {
1627        Project::init(&client, cx);
1628        language::init(cx);
1629    });
1630
1631    cx.update(|cx| Project::ssh(ssh, client, node, user_store, languages, fs, cx))
1632}