1use anyhow::Result;
2use context_server::test::create_fake_transport;
3use context_server::{ContextServer, ContextServerId};
4use gpui::{AppContext, AsyncApp, Entity, Subscription, Task, TestAppContext, UpdateGlobal as _};
5use http_client::{FakeHttpClient, Response};
6use project::context_server_store::registry::ContextServerDescriptorRegistry;
7use project::context_server_store::*;
8use project::project_settings::ContextServerSettings;
9use project::worktree_store::WorktreeStore;
10use project::{
11 FakeFs, Project, context_server_store::registry::ContextServerDescriptor,
12 project_settings::ProjectSettings,
13};
14use serde_json::json;
15use settings::{ContextServerCommand, Settings, SettingsStore};
16use std::sync::Arc;
17use std::{cell::RefCell, path::PathBuf, rc::Rc};
18use util::path;
19
20#[gpui::test]
21async fn test_context_server_status(cx: &mut TestAppContext) {
22 const SERVER_1_ID: &str = "mcp-1";
23 const SERVER_2_ID: &str = "mcp-2";
24
25 let (_fs, project) = setup_context_server_test(cx, json!({"code.rs": ""}), vec![]).await;
26
27 let registry = cx.new(|_| ContextServerDescriptorRegistry::new());
28 let store = cx.new(|cx| {
29 ContextServerStore::test(
30 registry.clone(),
31 project.read(cx).worktree_store(),
32 Some(project.downgrade()),
33 cx,
34 )
35 });
36
37 let server_1_id = ContextServerId(SERVER_1_ID.into());
38 let server_2_id = ContextServerId(SERVER_2_ID.into());
39
40 let server_1 = Arc::new(ContextServer::new(
41 server_1_id.clone(),
42 Arc::new(create_fake_transport(SERVER_1_ID, cx.executor())),
43 ));
44 let server_2 = Arc::new(ContextServer::new(
45 server_2_id.clone(),
46 Arc::new(create_fake_transport(SERVER_2_ID, cx.executor())),
47 ));
48
49 store.update(cx, |store, cx| store.test_start_server(server_1, cx));
50
51 cx.run_until_parked();
52
53 cx.update(|cx| {
54 assert_eq!(
55 store.read(cx).status_for_server(&server_1_id),
56 Some(ContextServerStatus::Running)
57 );
58 assert_eq!(store.read(cx).status_for_server(&server_2_id), None);
59 });
60
61 store.update(cx, |store, cx| {
62 store.test_start_server(server_2.clone(), cx)
63 });
64
65 cx.run_until_parked();
66
67 cx.update(|cx| {
68 assert_eq!(
69 store.read(cx).status_for_server(&server_1_id),
70 Some(ContextServerStatus::Running)
71 );
72 assert_eq!(
73 store.read(cx).status_for_server(&server_2_id),
74 Some(ContextServerStatus::Running)
75 );
76 });
77
78 store
79 .update(cx, |store, cx| store.stop_server(&server_2_id, cx))
80 .unwrap();
81
82 cx.update(|cx| {
83 assert_eq!(
84 store.read(cx).status_for_server(&server_1_id),
85 Some(ContextServerStatus::Running)
86 );
87 assert_eq!(
88 store.read(cx).status_for_server(&server_2_id),
89 Some(ContextServerStatus::Stopped)
90 );
91 });
92}
93
94#[gpui::test]
95async fn test_context_server_status_events(cx: &mut TestAppContext) {
96 const SERVER_1_ID: &str = "mcp-1";
97 const SERVER_2_ID: &str = "mcp-2";
98
99 let (_fs, project) = setup_context_server_test(cx, json!({"code.rs": ""}), vec![]).await;
100
101 let registry = cx.new(|_| ContextServerDescriptorRegistry::new());
102 let store = cx.new(|cx| {
103 ContextServerStore::test(
104 registry.clone(),
105 project.read(cx).worktree_store(),
106 Some(project.downgrade()),
107 cx,
108 )
109 });
110
111 let server_1_id = ContextServerId(SERVER_1_ID.into());
112 let server_2_id = ContextServerId(SERVER_2_ID.into());
113
114 let server_1 = Arc::new(ContextServer::new(
115 server_1_id.clone(),
116 Arc::new(create_fake_transport(SERVER_1_ID, cx.executor())),
117 ));
118 let server_2 = Arc::new(ContextServer::new(
119 server_2_id.clone(),
120 Arc::new(create_fake_transport(SERVER_2_ID, cx.executor())),
121 ));
122
123 let _server_events = assert_server_events(
124 &store,
125 vec![
126 (server_1_id.clone(), ContextServerStatus::Starting),
127 (server_1_id, ContextServerStatus::Running),
128 (server_2_id.clone(), ContextServerStatus::Starting),
129 (server_2_id.clone(), ContextServerStatus::Running),
130 (server_2_id.clone(), ContextServerStatus::Stopped),
131 ],
132 cx,
133 );
134
135 store.update(cx, |store, cx| store.test_start_server(server_1, cx));
136
137 cx.run_until_parked();
138
139 store.update(cx, |store, cx| {
140 store.test_start_server(server_2.clone(), cx)
141 });
142
143 cx.run_until_parked();
144
145 store
146 .update(cx, |store, cx| store.stop_server(&server_2_id, cx))
147 .unwrap();
148}
149
150#[gpui::test(iterations = 25)]
151async fn test_context_server_concurrent_starts(cx: &mut TestAppContext) {
152 const SERVER_1_ID: &str = "mcp-1";
153
154 let (_fs, project) = setup_context_server_test(cx, json!({"code.rs": ""}), vec![]).await;
155
156 let registry = cx.new(|_| ContextServerDescriptorRegistry::new());
157 let store = cx.new(|cx| {
158 ContextServerStore::test(
159 registry.clone(),
160 project.read(cx).worktree_store(),
161 Some(project.downgrade()),
162 cx,
163 )
164 });
165
166 let server_id = ContextServerId(SERVER_1_ID.into());
167
168 let server_with_same_id_1 = Arc::new(ContextServer::new(
169 server_id.clone(),
170 Arc::new(create_fake_transport(SERVER_1_ID, cx.executor())),
171 ));
172 let server_with_same_id_2 = Arc::new(ContextServer::new(
173 server_id.clone(),
174 Arc::new(create_fake_transport(SERVER_1_ID, cx.executor())),
175 ));
176
177 // If we start another server with the same id, we should report that we stopped the previous one
178 let _server_events = assert_server_events(
179 &store,
180 vec![
181 (server_id.clone(), ContextServerStatus::Starting),
182 (server_id.clone(), ContextServerStatus::Stopped),
183 (server_id.clone(), ContextServerStatus::Starting),
184 (server_id.clone(), ContextServerStatus::Running),
185 ],
186 cx,
187 );
188
189 store.update(cx, |store, cx| {
190 store.test_start_server(server_with_same_id_1.clone(), cx)
191 });
192 store.update(cx, |store, cx| {
193 store.test_start_server(server_with_same_id_2.clone(), cx)
194 });
195
196 cx.run_until_parked();
197
198 cx.update(|cx| {
199 assert_eq!(
200 store.read(cx).status_for_server(&server_id),
201 Some(ContextServerStatus::Running)
202 );
203 });
204}
205
206#[gpui::test]
207async fn test_context_server_maintain_servers_loop(cx: &mut TestAppContext) {
208 const SERVER_1_ID: &str = "mcp-1";
209 const SERVER_2_ID: &str = "mcp-2";
210
211 let server_1_id = ContextServerId(SERVER_1_ID.into());
212 let server_2_id = ContextServerId(SERVER_2_ID.into());
213
214 let fake_descriptor_1 = Arc::new(FakeContextServerDescriptor::new(SERVER_1_ID));
215
216 let (_fs, project) = setup_context_server_test(cx, json!({"code.rs": ""}), vec![]).await;
217
218 let executor = cx.executor();
219 let store = project.read_with(cx, |project, _| project.context_server_store());
220 store.update(cx, |store, cx| {
221 store.set_context_server_factory(Box::new(move |id, _| {
222 Arc::new(ContextServer::new(
223 id.clone(),
224 Arc::new(create_fake_transport(id.0.to_string(), executor.clone())),
225 ))
226 }));
227 store.registry().update(cx, |registry, cx| {
228 registry.register_context_server_descriptor(SERVER_1_ID.into(), fake_descriptor_1, cx);
229 });
230 });
231
232 set_context_server_configuration(
233 vec![(
234 server_1_id.0.clone(),
235 settings::ContextServerSettingsContent::Extension {
236 enabled: true,
237 remote: false,
238 settings: json!({
239 "somevalue": true
240 }),
241 },
242 )],
243 cx,
244 );
245
246 // Ensure that mcp-1 starts up
247 {
248 let _server_events = assert_server_events(
249 &store,
250 vec![
251 (server_1_id.clone(), ContextServerStatus::Starting),
252 (server_1_id.clone(), ContextServerStatus::Running),
253 ],
254 cx,
255 );
256 cx.run_until_parked();
257 }
258
259 // Ensure that mcp-1 is restarted when the configuration was changed
260 {
261 let _server_events = assert_server_events(
262 &store,
263 vec![
264 (server_1_id.clone(), ContextServerStatus::Stopped),
265 (server_1_id.clone(), ContextServerStatus::Starting),
266 (server_1_id.clone(), ContextServerStatus::Running),
267 ],
268 cx,
269 );
270 set_context_server_configuration(
271 vec![(
272 server_1_id.0.clone(),
273 settings::ContextServerSettingsContent::Extension {
274 enabled: true,
275 remote: false,
276 settings: json!({
277 "somevalue": false
278 }),
279 },
280 )],
281 cx,
282 );
283
284 cx.run_until_parked();
285 }
286
287 // Ensure that mcp-1 is not restarted when the configuration was not changed
288 {
289 let _server_events = assert_server_events(&store, vec![], cx);
290 set_context_server_configuration(
291 vec![(
292 server_1_id.0.clone(),
293 settings::ContextServerSettingsContent::Extension {
294 enabled: true,
295 remote: false,
296 settings: json!({
297 "somevalue": false
298 }),
299 },
300 )],
301 cx,
302 );
303
304 cx.run_until_parked();
305 }
306
307 // Ensure that mcp-2 is started once it is added to the settings
308 {
309 let _server_events = assert_server_events(
310 &store,
311 vec![
312 (server_2_id.clone(), ContextServerStatus::Starting),
313 (server_2_id.clone(), ContextServerStatus::Running),
314 ],
315 cx,
316 );
317 set_context_server_configuration(
318 vec![
319 (
320 server_1_id.0.clone(),
321 settings::ContextServerSettingsContent::Extension {
322 enabled: true,
323 remote: false,
324 settings: json!({
325 "somevalue": false
326 }),
327 },
328 ),
329 (
330 server_2_id.0.clone(),
331 settings::ContextServerSettingsContent::Stdio {
332 enabled: true,
333 remote: false,
334 command: ContextServerCommand {
335 path: "somebinary".into(),
336 args: vec!["arg".to_string()],
337 env: None,
338 timeout: None,
339 },
340 },
341 ),
342 ],
343 cx,
344 );
345
346 cx.run_until_parked();
347 }
348
349 // Ensure that mcp-2 is restarted once the args have changed
350 {
351 let _server_events = assert_server_events(
352 &store,
353 vec![
354 (server_2_id.clone(), ContextServerStatus::Stopped),
355 (server_2_id.clone(), ContextServerStatus::Starting),
356 (server_2_id.clone(), ContextServerStatus::Running),
357 ],
358 cx,
359 );
360 set_context_server_configuration(
361 vec![
362 (
363 server_1_id.0.clone(),
364 settings::ContextServerSettingsContent::Extension {
365 enabled: true,
366 remote: false,
367 settings: json!({
368 "somevalue": false
369 }),
370 },
371 ),
372 (
373 server_2_id.0.clone(),
374 settings::ContextServerSettingsContent::Stdio {
375 enabled: true,
376 remote: false,
377 command: ContextServerCommand {
378 path: "somebinary".into(),
379 args: vec!["anotherArg".to_string()],
380 env: None,
381 timeout: None,
382 },
383 },
384 ),
385 ],
386 cx,
387 );
388
389 cx.run_until_parked();
390 }
391
392 // Ensure that mcp-2 is removed once it is removed from the settings
393 {
394 let _server_events = assert_server_events(
395 &store,
396 vec![(server_2_id.clone(), ContextServerStatus::Stopped)],
397 cx,
398 );
399 set_context_server_configuration(
400 vec![(
401 server_1_id.0.clone(),
402 settings::ContextServerSettingsContent::Extension {
403 enabled: true,
404 remote: false,
405 settings: json!({
406 "somevalue": false
407 }),
408 },
409 )],
410 cx,
411 );
412
413 cx.run_until_parked();
414
415 cx.update(|cx| {
416 assert_eq!(store.read(cx).status_for_server(&server_2_id), None);
417 });
418 }
419
420 // Ensure that nothing happens if the settings do not change
421 {
422 let _server_events = assert_server_events(&store, vec![], cx);
423 set_context_server_configuration(
424 vec![(
425 server_1_id.0.clone(),
426 settings::ContextServerSettingsContent::Extension {
427 enabled: true,
428 remote: false,
429 settings: json!({
430 "somevalue": false
431 }),
432 },
433 )],
434 cx,
435 );
436
437 cx.run_until_parked();
438
439 cx.update(|cx| {
440 assert_eq!(
441 store.read(cx).status_for_server(&server_1_id),
442 Some(ContextServerStatus::Running)
443 );
444 assert_eq!(store.read(cx).status_for_server(&server_2_id), None);
445 });
446 }
447}
448
449#[gpui::test]
450async fn test_context_server_enabled_disabled(cx: &mut TestAppContext) {
451 const SERVER_1_ID: &str = "mcp-1";
452
453 let server_1_id = ContextServerId(SERVER_1_ID.into());
454
455 let (_fs, project) = setup_context_server_test(cx, json!({"code.rs": ""}), vec![]).await;
456
457 let executor = cx.executor();
458 let store = project.read_with(cx, |project, _| project.context_server_store());
459 store.update(cx, |store, _| {
460 store.set_context_server_factory(Box::new(move |id, _| {
461 Arc::new(ContextServer::new(
462 id.clone(),
463 Arc::new(create_fake_transport(id.0.to_string(), executor.clone())),
464 ))
465 }));
466 });
467
468 set_context_server_configuration(
469 vec![(
470 server_1_id.0.clone(),
471 settings::ContextServerSettingsContent::Stdio {
472 enabled: true,
473 remote: false,
474 command: ContextServerCommand {
475 path: "somebinary".into(),
476 args: vec!["arg".to_string()],
477 env: None,
478 timeout: None,
479 },
480 },
481 )],
482 cx,
483 );
484
485 // Ensure that mcp-1 starts up
486 {
487 let _server_events = assert_server_events(
488 &store,
489 vec![
490 (server_1_id.clone(), ContextServerStatus::Starting),
491 (server_1_id.clone(), ContextServerStatus::Running),
492 ],
493 cx,
494 );
495 cx.run_until_parked();
496 }
497
498 // Ensure that mcp-1 is stopped once it is disabled.
499 {
500 let _server_events = assert_server_events(
501 &store,
502 vec![(server_1_id.clone(), ContextServerStatus::Stopped)],
503 cx,
504 );
505 set_context_server_configuration(
506 vec![(
507 server_1_id.0.clone(),
508 settings::ContextServerSettingsContent::Stdio {
509 enabled: false,
510 remote: false,
511 command: ContextServerCommand {
512 path: "somebinary".into(),
513 args: vec!["arg".to_string()],
514 env: None,
515 timeout: None,
516 },
517 },
518 )],
519 cx,
520 );
521
522 cx.run_until_parked();
523 }
524
525 // Ensure that mcp-1 is started once it is enabled again.
526 {
527 let _server_events = assert_server_events(
528 &store,
529 vec![
530 (server_1_id.clone(), ContextServerStatus::Starting),
531 (server_1_id.clone(), ContextServerStatus::Running),
532 ],
533 cx,
534 );
535 set_context_server_configuration(
536 vec![(
537 server_1_id.0.clone(),
538 settings::ContextServerSettingsContent::Stdio {
539 enabled: true,
540 remote: false,
541 command: ContextServerCommand {
542 path: "somebinary".into(),
543 args: vec!["arg".to_string()],
544 timeout: None,
545 env: None,
546 },
547 },
548 )],
549 cx,
550 );
551
552 cx.run_until_parked();
553 }
554}
555
556fn set_context_server_configuration(
557 context_servers: Vec<(Arc<str>, settings::ContextServerSettingsContent)>,
558 cx: &mut TestAppContext,
559) {
560 cx.update(|cx| {
561 SettingsStore::update_global(cx, |store, cx| {
562 store.update_user_settings(cx, |content| {
563 content.project.context_servers.clear();
564 for (id, config) in context_servers {
565 content.project.context_servers.insert(id, config);
566 }
567 });
568 })
569 });
570}
571
572#[gpui::test]
573async fn test_remote_context_server(cx: &mut TestAppContext) {
574 const SERVER_ID: &str = "remote-server";
575 let server_id = ContextServerId(SERVER_ID.into());
576 let server_url = "http://example.com/api";
577
578 let client = FakeHttpClient::create(|_| async move {
579 use http_client::AsyncBody;
580
581 let response = Response::builder()
582 .status(200)
583 .header("Content-Type", "application/json")
584 .body(AsyncBody::from(
585 serde_json::to_string(&json!({
586 "jsonrpc": "2.0",
587 "id": 0,
588 "result": {
589 "protocolVersion": "2024-11-05",
590 "capabilities": {},
591 "serverInfo": {
592 "name": "test-server",
593 "version": "1.0.0"
594 }
595 }
596 }))
597 .unwrap(),
598 ))
599 .unwrap();
600 Ok(response)
601 });
602 cx.update(|cx| cx.set_http_client(client));
603
604 let (_fs, project) = setup_context_server_test(cx, json!({ "code.rs": "" }), vec![]).await;
605
606 let store = project.read_with(cx, |project, _| project.context_server_store());
607
608 set_context_server_configuration(
609 vec![(
610 server_id.0.clone(),
611 settings::ContextServerSettingsContent::Http {
612 enabled: true,
613 url: server_url.to_string(),
614 headers: Default::default(),
615 timeout: None,
616 },
617 )],
618 cx,
619 );
620
621 let _server_events = assert_server_events(
622 &store,
623 vec![
624 (server_id.clone(), ContextServerStatus::Starting),
625 (server_id.clone(), ContextServerStatus::Running),
626 ],
627 cx,
628 );
629 cx.run_until_parked();
630}
631
632struct ServerEvents {
633 received_event_count: Rc<RefCell<usize>>,
634 expected_event_count: usize,
635 _subscription: Subscription,
636}
637
638impl Drop for ServerEvents {
639 fn drop(&mut self) {
640 let actual_event_count = *self.received_event_count.borrow();
641 assert_eq!(
642 actual_event_count, self.expected_event_count,
643 "
644 Expected to receive {} context server store events, but received {} events",
645 self.expected_event_count, actual_event_count
646 );
647 }
648}
649
650#[gpui::test]
651async fn test_context_server_global_timeout(cx: &mut TestAppContext) {
652 cx.update(|cx| {
653 let settings_store = SettingsStore::test(cx);
654 cx.set_global(settings_store);
655 SettingsStore::update_global(cx, |store, cx| {
656 store
657 .set_user_settings(r#"{"context_server_timeout": 90}"#, cx)
658 .expect("Failed to set test user settings");
659 });
660 });
661
662 let (_fs, project) = setup_context_server_test(cx, json!({"code.rs": ""}), vec![]).await;
663
664 let registry = cx.new(|_| ContextServerDescriptorRegistry::new());
665 let store = cx.new(|cx| {
666 ContextServerStore::test(
667 registry.clone(),
668 project.read(cx).worktree_store(),
669 Some(project.downgrade()),
670 cx,
671 )
672 });
673
674 let mut async_cx = cx.to_async();
675 let result = ContextServerStore::create_context_server(
676 store.downgrade(),
677 ContextServerId("test-server".into()),
678 Arc::new(ContextServerConfiguration::Http {
679 url: url::Url::parse("http://localhost:8080").expect("Failed to parse test URL"),
680 headers: Default::default(),
681 timeout: None,
682 }),
683 &mut async_cx,
684 )
685 .await;
686
687 assert!(
688 result.is_ok(),
689 "Server should be created successfully with global timeout"
690 );
691}
692
693#[gpui::test]
694async fn test_context_server_per_server_timeout_override(cx: &mut TestAppContext) {
695 const SERVER_ID: &str = "test-server";
696
697 cx.update(|cx| {
698 let settings_store = SettingsStore::test(cx);
699 cx.set_global(settings_store);
700 SettingsStore::update_global(cx, |store, cx| {
701 store
702 .set_user_settings(r#"{"context_server_timeout": 60}"#, cx)
703 .expect("Failed to set test user settings");
704 });
705 });
706
707 let (_fs, project) = setup_context_server_test(
708 cx,
709 json!({"code.rs": ""}),
710 vec![(
711 SERVER_ID.into(),
712 ContextServerSettings::Http {
713 enabled: true,
714 url: "http://localhost:8080".to_string(),
715 headers: Default::default(),
716 timeout: Some(120),
717 },
718 )],
719 )
720 .await;
721
722 let registry = cx.new(|_| ContextServerDescriptorRegistry::new());
723 let store = cx.new(|cx| {
724 ContextServerStore::test(
725 registry.clone(),
726 project.read(cx).worktree_store(),
727 Some(project.downgrade()),
728 cx,
729 )
730 });
731
732 let mut async_cx = cx.to_async();
733 let result = ContextServerStore::create_context_server(
734 store.downgrade(),
735 ContextServerId("test-server".into()),
736 Arc::new(ContextServerConfiguration::Http {
737 url: url::Url::parse("http://localhost:8080").expect("Failed to parse test URL"),
738 headers: Default::default(),
739 timeout: Some(120),
740 }),
741 &mut async_cx,
742 )
743 .await;
744
745 assert!(
746 result.is_ok(),
747 "Server should be created successfully with per-server timeout override"
748 );
749}
750
751#[gpui::test]
752async fn test_context_server_stdio_timeout(cx: &mut TestAppContext) {
753 let (_fs, project) = setup_context_server_test(cx, json!({"code.rs": ""}), vec![]).await;
754
755 let registry = cx.new(|_| ContextServerDescriptorRegistry::new());
756 let store = cx.new(|cx| {
757 ContextServerStore::test(
758 registry.clone(),
759 project.read(cx).worktree_store(),
760 Some(project.downgrade()),
761 cx,
762 )
763 });
764
765 let mut async_cx = cx.to_async();
766 let result = ContextServerStore::create_context_server(
767 store.downgrade(),
768 ContextServerId("stdio-server".into()),
769 Arc::new(ContextServerConfiguration::Custom {
770 command: ContextServerCommand {
771 path: "/usr/bin/node".into(),
772 args: vec!["server.js".into()],
773 env: None,
774 timeout: Some(180000),
775 },
776 remote: false,
777 }),
778 &mut async_cx,
779 )
780 .await;
781
782 assert!(
783 result.is_ok(),
784 "Stdio server should be created successfully with timeout"
785 );
786}
787
788fn assert_server_events(
789 store: &Entity<ContextServerStore>,
790 expected_events: Vec<(ContextServerId, ContextServerStatus)>,
791 cx: &mut TestAppContext,
792) -> ServerEvents {
793 cx.update(|cx| {
794 let mut ix = 0;
795 let received_event_count = Rc::new(RefCell::new(0));
796 let expected_event_count = expected_events.len();
797 let subscription = cx.subscribe(store, {
798 let received_event_count = received_event_count.clone();
799 move |_, event, _| match event {
800 Event::ServerStatusChanged {
801 server_id: actual_server_id,
802 status: actual_status,
803 } => {
804 let (expected_server_id, expected_status) = &expected_events[ix];
805
806 assert_eq!(
807 actual_server_id, expected_server_id,
808 "Expected different server id at index {}",
809 ix
810 );
811 assert_eq!(
812 actual_status, expected_status,
813 "Expected different status at index {}",
814 ix
815 );
816 ix += 1;
817 *received_event_count.borrow_mut() += 1;
818 }
819 }
820 });
821 ServerEvents {
822 expected_event_count,
823 received_event_count,
824 _subscription: subscription,
825 }
826 })
827}
828
829async fn setup_context_server_test(
830 cx: &mut TestAppContext,
831 files: serde_json::Value,
832 context_server_configurations: Vec<(Arc<str>, ContextServerSettings)>,
833) -> (Arc<FakeFs>, Entity<Project>) {
834 cx.update(|cx| {
835 let settings_store = SettingsStore::test(cx);
836 cx.set_global(settings_store);
837 let mut settings = ProjectSettings::get_global(cx).clone();
838 for (id, config) in context_server_configurations {
839 settings.context_servers.insert(id, config);
840 }
841 ProjectSettings::override_global(settings, cx);
842 });
843
844 let fs = FakeFs::new(cx.executor());
845 fs.insert_tree(path!("/test"), files).await;
846 let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
847
848 (fs, project)
849}
850
851struct FakeContextServerDescriptor {
852 path: PathBuf,
853}
854
855impl FakeContextServerDescriptor {
856 fn new(path: impl Into<PathBuf>) -> Self {
857 Self { path: path.into() }
858 }
859}
860
861impl ContextServerDescriptor for FakeContextServerDescriptor {
862 fn command(
863 &self,
864 _worktree_store: Entity<WorktreeStore>,
865 _cx: &AsyncApp,
866 ) -> Task<Result<ContextServerCommand>> {
867 Task::ready(Ok(ContextServerCommand {
868 path: self.path.clone(),
869 args: vec!["arg1".to_string(), "arg2".to_string()],
870 env: None,
871 timeout: None,
872 }))
873 }
874
875 fn configuration(
876 &self,
877 _worktree_store: Entity<WorktreeStore>,
878 _cx: &AsyncApp,
879 ) -> Task<Result<Option<::extension::ContextServerConfiguration>>> {
880 Task::ready(Ok(None))
881 }
882}