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