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