1use crate::{
2 debugger_panel::DebugPanel,
3 session::ThreadItem,
4 tests::{active_debug_session_panel, init_test, init_test_workspace},
5};
6use dap::{
7 requests::{Modules, StackTrace, Threads},
8 DebugRequestType, StoppedEvent,
9};
10use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
11use project::{FakeFs, Project};
12use std::sync::{
13 atomic::{AtomicBool, AtomicI32, Ordering},
14 Arc,
15};
16
17#[gpui::test]
18async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19 init_test(cx);
20
21 let fs = FakeFs::new(executor.clone());
22
23 let project = Project::test(fs, ["/project".as_ref()], cx).await;
24 let workspace = init_test_workspace(&project, cx).await;
25 workspace
26 .update(cx, |workspace, window, cx| {
27 workspace.focus_panel::<DebugPanel>(window, cx);
28 })
29 .unwrap();
30 let cx = &mut VisualTestContext::from_window(*workspace, cx);
31
32 let task = project.update(cx, |project, cx| {
33 project.start_debug_session(
34 dap::test_config(
35 DebugRequestType::Launch,
36 None,
37 Some(dap::Capabilities {
38 supports_modules_request: Some(true),
39 ..Default::default()
40 }),
41 ),
42 cx,
43 )
44 });
45
46 let session = task.await.unwrap();
47 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
48
49 client
50 .on_request::<StackTrace, _>(move |_, args| {
51 assert!(args.thread_id == 1);
52 Ok(dap::StackTraceResponse {
53 stack_frames: Vec::default(),
54 total_frames: None,
55 })
56 })
57 .await;
58
59 let called_modules = Arc::new(AtomicBool::new(false));
60 let modules = vec![
61 dap::Module {
62 id: dap::ModuleId::Number(1),
63 name: "First Module".into(),
64 address_range: None,
65 date_time_stamp: None,
66 path: None,
67 symbol_file_path: None,
68 symbol_status: None,
69 version: None,
70 is_optimized: None,
71 is_user_code: None,
72 },
73 dap::Module {
74 id: dap::ModuleId::Number(2),
75 name: "Second Module".into(),
76 address_range: None,
77 date_time_stamp: None,
78 path: None,
79 symbol_file_path: None,
80 symbol_status: None,
81 version: None,
82 is_optimized: None,
83 is_user_code: None,
84 },
85 ];
86
87 client
88 .on_request::<Threads, _>(move |_, _| {
89 Ok(dap::ThreadsResponse {
90 threads: vec![dap::Thread {
91 id: 1,
92 name: "Thread 1".into(),
93 }],
94 })
95 })
96 .await;
97
98 client
99 .on_request::<Modules, _>({
100 let called_modules = called_modules.clone();
101 let modules_request_count = AtomicI32::new(0);
102 let modules = modules.clone();
103 move |_, _| {
104 modules_request_count.fetch_add(1, Ordering::SeqCst);
105 assert_eq!(
106 1,
107 modules_request_count.load(Ordering::SeqCst),
108 "This request should only be called once from the host"
109 );
110 called_modules.store(true, Ordering::SeqCst);
111
112 Ok(dap::ModulesResponse {
113 modules: modules.clone(),
114 total_modules: Some(2u64),
115 })
116 }
117 })
118 .await;
119
120 client
121 .fake_event(dap::messages::Events::Stopped(StoppedEvent {
122 reason: dap::StoppedEventReason::Pause,
123 description: None,
124 thread_id: Some(1),
125 preserve_focus_hint: None,
126 text: None,
127 all_threads_stopped: None,
128 hit_breakpoint_ids: None,
129 }))
130 .await;
131
132 cx.run_until_parked();
133
134 let running_state =
135 active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
136 cx.focus_self(window);
137 item.mode()
138 .as_running()
139 .expect("Session should be running by this point")
140 .clone()
141 });
142
143 assert!(
144 !called_modules.load(std::sync::atomic::Ordering::SeqCst),
145 "Request Modules shouldn't be called before it's needed"
146 );
147
148 running_state.update(cx, |state, cx| {
149 state.set_thread_item(ThreadItem::Modules, cx);
150 cx.refresh_windows();
151 });
152
153 cx.run_until_parked();
154
155 assert!(
156 called_modules.load(std::sync::atomic::Ordering::SeqCst),
157 "Request Modules should be called because a user clicked on the module list"
158 );
159
160 active_debug_session_panel(workspace, cx).update(cx, |_, cx| {
161 running_state.update(cx, |state, cx| {
162 state.set_thread_item(ThreadItem::Modules, cx)
163 });
164 let actual_modules = running_state.update(cx, |state, cx| {
165 state.module_list().update(cx, |list, cx| list.modules(cx))
166 });
167
168 assert_eq!(modules, actual_modules);
169 });
170
171 // Test all module events now
172 // New Module
173 // Changed
174 // Removed
175
176 let new_module = dap::Module {
177 id: dap::ModuleId::Number(3),
178 name: "Third Module".into(),
179 address_range: None,
180 date_time_stamp: None,
181 path: None,
182 symbol_file_path: None,
183 symbol_status: None,
184 version: None,
185 is_optimized: None,
186 is_user_code: None,
187 };
188
189 client
190 .fake_event(dap::messages::Events::Module(dap::ModuleEvent {
191 reason: dap::ModuleEventReason::New,
192 module: new_module.clone(),
193 }))
194 .await;
195
196 cx.run_until_parked();
197
198 active_debug_session_panel(workspace, cx).update(cx, |_, cx| {
199 let actual_modules = running_state.update(cx, |state, cx| {
200 state.module_list().update(cx, |list, cx| list.modules(cx))
201 });
202 assert_eq!(actual_modules.len(), 3);
203 assert!(actual_modules.contains(&new_module));
204 });
205
206 let changed_module = dap::Module {
207 id: dap::ModuleId::Number(2),
208 name: "Modified Second Module".into(),
209 address_range: None,
210 date_time_stamp: None,
211 path: None,
212 symbol_file_path: None,
213 symbol_status: None,
214 version: None,
215 is_optimized: None,
216 is_user_code: None,
217 };
218
219 client
220 .fake_event(dap::messages::Events::Module(dap::ModuleEvent {
221 reason: dap::ModuleEventReason::Changed,
222 module: changed_module.clone(),
223 }))
224 .await;
225
226 cx.run_until_parked();
227
228 active_debug_session_panel(workspace, cx).update(cx, |_, cx| {
229 let actual_modules = running_state.update(cx, |state, cx| {
230 state.module_list().update(cx, |list, cx| list.modules(cx))
231 });
232
233 assert_eq!(actual_modules.len(), 3);
234 assert!(actual_modules.contains(&changed_module));
235 });
236
237 client
238 .fake_event(dap::messages::Events::Module(dap::ModuleEvent {
239 reason: dap::ModuleEventReason::Removed,
240 module: changed_module.clone(),
241 }))
242 .await;
243
244 cx.run_until_parked();
245
246 active_debug_session_panel(workspace, cx).update(cx, |_, cx| {
247 let actual_modules = running_state.update(cx, |state, cx| {
248 state.module_list().update(cx, |list, cx| list.modules(cx))
249 });
250
251 assert_eq!(actual_modules.len(), 2);
252 assert!(!actual_modules.contains(&changed_module));
253 });
254
255 let shutdown_session = project.update(cx, |project, cx| {
256 project.dap_store().update(cx, |dap_store, cx| {
257 dap_store.shutdown_session(session.read(cx).session_id(), cx)
258 })
259 });
260
261 shutdown_session.await.unwrap();
262}