ZIm/crates/debugger_ui/src/tests/attach_modal.rs
Cole Miller e0c860c42a
debugger: Fix issues with restarting sessions (#33932)
Restarting sessions was broken in #33273 when we moved away from calling
`kill` in the shutdown sequence. This PR re-adds that `kill` call so
that old debug adapter processes will be cleaned up when sessions are
restarted within Zed. This doesn't re-introduce the issue that motivated
the original changes to the shutdown sequence, because we still send
Disconnect/Terminate to debug adapters when quitting Zed without killing
the process directly.

We also now remove manually-restarted sessions eagerly from the session
list.

Closes #33916 

Release Notes:

- debugger: Fixed not being able to restart sessions for Debugpy and
other adapters that communicate over TCP.
- debugger: Fixed debug adapter processes not being cleaned up.

---------

Co-authored-by: Remco Smits <djsmits12@gmail.com>
2025-07-07 18:28:59 +00:00

173 lines
5.4 KiB
Rust

use crate::{attach_modal::Candidate, tests::start_debug_session_with, *};
use attach_modal::AttachModal;
use dap::{FakeAdapter, adapters::DebugTaskDefinition};
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
use menu::Confirm;
use project::{FakeFs, Project};
use serde_json::json;
use task::AttachRequest;
use tests::{init_test, init_test_workspace};
use util::path;
#[gpui::test]
async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(executor.clone());
fs.insert_tree(
path!("/project"),
json!({
"main.rs": "First line\nSecond line\nThird line\nFourth line",
}),
)
.await;
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let _session = start_debug_session_with(
&workspace,
cx,
DebugTaskDefinition {
adapter: "fake-adapter".into(),
label: "label".into(),
config: json!({
"request": "attach",
"process_id": 10,
}),
tcp_connection: None,
},
|client| {
client.on_request::<dap::requests::Attach, _>(move |_, args| {
let raw = &args.raw;
assert_eq!(raw["request"], "attach");
assert_eq!(raw["process_id"], 10);
Ok(())
});
},
)
.unwrap();
cx.run_until_parked();
// assert we didn't show the attach modal
workspace
.update(cx, |workspace, _window, cx| {
assert!(workspace.active_modal::<AttachModal>(cx).is_none());
})
.unwrap();
}
#[gpui::test]
async fn test_show_attach_modal_and_select_process(
executor: BackgroundExecutor,
cx: &mut TestAppContext,
) {
init_test(cx);
let fs = FakeFs::new(executor.clone());
fs.insert_tree(
path!("/project"),
json!({
"main.rs": "First line\nSecond line\nThird line\nFourth line",
}),
)
.await;
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
// Set up handlers for sessions spawned via modal.
let _initialize_subscription =
project::debugger::test::intercept_debug_sessions(cx, |client| {
client.on_request::<dap::requests::Attach, _>(move |_, args| {
let raw = &args.raw;
assert_eq!(raw["request"], "attach");
assert_eq!(raw["process_id"], 1);
Ok(())
});
});
let attach_modal = workspace
.update(cx, |workspace, window, cx| {
let workspace_handle = cx.weak_entity();
workspace.toggle_modal(window, cx, |window, cx| {
AttachModal::with_processes(
workspace_handle,
task::ZedDebugConfig {
adapter: FakeAdapter::ADAPTER_NAME.into(),
request: dap::DebugRequest::Attach(AttachRequest::default()),
label: "attach example".into(),
stop_on_entry: None,
},
vec![
Candidate {
pid: 0,
name: "fake-binary-1".into(),
command: vec![],
},
Candidate {
pid: 3,
name: "real-binary-1".into(),
command: vec![],
},
Candidate {
pid: 1,
name: "fake-binary-2".into(),
command: vec![],
},
]
.into_iter()
.collect(),
true,
window,
cx,
)
});
workspace.active_modal::<AttachModal>(cx).unwrap()
})
.unwrap();
cx.run_until_parked();
// assert we got the expected processes
workspace
.update(cx, |_, window, cx| {
let names =
attach_modal.update(cx, |modal, cx| attach_modal::_process_names(&modal, cx));
// Initially all processes are visible.
assert_eq!(3, names.len());
attach_modal.update(cx, |this, cx| {
this.picker.update(cx, |this, cx| {
this.set_query("fakb", window, cx);
})
})
})
.unwrap();
cx.run_until_parked();
// assert we got the expected processes
workspace
.update(cx, |_, _, cx| {
let names =
attach_modal.update(cx, |modal, cx| attach_modal::_process_names(&modal, cx));
// Initially all processes are visible.
assert_eq!(2, names.len());
})
.unwrap();
// select the only existing process
cx.dispatch_action(Confirm);
cx.run_until_parked();
// assert attach modal was dismissed
workspace
.update(cx, |workspace, _window, cx| {
assert!(workspace.active_modal::<AttachModal>(cx).is_none());
})
.unwrap();
}