attach_modal.rs

  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}