1#![expect(clippy::result_large_err)]
2use crate::tests::{init_test, init_test_workspace, start_debug_session};
3use dap::requests::{StackTrace, Threads};
4use debugger_tools::LogStore;
5use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
6use project::Project;
7use serde_json::json;
8use std::cell::OnceCell;
9use util::path;
10
11#[gpui::test]
12async fn test_dap_logger_captures_all_session_rpc_messages(
13 executor: BackgroundExecutor,
14 cx: &mut TestAppContext,
15) {
16 let log_store_cell = std::rc::Rc::new(OnceCell::new());
17
18 cx.update(|cx| {
19 let log_store_cell = log_store_cell.clone();
20 cx.observe_new::<LogStore>(move |_, _, cx| {
21 log_store_cell.set(cx.entity()).unwrap();
22 })
23 .detach();
24 debugger_tools::init(cx);
25 });
26 init_test(cx);
27
28 let log_store = log_store_cell.get().unwrap().clone();
29
30 // Create a filesystem with a simple project
31 let fs = project::FakeFs::new(executor.clone());
32 fs.insert_tree(
33 path!("/project"),
34 json!({
35 "main.rs": "fn main() {\n println!(\"Hello, world!\");\n}"
36 }),
37 )
38 .await;
39
40 assert!(
41 log_store.read_with(cx, |log_store, _| !log_store.has_projects()),
42 "log_store shouldn't contain any projects before any projects were created"
43 );
44
45 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
46
47 let workspace = init_test_workspace(&project, cx).await;
48 assert!(
49 log_store.read_with(cx, |log_store, _| log_store.has_projects()),
50 "log_store shouldn't contain any projects before any projects were created"
51 );
52 assert!(
53 log_store.read_with(cx, |log_store, _| log_store
54 .contained_session_ids(&project.downgrade())
55 .is_empty()),
56 "log_store shouldn't contain any projects before any projects were created"
57 );
58 let cx = &mut VisualTestContext::from_window(*workspace, cx);
59
60 // Start a debug session
61 let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
62 let session_id = session.read_with(cx, |session, _| session.session_id());
63 let client = session.update(cx, |session, _| session.adapter_client().unwrap());
64
65 assert_eq!(
66 log_store.read_with(cx, |log_store, _| log_store
67 .contained_session_ids(&project.downgrade())
68 .len()),
69 1,
70 );
71
72 assert!(
73 log_store.read_with(cx, |log_store, _| log_store
74 .contained_session_ids(&project.downgrade())
75 .contains(&session_id)),
76 "log_store should contain the session IDs of the started session"
77 );
78
79 assert!(
80 !log_store.read_with(cx, |log_store, _| log_store
81 .rpc_messages_for_session_id(&project.downgrade(), session_id)
82 .is_empty()),
83 "We should have the initialization sequence in the log store"
84 );
85
86 // Set up basic responses for common requests
87 client.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
96 client.on_request::<StackTrace, _>(move |_, _| {
97 Ok(dap::StackTraceResponse {
98 stack_frames: Vec::default(),
99 total_frames: None,
100 })
101 });
102
103 // Run until all pending tasks are executed
104 cx.run_until_parked();
105
106 // Simulate a stopped event to generate more DAP messages
107 client
108 .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
109 reason: dap::StoppedEventReason::Pause,
110 description: None,
111 thread_id: Some(1),
112 preserve_focus_hint: None,
113 text: None,
114 all_threads_stopped: None,
115 hit_breakpoint_ids: None,
116 }))
117 .await;
118}