Skip to content

Commit

Permalink
Add FakeAdapter and FakeTransport for testing purposes (#73)
Browse files Browse the repository at this point in the history
* Move process to transport struct itself

* Add test to make sure we can send request and receive a response

* Align other handle methods

* Fix issues inside cargo.toml require test features inside the correct *-dependencies

* Remove comments that are not needed

* Add as_fake instead of downcasting

* Clean up

* Override get_binary method so we don't fail on install

* Fix false positive clippy error

This error was a match variant not being covered when the variant wasn't possible dued
to a feature flag. I'm pretty sure this is a bug in clippy/rust-analyzer and will
open an issue on their repos

* Remove not needed clone

* Panic when we receive an event/reverse request inside the test

* reuse the type of the closure

* Add a way to fake receiving events

* Oops remove fake event from different test

* Clipppyyyy

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
  • Loading branch information
RemcoSmitsDev and Anthony-Eid authored Dec 9, 2024
1 parent 2b8ae36 commit 1a0ecf0
Show file tree
Hide file tree
Showing 18 changed files with 591 additions and 115 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions crates/dap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"] }
72 changes: 71 additions & 1 deletion crates/dap/src/adapters.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -260,7 +262,7 @@ pub trait DebugAdapter: 'static + Send + Sync {
.await
}

fn transport(&self) -> Box<dyn Transport>;
fn transport(&self) -> Arc<dyn Transport>;

async fn fetch_latest_adapter_version(
&self,
Expand Down Expand Up @@ -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<dyn Transport> {
Arc::new(FakeTransport::new())
}

async fn get_binary(
&self,
_delegate: &dyn DapDelegate,
_config: &DebugAdapterConfig,
_user_installed_path: Option<PathBuf>,
) -> Result<DebugAdapterBinary> {
Ok(DebugAdapterBinary {
command: "command".into(),
arguments: None,
envs: None,
cwd: None,
})
}

async fn fetch_latest_adapter_version(
&self,
_delegate: &dyn DapDelegate,
) -> Result<AdapterVersion> {
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<PathBuf>,
) -> Result<DebugAdapterBinary> {
unimplemented!("get installed binary");
}

fn request_args(&self, _config: &DebugAdapterConfig) -> Value {
use serde_json::json;

json!({})
}
}
187 changes: 177 additions & 10 deletions crates/dap/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub struct DebugAdapterClient {
sequence_count: AtomicU64,
binary: DebugAdapterBinary,
executor: BackgroundExecutor,
adapter: Arc<Box<dyn DebugAdapter>>,
adapter: Arc<dyn DebugAdapter>,
transport_delegate: TransportDelegate,
config: Arc<Mutex<DebugAdapterConfig>>,
}
Expand All @@ -49,7 +49,7 @@ impl DebugAdapterClient {
pub fn new(
id: DebugAdapterClientId,
config: DebugAdapterConfig,
adapter: Arc<Box<dyn DebugAdapter>>,
adapter: Arc<dyn DebugAdapter>,
binary: DebugAdapterBinary,
cx: &AsyncAppContext,
) -> Self {
Expand All @@ -66,16 +66,11 @@ impl DebugAdapterClient {
}
}

pub async fn start<F>(
&mut self,
binary: &DebugAdapterBinary,
message_handler: F,
cx: &mut AsyncAppContext,
) -> Result<()>
pub async fn start<F>(&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
Expand Down Expand Up @@ -195,7 +190,7 @@ impl DebugAdapterClient {
self.config.lock().unwrap().clone()
}

pub fn adapter(&self) -> &Arc<Box<dyn DebugAdapter>> {
pub fn adapter(&self) -> &Arc<dyn DebugAdapter> {
&self.adapter
}

Expand Down Expand Up @@ -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<R: dap_types::requests::Request, F>(&self, handler: F)
where
F: 'static + Send + FnMut(u64, R::Arguments) -> Result<R::Response>,
{
let transport = self.transport_delegate.transport();

transport.as_fake().on_request::<R, F>(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::<Initialize, _>(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::<Initialize>(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();
}
}
3 changes: 3 additions & 0 deletions crates/dap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Loading

0 comments on commit 1a0ecf0

Please sign in to comment.