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