1use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
2use project::{FakeFs, Project};
3use serde_json::json;
4use std::sync::Arc;
5use std::sync::atomic::{AtomicBool, Ordering};
6use task::{DebugScenario, TaskContext, VariableName};
7use util::path;
8
9use crate::tests::{init_test, init_test_workspace};
10
11// todo(tasks) figure out why task replacement is broken on windows
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 // Set up task variables to simulate a real environment
33 let test_variables = vec![(
34 VariableName::WorktreeRoot,
35 "/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 sep = std::path::MAIN_SEPARATOR;
49
50 // Test cases for different path formats
51 let test_cases: Vec<(Arc<String>, Arc<String>)> = vec![
52 // Absolute path - should not be relativized
53 (
54 Arc::from(format!("{0}absolute{0}path{0}to{0}program", sep)),
55 Arc::from(format!("{0}absolute{0}path{0}to{0}program", sep)),
56 ),
57 // Relative path - should be prefixed with worktree root
58 (
59 Arc::from(format!(".{0}src{0}program", sep)),
60 Arc::from(format!("{0}test{0}worktree{0}path{0}src{0}program", sep)),
61 ),
62 // Home directory path - should be prefixed with worktree root
63 (
64 Arc::from(format!("~{0}src{0}program", sep)),
65 Arc::from(format!(
66 "{1}{0}src{0}program",
67 sep,
68 home_dir.to_string_lossy()
69 )),
70 ),
71 // Path with $ZED_WORKTREE_ROOT - should be substituted without double appending
72 (
73 Arc::from(format!("$ZED_WORKTREE_ROOT{0}src{0}program", sep)),
74 Arc::from(format!("{0}test{0}worktree{0}path{0}src{0}program", sep)),
75 ),
76 ];
77
78 let called_launch = Arc::new(AtomicBool::new(false));
79
80 for (input_path, expected_path) in test_cases {
81 let _subscription = project::debugger::test::intercept_debug_sessions(cx, {
82 let called_launch = called_launch.clone();
83 let input_path = input_path.clone();
84 let expected_path = expected_path.clone();
85 move |client| {
86 client.on_request::<dap::requests::Launch, _>({
87 let called_launch = called_launch.clone();
88 let input_path = input_path.clone();
89 let expected_path = expected_path.clone();
90
91 move |_, args| {
92 let config = args.raw.as_object().unwrap();
93
94 // Verify the program path was substituted correctly
95 assert_eq!(
96 config["program"].as_str().unwrap(),
97 expected_path.as_str(),
98 "Program path was not correctly substituted for input: {}",
99 input_path.as_str()
100 );
101
102 // Verify the cwd path was substituted correctly
103 assert_eq!(
104 config["cwd"].as_str().unwrap(),
105 expected_path.as_str(),
106 "CWD path was not correctly substituted for input: {}",
107 input_path.as_str()
108 );
109
110 // Verify that otherField was substituted but not relativized
111 // It should still have $ZED_WORKTREE_ROOT substituted if present
112 let expected_other_field = if input_path.contains("$ZED_WORKTREE_ROOT") {
113 input_path.replace("$ZED_WORKTREE_ROOT", "/test/worktree/path")
114 } else {
115 input_path.to_string()
116 };
117
118 assert_eq!(
119 config["otherField"].as_str().unwrap(),
120 expected_other_field,
121 "Other field was incorrectly modified for input: {}",
122 input_path
123 );
124
125 called_launch.store(true, Ordering::SeqCst);
126
127 Ok(())
128 }
129 });
130 }
131 });
132
133 let scenario = DebugScenario {
134 adapter: "fake-adapter".into(),
135 label: "test-debug-session".into(),
136 build: None,
137 config: json!({
138 "request": "launch",
139 "program": input_path,
140 "cwd": input_path,
141 "otherField": input_path
142 }),
143 tcp_connection: None,
144 };
145
146 workspace
147 .update(cx, |workspace, window, cx| {
148 workspace.start_debug_session(scenario, task_context.clone(), None, window, cx)
149 })
150 .unwrap();
151
152 cx.run_until_parked();
153
154 assert!(called_launch.load(Ordering::SeqCst));
155 called_launch.store(false, Ordering::SeqCst);
156 }
157}