diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml index 2e084756..14e12490 100644 --- a/.github/workflows/test-linux.yml +++ b/.github/workflows/test-linux.yml @@ -54,5 +54,4 @@ jobs: env: AIR_LOG_LEVEL: trace # `--nocapture` to see our own `tracing` logs - # `--test-threads 1` to ensure `tracing` logs aren't interleaved - run: cargo test -- --nocapture --test-threads 1 + run: cargo test -- --nocapture diff --git a/.github/workflows/test-mac.yml b/.github/workflows/test-mac.yml index dbe44a94..b6fe1ca2 100644 --- a/.github/workflows/test-mac.yml +++ b/.github/workflows/test-mac.yml @@ -32,5 +32,4 @@ jobs: env: AIR_LOG_LEVEL: trace # `--nocapture` to see our own `tracing` logs - # `--test-threads 1` to ensure `tracing` logs aren't interleaved - run: cargo test -- --nocapture --test-threads 1 + run: cargo test -- --nocapture diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index fa95a5b2..ede3e23f 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -32,5 +32,4 @@ jobs: env: AIR_LOG_LEVEL: trace # `--nocapture` to see our own `tracing` logs - # `--test-threads 1` to ensure `tracing` logs aren't interleaved - run: cargo test -- --nocapture --test-threads 1 + run: cargo test -- --nocapture diff --git a/crates/lsp/src/documents.rs b/crates/lsp/src/documents.rs index d2406dae..543c147d 100644 --- a/crates/lsp/src/documents.rs +++ b/crates/lsp/src/documents.rs @@ -91,7 +91,7 @@ impl Document { #[cfg(test)] pub fn doodle_and_range(contents: &str) -> (Self, biome_text_size::TextRange) { - let (contents, range) = crate::test_utils::extract_marked_range(contents); + let (contents, range) = crate::test::extract_marked_range(contents); let doc = Self::new(contents, None, PositionEncoding::Utf8); (doc, range) } diff --git a/crates/lsp/src/handlers.rs b/crates/lsp/src/handlers.rs index b7d18cb9..e74c204f 100644 --- a/crates/lsp/src/handlers.rs +++ b/crates/lsp/src/handlers.rs @@ -73,10 +73,13 @@ pub(crate) async fn handle_initialized( registrations.push(watch_air_toml_registration); } - client - .register_capability(registrations) - .instrument(span.exit()) - .await?; + if !registrations.is_empty() { + client + .register_capability(registrations) + .instrument(span.exit()) + .await?; + } + Ok(()) } diff --git a/crates/lsp/src/handlers_format.rs b/crates/lsp/src/handlers_format.rs index 0655075e..307661ef 100644 --- a/crates/lsp/src/handlers_format.rs +++ b/crates/lsp/src/handlers_format.rs @@ -244,133 +244,132 @@ fn find_expression_lists(node: &RSyntaxNode, offset: TextSize, end: bool) -> Vec #[cfg(test)] mod tests { - use crate::{ - documents::Document, tower_lsp::init_test_client, tower_lsp_test_client::TestClientExt, - }; + use crate::{documents::Document, test::with_client, test::TestClientExt}; - #[tests_macros::lsp_test] - async fn test_format() { - let mut client = init_test_client().await; + #[test] + fn test_format() { + with_client(|client| async { + let mut client = client.lock().await; - #[rustfmt::skip] - let doc = Document::doodle( + #[rustfmt::skip] + let doc = Document::doodle( " 1 2+2 3 + 3 + 3", - ); + ); - let formatted = client.format_document(&doc).await; - insta::assert_snapshot!(formatted); - - client + let formatted = client.format_document(&doc).await; + insta::assert_snapshot!(formatted); + }) } // https://github.com/posit-dev/air/issues/61 - #[tests_macros::lsp_test] - async fn test_format_minimal_diff() { - let mut client = init_test_client().await; + #[test] + fn test_format_minimal_diff() { + with_client(|client| async { + let mut client = client.lock().await; - #[rustfmt::skip] - let doc = Document::doodle( + #[rustfmt::skip] + let doc = Document::doodle( "1 2+2 3 ", - ); - - let edits = client.format_document_edits(&doc).await.unwrap(); - assert!(edits.len() == 1); + ); - let edit = &edits[0]; - assert_eq!(edit.new_text, " + "); + let edits = client.format_document_edits(&doc).await.unwrap(); + assert!(edits.len() == 1); - client + let edit = &edits[0]; + assert_eq!(edit.new_text, " + "); + }) } - #[tests_macros::lsp_test] - async fn test_format_range_none() { - let mut client = init_test_client().await; + #[test] + fn test_format_range_none() { + with_client(|client| async { + let mut client = client.lock().await; - #[rustfmt::skip] - let (doc, range) = Document::doodle_and_range( + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( "<<>>", - ); + ); - let output = client.format_document_range(&doc, range).await; - insta::assert_snapshot!(output); + let output = client.format_document_range(&doc, range).await; + insta::assert_snapshot!(output); - #[rustfmt::skip] - let (doc, range) = Document::doodle_and_range( + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( "<< >>", - ); + ); - let output = client.format_document_range(&doc, range).await; - insta::assert_snapshot!(output); + let output = client.format_document_range(&doc, range).await; + insta::assert_snapshot!(output); - #[rustfmt::skip] - let (doc, range) = Document::doodle_and_range( + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( "<<1 >>", - ); + ); - let output = client.format_document_range(&doc, range).await; - insta::assert_snapshot!(output); - - client + let output = client.format_document_range(&doc, range).await; + insta::assert_snapshot!(output); + }); } - #[tests_macros::lsp_test] - async fn test_format_range_logical_lines() { - let mut client = init_test_client().await; + #[test] + fn test_format_range_logical_lines() { + with_client(|client| async { + let mut client = client.lock().await; - // 2+2 is the logical line to format - #[rustfmt::skip] - let (doc, range) = Document::doodle_and_range( + // 2+2 is the logical line to format + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( "1+1 <<2+2>> ", - ); - let output = client.format_document_range(&doc, range).await; - insta::assert_snapshot!(output); + ); + let output = client.format_document_range(&doc, range).await; + insta::assert_snapshot!(output); - #[rustfmt::skip] - let (doc, range) = Document::doodle_and_range( + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( "1+1 # <<2+2>> ", - ); + ); - let output = client.format_document_range(&doc, range).await; - insta::assert_snapshot!(output); + let output = client.format_document_range(&doc, range).await; + insta::assert_snapshot!(output); - // The element in the braced expression is a logical line - // FIXME: Should this be the whole `{2+2}` instead? - #[rustfmt::skip] - let (doc, range) = Document::doodle_and_range( + // The element in the braced expression is a logical line + // FIXME: Should this be the whole `{2+2}` instead? + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( "1+1 {<<2+2>>} ", - ); + ); - let output = client.format_document_range(&doc, range).await; - insta::assert_snapshot!(output); + let output = client.format_document_range(&doc, range).await; + insta::assert_snapshot!(output); - #[rustfmt::skip] - let (doc, range) = Document::doodle_and_range( + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( "1+1 <<{2+2}>> ", - ); - let output = client.format_document_range(&doc, range).await; - insta::assert_snapshot!(output); + ); + let output = client.format_document_range(&doc, range).await; + insta::assert_snapshot!(output); - // The deepest element in the braced expression is our target - #[rustfmt::skip] - let (doc, range) = Document::doodle_and_range( + // The deepest element in the braced expression is our target + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( "1+1 { 2+2 @@ -379,76 +378,77 @@ mod tests { } } ", - ); + ); - let output = client.format_document_range(&doc, range).await; - insta::assert_snapshot!(output); - client + let output = client.format_document_range(&doc, range).await; + insta::assert_snapshot!(output); + }); } - #[tests_macros::lsp_test] - async fn test_format_range_mismatched_indent() { - let mut client = init_test_client().await; + #[test] + fn test_format_range_mismatched_indent() { + with_client(|client| async { + let mut client = client.lock().await; - #[rustfmt::skip] - let (doc, range) = Document::doodle_and_range( + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( "1 <<2+2>> ", - ); + ); - // We don't change indentation when `2+2` is formatted - let output = client.format_document_range(&doc, range).await; - insta::assert_snapshot!(output); + // We don't change indentation when `2+2` is formatted + let output = client.format_document_range(&doc, range).await; + insta::assert_snapshot!(output); - // Debatable: Should we make an effort to remove unneeded indentation - // when it's part of the range? - #[rustfmt::skip] - let (doc, range) = Document::doodle_and_range( + // Debatable: Should we make an effort to remove unneeded indentation + // when it's part of the range? + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( "1 << 2+2>> ", - ); - let output_wide = client.format_document_range(&doc, range).await; - assert_eq!(output, output_wide); - - client + ); + let output_wide = client.format_document_range(&doc, range).await; + assert_eq!(output, output_wide); + }); } - #[tests_macros::lsp_test] - async fn test_format_range_multiple_lines() { - let mut client = init_test_client().await; + #[test] + fn test_format_range_multiple_lines() { + with_client(|client| async { + let mut client = client.lock().await; - #[rustfmt::skip] - let (doc, range) = Document::doodle_and_range( + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( "1+1 <<# 2+2>> ", - ); + ); - let output1 = client.format_document_range(&doc, range).await; - insta::assert_snapshot!(output1); + let output1 = client.format_document_range(&doc, range).await; + insta::assert_snapshot!(output1); - #[rustfmt::skip] - let (doc, range) = Document::doodle_and_range( + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( "<<1+1 # 2+2>> ", - ); - let output2 = client.format_document_range(&doc, range).await; - insta::assert_snapshot!(output2); - - client + ); + let output2 = client.format_document_range(&doc, range).await; + insta::assert_snapshot!(output2); + }); } - #[tests_macros::lsp_test] - async fn test_format_range_unmatched_lists() { - let mut client = init_test_client().await; + #[test] + fn test_format_range_unmatched_lists() { + with_client(|client| async { + let mut client = client.lock().await; - #[rustfmt::skip] - let (doc, range) = Document::doodle_and_range( + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( "0+0 <<1+1 { @@ -456,13 +456,13 @@ mod tests { } 3+3 ", - ); + ); - let output1 = client.format_document_range(&doc, range).await; - insta::assert_snapshot!(output1); + let output1 = client.format_document_range(&doc, range).await; + insta::assert_snapshot!(output1); - #[rustfmt::skip] - let (doc, range) = Document::doodle_and_range( + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( "0+0 <<1+1 { @@ -470,12 +470,12 @@ mod tests { } 3+3 ", - ); - let output2 = client.format_document_range(&doc, range).await; - insta::assert_snapshot!(output2); + ); + let output2 = client.format_document_range(&doc, range).await; + insta::assert_snapshot!(output2); - #[rustfmt::skip] - let (doc, range) = Document::doodle_and_range( + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( "0+0 <<1+1 { @@ -483,12 +483,12 @@ mod tests { } >>3+3 ", - ); - let output3 = client.format_document_range(&doc, range).await; - insta::assert_snapshot!(output3); + ); + let output3 = client.format_document_range(&doc, range).await; + insta::assert_snapshot!(output3); - #[rustfmt::skip] - let (doc, range) = Document::doodle_and_range( + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( "0+0 1+1 { @@ -496,30 +496,29 @@ mod tests { } >>3+3 ", - ); - let output4 = client.format_document_range(&doc, range).await; - insta::assert_snapshot!(output4); + ); + let output4 = client.format_document_range(&doc, range).await; + insta::assert_snapshot!(output4); - #[rustfmt::skip] - let (doc, range) = Document::doodle_and_range( + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( "<<1+1>> 2+2 ", - ); + ); - let output5 = client.format_document_range(&doc, range).await; - insta::assert_snapshot!(output5); + let output5 = client.format_document_range(&doc, range).await; + insta::assert_snapshot!(output5); - #[rustfmt::skip] - let (doc, range) = Document::doodle_and_range( + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( "1+1 <<2+2>> ", - ); - - let output6 = client.format_document_range(&doc, range).await; - insta::assert_snapshot!(output6); + ); - client + let output6 = client.format_document_range(&doc, range).await; + insta::assert_snapshot!(output6); + }); } } diff --git a/crates/lsp/src/lib.rs b/crates/lsp/src/lib.rs index 452ad8d8..30f7de84 100644 --- a/crates/lsp/src/lib.rs +++ b/crates/lsp/src/lib.rs @@ -23,6 +23,4 @@ pub mod tower_lsp; pub mod workspaces; #[cfg(test)] -pub mod test_utils; -#[cfg(test)] -pub mod tower_lsp_test_client; +pub mod test; diff --git a/crates/lsp/src/logging.rs b/crates/lsp/src/logging.rs index 78db0373..5a78c507 100644 --- a/crates/lsp/src/logging.rs +++ b/crates/lsp/src/logging.rs @@ -204,26 +204,18 @@ pub(crate) fn init_logging( let subscriber = tracing_subscriber::Registry::default().with(layer); - if is_test_client(client_info) { - // During parallel testing, `set_global_default()` gets called multiple times - // per process. That causes it to error, but we ignore this. - tracing::subscriber::set_global_default(subscriber).ok(); - } else { - tracing::subscriber::set_global_default(subscriber) - .expect("Should be able to set the global subscriber exactly once."); - } + tracing::subscriber::set_global_default(subscriber) + .expect("Should be able to set the global subscriber exactly once."); tracing::info!("Logging initialized with level: {log_level}"); } /// We use a special `TestWriter` during tests to be compatible with `cargo test`'s -/// typical output capturing behavior. -/// -/// Important notes: -/// - `cargo test` swallows all logs unless you use `-- --nocapture`. -/// - Tests run in parallel, so logs can be interleaved unless you run `--test-threads 1`. +/// typical output capturing behavior (even during integration tests!). /// -/// We use `cargo test -- --nocapture --test-threads 1` on CI because of all of this. +/// Importantly, note that `cargo test` swallows all logs unless you use `-- --nocapture`, +/// which is the correct expected behavior. We use `cargo test -- --nocapture` on CI +/// because of this. fn is_test_client(client_info: Option<&ClientInfo>) -> bool { client_info.map_or(false, |client_info| client_info.name == "AirTestClient") } diff --git a/crates/lsp/src/test.rs b/crates/lsp/src/test.rs new file mode 100644 index 00000000..f2a9279e --- /dev/null +++ b/crates/lsp/src/test.rs @@ -0,0 +1,7 @@ +mod client; +mod client_ext; +mod utils; + +pub(crate) use client::with_client; +pub(crate) use client_ext::TestClientExt; +pub(crate) use utils::extract_marked_range; diff --git a/crates/lsp/src/test/client.rs b/crates/lsp/src/test/client.rs new file mode 100644 index 00000000..725446b6 --- /dev/null +++ b/crates/lsp/src/test/client.rs @@ -0,0 +1,72 @@ +use std::future::Future; +use std::sync::LazyLock; + +use tokio::sync::Mutex; + +use lsp_test::lsp_client::TestClient; +use tokio::sync::OnceCell; + +use crate::start_lsp; + +/// Global test client used by all unit tests +/// +/// The language server has per-process global state, such as the global `tracing` +/// subscriber that sends log messages to the client. Because of this, we cannot just +/// repeatedly call [new_test_client()] to start up a new client/server pair per unit +/// test. Instead, unit tests use [with_client()] to access the global test client, which +/// they can then manipulate. Synchronization is managed through a [Mutex], ensuring that +/// multiple unit tests that need to mutate the client can't run simultaneously (while +/// still allowing other unit tests to run in parallel). Unit tests should be careful not +/// to put the client/server pair into a state that prevents other unit tests from running +/// successfully. +/// +/// If you need to modify a client/server pair in such a way that no other unit tests will +/// be able to use it, create an integration test instead, which runs in its own process. +/// +/// TODO: When we switch off async tower-lsp, make this a simpler `LazyLock` around a +/// `std::sync::Mutex`, and then actually `lock()` in `with_client()`, only providing +/// a `&mut TestClient` to the unit test. +static TEST_CLIENT: OnceCell> = OnceCell::const_new(); + +/// `#[tokio::test]` creates a new runtime for each individual test. That doesn't work +/// for us because we want access to the `TEST_CLIENT` to be synchronized within a single +/// tokio runtime. The only way to do this seems to be to create a shared tokio runtime +/// as well. +static TEST_RUNTIME: LazyLock = LazyLock::new(|| { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("Should create a tokio runtime") +}); + +pub(crate) fn with_client(f: F) +where + F: FnOnce(&'static Mutex) -> Fut, + Fut: Future + Send + 'static, +{ + TEST_RUNTIME.block_on(async { + let client = get_client().await; + f(client).await + }); +} + +async fn get_client() -> &'static Mutex { + TEST_CLIENT + .get_or_init(|| async { Mutex::new(new_test_client().await) }) + .await +} + +async fn new_test_client() -> TestClient { + let mut client = + TestClient::new(|server_rx, client_tx| async { start_lsp(server_rx, client_tx).await }); + + // Initialize and wait for the server response + let id = client.initialize().await; + let response = client.recv_response().await; + assert_eq!(&id, response.id()); + + // Notify the server we have received its initialize response + client.initialized().await; + + client +} diff --git a/crates/lsp/src/tower_lsp_test_client.rs b/crates/lsp/src/test/client_ext.rs similarity index 100% rename from crates/lsp/src/tower_lsp_test_client.rs rename to crates/lsp/src/test/client_ext.rs diff --git a/crates/lsp/src/test_utils.rs b/crates/lsp/src/test/utils.rs similarity index 100% rename from crates/lsp/src/test_utils.rs rename to crates/lsp/src/test/utils.rs diff --git a/crates/lsp/src/tower_lsp.rs b/crates/lsp/src/tower_lsp.rs index 60de59d0..752430ed 100644 --- a/crates/lsp/src/tower_lsp.rs +++ b/crates/lsp/src/tower_lsp.rs @@ -300,62 +300,3 @@ fn new_jsonrpc_error(message: String) -> jsonrpc::Error { data: None, } } - -#[cfg(test)] -pub(crate) async fn start_test_client() -> lsp_test::lsp_client::TestClient { - lsp_test::lsp_client::TestClient::new(|server_rx, client_tx| async { - start_lsp(server_rx, client_tx).await - }) -} - -#[cfg(test)] -pub(crate) async fn init_test_client() -> lsp_test::lsp_client::TestClient { - let mut client = start_test_client().await; - - client.initialize().await; - client.recv_response().await; - - client -} - -#[cfg(test)] -mod tests { - use super::*; - use assert_matches::assert_matches; - use tower_lsp::lsp_types; - - #[tests_macros::lsp_test] - async fn test_init() { - let mut client = start_test_client().await; - - client.initialize().await; - - let value = client.recv_response().await; - let value: lsp_types::InitializeResult = - serde_json::from_value(value.result().unwrap().clone()).unwrap(); - - assert_matches!( - value, - lsp_types::InitializeResult { - capabilities, - server_info - } => { - assert_matches!(capabilities, ServerCapabilities { - position_encoding, - text_document_sync, - .. - } => { - assert_eq!(position_encoding, Some(PositionEncodingKind::UTF16)); - assert_eq!(text_document_sync, Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::INCREMENTAL))); - }); - - assert_matches!(server_info, Some(ServerInfo { name, version }) => { - assert!(name.contains("Air Language Server")); - assert!(version.is_some()); - }); - } - ); - - client - } -} diff --git a/crates/lsp/tests/initialization.rs b/crates/lsp/tests/initialization.rs new file mode 100644 index 00000000..db60a9e6 --- /dev/null +++ b/crates/lsp/tests/initialization.rs @@ -0,0 +1,52 @@ +use assert_matches::assert_matches; +use lsp::start_lsp; +use tower_lsp::lsp_types::PositionEncodingKind; +use tower_lsp::lsp_types::ServerCapabilities; +use tower_lsp::lsp_types::ServerInfo; +use tower_lsp::lsp_types::TextDocumentSyncCapability; +use tower_lsp::lsp_types::TextDocumentSyncKind; + +// Normal usage of `with_client()` handles client initialization, so to test it we have +// to run this particular test in its own process and manually start up and initialize +// the client. This also gives us a chance to test the shutdown/exit procedure. + +#[tokio::test] +async fn test_initialization_and_shutdown() { + let mut client = lsp_test::lsp_client::TestClient::new(|server_rx, client_tx| async { + start_lsp(server_rx, client_tx).await + }); + + let id = client.initialize().await; + + let value = client.recv_response().await; + assert_eq!(&id, value.id()); + let value: tower_lsp::lsp_types::InitializeResult = + serde_json::from_value(value.result().unwrap().clone()).unwrap(); + + client.initialized().await; + + assert_matches!( + value, + tower_lsp::lsp_types::InitializeResult { + capabilities, + server_info + } => { + assert_matches!(capabilities, ServerCapabilities { + position_encoding, + text_document_sync, + .. + } => { + assert_eq!(position_encoding, Some(PositionEncodingKind::UTF16)); + assert_eq!(text_document_sync, Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::INCREMENTAL))); + }); + + assert_matches!(server_info, Some(ServerInfo { name, version }) => { + assert!(name.contains("Air Language Server")); + assert!(version.is_some()); + }); + } + ); + + client.shutdown().await; + client.exit().await; +} diff --git a/crates/lsp_test/src/lsp_client.rs b/crates/lsp_test/src/lsp_client.rs index dc6cd1da..3cc295a0 100644 --- a/crates/lsp_test/src/lsp_client.rs +++ b/crates/lsp_test/src/lsp_client.rs @@ -50,11 +50,10 @@ impl TestClient { } } - // `jsonrpc::Id` requires i64 IDs - fn id(&mut self) -> i64 { + fn id(&mut self) -> jsonrpc::Id { let id = self.id_counter; self.id_counter = id + 1; - id + jsonrpc::Id::Number(id) } pub async fn recv_response(&mut self) -> jsonrpc::Response { @@ -72,12 +71,12 @@ impl TestClient { self.tx.send(not).await.unwrap(); } - pub async fn request(&mut self, params: R::Params) -> i64 + pub async fn request(&mut self, params: R::Params) -> jsonrpc::Id where R: lsp_types::request::Request, { let id = self.id(); - let req = Request::from_request::(jsonrpc::Id::Number(id), params); + let req = Request::from_request::(id.clone(), params); // Unwrap: For this test client it's fine to panic if we can't send self.tx.send(req).await.unwrap(); @@ -85,7 +84,7 @@ impl TestClient { id } - pub async fn initialize(&mut self) -> i64 { + pub async fn initialize(&mut self) -> jsonrpc::Id { let params: Option = std::mem::take(&mut self.init_params); let params = params.unwrap_or_default(); let params = Self::with_client_info(params); @@ -108,6 +107,12 @@ impl TestClient { self.init_params = Some(init_params); } + pub async fn initialized(&mut self) { + let params = lsp_types::InitializedParams {}; + self.notify::(params) + .await + } + pub async fn close_document(&mut self, uri: url::Url) { let params = lsp_types::DidCloseTextDocumentParams { text_document: lsp_types::TextDocumentIdentifier { uri }, @@ -117,15 +122,18 @@ impl TestClient { pub async fn shutdown(&mut self) { // TODO: Check that no messages are incoming + let id = self.id(); // Don't use `Request::from_request()`. It has a bug with undefined // params (when `R::Params = ()`) which causes tower-lsp to not // recognise the Shutdown request. - let req = Request::build("shutdown").id(self.id()).finish(); + let req = Request::build("shutdown").id(id.clone()).finish(); // Unwrap: For this test client it's fine to panic if we can't send self.tx.send(req).await.unwrap(); - self.recv_response().await; + + let response = self.recv_response().await; + assert_eq!(&id, response.id()); } pub async fn exit(&mut self) { @@ -149,14 +157,14 @@ impl TestClient { .await } - pub async fn formatting(&mut self, params: lsp_types::DocumentFormattingParams) -> i64 { + pub async fn formatting(&mut self, params: lsp_types::DocumentFormattingParams) -> jsonrpc::Id { self.request::(params).await } pub async fn range_formatting( &mut self, params: lsp_types::DocumentRangeFormattingParams, - ) -> i64 { + ) -> jsonrpc::Id { self.request::(params) .await } diff --git a/crates/tests_macros/src/lib.rs b/crates/tests_macros/src/lib.rs index 3ff3db27..8b83af54 100644 --- a/crates/tests_macros/src/lib.rs +++ b/crates/tests_macros/src/lib.rs @@ -252,24 +252,3 @@ pub fn gen_tests(input: TokenStream) -> TokenStream { Err(e) => abort!(e, "{}", e), } } - -// We can't effectively interact with the server task and shut it down from `Drop` -// so we do it on teardown in this procedural macro. Users must return their client. -#[proc_macro_attribute] -pub fn lsp_test(_attr: TokenStream, item: TokenStream) -> TokenStream { - let input = syn::parse_macro_input!(item as syn::ItemFn); - - let name = &input.sig.ident; - let block = &input.block; - - let expanded = quote! { - #[tokio::test] - async fn #name() { - let mut client = #block; - client.shutdown().await; - client.exit().await; - } - }; - - TokenStream::from(expanded) -}