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};
15
16#[gpui::test]
17async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18 init_test(cx);
19
20 let fs = FakeFs::new(executor.clone());
21
22 let project = Project::test(fs, ["/project".as_ref()], cx).await;
23 let workspace = init_test_workspace(&project, cx).await;
24 workspace
25 .update(cx, |workspace, window, cx| {
26 workspace.focus_panel::<DebugPanel>(window, cx);
27 })
28 .unwrap();
29 let cx = &mut VisualTestContext::from_window(*workspace, cx);
30
31 let session = start_debug_session(&workspace, cx, |client| {
32 client.on_request::<Initialize, _>(move |_, _| {
33 Ok(dap::Capabilities {
34 supports_modules_request: Some(true),
35 ..Default::default()
36 })
37 });
38 })
39 .unwrap();
40
41 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
42
43 let called_modules = Arc::new(AtomicBool::new(false));
44 let modules = vec![
45 dap::Module {
46 id: dap::ModuleId::Number(1),
47 name: "First Module".into(),
48 address_range: None,
49 date_time_stamp: None,
50 path: None,
51 symbol_file_path: None,
52 symbol_status: None,
53 version: None,
54 is_optimized: None,
55 is_user_code: None,
56 },
57 dap::Module {
58 id: dap::ModuleId::Number(2),
59 name: "Second Module".into(),
60 address_range: None,
61 date_time_stamp: None,
62 path: None,
63 symbol_file_path: None,
64 symbol_status: None,
65 version: None,
66 is_optimized: None,
67 is_user_code: None,
68 },
69 ];
70
71 client.on_request::<Modules, _>({
72 let called_modules = called_modules.clone();
73 let modules_request_count = AtomicI32::new(0);
74 let modules = modules.clone();
75 move |_, _| {
76 modules_request_count.fetch_add(1, Ordering::SeqCst);
77 assert_eq!(
78 1,
79 modules_request_count.load(Ordering::SeqCst),
80 "This request should only be called once from the host"
81 );
82 called_modules.store(true, Ordering::SeqCst);
83
84 Ok(dap::ModulesResponse {
85 modules: modules.clone(),
86 total_modules: Some(2u64),
87 })
88 }
89 });
90
91 client
92 .fake_event(dap::messages::Events::Stopped(StoppedEvent {
93 reason: dap::StoppedEventReason::Pause,
94 description: None,
95 thread_id: Some(1),
96 preserve_focus_hint: None,
97 text: None,
98 all_threads_stopped: None,
99 hit_breakpoint_ids: None,
100 }))
101 .await;
102
103 cx.run_until_parked();
104
105 let running_state =
106 active_debug_session_panel(workspace, cx).update_in(cx, |item, window, cx| {
107 cx.focus_self(window);
108 item.mode()
109 .as_running()
110 .expect("Session should be running by this point")
111 .clone()
112 });
113
114 running_state.update_in(cx, |this, window, cx| {
115 this.activate_modules_list(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}