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