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