1use crate::{
2 attach_modal::{Candidate, ModalIntent},
3 tests::start_debug_session_with,
4 *,
5};
6use attach_modal::AttachModal;
7use dap::{FakeAdapter, adapters::DebugTaskDefinition};
8use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
9use menu::Confirm;
10use project::{FakeFs, Project};
11use serde_json::json;
12use task::AttachRequest;
13use tests::{init_test, init_test_workspace};
14use util::path;
15
16#[gpui::test]
17async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18 init_test(cx);
19
20 let fs = FakeFs::new(executor.clone());
21
22 fs.insert_tree(
23 path!("/project"),
24 json!({
25 "main.rs": "First line\nSecond line\nThird line\nFourth line",
26 }),
27 )
28 .await;
29
30 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
31 let workspace = init_test_workspace(&project, cx).await;
32 let cx = &mut VisualTestContext::from_window(*workspace, cx);
33
34 let _session = start_debug_session_with(
35 &workspace,
36 cx,
37 DebugTaskDefinition {
38 adapter: "fake-adapter".into(),
39 label: "label".into(),
40 config: json!({
41 "request": "attach",
42 "process_id": 10,
43 }),
44 tcp_connection: None,
45 },
46 |client| {
47 client.on_request::<dap::requests::Attach, _>(move |_, args| {
48 let raw = &args.raw;
49 assert_eq!(raw["request"], "attach");
50 assert_eq!(raw["process_id"], 10);
51
52 Ok(())
53 });
54 },
55 )
56 .unwrap();
57
58 cx.run_until_parked();
59
60 // assert we didn't show the attach modal
61 workspace
62 .update(cx, |workspace, _window, cx| {
63 assert!(workspace.active_modal::<AttachModal>(cx).is_none());
64 })
65 .unwrap();
66}
67
68#[gpui::test]
69async fn test_show_attach_modal_and_select_process(
70 executor: BackgroundExecutor,
71 cx: &mut TestAppContext,
72) {
73 init_test(cx);
74
75 let fs = FakeFs::new(executor.clone());
76
77 fs.insert_tree(
78 path!("/project"),
79 json!({
80 "main.rs": "First line\nSecond line\nThird line\nFourth line",
81 }),
82 )
83 .await;
84
85 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
86 let workspace = init_test_workspace(&project, cx).await;
87 let cx = &mut VisualTestContext::from_window(*workspace, cx);
88 // Set up handlers for sessions spawned via modal.
89 let _initialize_subscription =
90 project::debugger::test::intercept_debug_sessions(cx, |client| {
91 client.on_request::<dap::requests::Attach, _>(move |_, args| {
92 let raw = &args.raw;
93 assert_eq!(raw["request"], "attach");
94 assert_eq!(raw["process_id"], 1);
95
96 Ok(())
97 });
98 });
99 let attach_modal = workspace
100 .update(cx, |workspace, window, cx| {
101 let workspace_handle = cx.weak_entity();
102 workspace.toggle_modal(window, cx, |window, cx| {
103 AttachModal::with_processes(
104 workspace_handle,
105 vec![
106 Candidate {
107 pid: 0,
108 name: "fake-binary-1".into(),
109 command: vec![],
110 },
111 Candidate {
112 pid: 3,
113 name: "real-binary-1".into(),
114 command: vec![],
115 },
116 Candidate {
117 pid: 1,
118 name: "fake-binary-2".into(),
119 command: vec![],
120 },
121 ]
122 .into_iter()
123 .collect(),
124 true,
125 ModalIntent::AttachToProcess(task::ZedDebugConfig {
126 adapter: FakeAdapter::ADAPTER_NAME.into(),
127 request: dap::DebugRequest::Attach(AttachRequest::default()),
128 label: "attach example".into(),
129 stop_on_entry: None,
130 }),
131 window,
132 cx,
133 )
134 });
135
136 workspace.active_modal::<AttachModal>(cx).unwrap()
137 })
138 .unwrap();
139
140 cx.run_until_parked();
141
142 // assert we got the expected processes
143 workspace
144 .update(cx, |_, window, cx| {
145 let names = attach_modal.update(cx, |modal, cx| attach_modal::process_names(modal, cx));
146 // Initially all processes are visible.
147 assert_eq!(3, names.len());
148 attach_modal.update(cx, |this, cx| {
149 this.picker.update(cx, |this, cx| {
150 this.set_query("fakb", window, cx);
151 })
152 })
153 })
154 .unwrap();
155 cx.run_until_parked();
156 // assert we got the expected processes
157 workspace
158 .update(cx, |_, _, cx| {
159 let names = attach_modal.update(cx, |modal, cx| attach_modal::process_names(modal, cx));
160 // Initially all processes are visible.
161 assert_eq!(2, names.len());
162 })
163 .unwrap();
164 // select the only existing process
165 cx.dispatch_action(Confirm);
166
167 cx.run_until_parked();
168
169 // assert attach modal was dismissed
170 workspace
171 .update(cx, |workspace, _window, cx| {
172 assert!(workspace.active_modal::<AttachModal>(cx).is_none());
173 })
174 .unwrap();
175}
176
177#[gpui::test]
178async fn test_attach_with_pick_pid_variable(executor: BackgroundExecutor, cx: &mut TestAppContext) {
179 init_test(cx);
180
181 let fs = FakeFs::new(executor.clone());
182
183 fs.insert_tree(
184 path!("/project"),
185 json!({
186 "main.rs": "First line\nSecond line\nThird line\nFourth line",
187 }),
188 )
189 .await;
190
191 let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
192 let workspace = init_test_workspace(&project, cx).await;
193 let cx = &mut VisualTestContext::from_window(*workspace, cx);
194
195 let _initialize_subscription =
196 project::debugger::test::intercept_debug_sessions(cx, |client| {
197 client.on_request::<dap::requests::Attach, _>(move |_, args| {
198 let raw = &args.raw;
199 assert_eq!(raw["request"], "attach");
200 assert_eq!(
201 raw["process_id"], "42",
202 "verify process id has been replaced"
203 );
204
205 Ok(())
206 });
207 });
208
209 let pick_pid_placeholder = task::VariableName::PickProcessId.template_value();
210 workspace
211 .update(cx, |workspace, window, cx| {
212 workspace.start_debug_session(
213 DebugTaskDefinition {
214 adapter: FakeAdapter::ADAPTER_NAME.into(),
215 label: "attach with picker".into(),
216 config: json!({
217 "request": "attach",
218 "process_id": pick_pid_placeholder,
219 }),
220 tcp_connection: None,
221 }
222 .to_scenario(),
223 task::TaskContext::default(),
224 None,
225 None,
226 window,
227 cx,
228 )
229 })
230 .unwrap();
231
232 cx.run_until_parked();
233
234 let attach_modal = workspace
235 .update(cx, |workspace, _window, cx| {
236 workspace.active_modal::<AttachModal>(cx)
237 })
238 .unwrap();
239
240 assert!(
241 attach_modal.is_some(),
242 "Attach modal should open when config contains ZED_PICK_PID"
243 );
244
245 let attach_modal = attach_modal.unwrap();
246
247 workspace
248 .update(cx, |_, window, cx| {
249 attach_modal.update(cx, |modal, cx| {
250 attach_modal::set_candidates(
251 modal,
252 vec![
253 Candidate {
254 pid: 10,
255 name: "process-1".into(),
256 command: vec![],
257 },
258 Candidate {
259 pid: 42,
260 name: "target-process".into(),
261 command: vec![],
262 },
263 Candidate {
264 pid: 99,
265 name: "process-3".into(),
266 command: vec![],
267 },
268 ]
269 .into_iter()
270 .collect(),
271 window,
272 cx,
273 )
274 })
275 })
276 .unwrap();
277
278 cx.run_until_parked();
279
280 workspace
281 .update(cx, |_, window, cx| {
282 attach_modal.update(cx, |modal, cx| {
283 modal.picker.update(cx, |picker, cx| {
284 picker.set_query("target", window, cx);
285 })
286 })
287 })
288 .unwrap();
289
290 cx.run_until_parked();
291
292 workspace
293 .update(cx, |_, _, cx| {
294 let names = attach_modal.update(cx, |modal, cx| attach_modal::process_names(modal, cx));
295 assert_eq!(names.len(), 1);
296 assert_eq!(names[0], " 42 target-process");
297 })
298 .unwrap();
299
300 cx.dispatch_action(Confirm);
301 cx.run_until_parked();
302
303 workspace
304 .update(cx, |workspace, _window, cx| {
305 assert!(
306 workspace.active_modal::<AttachModal>(cx).is_none(),
307 "Attach modal should be dismissed after selection"
308 );
309 })
310 .unwrap();
311}