1use crate::tests::{init_test, init_test_workspace, start_debug_session};
2use dap::requests::{StackTrace, Threads};
3use debugger_tools::LogStore;
4use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
5use project::Project;
6use serde_json::json;
7use std::cell::OnceCell;
8use util::path;
9
10#[gpui::test]
11async fn test_dap_logger_captures_all_session_rpc_messages(
12 executor: BackgroundExecutor,
13 cx: &mut TestAppContext,
14) {
15 let log_store_cell = std::rc::Rc::new(OnceCell::new());
16
17 cx.update(|cx| {
18 let log_store_cell = log_store_cell.clone();
19 cx.observe_new::<LogStore>(move |_, _, cx| {
20 log_store_cell.set(cx.entity()).unwrap();
21 })
22 .detach();
23 debugger_tools::init(cx);
24 });
25 init_test(cx);
26
27 let log_store = log_store_cell.get().unwrap().clone();
28
29 // Create a filesystem with a simple project
30 let fs = project::FakeFs::new(executor.clone());
31 fs.insert_tree(
32 path!("/project"),
33 json!({
34 "main.rs": "fn main() {\n println!(\"Hello, world!\");\n}"
35 }),
36 )
37 .await;
38
39 assert!(
40 log_store.read_with(cx, |log_store, _| !log_store.has_projects()),
41 "log_store shouldn't contain any projects before any projects were created"
42 );
43
44 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
45
46 let workspace = init_test_workspace(&project, cx).await;
47 assert!(
48 log_store.read_with(cx, |log_store, _| log_store.has_projects()),
49 "log_store shouldn't contain any projects before any projects were created"
50 );
51 assert!(
52 log_store.read_with(cx, |log_store, _| log_store
53 .contained_session_ids(&project.downgrade())
54 .is_empty()),
55 "log_store shouldn't contain any projects before any projects were created"
56 );
57 let cx = &mut VisualTestContext::from_window(*workspace, cx);
58
59 // Start a debug session
60 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
61 let session_id = session.read_with(cx, |session, _| session.session_id());
62 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
63
64 assert_eq!(
65 log_store.read_with(cx, |log_store, _| log_store
66 .contained_session_ids(&project.downgrade())
67 .len()),
68 1,
69 );
70
71 assert!(
72 log_store.read_with(cx, |log_store, _| log_store
73 .contained_session_ids(&project.downgrade())
74 .contains(&session_id)),
75 "log_store should contain the session IDs of the started session"
76 );
77
78 assert!(
79 !log_store.read_with(cx, |log_store, _| log_store
80 .rpc_messages_for_session_id(&project.downgrade(), session_id)
81 .is_empty()),
82 "We should have the initialization sequence in the log store"
83 );
84
85 // Set up basic responses for common requests
86 client.on_request::<Threads, _>(move |_, _| {
87 Ok(dap::ThreadsResponse {
88 threads: vec![dap::Thread {
89 id: 1,
90 name: "Thread 1".into(),
91 }],
92 })
93 });
94
95 client.on_request::<StackTrace, _>(move |_, _| {
96 Ok(dap::StackTraceResponse {
97 stack_frames: Vec::default(),
98 total_frames: None,
99 })
100 });
101
102 // Run until all pending tasks are executed
103 cx.run_until_parked();
104
105 // Simulate a stopped event to generate more DAP messages
106 client
107 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
108 reason: dap::StoppedEventReason::Pause,
109 description: None,
110 thread_id: Some(1),
111 preserve_focus_hint: None,
112 text: None,
113 all_threads_stopped: None,
114 hit_breakpoint_ids: None,
115 }))
116 .await;
117}