new_session_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
 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}