new_process_modal.rs

  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}