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