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