3800 lines · 111.1 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, NativeAgent, NativeAgentConnection, ReadFileTool, ReadFileToolInput, SkillTool,    SkillToolInput, SkillToolOutput, Templates, ThreadStore, ToolCallEventStream, ToolInput,    skill_body_resolver_for_project, skills_resolver_for_project,};use client::{Client, UserStore};use clock::FakeSystemClock;use collections::{HashMap, HashSet};use language_model::{LanguageModelRegistry, LanguageModelToolResultContent};use languages::rust_lang; use editor::{    Editor, SelectionEffects,    actions::{ConfirmCodeAction, ToggleCodeActions},    code_context_menus::CodeContextMenu,};use extension::ExtensionHostProxy;use fs::{FakeFs, Fs};use git::{    Oid,    repository::{CommitData, RepoPath, Worktree as GitWorktree},};use gpui::{AppContext as _, Entity, SharedString, TestAppContext, UpdateGlobal, VisualContext};use http_client::{BlockedHttpClient, FakeHttpClient};use language::{    Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LineEnding, Point,    language_settings::{AllLanguageSettings, LanguageSettings},};use lsp::{    CompletionContext, CompletionResponse, CompletionTriggerKind, DEFAULT_LSP_REQUEST_TIMEOUT,    LanguageServerName,};use node_runtime::NodeRuntime;use project::{    ProgressToken, Project,    agent_server_store::AgentServerCommand,    search::{SearchQuery, SearchResult},};use remote::RemoteClient;use rpc::proto;use serde_json::json;use settings::{Settings, SettingsLocation, SettingsStore, initial_server_settings_content};use smol::stream::StreamExt;use std::{    path::{Path, PathBuf},    rc::Rc,    str::FromStr,    sync::Arc,};use unindent::Unindent as _;use util::{path, path_list::PathList, 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 }"        );    });} #[gpui::test]async fn test_remote_telemetry_event_forwarding(    cx: &mut TestAppContext,    server_cx: &mut TestAppContext,) {    // This mirrors `init_test`, but retains the server-side session so the test    // can drive a forwarded telemetry event over the proto channel (as    // `init_telemetry_forwarding` does on a real remote server).    let server_fs = FakeFs::new(server_cx.executor());    server_fs        .insert_tree(            path!("/code"),            json!({ "project1": { "README.md": "# project 1" } }),        )        .await;     cx.update(|cx| release_channel::init(semver::Version::new(0, 0, 0), cx));    server_cx.update(|cx| release_channel::init(semver::Version::new(0, 0, 0), cx));    init_logger();     let (opts, server_session, _) = RemoteClient::fake_server(cx, server_cx);    let http_client = Arc::new(BlockedHttpClient);    let node_runtime = NodeRuntime::unavailable();    let languages = Arc::new(LanguageRegistry::new(cx.executor()));    let proxy = Arc::new(ExtensionHostProxy::new());    server_cx.update(HeadlessProject::init);    let headless = server_cx.new(|cx| {        HeadlessProject::new(            crate::HeadlessAppState {                session: server_session.clone(),                fs: server_fs.clone(),                http_client,                node_runtime,                languages,                extension_host_proxy: proxy,                startup_time: std::time::Instant::now(),            },            false,            cx,        )    });     let ssh = RemoteClient::connect_mock(opts, cx).await;    let project = build_project(ssh, cx);    project        .update(cx, {            let headless = headless.clone();            |_, cx| cx.on_release(|_, _| drop(headless))        })        .detach();     // The remote server forwards a bare `FlexibleEvent` as JSON; mirror that    // here by sending the proto message the forwarding task would send.    let event_json = json!({        "event_type": "fs_watcher_poll",        "event_properties": { "path": "/code/project1" },    })    .to_string();    server_session        .send(proto::TelemetryEvent {            project_id: proto::REMOTE_SERVER_PROJECT_ID,            event_json,        })        .unwrap();    cx.executor().run_until_parked();     let events = project.read_with(cx, |project, _| {        project.client().telemetry().queued_events()    });    assert_eq!(        events.len(),        1,        "the forwarded event should be reported once"    );    let event = &events[0];    assert_eq!(event.event_type, "fs_watcher_poll");    // The event's original properties survive the round-trip.    assert_eq!(        event.event_properties.get("path"),        Some(&serde_json::Value::String("/code/project1".to_string()))    );    // The client stamps the remote host metadata it learned at connection time.    // The mock connection reports a Linux/x86_64 host over a "mock" connection.    assert_eq!(        event.event_properties.get("remote"),        Some(&serde_json::Value::Bool(true))    );    assert_eq!(        event.event_properties.get("remote_connection_type"),        Some(&serde_json::Value::String("mock".to_string()))    );    assert_eq!(        event.event_properties.get("remote_os_name"),        Some(&serde_json::Value::String("Linux".to_string()))    );    assert_eq!(        event.event_properties.get("remote_architecture"),        Some(&serde_json::Value::String("x86_64".to_string()))    );} 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 buffer = loop {            let response = receiver.rx.recv().await.unwrap();            match response {                SearchResult::Buffer { buffer, .. } => break buffer,                SearchResult::LimitReached => panic!("incorrect result"),                SearchResult::WaitingForScan | SearchResult::Searching => continue,            }        };        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_single_cpu(    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;     // Simulate a single-CPU environment (e.g. a devcontainer with 1 visible CPU).    // This causes the worker pool in project search to spawn num_cpus - 1 = 0 workers,    // which silently drops all search channels and produces zero results.    server_cx.executor().set_num_cpus(1);     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();     cx.run_until_parked();     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()],

Lines 1–500 of 3800.