module_list.rs

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