diff --git a/Cargo.lock b/Cargo.lock index c165b80b92f3c..5a7ed0f60c9b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3432,10 +3432,13 @@ version = "0.1.0" dependencies = [ "anyhow", "async-compression", + "async-pipe", "async-tar", "async-trait", "collections", + "ctor", "dap-types", + "env_logger 0.11.5", "fs", "futures 0.3.31", "gpui", diff --git a/crates/dap/Cargo.toml b/crates/dap/Cargo.toml index 2097bea26a6ac..dd101a4864701 100644 --- a/crates/dap/Cargo.toml +++ b/crates/dap/Cargo.toml @@ -8,9 +8,18 @@ license = "GPL-3.0-or-later" [lints] workspace = true +[features] +test-support = [ + "gpui/test-support", + "util/test-support", + "task/test-support", + "async-pipe", +] + [dependencies] anyhow.workspace = true async-compression.workspace = true +async-pipe = { workspace = true, optional = true } async-tar.workspace = true async-trait.workspace = true collections.workspace = true @@ -32,3 +41,11 @@ smol.workspace = true sysinfo.workspace = true task.workspace = true util.workspace = true + +[dev-dependencies] +async-pipe.workspace = true +ctor.workspace = true +env_logger.workspace = true +gpui = { workspace = true, features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } +task = { workspace = true, features = ["test-support"] } diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index d6fc9696564d2..c51b7f22fc3d8 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -1,3 +1,5 @@ +#[cfg(any(test, feature = "test-support"))] +use crate::transport::FakeTransport; use crate::transport::Transport; use ::fs::Fs; use anyhow::{anyhow, Context as _, Result}; @@ -260,7 +262,7 @@ pub trait DebugAdapter: 'static + Send + Sync { .await } - fn transport(&self) -> Box; + fn transport(&self) -> Arc; async fn fetch_latest_adapter_version( &self, @@ -300,3 +302,71 @@ pub trait DebugAdapter: 'static + Send + Sync { None } } + +#[cfg(any(test, feature = "test-support"))] +pub struct FakeAdapter {} + +#[cfg(any(test, feature = "test-support"))] +impl FakeAdapter { + const ADAPTER_NAME: &'static str = "fake-adapter"; + + pub fn new() -> Self { + Self {} + } +} + +#[cfg(any(test, feature = "test-support"))] +#[async_trait(?Send)] +impl DebugAdapter for FakeAdapter { + fn name(&self) -> DebugAdapterName { + DebugAdapterName(Self::ADAPTER_NAME.into()) + } + + fn transport(&self) -> Arc { + Arc::new(FakeTransport::new()) + } + + async fn get_binary( + &self, + _delegate: &dyn DapDelegate, + _config: &DebugAdapterConfig, + _user_installed_path: Option, + ) -> Result { + Ok(DebugAdapterBinary { + command: "command".into(), + arguments: None, + envs: None, + cwd: None, + }) + } + + async fn fetch_latest_adapter_version( + &self, + _delegate: &dyn DapDelegate, + ) -> Result { + unimplemented!("fetch latest adapter version"); + } + + async fn install_binary( + &self, + _version: AdapterVersion, + _delegate: &dyn DapDelegate, + ) -> Result<()> { + unimplemented!("install binary"); + } + + async fn get_installed_binary( + &self, + _delegate: &dyn DapDelegate, + _config: &DebugAdapterConfig, + _user_installed_path: Option, + ) -> Result { + unimplemented!("get installed binary"); + } + + fn request_args(&self, _config: &DebugAdapterConfig) -> Value { + use serde_json::json; + + json!({}) + } +} diff --git a/crates/dap/src/client.rs b/crates/dap/src/client.rs index c06107e8dcf2a..45f2ec1ddd4a8 100644 --- a/crates/dap/src/client.rs +++ b/crates/dap/src/client.rs @@ -40,7 +40,7 @@ pub struct DebugAdapterClient { sequence_count: AtomicU64, binary: DebugAdapterBinary, executor: BackgroundExecutor, - adapter: Arc>, + adapter: Arc, transport_delegate: TransportDelegate, config: Arc>, } @@ -49,7 +49,7 @@ impl DebugAdapterClient { pub fn new( id: DebugAdapterClientId, config: DebugAdapterConfig, - adapter: Arc>, + adapter: Arc, binary: DebugAdapterBinary, cx: &AsyncAppContext, ) -> Self { @@ -66,16 +66,11 @@ impl DebugAdapterClient { } } - pub async fn start( - &mut self, - binary: &DebugAdapterBinary, - message_handler: F, - cx: &mut AsyncAppContext, - ) -> Result<()> + pub async fn start(&mut self, message_handler: F, cx: &mut AsyncAppContext) -> Result<()> where F: FnMut(Message, &mut AppContext) + 'static + Send + Sync + Clone, { - let (server_rx, server_tx) = self.transport_delegate.start(binary, cx).await?; + let (server_rx, server_tx) = self.transport_delegate.start(&self.binary, cx).await?; log::info!("Successfully connected to debug adapter"); // start handling events/reverse requests @@ -195,7 +190,7 @@ impl DebugAdapterClient { self.config.lock().unwrap().clone() } - pub fn adapter(&self) -> &Arc> { + pub fn adapter(&self) -> &Arc { &self.adapter } @@ -234,4 +229,176 @@ impl DebugAdapterClient { { self.transport_delegate.add_log_handler(f, kind); } + + #[cfg(any(test, feature = "test-support"))] + pub async fn on_request(&self, handler: F) + where + F: 'static + Send + FnMut(u64, R::Arguments) -> Result, + { + let transport = self.transport_delegate.transport(); + + transport.as_fake().on_request::(handler).await; + } + + #[cfg(any(test, feature = "test-support"))] + pub async fn fake_event(&self, event: dap_types::messages::Events) { + self.send_message(Message::Event(Box::new(event))) + .await + .unwrap(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{adapters::FakeAdapter, client::DebugAdapterClient}; + use dap_types::{ + messages::Events, requests::Initialize, Capabilities, InitializeRequestArguments, + InitializeRequestArgumentsPathFormat, + }; + use gpui::TestAppContext; + use std::sync::atomic::{AtomicBool, Ordering}; + use task::DebugAdapterConfig; + + #[ctor::ctor] + fn init_logger() { + if std::env::var("RUST_LOG").is_ok() { + env_logger::init(); + } + } + + #[gpui::test] + pub async fn test_initialize_client(cx: &mut TestAppContext) { + let adapter = Arc::new(FakeAdapter::new()); + + let mut client = DebugAdapterClient::new( + crate::client::DebugAdapterClientId(1), + DebugAdapterConfig { + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + adapter, + DebugAdapterBinary { + command: "command".into(), + arguments: Default::default(), + envs: Default::default(), + cwd: None, + }, + &mut cx.to_async(), + ); + + client + .on_request::(move |_, _| { + Ok(dap_types::Capabilities { + supports_configuration_done_request: Some(true), + ..Default::default() + }) + }) + .await; + + client + .start( + |_, _| panic!("Did not expect to hit this code path"), + &mut cx.to_async(), + ) + .await + .unwrap(); + + cx.run_until_parked(); + + let response = client + .request::(InitializeRequestArguments { + client_id: Some("zed".to_owned()), + client_name: Some("Zed".to_owned()), + adapter_id: "fake-adapter".to_owned(), + locale: Some("en-US".to_owned()), + path_format: Some(InitializeRequestArgumentsPathFormat::Path), + supports_variable_type: Some(true), + supports_variable_paging: Some(false), + supports_run_in_terminal_request: Some(true), + supports_memory_references: Some(true), + supports_progress_reporting: Some(false), + supports_invalidated_event: Some(false), + lines_start_at1: Some(true), + columns_start_at1: Some(true), + supports_memory_event: Some(false), + supports_args_can_be_interpreted_by_shell: Some(false), + supports_start_debugging_request: Some(true), + }) + .await + .unwrap(); + + cx.run_until_parked(); + + assert_eq!( + dap_types::Capabilities { + supports_configuration_done_request: Some(true), + ..Default::default() + }, + response + ); + + client.shutdown().await.unwrap(); + } + + #[gpui::test] + pub async fn test_calls_event_handler(cx: &mut TestAppContext) { + let adapter = Arc::new(FakeAdapter::new()); + let was_called = Arc::new(AtomicBool::new(false)); + let was_called_clone = was_called.clone(); + + let mut client = DebugAdapterClient::new( + crate::client::DebugAdapterClientId(1), + DebugAdapterConfig { + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + adapter, + DebugAdapterBinary { + command: "command".into(), + arguments: Default::default(), + envs: Default::default(), + cwd: None, + }, + &mut cx.to_async(), + ); + + client + .start( + move |event, _| { + was_called_clone.store(true, Ordering::SeqCst); + + assert_eq!( + Message::Event(Box::new(Events::Initialized( + Some(Capabilities::default()) + ))), + event + ); + }, + &mut cx.to_async(), + ) + .await + .unwrap(); + + cx.run_until_parked(); + + client + .fake_event(Events::Initialized(Some(Capabilities::default()))) + .await; + + cx.run_until_parked(); + + assert!( + was_called.load(std::sync::atomic::Ordering::SeqCst), + "Event handler was not called" + ); + + client.shutdown().await.unwrap(); + } } diff --git a/crates/dap/src/lib.rs b/crates/dap/src/lib.rs index df62861da7bf1..b7a48be87b4d4 100644 --- a/crates/dap/src/lib.rs +++ b/crates/dap/src/lib.rs @@ -3,3 +3,6 @@ pub mod client; pub mod transport; pub use dap_types::*; pub mod debugger_settings; + +#[cfg(any(test, feature = "test-support"))] +pub use adapters::FakeAdapter; diff --git a/crates/dap/src/transport.rs b/crates/dap/src/transport.rs index 04c3eaf391b11..3051f3f839fcd 100644 --- a/crates/dap/src/transport.rs +++ b/crates/dap/src/transport.rs @@ -4,9 +4,7 @@ use dap_types::{ messages::{Message, Response}, ErrorResponse, }; -use futures::{ - channel::oneshot, select, AsyncBufRead, AsyncReadExt as _, AsyncWrite, FutureExt as _, -}; +use futures::{channel::oneshot, select, AsyncRead, AsyncReadExt as _, AsyncWrite, FutureExt as _}; use gpui::AsyncAppContext; use settings::Settings as _; use smallvec::SmallVec; @@ -15,9 +13,10 @@ use smol::{ io::{AsyncBufReadExt as _, AsyncWriteExt, BufReader}, lock::Mutex, net::{TcpListener, TcpStream}, - process::{self, Child, ChildStderr, ChildStdout}, + process::{self, Child}, }; use std::{ + any::Any, collections::HashMap, net::{Ipv4Addr, SocketAddrV4}, process::Stdio, @@ -43,22 +42,25 @@ pub enum IoKind { StdErr, } -pub struct TransportParams { - input: Box, - output: Box, - process: Child, +pub struct TransportPipe { + input: Box, + output: Box, + stdout: Option>, + stderr: Option>, } -impl TransportParams { +impl TransportPipe { pub fn new( - input: Box, - output: Box, - process: Child, + input: Box, + output: Box, + stdout: Option>, + stderr: Option>, ) -> Self { - TransportParams { + TransportPipe { input, output, - process, + stdout, + stderr, } } } @@ -70,16 +72,14 @@ pub(crate) struct TransportDelegate { log_handlers: LogHandlers, current_requests: Requests, pending_requests: Requests, - transport: Box, - process: Arc>>, + transport: Arc, server_tx: Arc>>>, } impl TransportDelegate { - pub fn new(transport: Box) -> Self { + pub fn new(transport: Arc) -> Self { Self { transport, - process: Default::default(), server_tx: Default::default(), log_handlers: Default::default(), current_requests: Default::default(), @@ -98,7 +98,7 @@ impl TransportDelegate { let (server_tx, client_rx) = unbounded::(); cx.update(|cx| { - if let Some(stdout) = params.process.stdout.take() { + if let Some(stdout) = params.stdout.take() { cx.background_executor() .spawn(Self::handle_adapter_log(stdout, self.log_handlers.clone())) .detach_and_log_err(cx); @@ -113,7 +113,7 @@ impl TransportDelegate { )) .detach_and_log_err(cx); - if let Some(stderr) = params.process.stderr.take() { + if let Some(stderr) = params.stderr.take() { cx.background_executor() .spawn(Self::handle_error(stderr, self.log_handlers.clone())) .detach_and_log_err(cx); @@ -131,9 +131,6 @@ impl TransportDelegate { })?; { - let mut lock = self.process.lock().await; - *lock = Some(params.process); - let mut lock = self.server_tx.lock().await; *lock = Some(server_tx.clone()); } @@ -166,7 +163,10 @@ impl TransportDelegate { } } - async fn handle_adapter_log(stdout: ChildStdout, log_handlers: LogHandlers) -> Result<()> { + async fn handle_adapter_log(stdout: Stdout, log_handlers: LogHandlers) -> Result<()> + where + Stdout: AsyncRead + Unpin + Send + 'static, + { let mut reader = BufReader::new(stdout); let mut line = String::new(); @@ -196,13 +196,20 @@ impl TransportDelegate { result } - async fn handle_input( - mut server_stdin: Box, + fn build_rpc_message(message: String) -> String { + format!("Content-Length: {}\r\n\r\n{}", message.len(), message) + } + + async fn handle_input( + mut server_stdin: Stdin, client_rx: Receiver, current_requests: Requests, pending_requests: Requests, log_handlers: LogHandlers, - ) -> Result<()> { + ) -> Result<()> + where + Stdin: AsyncWrite + Unpin + Send + 'static, + { let result = loop { match client_rx.recv().await { Ok(message) => { @@ -224,10 +231,7 @@ impl TransportDelegate { } if let Err(e) = server_stdin - .write_all( - format!("Content-Length: {}\r\n\r\n{}", message.len(), message) - .as_bytes(), - ) + .write_all(Self::build_rpc_message(message).as_bytes()) .await { break Err(e.into()); @@ -248,18 +252,21 @@ impl TransportDelegate { result } - async fn handle_output( - mut server_stdout: Box, + async fn handle_output( + server_stdout: Stdout, client_tx: Sender, pending_requests: Requests, log_handlers: LogHandlers, - ) -> Result<()> { + ) -> Result<()> + where + Stdout: AsyncRead + Unpin + Send + 'static, + { let mut recv_buffer = String::new(); + let mut reader = BufReader::new(server_stdout); let result = loop { let message = - Self::receive_server_message(&mut server_stdout, &mut recv_buffer, &log_handlers) - .await; + Self::receive_server_message(&mut reader, &mut recv_buffer, &log_handlers).await; match message { Ok(Message::Response(res)) => { @@ -287,7 +294,10 @@ impl TransportDelegate { result } - async fn handle_error(stderr: ChildStderr, log_handlers: LogHandlers) -> Result<()> { + async fn handle_error(stderr: Stderr, log_handlers: LogHandlers) -> Result<()> + where + Stderr: AsyncRead + Unpin + Send + 'static, + { let mut buffer = String::new(); let mut reader = BufReader::new(stderr); @@ -331,11 +341,14 @@ impl TransportDelegate { } } - async fn receive_server_message( - reader: &mut Box, + async fn receive_server_message( + reader: &mut BufReader, buffer: &mut String, log_handlers: &LogHandlers, - ) -> Result { + ) -> Result + where + Stdout: AsyncRead + Unpin + Send + 'static, + { let mut content_length = None; loop { buffer.truncate(0); @@ -389,20 +402,16 @@ impl TransportDelegate { server_tx.close(); } - let mut adapter = self.process.lock().await.take(); let mut current_requests = self.current_requests.lock().await; let mut pending_requests = self.pending_requests.lock().await; current_requests.clear(); pending_requests.clear(); - if let Some(mut adapter) = adapter.take() { - let _ = adapter.kill().log_err(); - } + let _ = self.transport.kill().await.log_err(); drop(current_requests); drop(pending_requests); - drop(adapter); log::debug!("Shutdown client completed"); @@ -413,6 +422,11 @@ impl TransportDelegate { self.transport.has_adapter_logs() } + #[cfg(any(test, feature = "test-support"))] + pub fn transport(&self) -> &Arc { + &self.transport + } + pub fn add_log_handler(&self, f: F, kind: LogKind) where F: 'static + Send + FnMut(IoKind, &str), @@ -423,23 +437,28 @@ impl TransportDelegate { } #[async_trait(?Send)] -pub trait Transport: 'static + Send + Sync { +pub trait Transport: 'static + Send + Sync + Any { async fn start( - &mut self, + &self, binary: &DebugAdapterBinary, cx: &mut AsyncAppContext, - ) -> Result; + ) -> Result; fn has_adapter_logs(&self) -> bool; - fn clone_box(&self) -> Box; + async fn kill(&self) -> Result<()>; + + #[cfg(any(test, feature = "test-support"))] + fn as_fake(&self) -> &FakeTransport { + panic!("called as_fake on a real adapter"); + } } -#[derive(Clone)] pub struct TcpTransport { port: u16, host: Ipv4Addr, timeout: Option, + process: Arc>>, } impl TcpTransport { @@ -448,6 +467,7 @@ impl TcpTransport { port, host, timeout, + process: Arc::new(Mutex::new(None)), } } @@ -467,10 +487,10 @@ impl TcpTransport { #[async_trait(?Send)] impl Transport for TcpTransport { async fn start( - &mut self, + &self, binary: &DebugAdapterBinary, cx: &mut AsyncAppContext, - ) -> Result { + ) -> Result { let mut command = process::Command::new(&binary.command); if let Some(cwd) = &binary.cwd { @@ -491,7 +511,7 @@ impl Transport for TcpTransport { .stderr(Stdio::piped()) .kill_on_drop(true); - let process = command + let mut process = command .spawn() .with_context(|| "failed to start debug adapter.")?; @@ -523,10 +543,18 @@ impl Transport for TcpTransport { self.port ); - Ok(TransportParams::new( + let stdout = process.stdout.take(); + let stderr = process.stderr.take(); + + { + *self.process.lock().await = Some(process); + } + + Ok(TransportPipe::new( Box::new(tx), Box::new(BufReader::new(rx)), - process, + stdout.map(|s| Box::new(s) as Box), + stderr.map(|s| Box::new(s) as Box), )) } @@ -534,27 +562,34 @@ impl Transport for TcpTransport { true } - fn clone_box(&self) -> Box { - Box::new(self.clone()) + async fn kill(&self) -> Result<()> { + if let Some(mut process) = self.process.lock().await.take() { + process.kill()?; + } + + Ok(()) } } -#[derive(Clone)] -pub struct StdioTransport {} +pub struct StdioTransport { + process: Arc>>, +} impl StdioTransport { pub fn new() -> Self { - Self {} + Self { + process: Arc::new(Mutex::new(None)), + } } } #[async_trait(?Send)] impl Transport for StdioTransport { async fn start( - &mut self, + &self, binary: &DebugAdapterBinary, _: &mut AsyncAppContext, - ) -> Result { + ) -> Result { let mut command = process::Command::new(&binary.command); if let Some(cwd) = &binary.cwd { @@ -587,13 +622,162 @@ impl Transport for StdioTransport { .stdout .take() .ok_or_else(|| anyhow!("Failed to open stdout"))?; + let stderr = process + .stdout + .take() + .ok_or_else(|| anyhow!("Failed to open stderr"))?; log::info!("Debug adapter has connected to stdio adapter"); - Ok(TransportParams::new( + { + *self.process.lock().await = Some(process); + } + + Ok(TransportPipe::new( Box::new(stdin), Box::new(BufReader::new(stdout)), - process, + None, + Some(Box::new(stderr) as Box), + )) + } + + fn has_adapter_logs(&self) -> bool { + false + } + + async fn kill(&self) -> Result<()> { + if let Some(mut process) = self.process.lock().await.take() { + process.kill()?; + } + + Ok(()) + } +} + +#[cfg(any(test, feature = "test-support"))] +type RequestHandler = Box< + dyn Send + + FnMut( + u64, + serde_json::Value, + Arc>, + ) -> std::pin::Pin + Send>>, +>; + +#[cfg(any(test, feature = "test-support"))] +pub struct FakeTransport { + request_handlers: Arc>>, +} + +#[cfg(any(test, feature = "test-support"))] +impl FakeTransport { + pub fn new() -> Self { + Self { + request_handlers: Arc::new(Mutex::new(HashMap::default())), + } + } + + pub async fn on_request(&self, mut handler: F) + where + F: 'static + Send + FnMut(u64, R::Arguments) -> Result, + { + self.request_handlers.lock().await.insert( + R::COMMAND, + Box::new( + move |seq, args, writer: Arc>| { + let response = handler(seq, serde_json::from_value(args).unwrap()).unwrap(); + + let message = Message::Response(Response { + seq: seq + 1, + request_seq: seq, + success: true, + command: R::COMMAND.into(), + body: Some(serde_json::to_value(response).unwrap()), + }); + + let message = serde_json::to_string(&message).unwrap(); + + let writer = writer.clone(); + + Box::pin(async move { + let mut writer = writer.lock().await; + writer + .write_all(TransportDelegate::build_rpc_message(message).as_bytes()) + .await + .unwrap(); + writer.flush().await.unwrap(); + }) + }, + ), + ); + } +} + +#[cfg(any(test, feature = "test-support"))] +#[async_trait(?Send)] +impl Transport for FakeTransport { + async fn start( + &self, + _binary: &DebugAdapterBinary, + cx: &mut AsyncAppContext, + ) -> Result { + let (stdin_writer, stdin_reader) = async_pipe::pipe(); + let (stdout_writer, stdout_reader) = async_pipe::pipe(); + + let handlers = self.request_handlers.clone(); + let stdout_writer = Arc::new(Mutex::new(stdout_writer)); + + cx.background_executor().spawn(async move { + let mut reader = BufReader::new(stdin_reader); + let mut buffer = String::new(); + + loop { + let message = TransportDelegate::receive_server_message( + &mut reader, + &mut buffer, + &Default::default(), + ) + .await; + + match message { + Err(error) => { + break anyhow!(error); + } + Ok(Message::Request(request)) => { + if let Some(mut handle) = + handlers.lock().await.remove(request.command.as_str()) + { + handle( + request.seq, + request.arguments.unwrap(), + stdout_writer.clone(), + ) + .await; + } else { + log::debug!("No handler for {}", request.command); + } + } + Ok(Message::Event(event)) => { + let message = serde_json::to_string(&Message::Event(event)).unwrap(); + + let mut writer = stdout_writer.lock().await; + writer + .write_all(TransportDelegate::build_rpc_message(message).as_bytes()) + .await + .unwrap(); + writer.flush().await.unwrap(); + } + _ => unreachable!("You can only send a request and an event that is redirected to the ouput reader"), + } + } + }) + .detach(); + + Ok(TransportPipe::new( + Box::new(stdin_writer), + Box::new(stdout_reader), + None, + None, )) } @@ -601,7 +785,12 @@ impl Transport for StdioTransport { false } - fn clone_box(&self) -> Box { - Box::new(self.clone()) + async fn kill(&self) -> Result<()> { + Ok(()) + } + + #[cfg(any(test, feature = "test-support"))] + fn as_fake(&self) -> &FakeTransport { + self } } diff --git a/crates/dap_adapters/Cargo.toml b/crates/dap_adapters/Cargo.toml index 6f390f4e66f46..a1bb17fb806aa 100644 --- a/crates/dap_adapters/Cargo.toml +++ b/crates/dap_adapters/Cargo.toml @@ -5,6 +5,13 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[features] +test-support = [ + "util/test-support", + "task/test-support", + "dap/test-support", +] + [lints] workspace = true @@ -23,3 +30,7 @@ serde_json.workspace = true sysinfo.workspace = true task.workspace = true util.workspace = true + +[dev-dependencies] +dap = { workspace = true, features = ["test-support"] } +task = { workspace = true, features = ["test-support"] } diff --git a/crates/dap_adapters/src/custom.rs b/crates/dap_adapters/src/custom.rs index 30a21dd7d404d..ed54a828f882f 100644 --- a/crates/dap_adapters/src/custom.rs +++ b/crates/dap_adapters/src/custom.rs @@ -1,4 +1,4 @@ -use std::{ffi::OsString, path::PathBuf}; +use std::{ffi::OsString, path::PathBuf, sync::Arc}; use dap::transport::{StdioTransport, TcpTransport, Transport}; use serde_json::Value; @@ -8,7 +8,7 @@ use crate::*; pub(crate) struct CustomDebugAdapter { custom_args: CustomArgs, - transport: Box, + transport: Arc, } impl CustomDebugAdapter { @@ -17,12 +17,12 @@ impl CustomDebugAdapter { pub(crate) async fn new(custom_args: CustomArgs) -> Result { Ok(CustomDebugAdapter { transport: match &custom_args.connection { - DebugConnectionType::TCP(host) => Box::new(TcpTransport::new( + DebugConnectionType::TCP(host) => Arc::new(TcpTransport::new( host.host(), TcpTransport::port(&host).await?, host.timeout, )), - DebugConnectionType::STDIO => Box::new(StdioTransport::new()), + DebugConnectionType::STDIO => Arc::new(StdioTransport::new()), }, custom_args, }) @@ -35,8 +35,8 @@ impl DebugAdapter for CustomDebugAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } - fn transport(&self) -> Box { - self.transport.clone_box() + fn transport(&self) -> Arc { + self.transport.clone() } async fn get_binary( diff --git a/crates/dap_adapters/src/dap_adapters.rs b/crates/dap_adapters/src/dap_adapters.rs index 4fddcd31f2520..21d8a8cc08409 100644 --- a/crates/dap_adapters/src/dap_adapters.rs +++ b/crates/dap_adapters/src/dap_adapters.rs @@ -7,6 +7,8 @@ mod lldb; mod php; mod python; +use std::sync::Arc; + use anyhow::{anyhow, bail, Result}; use async_trait::async_trait; use custom::CustomDebugAdapter; @@ -24,19 +26,24 @@ use python::PythonDebugAdapter; use serde_json::{json, Value}; use task::{CustomArgs, DebugAdapterConfig, DebugAdapterKind, DebugConnectionType, TCPHost}; -pub async fn build_adapter(kind: &DebugAdapterKind) -> Result> { - match &kind { +pub async fn build_adapter(kind: &DebugAdapterKind) -> Result> { + match kind { DebugAdapterKind::Custom(start_args) => { - Ok(Box::new(CustomDebugAdapter::new(start_args.clone()).await?)) + Ok(Arc::new(CustomDebugAdapter::new(start_args.clone()).await?)) } - DebugAdapterKind::Python(host) => Ok(Box::new(PythonDebugAdapter::new(host).await?)), - DebugAdapterKind::Php(host) => Ok(Box::new(PhpDebugAdapter::new(host.clone()).await?)), + DebugAdapterKind::Python(host) => Ok(Arc::new(PythonDebugAdapter::new(host).await?)), + DebugAdapterKind::Php(host) => Ok(Arc::new(PhpDebugAdapter::new(host.clone()).await?)), DebugAdapterKind::Javascript(host) => { - Ok(Box::new(JsDebugAdapter::new(host.clone()).await?)) + Ok(Arc::new(JsDebugAdapter::new(host.clone()).await?)) } - DebugAdapterKind::Lldb => Ok(Box::new(LldbDebugAdapter::new())), - DebugAdapterKind::Go(host) => Ok(Box::new(GoDebugAdapter::new(host).await?)), + DebugAdapterKind::Lldb => Ok(Arc::new(LldbDebugAdapter::new())), + DebugAdapterKind::Go(host) => Ok(Arc::new(GoDebugAdapter::new(host).await?)), #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - DebugAdapterKind::Gdb => Ok(Box::new(GdbDebugAdapter::new())), + DebugAdapterKind::Gdb => Ok(Arc::new(GdbDebugAdapter::new())), + #[cfg(any(test, feature = "test-support"))] + DebugAdapterKind::Fake => Ok(Arc::new(dap::adapters::FakeAdapter::new())), + #[cfg(not(any(test, feature = "test-support")))] + #[allow(unreachable_patterns)] + _ => unreachable!("Fake variant only exists with test-support feature"), } } diff --git a/crates/dap_adapters/src/gdb.rs b/crates/dap_adapters/src/gdb.rs index 4f7ffcf7f621a..de94d1a1089e7 100644 --- a/crates/dap_adapters/src/gdb.rs +++ b/crates/dap_adapters/src/gdb.rs @@ -23,8 +23,8 @@ impl DebugAdapter for GdbDebugAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } - fn transport(&self) -> Box { - Box::new(StdioTransport::new()) + fn transport(&self) -> Arc { + Arc::new(StdioTransport::new()) } async fn get_binary( diff --git a/crates/dap_adapters/src/go.rs b/crates/dap_adapters/src/go.rs index 587526eec9f05..b1e8b34c526dc 100644 --- a/crates/dap_adapters/src/go.rs +++ b/crates/dap_adapters/src/go.rs @@ -1,5 +1,5 @@ use dap::transport::{TcpTransport, Transport}; -use std::{ffi::OsStr, net::Ipv4Addr, path::PathBuf}; +use std::{ffi::OsStr, net::Ipv4Addr, path::PathBuf, sync::Arc}; use crate::*; @@ -28,8 +28,8 @@ impl DebugAdapter for GoDebugAdapter { DebugAdapterName(Self::_ADAPTER_NAME.into()) } - fn transport(&self) -> Box { - Box::new(TcpTransport::new(self.host, self.port, self.timeout)) + fn transport(&self) -> Arc { + Arc::new(TcpTransport::new(self.host, self.port, self.timeout)) } async fn get_binary( diff --git a/crates/dap_adapters/src/javascript.rs b/crates/dap_adapters/src/javascript.rs index 805913fc51024..da425ba70553d 100644 --- a/crates/dap_adapters/src/javascript.rs +++ b/crates/dap_adapters/src/javascript.rs @@ -1,7 +1,7 @@ use adapters::latest_github_release; use dap::transport::{TcpTransport, Transport}; use regex::Regex; -use std::{collections::HashMap, net::Ipv4Addr, path::PathBuf}; +use std::{collections::HashMap, net::Ipv4Addr, path::PathBuf, sync::Arc}; use sysinfo::{Pid, Process}; use task::DebugRequestType; @@ -32,8 +32,8 @@ impl DebugAdapter for JsDebugAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } - fn transport(&self) -> Box { - Box::new(TcpTransport::new(self.host, self.port, self.timeout)) + fn transport(&self) -> Arc { + Arc::new(TcpTransport::new(self.host, self.port, self.timeout)) } async fn fetch_latest_adapter_version( diff --git a/crates/dap_adapters/src/lldb.rs b/crates/dap_adapters/src/lldb.rs index 8a6bb9ad8b1d1..b007f0f89b0de 100644 --- a/crates/dap_adapters/src/lldb.rs +++ b/crates/dap_adapters/src/lldb.rs @@ -1,4 +1,4 @@ -use std::{ffi::OsStr, path::PathBuf}; +use std::{ffi::OsStr, path::PathBuf, sync::Arc}; use anyhow::Result; use async_trait::async_trait; @@ -23,8 +23,8 @@ impl DebugAdapter for LldbDebugAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } - fn transport(&self) -> Box { - Box::new(StdioTransport::new()) + fn transport(&self) -> Arc { + Arc::new(StdioTransport::new()) } async fn get_binary( diff --git a/crates/dap_adapters/src/php.rs b/crates/dap_adapters/src/php.rs index db03190015075..1cba2b4ce2fa3 100644 --- a/crates/dap_adapters/src/php.rs +++ b/crates/dap_adapters/src/php.rs @@ -1,6 +1,6 @@ use adapters::latest_github_release; use dap::transport::{TcpTransport, Transport}; -use std::{net::Ipv4Addr, path::PathBuf}; +use std::{net::Ipv4Addr, path::PathBuf, sync::Arc}; use crate::*; @@ -29,8 +29,8 @@ impl DebugAdapter for PhpDebugAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } - fn transport(&self) -> Box { - Box::new(TcpTransport::new(self.host, self.port, self.timeout)) + fn transport(&self) -> Arc { + Arc::new(TcpTransport::new(self.host, self.port, self.timeout)) } async fn fetch_latest_adapter_version( diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index 38e1be293aca6..eb5118eb79eb0 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -1,5 +1,5 @@ use dap::transport::{TcpTransport, Transport}; -use std::{ffi::OsStr, net::Ipv4Addr, path::PathBuf}; +use std::{ffi::OsStr, net::Ipv4Addr, path::PathBuf, sync::Arc}; use crate::*; @@ -28,8 +28,8 @@ impl DebugAdapter for PythonDebugAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } - fn transport(&self) -> Box { - Box::new(TcpTransport::new(self.host, self.port, self.timeout)) + fn transport(&self) -> Arc { + Arc::new(TcpTransport::new(self.host, self.port, self.timeout)) } async fn fetch_latest_adapter_version( diff --git a/crates/project/src/dap_store.rs b/crates/project/src/dap_store.rs index 007ae83b99b62..e89ba63c000e6 100644 --- a/crates/project/src/dap_store.rs +++ b/crates/project/src/dap_store.rs @@ -382,7 +382,7 @@ impl DapStore { pub fn start_client( &mut self, - adapter: Arc>, + adapter: Arc, binary: DebugAdapterBinary, config: DebugAdapterConfig, cx: &mut ModelContext, @@ -403,12 +403,10 @@ impl DapStore { let start_client_task = cx.spawn(|this, mut cx| async move { let dap_store = this.clone(); - let mut client = - DebugAdapterClient::new(client_id, config, adapter, binary.clone(), &cx); + let mut client = DebugAdapterClient::new(client_id, config, adapter, binary, &cx); let result = client .start( - &binary, move |message, cx| { dap_store .update(cx, |_, cx| { @@ -470,7 +468,7 @@ impl DapStore { let adapter_delegate = Arc::new(adapter_delegate); cx.spawn(|this, mut cx| async move { - let adapter = Arc::new(build_adapter(&config.kind).await?); + let adapter = build_adapter(&config.kind).await?; let binary = cx.update(|cx| { let name = DebugAdapterName::from(adapter.name().as_ref()); @@ -1181,7 +1179,7 @@ impl DapStore { self.ignore_breakpoints.remove(client_id); let capabilities = self.capabilities.remove(client_id); - cx.background_executor().spawn(async move { + cx.spawn(|_, _| async move { let client = match client { DebugAdapterClientState::Starting(task) => task.await, DebugAdapterClientState::Running(client) => Some(client), diff --git a/crates/task/Cargo.toml b/crates/task/Cargo.toml index 7a22ea6157e4e..008516524e9ea 100644 --- a/crates/task/Cargo.toml +++ b/crates/task/Cargo.toml @@ -5,6 +5,12 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[features] +test-support = [ + "gpui/test-support", + "util/test-support" +] + [lints] workspace = true diff --git a/crates/task/src/debug_format.rs b/crates/task/src/debug_format.rs index f5ca35b68ce5e..29cad2b1914ab 100644 --- a/crates/task/src/debug_format.rs +++ b/crates/task/src/debug_format.rs @@ -76,6 +76,9 @@ pub enum DebugAdapterKind { /// Use GDB's built-in DAP support #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] Gdb, + /// Used for integration tests + #[cfg(any(test, feature = "test-support"))] + Fake, } impl DebugAdapterKind { @@ -90,6 +93,8 @@ impl DebugAdapterKind { #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] Self::Gdb => "GDB", Self::Go(_) => "Go", + #[cfg(any(test, feature = "test-support"))] + Self::Fake => "Fake", } } }