1use dap::DapRegistry;
2use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
3use project::{FakeFs, Project};
4use serde_json::json;
5use std::sync::Arc;
6use std::sync::atomic::{AtomicBool, Ordering};
7use task::{DebugRequest, DebugScenario, LaunchRequest, TaskContext, VariableName, ZedDebugConfig};
8use util::path;
9
10// use crate::new_process_modal::NewProcessMode;
11use crate::tests::{init_test, init_test_workspace};
12
13#[gpui::test]
14async fn test_debug_session_substitutes_variables_and_relativizes_paths(
15 executor: BackgroundExecutor,
16 cx: &mut TestAppContext,
17) {
18 init_test(cx);
19
20 let fs = FakeFs::new(executor.clone());
21 fs.insert_tree(
22 path!("/project"),
23 json!({
24 "main.rs": "fn main() {}"
25 }),
26 )
27 .await;
28
29 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
30 let workspace = init_test_workspace(&project, cx).await;
31 let cx = &mut VisualTestContext::from_window(*workspace, cx);
32
33 let test_variables = vec![(
34 VariableName::WorktreeRoot,
35 path!("/test/worktree/path").to_string(),
36 )]
37 .into_iter()
38 .collect();
39
40 let task_context = TaskContext {
41 cwd: None,
42 task_variables: test_variables,
43 project_env: Default::default(),
44 };
45
46 let home_dir = paths::home_dir();
47
48 let test_cases: Vec<(&'static str, &'static str)> = vec![
49 // Absolute path - should not be relativized
50 (
51 path!("/absolute/path/to/program"),
52 path!("/absolute/path/to/program"),
53 ),
54 // Relative path - should be prefixed with worktree root
55 (
56 format!(".{0}src{0}program", std::path::MAIN_SEPARATOR).leak(),
57 path!("/test/worktree/path/src/program"),
58 ),
59 // Home directory path - should be expanded to full home directory path
60 (
61 format!("~{0}src{0}program", std::path::MAIN_SEPARATOR).leak(),
62 home_dir
63 .join("src")
64 .join("program")
65 .to_string_lossy()
66 .to_string()
67 .leak(),
68 ),
69 // Path with $ZED_WORKTREE_ROOT - should be substituted without double appending
70 (
71 format!(
72 "$ZED_WORKTREE_ROOT{0}src{0}program",
73 std::path::MAIN_SEPARATOR
74 )
75 .leak(),
76 path!("/test/worktree/path/src/program"),
77 ),
78 ];
79
80 let called_launch = Arc::new(AtomicBool::new(false));
81
82 for (input_path, expected_path) in test_cases {
83 let _subscription = project::debugger::test::intercept_debug_sessions(cx, {
84 let called_launch = called_launch.clone();
85 move |client| {
86 client.on_request::<dap::requests::Launch, _>({
87 let called_launch = called_launch.clone();
88
89 move |_, args| {
90 let config = args.raw.as_object().unwrap();
91
92 assert_eq!(
93 config["program"].as_str().unwrap(),
94 expected_path,
95 "Program path was not correctly substituted for input: {}",
96 input_path
97 );
98
99 assert_eq!(
100 config["cwd"].as_str().unwrap(),
101 expected_path,
102 "CWD path was not correctly substituted for input: {}",
103 input_path
104 );
105
106 let expected_other_field = if input_path.contains("$ZED_WORKTREE_ROOT") {
107 input_path
108 .replace("$ZED_WORKTREE_ROOT", &path!("/test/worktree/path"))
109 .to_owned()
110 } else {
111 input_path.to_string()
112 };
113
114 assert_eq!(
115 config["otherField"].as_str().unwrap(),
116 &expected_other_field,
117 "Other field was incorrectly modified for input: {}",
118 input_path
119 );
120
121 called_launch.store(true, Ordering::SeqCst);
122
123 Ok(())
124 }
125 });
126 }
127 });
128
129 let scenario = DebugScenario {
130 adapter: "fake-adapter".into(),
131 label: "test-debug-session".into(),
132 build: None,
133 config: json!({
134 "request": "launch",
135 "program": input_path,
136 "cwd": input_path,
137 "otherField": input_path
138 }),
139 tcp_connection: None,
140 };
141
142 workspace
143 .update(cx, |workspace, window, cx| {
144 workspace.start_debug_session(
145 scenario,
146 task_context.clone(),
147 None,
148 None,
149 window,
150 cx,
151 )
152 })
153 .unwrap();
154
155 cx.run_until_parked();
156
157 assert!(called_launch.load(Ordering::SeqCst));
158 called_launch.store(false, Ordering::SeqCst);
159 }
160}
161
162// #[gpui::test]
163// async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut TestAppContext) {
164// init_test(cx);
165
166// let fs = FakeFs::new(executor.clone());
167// fs.insert_tree(
168// path!("/project"),
169// json!({
170// "main.rs": "fn main() {}"
171// }),
172// )
173// .await;
174
175// let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
176// let workspace = init_test_workspace(&project, cx).await;
177// let cx = &mut VisualTestContext::from_window(*workspace, cx);
178
179// workspace
180// .update(cx, |workspace, window, cx| {
181// crate::new_process_modal::NewProcessModal::show(
182// workspace,
183// window,
184// NewProcessMode::Debug,
185// None,
186// cx,
187// );
188// })
189// .unwrap();
190
191// cx.run_until_parked();
192
193// let modal = workspace
194// .update(cx, |workspace, _, cx| {
195// workspace.active_modal::<crate::new_process_modal::NewProcessModal>(cx)
196// })
197// .unwrap()
198// .expect("Modal should be active");
199
200// modal.update_in(cx, |modal, window, cx| {
201// modal.set_configure("/project/main", "/project", false, window, cx);
202// modal.save_scenario(window, cx);
203// });
204
205// cx.executor().run_until_parked();
206
207// let debug_json_content = fs
208// .load(path!("/project/.zed/debug.json").as_ref())
209// .await
210// .expect("debug.json should exist");
211
212// let expected_content = vec![
213// "[",
214// " {",
215// r#" "adapter": "fake-adapter","#,
216// r#" "label": "main (fake-adapter)","#,
217// r#" "request": "launch","#,
218// r#" "program": "/project/main","#,
219// r#" "cwd": "/project","#,
220// r#" "args": [],"#,
221// r#" "env": {}"#,
222// " }",
223// "]",
224// ];
225
226// let actual_lines: Vec<&str> = debug_json_content.lines().collect();
227// pretty_assertions::assert_eq!(expected_content, actual_lines);
228
229// modal.update_in(cx, |modal, window, cx| {
230// modal.set_configure("/project/other", "/project", true, window, cx);
231// modal.save_scenario(window, cx);
232// });
233
234// cx.executor().run_until_parked();
235
236// let debug_json_content = fs
237// .load(path!("/project/.zed/debug.json").as_ref())
238// .await
239// .expect("debug.json should exist after second save");
240
241// let expected_content = vec![
242// "[",
243// " {",
244// r#" "adapter": "fake-adapter","#,
245// r#" "label": "main (fake-adapter)","#,
246// r#" "request": "launch","#,
247// r#" "program": "/project/main","#,
248// r#" "cwd": "/project","#,
249// r#" "args": [],"#,
250// r#" "env": {}"#,
251// " },",
252// " {",
253// r#" "adapter": "fake-adapter","#,
254// r#" "label": "other (fake-adapter)","#,
255// r#" "request": "launch","#,
256// r#" "program": "/project/other","#,
257// r#" "cwd": "/project","#,
258// r#" "args": [],"#,
259// r#" "env": {}"#,
260// " }",
261// "]",
262// ];
263
264// let actual_lines: Vec<&str> = debug_json_content.lines().collect();
265// pretty_assertions::assert_eq!(expected_content, actual_lines);
266// }
267
268#[gpui::test]
269async fn test_dap_adapter_config_conversion_and_validation(cx: &mut TestAppContext) {
270 init_test(cx);
271
272 let mut expected_adapters = vec![
273 "CodeLLDB",
274 "Debugpy",
275 "PHP",
276 "JavaScript",
277 "Delve",
278 "GDB",
279 "fake-adapter",
280 ];
281
282 let adapter_names = cx.update(|cx| {
283 let registry = DapRegistry::global(cx);
284 registry.enumerate_adapters()
285 });
286
287 let zed_config = ZedDebugConfig {
288 label: "test_debug_session".into(),
289 adapter: "test_adapter".into(),
290 request: DebugRequest::Launch(LaunchRequest {
291 program: "test_program".into(),
292 cwd: None,
293 args: vec![],
294 env: Default::default(),
295 }),
296 stop_on_entry: Some(true),
297 };
298
299 for adapter_name in adapter_names {
300 let adapter_str = adapter_name.to_string();
301 if let Some(pos) = expected_adapters.iter().position(|&x| x == adapter_str) {
302 expected_adapters.remove(pos);
303 }
304
305 let adapter = cx
306 .update(|cx| {
307 let registry = DapRegistry::global(cx);
308 registry.adapter(adapter_name.as_ref())
309 })
310 .unwrap_or_else(|| panic!("Adapter {} should exist", adapter_name));
311
312 let mut adapter_specific_config = zed_config.clone();
313 adapter_specific_config.adapter = adapter_name.to_string().into();
314
315 let debug_scenario = adapter
316 .config_from_zed_format(adapter_specific_config)
317 .await
318 .unwrap_or_else(|_| {
319 panic!(
320 "Adapter {} should successfully convert from Zed format",
321 adapter_name
322 )
323 });
324
325 assert!(
326 debug_scenario.config.is_object(),
327 "Adapter {} should produce a JSON object for config",
328 adapter_name
329 );
330
331 let request_type = adapter
332 .request_kind(&debug_scenario.config)
333 .await
334 .unwrap_or_else(|_| {
335 panic!(
336 "Adapter {} should validate the config successfully",
337 adapter_name
338 )
339 });
340
341 match request_type {
342 dap::StartDebuggingRequestArgumentsRequest::Launch => {}
343 dap::StartDebuggingRequestArgumentsRequest::Attach => {
344 panic!(
345 "Expected Launch request but got Attach for adapter {}",
346 adapter_name
347 );
348 }
349 }
350 }
351
352 assert!(
353 expected_adapters.is_empty(),
354 "The following expected adapters were not found in the registry: {:?}",
355 expected_adapters
356 );
357}