Move ExtensionStore tests back to extension_host (#20682)

This PR moves the tests for the `ExtensionStore` back into the
`extension_host` crate.

We now have a separate `TestExtensionRegistrationHooks` to use in the
test that implements the minimal required functionality needed for the
tests. This means that we can depend on the `theme` crate only in the
tests.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2024-11-14 12:09:41 -05:00 committed by GitHub
parent 5d17cfab31
commit d177a1d4e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 106 additions and 88 deletions

11
Cargo.lock generated
View file

@ -4189,6 +4189,7 @@ dependencies = [
"serde_json_lenient", "serde_json_lenient",
"settings", "settings",
"task", "task",
"theme",
"toml 0.8.19", "toml 0.8.19",
"url", "url",
"util", "util",
@ -4203,36 +4204,26 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assistant_slash_command", "assistant_slash_command",
"async-compression",
"async-tar",
"client", "client",
"collections", "collections",
"context_servers", "context_servers",
"ctor",
"db", "db",
"editor", "editor",
"env_logger 0.11.5",
"extension", "extension",
"extension_host", "extension_host",
"fs", "fs",
"futures 0.3.30",
"fuzzy", "fuzzy",
"gpui", "gpui",
"http_client",
"indexed_docs", "indexed_docs",
"language", "language",
"log", "log",
"lsp", "lsp",
"node_runtime",
"num-format", "num-format",
"parking_lot",
"picker", "picker",
"project", "project",
"release_channel", "release_channel",
"reqwest_client",
"semantic_version", "semantic_version",
"serde", "serde",
"serde_json",
"settings", "settings",
"smallvec", "smallvec",
"snippet_provider", "snippet_provider",

View file

@ -58,3 +58,4 @@ language = { workspace = true, features = ["test-support"] }
parking_lot.workspace = true parking_lot.workspace = true
project = { workspace = true, features = ["test-support"] } project = { workspace = true, features = ["test-support"] }
reqwest_client.workspace = true reqwest_client.workspace = true
theme = { workspace = true, features = ["test-support"] }

View file

@ -2,6 +2,9 @@ pub mod extension_lsp_adapter;
pub mod extension_settings; pub mod extension_settings;
pub mod wasm_host; pub mod wasm_host;
#[cfg(test)]
mod extension_store_test;
use crate::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host::wit}; use crate::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host::wit};
use anyhow::{anyhow, bail, Context as _, Result}; use anyhow::{anyhow, bail, Context as _, Result};
use async_compression::futures::bufread::GzipDecoder; use async_compression::futures::bufread::GzipDecoder;

View file

@ -1,20 +1,17 @@
use assistant_slash_command::SlashCommandRegistry; use crate::extension_lsp_adapter::ExtensionLspAdapter;
use crate::{
Event, ExtensionIndex, ExtensionIndexEntry, ExtensionIndexLanguageEntry,
ExtensionIndexThemeEntry, ExtensionManifest, ExtensionSettings, ExtensionStore,
GrammarManifestEntry, SchemaVersion, RELOAD_DEBOUNCE_DURATION,
};
use anyhow::Result;
use async_compression::futures::bufread::GzipEncoder; use async_compression::futures::bufread::GzipEncoder;
use collections::BTreeMap; use collections::BTreeMap;
use context_servers::ContextServerFactoryRegistry;
use extension_host::ExtensionSettings;
use extension_host::SchemaVersion;
use extension_host::{
Event, ExtensionIndex, ExtensionIndexEntry, ExtensionIndexLanguageEntry,
ExtensionIndexThemeEntry, ExtensionManifest, ExtensionStore, GrammarManifestEntry,
RELOAD_DEBOUNCE_DURATION,
};
use fs::{FakeFs, Fs, RealFs}; use fs::{FakeFs, Fs, RealFs};
use futures::{io::BufReader, AsyncReadExt, StreamExt}; use futures::{io::BufReader, AsyncReadExt, StreamExt};
use gpui::{Context, SemanticVersion, TestAppContext}; use gpui::{BackgroundExecutor, Context, SemanticVersion, SharedString, Task, TestAppContext};
use http_client::{FakeHttpClient, Response}; use http_client::{FakeHttpClient, Response};
use indexed_docs::IndexedDocsRegistry; use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LoadedLanguage};
use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus};
use lsp::LanguageServerName; use lsp::LanguageServerName;
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use parking_lot::Mutex; use parking_lot::Mutex;
@ -23,7 +20,6 @@ use release_channel::AppVersion;
use reqwest_client::ReqwestClient; use reqwest_client::ReqwestClient;
use serde_json::json; use serde_json::json;
use settings::{Settings as _, SettingsStore}; use settings::{Settings as _, SettingsStore};
use snippet_provider::SnippetRegistry;
use std::{ use std::{
ffi::OsString, ffi::OsString,
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -32,6 +28,84 @@ use std::{
use theme::ThemeRegistry; use theme::ThemeRegistry;
use util::test::temp_tree; use util::test::temp_tree;
use crate::ExtensionRegistrationHooks;
struct TestExtensionRegistrationHooks {
executor: BackgroundExecutor,
language_registry: Arc<LanguageRegistry>,
theme_registry: Arc<ThemeRegistry>,
}
impl ExtensionRegistrationHooks for TestExtensionRegistrationHooks {
fn list_theme_names(&self, path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>> {
self.executor.spawn(async move {
let themes = theme::read_user_theme(&path, fs).await?;
Ok(themes.themes.into_iter().map(|theme| theme.name).collect())
})
}
fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn fs::Fs>) -> Task<Result<()>> {
let theme_registry = self.theme_registry.clone();
self.executor
.spawn(async move { theme_registry.load_user_theme(&theme_path, fs).await })
}
fn remove_user_themes(&self, themes: Vec<SharedString>) {
self.theme_registry.remove_user_themes(&themes);
}
fn register_language(
&self,
language: language::LanguageName,
grammar: Option<Arc<str>>,
matcher: language::LanguageMatcher,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
) {
self.language_registry
.register_language(language, grammar, matcher, load)
}
fn remove_languages(
&self,
languages_to_remove: &[language::LanguageName],
grammars_to_remove: &[Arc<str>],
) {
self.language_registry
.remove_languages(&languages_to_remove, &grammars_to_remove);
}
fn register_wasm_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>) {
self.language_registry.register_wasm_grammars(grammars)
}
fn register_lsp_adapter(
&self,
language_name: language::LanguageName,
adapter: ExtensionLspAdapter,
) {
self.language_registry
.register_lsp_adapter(language_name, Arc::new(adapter));
}
fn update_lsp_status(
&self,
server_name: lsp::LanguageServerName,
status: LanguageServerBinaryStatus,
) {
self.language_registry
.update_lsp_status(server_name, status);
}
fn remove_lsp_adapter(
&self,
language_name: &language::LanguageName,
server_name: &lsp::LanguageServerName,
) {
self.language_registry
.remove_lsp_adapter(language_name, server_name);
}
}
#[cfg(test)] #[cfg(test)]
#[ctor::ctor] #[ctor::ctor]
fn init_logger() { fn init_logger() {
@ -265,27 +339,18 @@ async fn test_extension_store(cx: &mut TestAppContext) {
let language_registry = Arc::new(LanguageRegistry::test(cx.executor())); let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(()))); let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
let slash_command_registry = SlashCommandRegistry::new(); let registration_hooks = Arc::new(TestExtensionRegistrationHooks {
let indexed_docs_registry = Arc::new(IndexedDocsRegistry::new(cx.executor())); executor: cx.executor(),
let snippet_registry = Arc::new(SnippetRegistry::new()); language_registry: language_registry.clone(),
let context_server_factory_registry = cx.new_model(|_| ContextServerFactoryRegistry::new()); theme_registry: theme_registry.clone(),
});
let node_runtime = NodeRuntime::unavailable(); let node_runtime = NodeRuntime::unavailable();
let store = cx.new_model(|cx| { let store = cx.new_model(|cx| {
let extension_registration_hooks = crate::ConcreteExtensionRegistrationHooks::new(
theme_registry.clone(),
slash_command_registry.clone(),
indexed_docs_registry.clone(),
snippet_registry.clone(),
language_registry.clone(),
context_server_factory_registry.clone(),
cx,
);
ExtensionStore::new( ExtensionStore::new(
PathBuf::from("/the-extension-dir"), PathBuf::from("/the-extension-dir"),
None, None,
extension_registration_hooks, registration_hooks.clone(),
fs.clone(), fs.clone(),
http_client.clone(), http_client.clone(),
http_client.clone(), http_client.clone(),
@ -407,20 +472,10 @@ async fn test_extension_store(cx: &mut TestAppContext) {
// Create new extension store, as if Zed were restarting. // Create new extension store, as if Zed were restarting.
drop(store); drop(store);
let store = cx.new_model(|cx| { let store = cx.new_model(|cx| {
let extension_api = crate::ConcreteExtensionRegistrationHooks::new(
theme_registry.clone(),
slash_command_registry,
indexed_docs_registry,
snippet_registry,
language_registry.clone(),
context_server_factory_registry.clone(),
cx,
);
ExtensionStore::new( ExtensionStore::new(
PathBuf::from("/the-extension-dir"), PathBuf::from("/the-extension-dir"),
None, None,
extension_api, registration_hooks,
fs.clone(), fs.clone(),
http_client.clone(), http_client.clone(),
http_client.clone(), http_client.clone(),
@ -505,10 +560,11 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
let language_registry = project.read_with(cx, |project, _cx| project.languages().clone()); let language_registry = project.read_with(cx, |project, _cx| project.languages().clone());
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(()))); let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
let slash_command_registry = SlashCommandRegistry::new(); let registration_hooks = Arc::new(TestExtensionRegistrationHooks {
let indexed_docs_registry = Arc::new(IndexedDocsRegistry::new(cx.executor())); executor: cx.executor(),
let snippet_registry = Arc::new(SnippetRegistry::new()); language_registry: language_registry.clone(),
let context_server_factory_registry = cx.new_model(|_| ContextServerFactoryRegistry::new()); theme_registry: theme_registry.clone(),
});
let node_runtime = NodeRuntime::unavailable(); let node_runtime = NodeRuntime::unavailable();
let mut status_updates = language_registry.language_server_binary_statuses(); let mut status_updates = language_registry.language_server_binary_statuses();
@ -599,19 +655,10 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
Arc::new(ReqwestClient::user_agent(&user_agent).expect("Could not create HTTP client")); Arc::new(ReqwestClient::user_agent(&user_agent).expect("Could not create HTTP client"));
let extension_store = cx.new_model(|cx| { let extension_store = cx.new_model(|cx| {
let extension_api = crate::ConcreteExtensionRegistrationHooks::new(
theme_registry.clone(),
slash_command_registry,
indexed_docs_registry,
snippet_registry,
language_registry.clone(),
context_server_factory_registry.clone(),
cx,
);
ExtensionStore::new( ExtensionStore::new(
extensions_dir.clone(), extensions_dir.clone(),
Some(cache_dir), Some(cache_dir),
extension_api, registration_hooks,
fs.clone(), fs.clone(),
extension_client.clone(), extension_client.clone(),
builder_client, builder_client,
@ -626,7 +673,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
let executor = cx.executor(); let executor = cx.executor();
let _task = cx.executor().spawn(async move { let _task = cx.executor().spawn(async move {
while let Some(event) = events.next().await { while let Some(event) = events.next().await {
if let extension_host::Event::StartedReloading = event { if let Event::StartedReloading = event {
executor.advance_clock(RELOAD_DEBOUNCE_DURATION); executor.advance_clock(RELOAD_DEBOUNCE_DURATION);
} }
} }

View file

@ -11,9 +11,6 @@ workspace = true
[lib] [lib]
path = "src/extensions_ui.rs" path = "src/extensions_ui.rs"
[features]
test-support = []
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
assistant_slash_command.workspace = true assistant_slash_command.workspace = true
@ -25,7 +22,6 @@ editor.workspace = true
extension.workspace = true extension.workspace = true
extension_host.workspace = true extension_host.workspace = true
fs.workspace = true fs.workspace = true
futures.workspace = true
fuzzy.workspace = true fuzzy.workspace = true
gpui.workspace = true gpui.workspace = true
indexed_docs.workspace = true indexed_docs.workspace = true
@ -50,21 +46,4 @@ wasmtime-wasi.workspace = true
workspace.workspace = true workspace.workspace = true
[dev-dependencies] [dev-dependencies]
async-compression.workspace = true
async-tar.workspace = true
ctor.workspace = true
editor = { workspace = true, features = ["test-support"] } editor = { workspace = true, features = ["test-support"] }
env_logger.workspace = true
extension_host = {workspace = true, features = ["test-support"] }
fs = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] }
http_client.workspace = true
indexed_docs.workspace = true
language = { workspace = true, features = ["test-support"] }
lsp.workspace = true
node_runtime.workspace = true
parking_lot.workspace = true
project = { workspace = true, features = ["test-support"] }
reqwest_client.workspace = true
serde_json.workspace = true
workspace = { workspace = true, features = ["test-support"] }

View file

@ -3,9 +3,6 @@ mod extension_registration_hooks;
mod extension_suggest; mod extension_suggest;
mod extension_version_selector; mod extension_version_selector;
#[cfg(test)]
mod extension_store_test;
pub use extension_registration_hooks::ConcreteExtensionRegistrationHooks; pub use extension_registration_hooks::ConcreteExtensionRegistrationHooks;
use std::ops::DerefMut; use std::ops::DerefMut;