debugger beta: Fix gdb/delve JSON data conversion from New Session Modal (#31501)
test that check's that each conversion works properly based on the adapter's config validation function. Co-authored-by: Zed AI \<ai@zed.dev\> Release Notes: - debugger beta: Fix bug where Go/GDB configuration's wouldn't work from NewSessionModal
This commit is contained in:
parent
b7c5540075
commit
b63cea1f17
5 changed files with 134 additions and 44 deletions
|
@ -26,10 +26,12 @@ impl DebugAdapter for GdbDebugAdapter {
|
||||||
|
|
||||||
match &zed_scenario.request {
|
match &zed_scenario.request {
|
||||||
dap::DebugRequest::Attach(attach) => {
|
dap::DebugRequest::Attach(attach) => {
|
||||||
|
obj.insert("request".into(), "attach".into());
|
||||||
obj.insert("pid".into(), attach.process_id.into());
|
obj.insert("pid".into(), attach.process_id.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
dap::DebugRequest::Launch(launch) => {
|
dap::DebugRequest::Launch(launch) => {
|
||||||
|
obj.insert("request".into(), "launch".into());
|
||||||
obj.insert("program".into(), launch.program.clone().into());
|
obj.insert("program".into(), launch.program.clone().into());
|
||||||
|
|
||||||
if !launch.args.is_empty() {
|
if !launch.args.is_empty() {
|
||||||
|
|
|
@ -307,10 +307,14 @@ impl DebugAdapter for GoDebugAdapter {
|
||||||
let mut args = match &zed_scenario.request {
|
let mut args = match &zed_scenario.request {
|
||||||
dap::DebugRequest::Attach(attach_config) => {
|
dap::DebugRequest::Attach(attach_config) => {
|
||||||
json!({
|
json!({
|
||||||
|
"request": "attach",
|
||||||
|
"mode": "debug",
|
||||||
"processId": attach_config.process_id,
|
"processId": attach_config.process_id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
dap::DebugRequest::Launch(launch_config) => json!({
|
dap::DebugRequest::Launch(launch_config) => json!({
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "debug",
|
||||||
"program": launch_config.program,
|
"program": launch_config.program,
|
||||||
"cwd": launch_config.cwd,
|
"cwd": launch_config.cwd,
|
||||||
"args": launch_config.args,
|
"args": launch_config.args,
|
||||||
|
|
|
@ -47,13 +47,6 @@ impl PhpDebugAdapter {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_config(
|
|
||||||
&self,
|
|
||||||
_: &serde_json::Value,
|
|
||||||
) -> Result<StartDebuggingRequestArgumentsRequest> {
|
|
||||||
Ok(StartDebuggingRequestArgumentsRequest::Launch)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_installed_binary(
|
async fn get_installed_binary(
|
||||||
&self,
|
&self,
|
||||||
delegate: &Arc<dyn DapDelegate>,
|
delegate: &Arc<dyn DapDelegate>,
|
||||||
|
@ -101,7 +94,7 @@ impl PhpDebugAdapter {
|
||||||
envs: HashMap::default(),
|
envs: HashMap::default(),
|
||||||
request_args: StartDebuggingRequestArguments {
|
request_args: StartDebuggingRequestArguments {
|
||||||
configuration: task_definition.config.clone(),
|
configuration: task_definition.config.clone(),
|
||||||
request: self.validate_config(&task_definition.config)?,
|
request: <Self as DebugAdapter>::validate_config(self, &task_definition.config)?,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -303,6 +296,13 @@ impl DebugAdapter for PhpDebugAdapter {
|
||||||
Some(SharedString::new_static("PHP").into())
|
Some(SharedString::new_static("PHP").into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_config(
|
||||||
|
&self,
|
||||||
|
_: &serde_json::Value,
|
||||||
|
) -> Result<StartDebuggingRequestArgumentsRequest> {
|
||||||
|
Ok(StartDebuggingRequestArgumentsRequest::Launch)
|
||||||
|
}
|
||||||
|
|
||||||
fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
|
fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
|
||||||
let obj = match &zed_scenario.request {
|
let obj = match &zed_scenario.request {
|
||||||
dap::DebugRequest::Attach(_) => {
|
dap::DebugRequest::Attach(_) => {
|
||||||
|
|
|
@ -25,7 +25,6 @@ mod inline_values;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod module_list;
|
mod module_list;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[cfg(not(windows))]
|
|
||||||
mod new_session_modal;
|
mod new_session_modal;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod persistence;
|
mod persistence;
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
|
use dap::DapRegistry;
|
||||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||||
use project::{FakeFs, Project};
|
use project::{FakeFs, Project};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use task::{DebugScenario, TaskContext, VariableName};
|
use task::{DebugRequest, DebugScenario, LaunchRequest, TaskContext, VariableName, ZedDebugConfig};
|
||||||
use util::path;
|
use util::path;
|
||||||
|
|
||||||
use crate::tests::{init_test, init_test_workspace};
|
use crate::tests::{init_test, init_test_workspace};
|
||||||
|
|
||||||
// todo(tasks) figure out why task replacement is broken on windows
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_debug_session_substitutes_variables_and_relativizes_paths(
|
async fn test_debug_session_substitutes_variables_and_relativizes_paths(
|
||||||
executor: BackgroundExecutor,
|
executor: BackgroundExecutor,
|
||||||
|
@ -29,10 +29,9 @@ async fn test_debug_session_substitutes_variables_and_relativizes_paths(
|
||||||
let workspace = init_test_workspace(&project, cx).await;
|
let workspace = init_test_workspace(&project, cx).await;
|
||||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
// Set up task variables to simulate a real environment
|
|
||||||
let test_variables = vec![(
|
let test_variables = vec![(
|
||||||
VariableName::WorktreeRoot,
|
VariableName::WorktreeRoot,
|
||||||
"/test/worktree/path".to_string(),
|
path!("/test/worktree/path").to_string(),
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -45,33 +44,35 @@ async fn test_debug_session_substitutes_variables_and_relativizes_paths(
|
||||||
|
|
||||||
let home_dir = paths::home_dir();
|
let home_dir = paths::home_dir();
|
||||||
|
|
||||||
let sep = std::path::MAIN_SEPARATOR;
|
let test_cases: Vec<(&'static str, &'static str)> = vec![
|
||||||
|
|
||||||
// Test cases for different path formats
|
|
||||||
let test_cases: Vec<(Arc<String>, Arc<String>)> = vec![
|
|
||||||
// Absolute path - should not be relativized
|
// Absolute path - should not be relativized
|
||||||
(
|
(
|
||||||
Arc::from(format!("{0}absolute{0}path{0}to{0}program", sep)),
|
path!("/absolute/path/to/program"),
|
||||||
Arc::from(format!("{0}absolute{0}path{0}to{0}program", sep)),
|
path!("/absolute/path/to/program"),
|
||||||
),
|
),
|
||||||
// Relative path - should be prefixed with worktree root
|
// Relative path - should be prefixed with worktree root
|
||||||
(
|
(
|
||||||
Arc::from(format!(".{0}src{0}program", sep)),
|
format!(".{0}src{0}program", std::path::MAIN_SEPARATOR).leak(),
|
||||||
Arc::from(format!("{0}test{0}worktree{0}path{0}src{0}program", sep)),
|
path!("/test/worktree/path/src/program"),
|
||||||
),
|
),
|
||||||
// Home directory path - should be prefixed with worktree root
|
// Home directory path - should be expanded to full home directory path
|
||||||
(
|
(
|
||||||
Arc::from(format!("~{0}src{0}program", sep)),
|
format!("~{0}src{0}program", std::path::MAIN_SEPARATOR).leak(),
|
||||||
Arc::from(format!(
|
home_dir
|
||||||
"{1}{0}src{0}program",
|
.join("src")
|
||||||
sep,
|
.join("program")
|
||||||
home_dir.to_string_lossy()
|
.to_string_lossy()
|
||||||
)),
|
.to_string()
|
||||||
|
.leak(),
|
||||||
),
|
),
|
||||||
// Path with $ZED_WORKTREE_ROOT - should be substituted without double appending
|
// Path with $ZED_WORKTREE_ROOT - should be substituted without double appending
|
||||||
(
|
(
|
||||||
Arc::from(format!("$ZED_WORKTREE_ROOT{0}src{0}program", sep)),
|
format!(
|
||||||
Arc::from(format!("{0}test{0}worktree{0}path{0}src{0}program", sep)),
|
"$ZED_WORKTREE_ROOT{0}src{0}program",
|
||||||
|
std::path::MAIN_SEPARATOR
|
||||||
|
)
|
||||||
|
.leak(),
|
||||||
|
path!("/test/worktree/path/src/program"),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -80,44 +81,38 @@ async fn test_debug_session_substitutes_variables_and_relativizes_paths(
|
||||||
for (input_path, expected_path) in test_cases {
|
for (input_path, expected_path) in test_cases {
|
||||||
let _subscription = project::debugger::test::intercept_debug_sessions(cx, {
|
let _subscription = project::debugger::test::intercept_debug_sessions(cx, {
|
||||||
let called_launch = called_launch.clone();
|
let called_launch = called_launch.clone();
|
||||||
let input_path = input_path.clone();
|
|
||||||
let expected_path = expected_path.clone();
|
|
||||||
move |client| {
|
move |client| {
|
||||||
client.on_request::<dap::requests::Launch, _>({
|
client.on_request::<dap::requests::Launch, _>({
|
||||||
let called_launch = called_launch.clone();
|
let called_launch = called_launch.clone();
|
||||||
let input_path = input_path.clone();
|
|
||||||
let expected_path = expected_path.clone();
|
|
||||||
|
|
||||||
move |_, args| {
|
move |_, args| {
|
||||||
let config = args.raw.as_object().unwrap();
|
let config = args.raw.as_object().unwrap();
|
||||||
|
|
||||||
// Verify the program path was substituted correctly
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config["program"].as_str().unwrap(),
|
config["program"].as_str().unwrap(),
|
||||||
expected_path.as_str(),
|
expected_path,
|
||||||
"Program path was not correctly substituted for input: {}",
|
"Program path was not correctly substituted for input: {}",
|
||||||
input_path.as_str()
|
input_path
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify the cwd path was substituted correctly
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config["cwd"].as_str().unwrap(),
|
config["cwd"].as_str().unwrap(),
|
||||||
expected_path.as_str(),
|
expected_path,
|
||||||
"CWD path was not correctly substituted for input: {}",
|
"CWD path was not correctly substituted for input: {}",
|
||||||
input_path.as_str()
|
input_path
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify that otherField was substituted but not relativized
|
|
||||||
// It should still have $ZED_WORKTREE_ROOT substituted if present
|
|
||||||
let expected_other_field = if input_path.contains("$ZED_WORKTREE_ROOT") {
|
let expected_other_field = if input_path.contains("$ZED_WORKTREE_ROOT") {
|
||||||
input_path.replace("$ZED_WORKTREE_ROOT", "/test/worktree/path")
|
input_path
|
||||||
|
.replace("$ZED_WORKTREE_ROOT", &path!("/test/worktree/path"))
|
||||||
|
.to_owned()
|
||||||
} else {
|
} else {
|
||||||
input_path.to_string()
|
input_path.to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config["otherField"].as_str().unwrap(),
|
config["otherField"].as_str().unwrap(),
|
||||||
expected_other_field,
|
&expected_other_field,
|
||||||
"Other field was incorrectly modified for input: {}",
|
"Other field was incorrectly modified for input: {}",
|
||||||
input_path
|
input_path
|
||||||
);
|
);
|
||||||
|
@ -155,3 +150,93 @@ async fn test_debug_session_substitutes_variables_and_relativizes_paths(
|
||||||
called_launch.store(false, Ordering::SeqCst);
|
called_launch.store(false, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_dap_adapter_config_conversion_and_validation(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let mut expected_adapters = vec![
|
||||||
|
"CodeLLDB",
|
||||||
|
"Debugpy",
|
||||||
|
"PHP",
|
||||||
|
"JavaScript",
|
||||||
|
"Ruby",
|
||||||
|
"Delve",
|
||||||
|
"GDB",
|
||||||
|
"fake-adapter",
|
||||||
|
];
|
||||||
|
|
||||||
|
let adapter_names = cx.update(|cx| {
|
||||||
|
let registry = DapRegistry::global(cx);
|
||||||
|
registry.enumerate_adapters()
|
||||||
|
});
|
||||||
|
|
||||||
|
let zed_config = ZedDebugConfig {
|
||||||
|
label: "test_debug_session".into(),
|
||||||
|
adapter: "test_adapter".into(),
|
||||||
|
request: DebugRequest::Launch(LaunchRequest {
|
||||||
|
program: "test_program".into(),
|
||||||
|
cwd: None,
|
||||||
|
args: vec![],
|
||||||
|
env: Default::default(),
|
||||||
|
}),
|
||||||
|
stop_on_entry: Some(true),
|
||||||
|
};
|
||||||
|
|
||||||
|
for adapter_name in adapter_names {
|
||||||
|
let adapter_str = adapter_name.to_string();
|
||||||
|
if let Some(pos) = expected_adapters.iter().position(|&x| x == adapter_str) {
|
||||||
|
expected_adapters.remove(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
let adapter = cx
|
||||||
|
.update(|cx| {
|
||||||
|
let registry = DapRegistry::global(cx);
|
||||||
|
registry.adapter(adapter_name.as_ref())
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| panic!("Adapter {} should exist", adapter_name));
|
||||||
|
|
||||||
|
let mut adapter_specific_config = zed_config.clone();
|
||||||
|
adapter_specific_config.adapter = adapter_name.to_string().into();
|
||||||
|
|
||||||
|
let debug_scenario = adapter
|
||||||
|
.config_from_zed_format(adapter_specific_config)
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
|
panic!(
|
||||||
|
"Adapter {} should successfully convert from Zed format",
|
||||||
|
adapter_name
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
debug_scenario.config.is_object(),
|
||||||
|
"Adapter {} should produce a JSON object for config",
|
||||||
|
adapter_name
|
||||||
|
);
|
||||||
|
|
||||||
|
let request_type = adapter
|
||||||
|
.validate_config(&debug_scenario.config)
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
|
panic!(
|
||||||
|
"Adapter {} should validate the config successfully",
|
||||||
|
adapter_name
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
match request_type {
|
||||||
|
dap::StartDebuggingRequestArgumentsRequest::Launch => {}
|
||||||
|
dap::StartDebuggingRequestArgumentsRequest::Attach => {
|
||||||
|
panic!(
|
||||||
|
"Expected Launch request but got Attach for adapter {}",
|
||||||
|
adapter_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
expected_adapters.is_empty(),
|
||||||
|
"The following expected adapters were not found in the registry: {:?}",
|
||||||
|
expected_adapters
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue