module_list.rs

  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}