1616 lines · 66.6 KB · Rust
RawDownload
use super::{RandomizedTest, TestClient, TestError, TestServer, UserTestPlan};use crate::{db::UserId, tests::run_randomized_test};use anyhow::{anyhow, Result};use async_trait::async_trait;use call::ActiveCall;use collections::{BTreeMap, HashMap};use editor::Bias;use fs::{FakeFs, Fs as _};use futures::StreamExt;use git::repository::GitFileStatus;use gpui::{BackgroundExecutor, Model, TestAppContext};use language::{    range_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, PointUtf16,};use lsp::FakeLanguageServer;use pretty_assertions::assert_eq;use project::{    search::SearchQuery, search::SearchResult, Project, ProjectPath, DEFAULT_COMPLETION_CONTEXT,};use rand::{    distributions::{Alphanumeric, DistString},    prelude::*,};use serde::{Deserialize, Serialize};use std::{    ops::{Deref, Range},    path::{Path, PathBuf},    rc::Rc,    sync::Arc,};use util::ResultExt; #[gpui::test(    iterations = 100,    on_failure = "crate::tests::save_randomized_test_plan")]async fn test_random_project_collaboration(    cx: &mut TestAppContext,    executor: BackgroundExecutor,    rng: StdRng,) {    run_randomized_test::<ProjectCollaborationTest>(cx, executor, rng).await;} #[derive(Clone, Debug, Serialize, Deserialize)]enum ClientOperation {    AcceptIncomingCall,    RejectIncomingCall,    LeaveCall,    InviteContactToCall {        user_id: UserId,    },    OpenLocalProject {        first_root_name: String,    },    OpenRemoteProject {        host_id: UserId,        first_root_name: String,    },    AddWorktreeToProject {        project_root_name: String,        new_root_path: PathBuf,    },    CloseRemoteProject {        project_root_name: String,    },    OpenBuffer {        project_root_name: String,        is_local: bool,        full_path: PathBuf,    },    SearchProject {        project_root_name: String,        is_local: bool,        query: String,        detach: bool,    },    EditBuffer {        project_root_name: String,        is_local: bool,        full_path: PathBuf,        edits: Vec<(Range<usize>, Arc<str>)>,    },    CloseBuffer {        project_root_name: String,        is_local: bool,        full_path: PathBuf,    },    SaveBuffer {        project_root_name: String,        is_local: bool,        full_path: PathBuf,        detach: bool,    },    RequestLspDataInBuffer {        project_root_name: String,        is_local: bool,        full_path: PathBuf,        offset: usize,        kind: LspRequestKind,        detach: bool,    },    CreateWorktreeEntry {        project_root_name: String,        is_local: bool,        full_path: PathBuf,        is_dir: bool,    },    WriteFsEntry {        path: PathBuf,        is_dir: bool,        content: String,    },    GitOperation {        operation: GitOperation,    },} #[derive(Clone, Debug, Serialize, Deserialize)]enum GitOperation {    WriteGitIndex {        repo_path: PathBuf,        contents: Vec<(PathBuf, String)>,    },    WriteGitBranch {        repo_path: PathBuf,        new_branch: Option<String>,    },    WriteGitStatuses {        repo_path: PathBuf,        statuses: Vec<(PathBuf, GitFileStatus)>,        git_operation: bool,    },} #[derive(Clone, Debug, Serialize, Deserialize)]enum LspRequestKind {    Rename,    Completion,    CodeAction,    Definition,    Highlights,} struct ProjectCollaborationTest; #[async_trait(?Send)]impl RandomizedTest for ProjectCollaborationTest {    type Operation = ClientOperation;     async fn initialize(server: &mut TestServer, users: &[UserTestPlan]) {        let db = &server.app_state.db;        for (ix, user_a) in users.iter().enumerate() {            for user_b in &users[ix + 1..] {                db.send_contact_request(user_a.user_id, user_b.user_id)                    .await                    .unwrap();                db.respond_to_contact_request(user_b.user_id, user_a.user_id, true)                    .await                    .unwrap();            }        }    }     fn generate_operation(        client: &TestClient,        rng: &mut StdRng,        plan: &mut UserTestPlan,        cx: &TestAppContext,    ) -> ClientOperation {        let call = cx.read(ActiveCall::global);        loop {            match rng.gen_range(0..100_u32) {                // Mutate the call                0..=29 => {                    // Respond to an incoming call                    if call.read_with(cx, |call, _| call.incoming().borrow().is_some()) {                        break if rng.gen_bool(0.7) {                            ClientOperation::AcceptIncomingCall                        } else {                            ClientOperation::RejectIncomingCall                        };                    }                     match rng.gen_range(0..100_u32) {                        // Invite a contact to the current call                        0..=70 => {                            let available_contacts =                                client.user_store().read_with(cx, |user_store, _| {                                    user_store                                        .contacts()                                        .iter()                                        .filter(|contact| contact.online && !contact.busy)                                        .cloned()                                        .collect::<Vec<_>>()                                });                            if !available_contacts.is_empty() {                                let contact = available_contacts.choose(rng).unwrap();                                break ClientOperation::InviteContactToCall {                                    user_id: UserId(contact.user.id as i32),                                };                            }                        }                         // Leave the current call                        71.. => {                            if plan.allow_client_disconnection                                && call.read_with(cx, |call, _| call.room().is_some())                            {                                break ClientOperation::LeaveCall;                            }                        }                    }                }                 // Mutate projects                30..=59 => match rng.gen_range(0..100_u32) {                    // Open a new project                    0..=70 => {                        // Open a remote project                        if let Some(room) = call.read_with(cx, |call, _| call.room().cloned()) {                            let existing_dev_server_project_ids = cx.read(|cx| {                                client                                    .dev_server_projects()                                    .iter()                                    .map(|p| p.read(cx).remote_id().unwrap())                                    .collect::<Vec<_>>()                            });                            let new_dev_server_projects = room.read_with(cx, |room, _| {                                room.remote_participants()                                    .values()                                    .flat_map(|participant| {                                        participant.projects.iter().filter_map(|project| {                                            if existing_dev_server_project_ids.contains(&project.id)                                            {                                                None                                            } else {                                                Some((                                                    UserId::from_proto(participant.user.id),                                                    project.worktree_root_names[0].clone(),                                                ))                                            }                                        })                                    })                                    .collect::<Vec<_>>()                            });                            if !new_dev_server_projects.is_empty() {                                let (host_id, first_root_name) =                                    new_dev_server_projects.choose(rng).unwrap().clone();                                break ClientOperation::OpenRemoteProject {                                    host_id,                                    first_root_name,                                };                            }                        }                        // Open a local project                        else {                            let first_root_name = plan.next_root_dir_name();                            break ClientOperation::OpenLocalProject { first_root_name };                        }                    }                     // Close a remote project                    71..=80 => {                        if !client.dev_server_projects().is_empty() {                            let project = client.dev_server_projects().choose(rng).unwrap().clone();                            let first_root_name = root_name_for_project(&project, cx);                            break ClientOperation::CloseRemoteProject {                                project_root_name: first_root_name,                            };                        }                    }                     // Mutate project worktrees                    81.. => match rng.gen_range(0..100_u32) {                        // Add a worktree to a local project                        0..=50 => {                            let Some(project) = client.local_projects().choose(rng).cloned() else {                                continue;                            };                            let project_root_name = root_name_for_project(&project, cx);                            let mut paths = client.fs().paths(false);                            paths.remove(0);                            let new_root_path = if paths.is_empty() || rng.gen() {                                Path::new("/").join(plan.next_root_dir_name())                            } else {                                paths.choose(rng).unwrap().clone()                            };                            break ClientOperation::AddWorktreeToProject {                                project_root_name,                                new_root_path,                            };                        }                         // Add an entry to a worktree                        _ => {                            let Some(project) = choose_random_project(client, rng) else {                                continue;                            };                            let project_root_name = root_name_for_project(&project, cx);                            let is_local = project.read_with(cx, |project, _| project.is_local());                            let worktree = project.read_with(cx, |project, cx| {                                project                                    .worktrees(cx)                                    .filter(|worktree| {                                        let worktree = worktree.read(cx);                                        worktree.is_visible()                                            && worktree.entries(false, 0).any(|e| e.is_file())                                            && worktree.root_entry().map_or(false, |e| e.is_dir())                                    })                                    .choose(rng)                            });                            let Some(worktree) = worktree else { continue };                            let is_dir = rng.gen::<bool>();                            let mut full_path =                                worktree.read_with(cx, |w, _| PathBuf::from(w.root_name()));                            full_path.push(gen_file_name(rng));                            if !is_dir {                                full_path.set_extension("rs");                            }                            break ClientOperation::CreateWorktreeEntry {                                project_root_name,                                is_local,                                full_path,                                is_dir,                            };                        }                    },                },                 // Query and mutate buffers                60..=90 => {                    let Some(project) = choose_random_project(client, rng) else {                        continue;                    };                    let project_root_name = root_name_for_project(&project, cx);                    let is_local = project.read_with(cx, |project, _| project.is_local());                     match rng.gen_range(0..100_u32) {                        // Manipulate an existing buffer                        0..=70 => {                            let Some(buffer) = client                                .buffers_for_project(&project)                                .iter()                                .choose(rng)                                .cloned()                            else {                                continue;                            };                             let full_path = buffer                                .read_with(cx, |buffer, cx| buffer.file().unwrap().full_path(cx));                             match rng.gen_range(0..100_u32) {                                // Close the buffer                                0..=15 => {                                    break ClientOperation::CloseBuffer {                                        project_root_name,                                        is_local,                                        full_path,                                    };                                }                                // Save the buffer                                16..=29 if buffer.read_with(cx, |b, _| b.is_dirty()) => {                                    let detach = rng.gen_bool(0.3);                                    break ClientOperation::SaveBuffer {                                        project_root_name,                                        is_local,                                        full_path,                                        detach,                                    };                                }                                // Edit the buffer                                30..=69 => {                                    let edits = buffer                                        .read_with(cx, |buffer, _| buffer.get_random_edits(rng, 3));                                    break ClientOperation::EditBuffer {                                        project_root_name,                                        is_local,                                        full_path,                                        edits,                                    };                                }                                // Make an LSP request                                _ => {                                    let offset = buffer.read_with(cx, |buffer, _| {                                        buffer.clip_offset(                                            rng.gen_range(0..=buffer.len()),                                            language::Bias::Left,                                        )                                    });                                    let detach = rng.gen();                                    break ClientOperation::RequestLspDataInBuffer {                                        project_root_name,                                        full_path,                                        offset,                                        is_local,                                        kind: match rng.gen_range(0..5_u32) {                                            0 => LspRequestKind::Rename,                                            1 => LspRequestKind::Highlights,                                            2 => LspRequestKind::Definition,                                            3 => LspRequestKind::CodeAction,                                            4.. => LspRequestKind::Completion,                                        },                                        detach,                                    };                                }                            }                        }                         71..=80 => {                            let query = rng.gen_range('a'..='z').to_string();                            let detach = rng.gen_bool(0.3);                            break ClientOperation::SearchProject {                                project_root_name,                                is_local,                                query,                                detach,                            };                        }                         // Open a buffer                        81.. => {                            let worktree = project.read_with(cx, |project, cx| {                                project                                    .worktrees(cx)                                    .filter(|worktree| {                                        let worktree = worktree.read(cx);                                        worktree.is_visible()                                            && worktree.entries(false, 0).any(|e| e.is_file())                                    })                                    .choose(rng)                            });                            let Some(worktree) = worktree else { continue };                            let full_path = worktree.read_with(cx, |worktree, _| {                                let entry = worktree                                    .entries(false, 0)                                    .filter(|e| e.is_file())                                    .choose(rng)                                    .unwrap();                                if entry.path.as_ref() == Path::new("") {                                    Path::new(worktree.root_name()).into()                                } else {                                    Path::new(worktree.root_name()).join(&entry.path)                                }                            });                            break ClientOperation::OpenBuffer {                                project_root_name,                                is_local,                                full_path,                            };                        }                    }                }                 // Update a git related action                91..=95 => {                    break ClientOperation::GitOperation {                        operation: generate_git_operation(rng, client),                    };                }                 // Create or update a file or directory                96.. => {                    let is_dir = rng.gen::<bool>();                    let content;                    let mut path;                    let dir_paths = client.fs().directories(false);                     if is_dir {                        content = String::new();                        path = dir_paths.choose(rng).unwrap().clone();                        path.push(gen_file_name(rng));                    } else {                        content = Alphanumeric.sample_string(rng, 16);                         // Create a new file or overwrite an existing file                        let file_paths = client.fs().files();                        if file_paths.is_empty() || rng.gen_bool(0.5) {                            path = dir_paths.choose(rng).unwrap().clone();                            path.push(gen_file_name(rng));                            path.set_extension("rs");                        } else {                            path = file_paths.choose(rng).unwrap().clone()                        };                    }                    break ClientOperation::WriteFsEntry {                        path,                        is_dir,                        content,                    };                }            }        }    }     async fn apply_operation(        client: &TestClient,        operation: ClientOperation,        cx: &mut TestAppContext,

Lines 1–500 of 1616.