1use crate::extension_builder::WASI_ADAPTER_URL;
2use crate::extension_manifest::SchemaVersion;
3use crate::extension_settings::ExtensionSettings;
4use crate::{
5 Event, ExtensionIndex, ExtensionIndexEntry, ExtensionIndexLanguageEntry,
6 ExtensionIndexThemeEntry, ExtensionManifest, ExtensionStore, GrammarManifestEntry,
7 RELOAD_DEBOUNCE_DURATION,
8};
9use assistant_slash_command::SlashCommandRegistry;
10use async_compression::futures::bufread::GzipEncoder;
11use collections::BTreeMap;
12use fs::{FakeFs, Fs, RealFs};
13use futures::{io::BufReader, AsyncReadExt, StreamExt};
14use gpui::{Context, SemanticVersion, TestAppContext};
15use http_client::{AsyncBody, FakeHttpClient, HttpClient, Response};
16use indexed_docs::IndexedDocsRegistry;
17use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
18use node_runtime::NodeRuntime;
19use parking_lot::Mutex;
20use project::{Project, DEFAULT_COMPLETION_CONTEXT};
21use release_channel::AppVersion;
22use reqwest_client::ReqwestClient;
23use serde_json::json;
24use settings::{Settings as _, SettingsStore};
25use snippet_provider::SnippetRegistry;
26use std::{
27 ffi::OsString,
28 path::{Path, PathBuf},
29 sync::Arc,
30};
31use theme::ThemeRegistry;
32use ureq_client::UreqClient;
33use util::test::temp_tree;
34
35#[cfg(test)]
36#[ctor::ctor]
37fn init_logger() {
38 if std::env::var("RUST_LOG").is_ok() {
39 env_logger::init();
40 }
41}
42
43#[gpui::test]
44async fn test_extension_store(cx: &mut TestAppContext) {
45 init_test(cx);
46
47 let fs = FakeFs::new(cx.executor());
48 let http_client = FakeHttpClient::with_200_response();
49
50 fs.insert_tree(
51 "/the-extension-dir",
52 json!({
53 "installed": {
54 "zed-monokai": {
55 "extension.json": r#"{
56 "id": "zed-monokai",
57 "name": "Zed Monokai",
58 "version": "2.0.0",
59 "themes": {
60 "Monokai Dark": "themes/monokai.json",
61 "Monokai Light": "themes/monokai.json",
62 "Monokai Pro Dark": "themes/monokai-pro.json",
63 "Monokai Pro Light": "themes/monokai-pro.json"
64 }
65 }"#,
66 "themes": {
67 "monokai.json": r#"{
68 "name": "Monokai",
69 "author": "Someone",
70 "themes": [
71 {
72 "name": "Monokai Dark",
73 "appearance": "dark",
74 "style": {}
75 },
76 {
77 "name": "Monokai Light",
78 "appearance": "light",
79 "style": {}
80 }
81 ]
82 }"#,
83 "monokai-pro.json": r#"{
84 "name": "Monokai Pro",
85 "author": "Someone",
86 "themes": [
87 {
88 "name": "Monokai Pro Dark",
89 "appearance": "dark",
90 "style": {}
91 },
92 {
93 "name": "Monokai Pro Light",
94 "appearance": "light",
95 "style": {}
96 }
97 ]
98 }"#,
99 }
100 },
101 "zed-ruby": {
102 "extension.json": r#"{
103 "id": "zed-ruby",
104 "name": "Zed Ruby",
105 "version": "1.0.0",
106 "grammars": {
107 "ruby": "grammars/ruby.wasm",
108 "embedded_template": "grammars/embedded_template.wasm"
109 },
110 "languages": {
111 "ruby": "languages/ruby",
112 "erb": "languages/erb"
113 }
114 }"#,
115 "grammars": {
116 "ruby.wasm": "",
117 "embedded_template.wasm": "",
118 },
119 "languages": {
120 "ruby": {
121 "config.toml": r#"
122 name = "Ruby"
123 grammar = "ruby"
124 path_suffixes = ["rb"]
125 "#,
126 "highlights.scm": "",
127 },
128 "erb": {
129 "config.toml": r#"
130 name = "ERB"
131 grammar = "embedded_template"
132 path_suffixes = ["erb"]
133 "#,
134 "highlights.scm": "",
135 }
136 },
137 }
138 }
139 }),
140 )
141 .await;
142
143 let mut expected_index = ExtensionIndex {
144 extensions: [
145 (
146 "zed-ruby".into(),
147 ExtensionIndexEntry {
148 manifest: Arc::new(ExtensionManifest {
149 id: "zed-ruby".into(),
150 name: "Zed Ruby".into(),
151 version: "1.0.0".into(),
152 schema_version: SchemaVersion::ZERO,
153 description: None,
154 authors: Vec::new(),
155 repository: None,
156 themes: Default::default(),
157 lib: Default::default(),
158 languages: vec!["languages/erb".into(), "languages/ruby".into()],
159 grammars: [
160 ("embedded_template".into(), GrammarManifestEntry::default()),
161 ("ruby".into(), GrammarManifestEntry::default()),
162 ]
163 .into_iter()
164 .collect(),
165 language_servers: BTreeMap::default(),
166 slash_commands: BTreeMap::default(),
167 indexed_docs_providers: BTreeMap::default(),
168 snippets: None,
169 }),
170 dev: false,
171 },
172 ),
173 (
174 "zed-monokai".into(),
175 ExtensionIndexEntry {
176 manifest: Arc::new(ExtensionManifest {
177 id: "zed-monokai".into(),
178 name: "Zed Monokai".into(),
179 version: "2.0.0".into(),
180 schema_version: SchemaVersion::ZERO,
181 description: None,
182 authors: vec![],
183 repository: None,
184 themes: vec![
185 "themes/monokai-pro.json".into(),
186 "themes/monokai.json".into(),
187 ],
188 lib: Default::default(),
189 languages: Default::default(),
190 grammars: BTreeMap::default(),
191 language_servers: BTreeMap::default(),
192 slash_commands: BTreeMap::default(),
193 indexed_docs_providers: BTreeMap::default(),
194 snippets: None,
195 }),
196 dev: false,
197 },
198 ),
199 ]
200 .into_iter()
201 .collect(),
202 languages: [
203 (
204 "ERB".into(),
205 ExtensionIndexLanguageEntry {
206 extension: "zed-ruby".into(),
207 path: "languages/erb".into(),
208 grammar: Some("embedded_template".into()),
209 matcher: LanguageMatcher {
210 path_suffixes: vec!["erb".into()],
211 first_line_pattern: None,
212 },
213 },
214 ),
215 (
216 "Ruby".into(),
217 ExtensionIndexLanguageEntry {
218 extension: "zed-ruby".into(),
219 path: "languages/ruby".into(),
220 grammar: Some("ruby".into()),
221 matcher: LanguageMatcher {
222 path_suffixes: vec!["rb".into()],
223 first_line_pattern: None,
224 },
225 },
226 ),
227 ]
228 .into_iter()
229 .collect(),
230 themes: [
231 (
232 "Monokai Dark".into(),
233 ExtensionIndexThemeEntry {
234 extension: "zed-monokai".into(),
235 path: "themes/monokai.json".into(),
236 },
237 ),
238 (
239 "Monokai Light".into(),
240 ExtensionIndexThemeEntry {
241 extension: "zed-monokai".into(),
242 path: "themes/monokai.json".into(),
243 },
244 ),
245 (
246 "Monokai Pro Dark".into(),
247 ExtensionIndexThemeEntry {
248 extension: "zed-monokai".into(),
249 path: "themes/monokai-pro.json".into(),
250 },
251 ),
252 (
253 "Monokai Pro Light".into(),
254 ExtensionIndexThemeEntry {
255 extension: "zed-monokai".into(),
256 path: "themes/monokai-pro.json".into(),
257 },
258 ),
259 ]
260 .into_iter()
261 .collect(),
262 };
263
264 let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
265 let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
266 let slash_command_registry = SlashCommandRegistry::new();
267 let indexed_docs_registry = Arc::new(IndexedDocsRegistry::new(cx.executor()));
268 let snippet_registry = Arc::new(SnippetRegistry::new());
269 let node_runtime = NodeRuntime::unavailable();
270
271 let store = cx.new_model(|cx| {
272 ExtensionStore::new(
273 PathBuf::from("/the-extension-dir"),
274 None,
275 fs.clone(),
276 http_client.clone(),
277 http_client.clone(),
278 None,
279 node_runtime.clone(),
280 language_registry.clone(),
281 theme_registry.clone(),
282 slash_command_registry.clone(),
283 indexed_docs_registry.clone(),
284 snippet_registry.clone(),
285 cx,
286 )
287 });
288
289 cx.executor().advance_clock(super::RELOAD_DEBOUNCE_DURATION);
290 store.read_with(cx, |store, _| {
291 let index = &store.extension_index;
292 assert_eq!(index.extensions, expected_index.extensions);
293 assert_eq!(index.languages, expected_index.languages);
294 assert_eq!(index.themes, expected_index.themes);
295
296 assert_eq!(
297 language_registry.language_names(),
298 ["ERB", "Plain Text", "Ruby"]
299 );
300 assert_eq!(
301 theme_registry.list_names(false),
302 [
303 "Monokai Dark",
304 "Monokai Light",
305 "Monokai Pro Dark",
306 "Monokai Pro Light",
307 "One Dark",
308 ]
309 );
310 });
311
312 fs.insert_tree(
313 "/the-extension-dir/installed/zed-gruvbox",
314 json!({
315 "extension.json": r#"{
316 "id": "zed-gruvbox",
317 "name": "Zed Gruvbox",
318 "version": "1.0.0",
319 "themes": {
320 "Gruvbox": "themes/gruvbox.json"
321 }
322 }"#,
323 "themes": {
324 "gruvbox.json": r#"{
325 "name": "Gruvbox",
326 "author": "Someone Else",
327 "themes": [
328 {
329 "name": "Gruvbox",
330 "appearance": "dark",
331 "style": {}
332 }
333 ]
334 }"#,
335 }
336 }),
337 )
338 .await;
339
340 expected_index.extensions.insert(
341 "zed-gruvbox".into(),
342 ExtensionIndexEntry {
343 manifest: Arc::new(ExtensionManifest {
344 id: "zed-gruvbox".into(),
345 name: "Zed Gruvbox".into(),
346 version: "1.0.0".into(),
347 schema_version: SchemaVersion::ZERO,
348 description: None,
349 authors: vec![],
350 repository: None,
351 themes: vec!["themes/gruvbox.json".into()],
352 lib: Default::default(),
353 languages: Default::default(),
354 grammars: BTreeMap::default(),
355 language_servers: BTreeMap::default(),
356 slash_commands: BTreeMap::default(),
357 indexed_docs_providers: BTreeMap::default(),
358 snippets: None,
359 }),
360 dev: false,
361 },
362 );
363 expected_index.themes.insert(
364 "Gruvbox".into(),
365 ExtensionIndexThemeEntry {
366 extension: "zed-gruvbox".into(),
367 path: "themes/gruvbox.json".into(),
368 },
369 );
370
371 #[allow(clippy::let_underscore_future)]
372 let _ = store.update(cx, |store, cx| store.reload(None, cx));
373
374 cx.executor().advance_clock(RELOAD_DEBOUNCE_DURATION);
375 store.read_with(cx, |store, _| {
376 let index = &store.extension_index;
377 assert_eq!(index.extensions, expected_index.extensions);
378 assert_eq!(index.languages, expected_index.languages);
379 assert_eq!(index.themes, expected_index.themes);
380
381 assert_eq!(
382 theme_registry.list_names(false),
383 [
384 "Gruvbox",
385 "Monokai Dark",
386 "Monokai Light",
387 "Monokai Pro Dark",
388 "Monokai Pro Light",
389 "One Dark",
390 ]
391 );
392 });
393
394 let prev_fs_metadata_call_count = fs.metadata_call_count();
395 let prev_fs_read_dir_call_count = fs.read_dir_call_count();
396
397 // Create new extension store, as if Zed were restarting.
398 drop(store);
399 let store = cx.new_model(|cx| {
400 ExtensionStore::new(
401 PathBuf::from("/the-extension-dir"),
402 None,
403 fs.clone(),
404 http_client.clone(),
405 http_client.clone(),
406 None,
407 node_runtime.clone(),
408 language_registry.clone(),
409 theme_registry.clone(),
410 slash_command_registry,
411 indexed_docs_registry,
412 snippet_registry,
413 cx,
414 )
415 });
416
417 cx.executor().run_until_parked();
418 store.read_with(cx, |store, _| {
419 assert_eq!(store.extension_index, expected_index);
420 assert_eq!(
421 language_registry.language_names(),
422 ["ERB", "Plain Text", "Ruby"]
423 );
424 assert_eq!(
425 language_registry.grammar_names(),
426 ["embedded_template".into(), "ruby".into()]
427 );
428 assert_eq!(
429 theme_registry.list_names(false),
430 [
431 "Gruvbox",
432 "Monokai Dark",
433 "Monokai Light",
434 "Monokai Pro Dark",
435 "Monokai Pro Light",
436 "One Dark",
437 ]
438 );
439
440 // The on-disk manifest limits the number of FS calls that need to be made
441 // on startup.
442 assert_eq!(fs.read_dir_call_count(), prev_fs_read_dir_call_count);
443 assert_eq!(fs.metadata_call_count(), prev_fs_metadata_call_count + 2);
444 });
445
446 store.update(cx, |store, cx| {
447 store.uninstall_extension("zed-ruby".into(), cx)
448 });
449
450 cx.executor().advance_clock(RELOAD_DEBOUNCE_DURATION);
451 expected_index.extensions.remove("zed-ruby");
452 expected_index.languages.remove("Ruby");
453 expected_index.languages.remove("ERB");
454
455 store.read_with(cx, |store, _| {
456 assert_eq!(store.extension_index, expected_index);
457 assert_eq!(language_registry.language_names(), ["Plain Text"]);
458 assert_eq!(language_registry.grammar_names(), []);
459 });
460}
461
462#[gpui::test]
463async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
464 init_test(cx);
465 cx.executor().allow_parking();
466
467 let root_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
468 .parent()
469 .unwrap()
470 .parent()
471 .unwrap();
472 let cache_dir = root_dir.join("target");
473 let test_extension_id = "test-extension";
474 let test_extension_dir = root_dir.join("extensions").join(test_extension_id);
475
476 let fs = Arc::new(RealFs::default());
477 let extensions_dir = temp_tree(json!({
478 "installed": {},
479 "work": {}
480 }));
481 let project_dir = temp_tree(json!({
482 "test.gleam": ""
483 }));
484
485 let extensions_dir = extensions_dir.path().canonicalize().unwrap();
486 let project_dir = project_dir.path().canonicalize().unwrap();
487
488 let project = Project::test(fs.clone(), [project_dir.as_path()], cx).await;
489
490 let language_registry = project.read_with(cx, |project, _cx| project.languages().clone());
491 let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
492 let slash_command_registry = SlashCommandRegistry::new();
493 let indexed_docs_registry = Arc::new(IndexedDocsRegistry::new(cx.executor()));
494 let snippet_registry = Arc::new(SnippetRegistry::new());
495 let node_runtime = NodeRuntime::unavailable();
496
497 let mut status_updates = language_registry.language_server_binary_statuses();
498
499 struct FakeLanguageServerVersion {
500 version: String,
501 binary_contents: String,
502 http_request_count: usize,
503 }
504
505 let language_server_version = Arc::new(Mutex::new(FakeLanguageServerVersion {
506 version: "v1.2.3".into(),
507 binary_contents: "the-binary-contents".into(),
508 http_request_count: 0,
509 }));
510
511 let extension_client = FakeHttpClient::create({
512 let language_server_version = language_server_version.clone();
513 move |request| {
514 let language_server_version = language_server_version.clone();
515 async move {
516 let version = language_server_version.lock().version.clone();
517 let binary_contents = language_server_version.lock().binary_contents.clone();
518
519 let github_releases_uri = "https://api.github.com/repos/gleam-lang/gleam/releases";
520 let asset_download_uri =
521 format!("https://fake-download.example.com/gleam-{version}");
522
523 let uri = request.uri().to_string();
524 if uri == github_releases_uri {
525 language_server_version.lock().http_request_count += 1;
526 Ok(Response::new(
527 json!([
528 {
529 "tag_name": version,
530 "prerelease": false,
531 "tarball_url": "",
532 "zipball_url": "",
533 "assets": [
534 {
535 "name": format!("gleam-{version}-aarch64-apple-darwin.tar.gz"),
536 "browser_download_url": asset_download_uri
537 },
538 {
539 "name": format!("gleam-{version}-x86_64-unknown-linux-musl.tar.gz"),
540 "browser_download_url": asset_download_uri
541 },
542 {
543 "name": format!("gleam-{version}-aarch64-unknown-linux-musl.tar.gz"),
544 "browser_download_url": asset_download_uri
545 }
546 ]
547 }
548 ])
549 .to_string()
550 .into(),
551 ))
552 } else if uri == asset_download_uri {
553 language_server_version.lock().http_request_count += 1;
554 let mut bytes = Vec::<u8>::new();
555 let mut archive = async_tar::Builder::new(&mut bytes);
556 let mut header = async_tar::Header::new_gnu();
557 header.set_size(binary_contents.len() as u64);
558 archive
559 .append_data(&mut header, "gleam", binary_contents.as_bytes())
560 .await
561 .unwrap();
562 archive.into_inner().await.unwrap();
563 let mut gzipped_bytes = Vec::new();
564 let mut encoder = GzipEncoder::new(BufReader::new(bytes.as_slice()));
565 encoder.read_to_end(&mut gzipped_bytes).await.unwrap();
566 Ok(Response::new(gzipped_bytes.into()))
567 } else {
568 Ok(Response::builder().status(404).body("not found".into())?)
569 }
570 }
571 }
572 });
573 let user_agent = cx.update(|cx| {
574 format!(
575 "Zed/{} ({}; {})",
576 AppVersion::global(cx),
577 std::env::consts::OS,
578 std::env::consts::ARCH
579 )
580 });
581 let builder_client = Arc::new(UreqClient::new(None, user_agent, cx.executor().clone()));
582
583 let extension_store = cx.new_model(|cx| {
584 ExtensionStore::new(
585 extensions_dir.clone(),
586 Some(cache_dir),
587 fs.clone(),
588 extension_client.clone(),
589 builder_client,
590 None,
591 node_runtime,
592 language_registry.clone(),
593 theme_registry.clone(),
594 slash_command_registry,
595 indexed_docs_registry,
596 snippet_registry,
597 cx,
598 )
599 });
600
601 // Ensure that debounces fire.
602 let mut events = cx.events(&extension_store);
603 let executor = cx.executor();
604 let _task = cx.executor().spawn(async move {
605 while let Some(event) = events.next().await {
606 if let crate::Event::StartedReloading = event {
607 executor.advance_clock(RELOAD_DEBOUNCE_DURATION);
608 }
609 }
610 });
611
612 extension_store.update(cx, |_, cx| {
613 cx.subscribe(&extension_store, |_, _, event, _| {
614 if matches!(event, Event::ExtensionFailedToLoad(_)) {
615 panic!("extension failed to load");
616 }
617 })
618 .detach();
619 });
620
621 extension_store
622 .update(cx, |store, cx| {
623 store.install_dev_extension(test_extension_dir.clone(), cx)
624 })
625 .await
626 .unwrap();
627
628 let mut fake_servers = language_registry.register_fake_language_server(
629 LanguageServerName("gleam".into()),
630 lsp::ServerCapabilities {
631 completion_provider: Some(Default::default()),
632 ..Default::default()
633 },
634 None,
635 );
636
637 let buffer = project
638 .update(cx, |project, cx| {
639 project.open_local_buffer(project_dir.join("test.gleam"), cx)
640 })
641 .await
642 .unwrap();
643
644 let fake_server = fake_servers.next().await.unwrap();
645 let expected_server_path =
646 extensions_dir.join(format!("work/{test_extension_id}/gleam-v1.2.3/gleam"));
647 let expected_binary_contents = language_server_version.lock().binary_contents.clone();
648
649 assert_eq!(fake_server.binary.path, expected_server_path);
650 assert_eq!(fake_server.binary.arguments, [OsString::from("lsp")]);
651 assert_eq!(
652 fs.load(&expected_server_path).await.unwrap(),
653 expected_binary_contents
654 );
655 assert_eq!(language_server_version.lock().http_request_count, 2);
656 assert_eq!(
657 [
658 status_updates.next().await.unwrap(),
659 status_updates.next().await.unwrap(),
660 status_updates.next().await.unwrap(),
661 ],
662 [
663 (
664 LanguageServerName("gleam".into()),
665 LanguageServerBinaryStatus::CheckingForUpdate
666 ),
667 (
668 LanguageServerName("gleam".into()),
669 LanguageServerBinaryStatus::Downloading
670 ),
671 (
672 LanguageServerName("gleam".into()),
673 LanguageServerBinaryStatus::None
674 )
675 ]
676 );
677
678 // The extension creates custom labels for completion items.
679 fake_server.handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
680 Ok(Some(lsp::CompletionResponse::Array(vec![
681 lsp::CompletionItem {
682 label: "foo".into(),
683 kind: Some(lsp::CompletionItemKind::FUNCTION),
684 detail: Some("fn() -> Result(Nil, Error)".into()),
685 ..Default::default()
686 },
687 lsp::CompletionItem {
688 label: "bar.baz".into(),
689 kind: Some(lsp::CompletionItemKind::FUNCTION),
690 detail: Some("fn(List(a)) -> a".into()),
691 ..Default::default()
692 },
693 lsp::CompletionItem {
694 label: "Quux".into(),
695 kind: Some(lsp::CompletionItemKind::CONSTRUCTOR),
696 detail: Some("fn(String) -> T".into()),
697 ..Default::default()
698 },
699 lsp::CompletionItem {
700 label: "my_string".into(),
701 kind: Some(lsp::CompletionItemKind::CONSTANT),
702 detail: Some("String".into()),
703 ..Default::default()
704 },
705 ])))
706 });
707
708 let completion_labels = project
709 .update(cx, |project, cx| {
710 project.completions(&buffer, 0, DEFAULT_COMPLETION_CONTEXT, cx)
711 })
712 .await
713 .unwrap()
714 .into_iter()
715 .map(|c| c.label.text)
716 .collect::<Vec<_>>();
717 assert_eq!(
718 completion_labels,
719 [
720 "foo: fn() -> Result(Nil, Error)".to_string(),
721 "bar.baz: fn(List(a)) -> a".to_string(),
722 "Quux: fn(String) -> T".to_string(),
723 "my_string: String".to_string(),
724 ]
725 );
726
727 // Simulate a new version of the language server being released
728 language_server_version.lock().version = "v2.0.0".into();
729 language_server_version.lock().binary_contents = "the-new-binary-contents".into();
730 language_server_version.lock().http_request_count = 0;
731
732 // Start a new instance of the language server.
733 project.update(cx, |project, cx| {
734 project.restart_language_servers_for_buffers([buffer.clone()], cx)
735 });
736
737 // The extension has cached the binary path, and does not attempt
738 // to reinstall it.
739 let fake_server = fake_servers.next().await.unwrap();
740 assert_eq!(fake_server.binary.path, expected_server_path);
741 assert_eq!(
742 fs.load(&expected_server_path).await.unwrap(),
743 expected_binary_contents
744 );
745 assert_eq!(language_server_version.lock().http_request_count, 0);
746
747 // Reload the extension, clearing its cache.
748 // Start a new instance of the language server.
749 extension_store
750 .update(cx, |store, cx| store.reload(Some("gleam".into()), cx))
751 .await;
752
753 cx.executor().run_until_parked();
754 project.update(cx, |project, cx| {
755 project.restart_language_servers_for_buffers([buffer.clone()], cx)
756 });
757
758 // The extension re-fetches the latest version of the language server.
759 let fake_server = fake_servers.next().await.unwrap();
760 let new_expected_server_path =
761 extensions_dir.join(format!("work/{test_extension_id}/gleam-v2.0.0/gleam"));
762 let expected_binary_contents = language_server_version.lock().binary_contents.clone();
763 assert_eq!(fake_server.binary.path, new_expected_server_path);
764 assert_eq!(fake_server.binary.arguments, [OsString::from("lsp")]);
765 assert_eq!(
766 fs.load(&new_expected_server_path).await.unwrap(),
767 expected_binary_contents
768 );
769
770 // The old language server directory has been cleaned up.
771 assert!(fs.metadata(&expected_server_path).await.unwrap().is_none());
772}
773
774#[gpui::test]
775async fn test_wasi_adapter_download(cx: &mut TestAppContext) {
776 let client = Arc::new(UreqClient::new(
777 None,
778 "zed-test-wasi-adapter-download".to_string(),
779 cx.executor().clone(),
780 ));
781
782 let mut response = client
783 .get(WASI_ADAPTER_URL, AsyncBody::default(), true)
784 .await
785 .unwrap();
786
787 let mut content = Vec::new();
788 let mut body = BufReader::new(response.body_mut());
789 body.read_to_end(&mut content).await.unwrap();
790
791 assert!(wasmparser::Parser::is_core_wasm(&content));
792 assert_eq!(content.len(), 96801); // Determined by downloading this to my computer
793 wit_component::ComponentEncoder::default()
794 .adapter("wasi_snapshot_preview1", &content)
795 .unwrap();
796}
797
798#[tokio::test]
799async fn test_wasi_adapter_download_tokio() {
800 let client = Arc::new(ReqwestClient::new());
801
802 let mut response = client
803 .get(WASI_ADAPTER_URL, AsyncBody::default(), true)
804 .await
805 .unwrap();
806
807 let mut content = Vec::new();
808 let mut body = BufReader::new(response.body_mut());
809 body.read_to_end(&mut content).await.unwrap();
810
811 assert!(wasmparser::Parser::is_core_wasm(&content));
812 assert_eq!(content.len(), 96801); // Determined by downloading this to my computer
813 wit_component::ComponentEncoder::default()
814 .adapter("wasi_snapshot_preview1", &content)
815 .unwrap();
816}
817
818fn init_test(cx: &mut TestAppContext) {
819 cx.update(|cx| {
820 let store = SettingsStore::test(cx);
821 cx.set_global(store);
822 release_channel::init(SemanticVersion::default(), cx);
823 theme::init(theme::LoadThemes::JustBase, cx);
824 Project::init_settings(cx);
825 ExtensionSettings::register(cx);
826 language::init(cx);
827 });
828}