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(cx, |_, cx| {
142 cx.refresh_windows();
143 });
144
145 cx.run_until_parked();
146
147 assert!(
148 called_modules.load(std::sync::atomic::Ordering::SeqCst),
149 "Request Modules should be called because a user clicked on the module list"
150 );
151
152 active_debug_session_panel(workspace, cx).update(cx, |_, cx| {
153 let actual_modules = running_state.update(cx, |state, cx| {
154 state.module_list().update(cx, |list, cx| list.modules(cx))
155 });
156
157 assert_eq!(modules, actual_modules);
158 });
159
160 // Test all module events now
161 // New Module
162 // Changed
163 // Removed
164
165 let new_module = dap::Module {
166 id: dap::ModuleId::Number(3),
167 name: "Third Module".into(),
168 address_range: None,
169 date_time_stamp: None,
170 path: None,
171 symbol_file_path: None,
172 symbol_status: None,
173 version: None,
174 is_optimized: None,
175 is_user_code: None,
176 };
177
178 client
179 .fake_event(dap::messages::Events::Module(dap::ModuleEvent {
180 reason: dap::ModuleEventReason::New,
181 module: new_module.clone(),
182 }))
183 .await;
184
185 cx.run_until_parked();
186
187 active_debug_session_panel(workspace, cx).update(cx, |_, cx| {
188 let actual_modules = running_state.update(cx, |state, cx| {
189 state.module_list().update(cx, |list, cx| list.modules(cx))
190 });
191 assert_eq!(actual_modules.len(), 3);
192 assert!(actual_modules.contains(&new_module));
193 });
194
195 let changed_module = dap::Module {
196 id: dap::ModuleId::Number(2),
197 name: "Modified Second Module".into(),
198 address_range: None,
199 date_time_stamp: None,
200 path: None,
201 symbol_file_path: None,
202 symbol_status: None,
203 version: None,
204 is_optimized: None,
205 is_user_code: None,
206 };
207
208 client
209 .fake_event(dap::messages::Events::Module(dap::ModuleEvent {
210 reason: dap::ModuleEventReason::Changed,
211 module: changed_module.clone(),
212 }))
213 .await;
214
215 cx.run_until_parked();
216
217 active_debug_session_panel(workspace, cx).update(cx, |_, cx| {
218 let actual_modules = running_state.update(cx, |state, cx| {
219 state.module_list().update(cx, |list, cx| list.modules(cx))
220 });
221
222 assert_eq!(actual_modules.len(), 3);
223 assert!(actual_modules.contains(&changed_module));
224 });
225
226 client
227 .fake_event(dap::messages::Events::Module(dap::ModuleEvent {
228 reason: dap::ModuleEventReason::Removed,
229 module: changed_module.clone(),
230 }))
231 .await;
232
233 cx.run_until_parked();
234
235 active_debug_session_panel(workspace, cx).update(cx, |_, cx| {
236 let actual_modules = running_state.update(cx, |state, cx| {
237 state.module_list().update(cx, |list, cx| list.modules(cx))
238 });
239
240 assert_eq!(actual_modules.len(), 2);
241 assert!(!actual_modules.contains(&changed_module));
242 });
243
244 let shutdown_session = project.update(cx, |project, cx| {
245 project.dap_store().update(cx, |dap_store, cx| {
246 dap_store.shutdown_session(session.read(cx).session_id(), cx)
247 })
248 });
249
250 shutdown_session.await.unwrap();
251}