2088 lines · 58.3 KB · Rust
RawDownload
/// todo(windows)/// The tests in this file assume that server_cx is running on Windows too./// We neead to find a way to test Windows-Non-Windows interactions.use crate::headless_project::HeadlessProject;use agent::{AgentTool, ReadFileTool, ReadFileToolInput, Templates, Thread, ToolCallEventStream};use client::{Client, UserStore};use clock::FakeSystemClock;use collections::{HashMap, HashSet};use language_model::{LanguageModelToolResultContent, fake_provider::FakeLanguageModel};use prompt_store::ProjectContext; use extension::ExtensionHostProxy;use fs::{FakeFs, Fs};use gpui::{AppContext as _, Entity, SharedString, TestAppContext};use http_client::{BlockedHttpClient, FakeHttpClient};use language::{    Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LineEnding,    language_settings::{AllLanguageSettings, language_settings},};use lsp::{CompletionContext, CompletionResponse, CompletionTriggerKind, LanguageServerName};use node_runtime::NodeRuntime;use project::{    ProgressToken, Project,    agent_server_store::AgentServerCommand,    search::{SearchQuery, SearchResult},};use remote::RemoteClient;use serde_json::json;use settings::{Settings, SettingsLocation, SettingsStore, initial_server_settings_content};use smol::stream::StreamExt;use std::{    path::{Path, PathBuf},    sync::Arc,};use unindent::Unindent as _;use util::{path, paths::PathMatcher, rel_path::rel_path}; #[gpui::test]async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {    let fs = FakeFs::new(server_cx.executor());    fs.insert_tree(        path!("/code"),        json!({            "project1": {                ".git": {},                "README.md": "# project 1",                "src": {                    "lib.rs": "fn one() -> usize { 1 }"                }            },            "project2": {                "README.md": "# project 2",            },        }),    )    .await;    fs.set_index_for_repo(        Path::new(path!("/code/project1/.git")),        &[("src/lib.rs", "fn one() -> usize { 0 }".into())],    );     let (project, _headless) = init_test(&fs, cx, server_cx).await;    let (worktree, _) = project        .update(cx, |project, cx| {            project.find_or_create_worktree(path!("/code/project1"), true, cx)        })        .await        .unwrap();     // The client sees the worktree's contents.    cx.executor().run_until_parked();    let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());    worktree.update(cx, |worktree, _cx| {        assert_eq!(            worktree.paths().collect::<Vec<_>>(),            vec![                rel_path("README.md"),                rel_path("src"),                rel_path("src/lib.rs"),            ]        );    });     // The user opens a buffer in the remote worktree. The buffer's    // contents are loaded from the remote filesystem.    let buffer = project        .update(cx, |project, cx| {            project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)        })        .await        .unwrap();    let diff = project        .update(cx, |project, cx| {            project.open_unstaged_diff(buffer.clone(), cx)        })        .await        .unwrap();     diff.update(cx, |diff, cx| {        assert_eq!(            diff.base_text_string(cx).unwrap(),            "fn one() -> usize { 0 }"        );    });     buffer.update(cx, |buffer, cx| {        assert_eq!(buffer.text(), "fn one() -> usize { 1 }");        let ix = buffer.text().find('1').unwrap();        buffer.edit([(ix..ix + 1, "100")], None, cx);    });     // The user saves the buffer. The new contents are written to the    // remote filesystem.    project        .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))        .await        .unwrap();    assert_eq!(        fs.load("/code/project1/src/lib.rs".as_ref()).await.unwrap(),        "fn one() -> usize { 100 }"    );     // A new file is created in the remote filesystem. The user    // sees the new file.    fs.save(        path!("/code/project1/src/main.rs").as_ref(),        &"fn main() {}".into(),        Default::default(),    )    .await    .unwrap();    cx.executor().run_until_parked();    worktree.update(cx, |worktree, _cx| {        assert_eq!(            worktree.paths().collect::<Vec<_>>(),            vec![                rel_path("README.md"),                rel_path("src"),                rel_path("src/lib.rs"),                rel_path("src/main.rs"),            ]        );    });     // A file that is currently open in a buffer is renamed.    fs.rename(        path!("/code/project1/src/lib.rs").as_ref(),        path!("/code/project1/src/lib2.rs").as_ref(),        Default::default(),    )    .await    .unwrap();    cx.executor().run_until_parked();    buffer.update(cx, |buffer, _| {        assert_eq!(&**buffer.file().unwrap().path(), rel_path("src/lib2.rs"));    });     fs.set_index_for_repo(        Path::new(path!("/code/project1/.git")),        &[("src/lib2.rs", "fn one() -> usize { 100 }".into())],    );    cx.executor().run_until_parked();    diff.update(cx, |diff, cx| {        assert_eq!(            diff.base_text_string(cx).unwrap(),            "fn one() -> usize { 100 }"        );    });} async fn do_search_and_assert(    project: &Entity<Project>,    query: &str,    files_to_include: PathMatcher,    match_full_paths: bool,    expected_paths: &[&str],    mut cx: TestAppContext,) -> Vec<Entity<Buffer>> {    let query = query.to_string();    let receiver = project.update(&mut cx, |project, cx| {        project.search(            SearchQuery::text(                query,                false,                true,                false,                files_to_include,                Default::default(),                match_full_paths,                None,            )            .unwrap(),            cx,        )    });     let mut buffers = Vec::new();    for expected_path in expected_paths {        let response = receiver.rx.recv().await.unwrap();        let SearchResult::Buffer { buffer, .. } = response else {            panic!("incorrect result");        };        buffer.update(&mut cx, |buffer, cx| {            assert_eq!(                buffer.file().unwrap().full_path(cx).to_string_lossy(),                *expected_path            )        });        buffers.push(buffer);    }     assert!(receiver.rx.recv().await.is_err());    buffers} #[gpui::test]async fn test_remote_project_search(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {    let fs = FakeFs::new(server_cx.executor());    fs.insert_tree(        path!("/code"),        json!({            "project1": {                ".git": {},                "README.md": "# project 1",                "src": {                    "lib.rs": "fn one() -> usize { 1 }"                }            },        }),    )    .await;     let (project, headless) = init_test(&fs, cx, server_cx).await;     project        .update(cx, |project, cx| {            project.find_or_create_worktree(path!("/code/project1"), true, cx)        })        .await        .unwrap();     cx.run_until_parked();     let buffers = do_search_and_assert(        &project,        "project",        Default::default(),        false,        &[path!("project1/README.md")],        cx.clone(),    )    .await;    let buffer = buffers.into_iter().next().unwrap();     // test that the headless server is tracking which buffers we have open correctly.    cx.run_until_parked();    headless.update(server_cx, |headless, cx| {        assert!(headless.buffer_store.read(cx).has_shared_buffers())    });    do_search_and_assert(        &project,        "project",        Default::default(),        false,        &[path!("project1/README.md")],        cx.clone(),    )    .await;    server_cx.run_until_parked();    cx.update(|_| {        drop(buffer);    });    cx.run_until_parked();    server_cx.run_until_parked();    headless.update(server_cx, |headless, cx| {        assert!(!headless.buffer_store.read(cx).has_shared_buffers())    });     do_search_and_assert(        &project,        "project",        Default::default(),        false,        &[path!("project1/README.md")],        cx.clone(),    )    .await;} #[gpui::test]async fn test_remote_project_search_inclusion(    cx: &mut TestAppContext,    server_cx: &mut TestAppContext,) {    let fs = FakeFs::new(server_cx.executor());    fs.insert_tree(        path!("/code"),        json!({            "project1": {                "README.md": "# project 1",            },            "project2": {                "README.md": "# project 2",            },        }),    )    .await;     let (project, _) = init_test(&fs, cx, server_cx).await;     project        .update(cx, |project, cx| {            project.find_or_create_worktree(path!("/code/project1"), true, cx)        })        .await        .unwrap();     project        .update(cx, |project, cx| {            project.find_or_create_worktree(path!("/code/project2"), true, cx)        })        .await        .unwrap();     cx.run_until_parked();     // Case 1: Test search with path matcher limiting to only one worktree    let path_matcher = PathMatcher::new(        &["project1/*.md".to_owned()],        util::paths::PathStyle::local(),    )    .unwrap();    do_search_and_assert(        &project,        "project",        path_matcher,        true, // should be true in case of multiple worktrees        &[path!("project1/README.md")],        cx.clone(),    )    .await;     // Case 2: Test search without path matcher, matching both worktrees    do_search_and_assert(        &project,        "project",        Default::default(),        true, // should be true in case of multiple worktrees        &[path!("project1/README.md"), path!("project2/README.md")],        cx.clone(),    )    .await;} #[gpui::test]async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {    let fs = FakeFs::new(server_cx.executor());    fs.insert_tree(        "/code",        json!({            "project1": {                ".git": {},                "README.md": "# project 1",                "src": {                    "lib.rs": "fn one() -> usize { 1 }"                }            },        }),    )    .await;     let (project, headless) = init_test(&fs, cx, server_cx).await;     cx.update_global(|settings_store: &mut SettingsStore, cx| {        settings_store.set_user_settings(            r#"{"languages":{"Rust":{"language_servers":["from-local-settings"]}}}"#,            cx,        )    })    .unwrap();     cx.run_until_parked();     server_cx.read(|cx| {        assert_eq!(            AllLanguageSettings::get_global(cx)                .language(None, Some(&"Rust".into()), cx)                .language_servers,            ["from-local-settings"],            "User language settings should be synchronized with the server settings"        )    });     server_cx        .update_global(|settings_store: &mut SettingsStore, cx| {            settings_store.set_server_settings(                r#"{"languages":{"Rust":{"language_servers":["from-server-settings"]}}}"#,                cx,            )        })        .unwrap();     cx.run_until_parked();     server_cx.read(|cx| {        assert_eq!(            AllLanguageSettings::get_global(cx)                .language(None, Some(&"Rust".into()), cx)                .language_servers,            ["from-server-settings".to_string()],            "Server language settings should take precedence over the user settings"        )    });     fs.insert_tree(        "/code/project1/.zed",        json!({            "settings.json": r#"                  {                    "languages": {"Rust":{"language_servers":["override-rust-analyzer"]}},                    "lsp": {                      "override-rust-analyzer": {                        "binary": {                          "path": "~/.cargo/bin/rust-analyzer"                        }                      }                    }                  }"#        }),    )    .await;     let worktree_id = project        .update(cx, |project, cx| {            project.find_or_create_worktree("/code/project1", true, cx)        })        .await        .unwrap()        .0        .read_with(cx, |worktree, _| worktree.id());     let buffer = project        .update(cx, |project, cx| {            project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)        })        .await        .unwrap();    cx.run_until_parked();     server_cx.read(|cx| {        let worktree_id = headless            .read(cx)            .worktree_store            .read(cx)            .worktrees()            .next()            .unwrap()            .read(cx)            .id();        assert_eq!(            AllLanguageSettings::get(                Some(SettingsLocation {                    worktree_id,                    path: rel_path("src/lib.rs")                }),                cx            )            .language(None, Some(&"Rust".into()), cx)            .language_servers,            ["override-rust-analyzer".to_string()]        )    });     cx.read(|cx| {        let file = buffer.read(cx).file();        assert_eq!(            language_settings(Some("Rust".into()), file, cx).language_servers,            ["override-rust-analyzer".to_string()]        )    });} #[gpui::test]async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {    let fs = FakeFs::new(server_cx.executor());    fs.insert_tree(        path!("/code"),        json!({            "project1": {                ".git": {},                "README.md": "# project 1",                "src": {                    "lib.rs": "fn one() -> usize { 1 }"                }            },        }),    )    .await;     let (project, headless) = init_test(&fs, cx, server_cx).await;

Lines 1–500 of 2088.