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