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
10use crate::tests::{init_test, init_test_workspace};
11
12#[gpui::test]
13async fn test_debug_session_substitutes_variables_and_relativizes_paths(
14 executor: BackgroundExecutor,
15 cx: &mut TestAppContext,
16) {
17 init_test(cx);
18
19 let fs = FakeFs::new(executor.clone());
20 fs.insert_tree(
21 path!("/project"),
22 json!({
23 "main.rs": "fn main() {}"
24 }),
25 )
26 .await;
27
28 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
29 let workspace = init_test_workspace(&project, cx).await;
30 let cx = &mut VisualTestContext::from_window(*workspace, cx);
31
32 let test_variables = vec![(
33 VariableName::WorktreeRoot,
34 path!("/test/worktree/path").to_string(),
35 )]
36 .into_iter()
37 .collect();
38
39 let task_context = TaskContext {
40 cwd: None,
41 task_variables: test_variables,
42 project_env: Default::default(),
43 };
44
45 let home_dir = paths::home_dir();
46
47 let test_cases: Vec<(&'static str, &'static str)> = vec![
48 // Absolute path - should not be relativized
49 (
50 path!("/absolute/path/to/program"),
51 path!("/absolute/path/to/program"),
52 ),
53 // Relative path - should be prefixed with worktree root
54 (
55 format!(".{0}src{0}program", std::path::MAIN_SEPARATOR).leak(),
56 path!("/test/worktree/path/src/program"),
57 ),
58 // Home directory path - should be expanded to full home directory path
59 (
60 format!("~{0}src{0}program", std::path::MAIN_SEPARATOR).leak(),
61 home_dir
62 .join("src")
63 .join("program")
64 .to_string_lossy()
65 .to_string()
66 .leak(),
67 ),
68 // Path with $ZED_WORKTREE_ROOT - should be substituted without double appending
69 (
70 format!(
71 "$ZED_WORKTREE_ROOT{0}src{0}program",
72 std::path::MAIN_SEPARATOR
73 )
74 .leak(),
75 path!("/test/worktree/path/src/program"),
76 ),
77 ];
78
79 let called_launch = Arc::new(AtomicBool::new(false));
80
81 for (input_path, expected_path) in test_cases {
82 let _subscription = project::debugger::test::intercept_debug_sessions(cx, {
83 let called_launch = called_launch.clone();
84 move |client| {
85 client.on_request::<dap::requests::Launch, _>({
86 let called_launch = called_launch.clone();
87
88 move |_, args| {
89 let config = args.raw.as_object().unwrap();
90
91 assert_eq!(
92 config["program"].as_str().unwrap(),
93 expected_path,
94 "Program path was not correctly substituted for input: {}",
95 input_path
96 );
97
98 assert_eq!(
99 config["cwd"].as_str().unwrap(),
100 expected_path,
101 "CWD path was not correctly substituted for input: {}",
102 input_path
103 );
104
105 let expected_other_field = if input_path.contains("$ZED_WORKTREE_ROOT") {
106 input_path
107 .replace("$ZED_WORKTREE_ROOT", &path!("/test/worktree/path"))
108 .to_owned()
109 } else {
110 input_path.to_string()
111 };
112
113 assert_eq!(
114 config["otherField"].as_str().unwrap(),
115 &expected_other_field,
116 "Other field was incorrectly modified for input: {}",
117 input_path
118 );
119
120 called_launch.store(true, Ordering::SeqCst);
121
122 Ok(())
123 }
124 });
125 }
126 });
127
128 let scenario = DebugScenario {
129 adapter: "fake-adapter".into(),
130 label: "test-debug-session".into(),
131 build: None,
132 config: json!({
133 "request": "launch",
134 "program": input_path,
135 "cwd": input_path,
136 "otherField": input_path
137 }),
138 tcp_connection: None,
139 };
140
141 workspace
142 .update(cx, |workspace, window, cx| {
143 workspace.start_debug_session(scenario, task_context.clone(), None, window, cx)
144 })
145 .unwrap();
146
147 cx.run_until_parked();
148
149 assert!(called_launch.load(Ordering::SeqCst));
150 called_launch.store(false, Ordering::SeqCst);
151 }
152}
153
154#[gpui::test]
155async fn test_dap_adapter_config_conversion_and_validation(cx: &mut TestAppContext) {
156 init_test(cx);
157
158 let mut expected_adapters = vec![
159 "CodeLLDB",
160 "Debugpy",
161 "PHP",
162 "JavaScript",
163 "Ruby",
164 "Delve",
165 "GDB",
166 "fake-adapter",
167 ];
168
169 let adapter_names = cx.update(|cx| {
170 let registry = DapRegistry::global(cx);
171 registry.enumerate_adapters()
172 });
173
174 let zed_config = ZedDebugConfig {
175 label: "test_debug_session".into(),
176 adapter: "test_adapter".into(),
177 request: DebugRequest::Launch(LaunchRequest {
178 program: "test_program".into(),
179 cwd: None,
180 args: vec![],
181 env: Default::default(),
182 }),
183 stop_on_entry: Some(true),
184 };
185
186 for adapter_name in adapter_names {
187 let adapter_str = adapter_name.to_string();
188 if let Some(pos) = expected_adapters.iter().position(|&x| x == adapter_str) {
189 expected_adapters.remove(pos);
190 }
191
192 let adapter = cx
193 .update(|cx| {
194 let registry = DapRegistry::global(cx);
195 registry.adapter(adapter_name.as_ref())
196 })
197 .unwrap_or_else(|| panic!("Adapter {} should exist", adapter_name));
198
199 let mut adapter_specific_config = zed_config.clone();
200 adapter_specific_config.adapter = adapter_name.to_string().into();
201
202 let debug_scenario = adapter
203 .config_from_zed_format(adapter_specific_config)
204 .unwrap_or_else(|_| {
205 panic!(
206 "Adapter {} should successfully convert from Zed format",
207 adapter_name
208 )
209 });
210
211 assert!(
212 debug_scenario.config.is_object(),
213 "Adapter {} should produce a JSON object for config",
214 adapter_name
215 );
216
217 let request_type = adapter
218 .validate_config(&debug_scenario.config)
219 .unwrap_or_else(|_| {
220 panic!(
221 "Adapter {} should validate the config successfully",
222 adapter_name
223 )
224 });
225
226 match request_type {
227 dap::StartDebuggingRequestArgumentsRequest::Launch => {}
228 dap::StartDebuggingRequestArgumentsRequest::Attach => {
229 panic!(
230 "Expected Launch request but got Attach for adapter {}",
231 adapter_name
232 );
233 }
234 }
235 }
236
237 assert!(
238 expected_adapters.is_empty(),
239 "The following expected adapters were not found in the registry: {:?}",
240 expected_adapters
241 );
242}