module_list.rs

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