From 4835699f31bb99f55761f0f4aca2c2afccc557b1 Mon Sep 17 00:00:00 2001 From: Riccardo Mazzarini Date: Sat, 21 Dec 2024 15:43:20 +0800 Subject: [PATCH] Refactor `nvim_oxi::tests` into a directory structure --- crates/macros/src/test.rs | 8 +- src/lib.rs | 4 - src/tests/mod.rs | 7 ++ src/tests/terminator.rs | 50 ++++++++ src/{tests.rs => tests/test_macro.rs} | 166 ++++++++++---------------- 5 files changed, 123 insertions(+), 112 deletions(-) create mode 100644 src/tests/mod.rs create mode 100644 src/tests/terminator.rs rename src/{tests.rs => tests/test_macro.rs} (77%) diff --git a/crates/macros/src/test.rs b/crates/macros/src/test.rs index 533a4018..ba853a36 100644 --- a/crates/macros/src/test.rs +++ b/crates/macros/src/test.rs @@ -45,13 +45,13 @@ pub fn test(attrs: TokenStream, item: TokenStream) -> TokenStream { fn __test_fn(#terminator) #ret { #block } - #nvim_oxi::tests::plugin_body_with_terminator(__test_fn) + #nvim_oxi::tests::test_macro::plugin_body_with_terminator(__test_fn) }, None => quote! { fn __test_fn() #ret { #block } - #nvim_oxi::tests::plugin_body(__test_fn) + #nvim_oxi::tests::test_macro::plugin_body(__test_fn) }, }; @@ -60,14 +60,14 @@ pub fn test(attrs: TokenStream, item: TokenStream) -> TokenStream { fn __test_fn() #ret { #block } - #nvim_oxi::tests::plugin_body(__test_fn) + #nvim_oxi::tests::test_macro::plugin_body(__test_fn) }; quote! { #[test] #test_attrs fn #test_name() -> ::core::result::Result<(), ::std::string::String> { - #nvim_oxi::tests::test_body( + #nvim_oxi::tests::test_macro::test_body( env!("CARGO_CRATE_NAME"), env!("CARGO_MANIFEST_DIR"), stringify!(#plugin_name), diff --git a/src/lib.rs b/src/lib.rs index 9d65b488..0d657caf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,10 +90,6 @@ pub use macros::plugin; pub use macros::test; pub use types::*; #[cfg(feature = "test")] -#[doc(hidden)] pub mod tests; -#[cfg(feature = "test-terminator")] -#[cfg_attr(docsrs, doc(cfg(feature = "test-terminator")))] -pub use tests::{TestFailure, TestTerminator}; pub use toplevel::*; pub use types::string; diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 00000000..cdf78fdd --- /dev/null +++ b/src/tests/mod.rs @@ -0,0 +1,7 @@ +#[cfg(feature = "test-terminator")] +mod terminator; +#[doc(hidden)] +pub mod r#test_macro; + +#[cfg(feature = "test-terminator")] +pub use terminator::{TestFailure, TestTerminator}; diff --git a/src/tests/terminator.rs b/src/tests/terminator.rs new file mode 100644 index 00000000..76b3818c --- /dev/null +++ b/src/tests/terminator.rs @@ -0,0 +1,50 @@ +use core::fmt::Display; +use std::panic::PanicHookInfo; +use std::sync::{Arc, OnceLock}; + +/// The error type given to [`TestTerminator::terminate`]. +/// +/// The two variants of this enum represent the two ways a test can fail: +/// either by returning an error or by panicking. +#[cfg_attr(docsrs, doc(cfg(feature = "test-terminator")))] +pub enum TestFailure<'a, E> { + /// This is used to indicate that the test failed due to an error being + /// returned from the test function. + Error(E), + + /// This is used to indicate that the test failed due to a panic. The + /// [`PanicHookInfo`](std::panic::PanicHookInfo) contains information about + /// the panic and can be obtained by calling + /// [`set_hook`](std::panic::set_hook). + Panic(&'a PanicHookInfo<'a>), +} + +/// A handle used to terminate a test annotated by [`test`](crate::test). +/// +/// The `test` macro works by turning the annotated function into its own +/// plugin, which is then loaded by Neovim and evalutated by `require`ing it +/// when the test is run, before immediately quitting. +/// +/// When testing asynchronous code this can be problematic, as the test may +/// need to continue running after the generated plugin has been `require`d. +/// +/// To allow for this, the test function can take a `TestTerminator` as its +/// only argument. This allows the test to be terminated asynchronously by +/// calling [`terminate`](Self::terminate). +/// +/// Note that if the `TestTerminator` is dropped without first calling +/// `terminate`, the test will run forever. +#[cfg_attr(docsrs, doc(cfg(feature = "test-terminator")))] +pub struct TestTerminator { + pub(super) lock: Arc>>, + pub(super) handle: crate::libuv::AsyncHandle, +} + +impl TestTerminator { + /// Terminates the test and consumes the `TestTerminator`. + pub fn terminate(self, res: Result<(), TestFailure<'_, E>>) { + let res = res.map_err(Into::into); + let Ok(()) = self.lock.set(res) else { unreachable!() }; + self.handle.send().unwrap(); + } +} diff --git a/src/tests.rs b/src/tests/test_macro.rs similarity index 77% rename from src/tests.rs rename to src/tests/test_macro.rs index 2a25c7e7..288b103f 100644 --- a/src/tests.rs +++ b/src/tests/test_macro.rs @@ -1,3 +1,5 @@ +//! Functions called by the code generated by `#[nvim_oxi::test].` + use std::any::Any; use std::env; use std::fmt::{Debug, Display}; @@ -12,32 +14,8 @@ use miniserde::json; use crate::IntoResult; -/// Returns the `target` directory in which cargo will place the compiled -/// artifacts for the crate whose manifest is located at `manifest_dir`. -pub fn target_dir(manifest_dir: &Path) -> PathBuf { - let output = Command::new( - env::var("CARGO").ok().unwrap_or_else(|| "cargo".to_owned()), - ) - .arg("metadata") - .arg("--format-version=1") - .arg("--no-deps") - .current_dir(manifest_dir) - .output() - .unwrap(); - - let object: json::Object = - json::from_str(&String::from_utf8(output.stdout).unwrap()).unwrap(); - - let target_dir = match object.get("target_directory").unwrap() { - json::Value::String(s) => s, - _ => panic!("must be string value"), - }; - - target_dir.into() -} - -/// This function is used as the body of the `#[nvim_oxi::plugin]` generated by -/// the `#[nvim_oxi::test]` macro. +/// The body of the `#[nvim_oxi::plugin]` generated by the `#[nvim_oxi::test]` +/// macro. pub fn plugin_body(test_body: F) where F: FnOnce() -> R + UnwindSafe, @@ -63,13 +41,13 @@ where exit(result); } -/// This function is used as the body of the `#[nvim_oxi::plugin]` generated by -/// the `#[nvim_oxi::test]` macro when the `test-terminator` feature is enabled -/// and the test function takes a `TestTerminator` argument. +/// The body of the `#[nvim_oxi::plugin]` generated by the `#[nvim_oxi::test]` +/// macro when the `test-terminator` feature is enabled and the test function +/// takes a `TestTerminator` argument. #[cfg(feature = "test-terminator")] pub fn plugin_body_with_terminator(test_body: F) where - F: FnOnce(TestTerminator), + F: FnOnce(super::terminator::TestTerminator), { let lock = Arc::new(OnceLock::>::new()); @@ -84,75 +62,10 @@ where } .unwrap(); - test_body(TestTerminator { lock, handle }); -} - -/// A handle used to terminate a test annotated by [`test`](crate::test). -/// -/// The `test` macro works by turning the annotated function into its own -/// plugin, which is then loaded by Neovim and evalutated by `require`ing it -/// when the test is run, before immediately quitting. -/// -/// When testing asynchronous code this can be problematic, as the test may -/// need to continue running after the generated plugin has been `require`d. -/// -/// To allow for this, the test function can take a `TestTerminator` as its -/// only argument. This allows the test to be terminated asynchronously by -/// calling [`terminate`](Self::terminate). -/// -/// Note that if the `TestTerminator` is dropped without first calling -/// `terminate`, the test will run forever. -#[cfg(feature = "test-terminator")] -pub struct TestTerminator { - lock: Arc>>, - handle: crate::libuv::AsyncHandle, -} - -#[cfg(feature = "test-terminator")] -impl TestTerminator { - /// Terminates the test and consumes the `TestTerminator`. - pub fn terminate(self, res: Result<(), TestFailure<'_, E>>) { - let res = res.map_err(Into::into); - let Ok(()) = self.lock.set(res) else { unreachable!() }; - self.handle.send().unwrap(); - } -} - -/// The error type given to [`TestTerminator::terminate`]. -/// -/// The two variants of this enum represent the two ways a test can fail: -/// either by returning an error or by panicking. -pub enum TestFailure<'a, E> { - /// This is used to indicate that the test failed due to an error being - /// returned from the test function. - Error(E), - - /// This is used to indicate that the test failed due to a panic. The - /// [`PanicHookInfo`](std::panic::PanicHookInfo) contains information about - /// the panic and can be obtained by calling - /// [`set_hook`](std::panic::set_hook). - Panic(&'a std::panic::PanicHookInfo<'a>), -} - -fn exit(result: Result<(), Failure>) { - #[cfg(all(feature = "neovim-0-9", not(feature = "neovim-0-10")))] - let exec = |cmd: &str| crate::api::exec(cmd, false).unwrap(); - - #[cfg(feature = "neovim-0-10")] - let exec = |cmd: &str| { - let opts = crate::api::opts::ExecOpts::builder().output(false).build(); - crate::api::exec2(cmd, &opts).unwrap(); - }; - - if let Err(failure) = result { - eprintln!("{failure}"); - exec("cquit 1"); - } else { - exec("qall!"); - } + test_body(super::terminator::TestTerminator { lock, handle }); } -/// TODO: docs +/// The body of the `#[test]` generated by the `#[nvim_oxi::test]` macro. pub fn test_body( crate_name: &str, manifest_dir: &str, @@ -219,7 +132,48 @@ pub fn test_body( } } -/// TODO: docs +/// Returns the `target` directory in which cargo will place the compiled +/// artifacts for the crate whose manifest is located at `manifest_dir`. +fn target_dir(manifest_dir: &Path) -> PathBuf { + let output = Command::new( + env::var("CARGO").ok().unwrap_or_else(|| "cargo".to_owned()), + ) + .arg("metadata") + .arg("--format-version=1") + .arg("--no-deps") + .current_dir(manifest_dir) + .output() + .unwrap(); + + let object: json::Object = + json::from_str(&String::from_utf8(output.stdout).unwrap()).unwrap(); + + let target_dir = match object.get("target_directory").unwrap() { + json::Value::String(s) => s, + _ => panic!("must be string value"), + }; + + target_dir.into() +} + +fn exit(result: Result<(), Failure>) { + #[cfg(all(feature = "neovim-0-9", not(feature = "neovim-0-10")))] + let exec = |cmd: &str| crate::api::exec(cmd, false).unwrap(); + + #[cfg(feature = "neovim-0-10")] + let exec = |cmd: &str| { + let opts = crate::api::opts::ExecOpts::builder().output(false).build(); + crate::api::exec2(cmd, &opts).unwrap(); + }; + + if let Err(failure) = result { + eprintln!("{failure}"); + exec("cquit 1"); + } else { + exec("qall!"); + } +} + fn run_nvim_command( crate_name: &str, manifest_dir: &str, @@ -267,7 +221,7 @@ fn run_nvim_command( } #[derive(Clone)] -struct PanicInfo { +pub(super) struct PanicInfo { msg: String, thread: String, file: Option, @@ -376,7 +330,7 @@ impl From<&panic::PanicHookInfo<'_>> for PanicInfo { } #[derive(Clone)] -enum Failure { +pub(super) enum Failure { Error(String), Panic(PanicInfo), } @@ -402,11 +356,15 @@ impl FromStr for Failure { } #[cfg(feature = "test-terminator")] -impl From> for Failure { - fn from(err: TestFailure<'_, E>) -> Self { +impl From> for Failure { + fn from(err: super::terminator::TestFailure<'_, E>) -> Self { match err { - TestFailure::Error(err) => Self::Error(err.to_string()), - TestFailure::Panic(info) => Self::Panic(info.into()), + super::terminator::TestFailure::Error(err) => { + Self::Error(err.to_string()) + }, + super::terminator::TestFailure::Panic(info) => { + Self::Panic(info.into()) + }, } } }