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