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.ensure_pane_item(DebuggerPaneItem::Modules, window, cx);
115 this.activate_item(DebuggerPaneItem::Modules, window, cx);
116 cx.refresh_windows();
117 });
118
119 cx.run_until_parked();
120
121 assert!(
122 called_modules.load(std::sync::atomic::Ordering::SeqCst),
123 "Request Modules should be called because a user clicked on the module list"
124 );
125
126 active_debug_session_panel(workspace, cx).update(cx, |_, cx| {
127 let actual_modules = running_state.update(cx, |state, cx| {
128 state.module_list().update(cx, |list, cx| list.modules(cx))
129 });
130
131 assert_eq!(modules, actual_modules);
132 });
133
134 // Test all module events now
135 // New Module
136 // Changed
137 // Removed
138
139 let new_module = dap::Module {
140 id: dap::ModuleId::Number(3),
141 name: "Third Module".into(),
142 address_range: None,
143 date_time_stamp: None,
144 path: None,
145 symbol_file_path: None,
146 symbol_status: None,
147 version: None,
148 is_optimized: None,
149 is_user_code: None,
150 };
151
152 client
153 .fake_event(dap::messages::Events::Module(dap::ModuleEvent {
154 reason: dap::ModuleEventReason::New,
155 module: new_module.clone(),
156 }))
157 .await;
158
159 cx.run_until_parked();
160
161 active_debug_session_panel(workspace, cx).update(cx, |_, cx| {
162 let actual_modules = running_state.update(cx, |state, cx| {
163 state.module_list().update(cx, |list, cx| list.modules(cx))
164 });
165 assert_eq!(actual_modules.len(), 3);
166 assert!(actual_modules.contains(&new_module));
167 });
168
169 let changed_module = dap::Module {
170 id: dap::ModuleId::Number(2),
171 name: "Modified Second Module".into(),
172 address_range: None,
173 date_time_stamp: None,
174 path: None,
175 symbol_file_path: None,
176 symbol_status: None,
177 version: None,
178 is_optimized: None,
179 is_user_code: None,
180 };
181
182 client
183 .fake_event(dap::messages::Events::Module(dap::ModuleEvent {
184 reason: dap::ModuleEventReason::Changed,
185 module: changed_module.clone(),
186 }))
187 .await;
188
189 cx.run_until_parked();
190
191 active_debug_session_panel(workspace, cx).update(cx, |_, cx| {
192 let actual_modules = running_state.update(cx, |state, cx| {
193 state.module_list().update(cx, |list, cx| list.modules(cx))
194 });
195
196 assert_eq!(actual_modules.len(), 3);
197 assert!(actual_modules.contains(&changed_module));
198 });
199
200 client
201 .fake_event(dap::messages::Events::Module(dap::ModuleEvent {
202 reason: dap::ModuleEventReason::Removed,
203 module: changed_module.clone(),
204 }))
205 .await;
206
207 cx.run_until_parked();
208
209 active_debug_session_panel(workspace, cx).update(cx, |_, cx| {
210 let actual_modules = running_state.update(cx, |state, cx| {
211 state.module_list().update(cx, |list, cx| list.modules(cx))
212 });
213
214 assert_eq!(actual_modules.len(), 2);
215 assert!(!actual_modules.contains(&changed_module));
216 });
217}