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