Skip to content

Commit

Permalink
Refactor nvim_oxi::tests into a directory structure
Browse files Browse the repository at this point in the history
  • Loading branch information
noib3 committed Dec 21, 2024
1 parent a30581c commit 4835699
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 112 deletions.
8 changes: 4 additions & 4 deletions crates/macros/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
},
};

Expand All @@ -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),
Expand Down
4 changes: 0 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
7 changes: 7 additions & 0 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -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};
50 changes: 50 additions & 0 deletions src/tests/terminator.rs
Original file line number Diff line number Diff line change
@@ -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<OnceLock<Result<(), super::test_macro::Failure>>>,
pub(super) handle: crate::libuv::AsyncHandle,
}

impl TestTerminator {
/// Terminates the test and consumes the `TestTerminator`.
pub fn terminate<E: Display>(self, res: Result<(), TestFailure<'_, E>>) {
let res = res.map_err(Into::into);
let Ok(()) = self.lock.set(res) else { unreachable!() };
self.handle.send().unwrap();
}
}
166 changes: 62 additions & 104 deletions src/tests.rs → src/tests/test_macro.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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<F, R>(test_body: F)
where
F: FnOnce() -> R + UnwindSafe,
Expand All @@ -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<F>(test_body: F)
where
F: FnOnce(TestTerminator),
F: FnOnce(super::terminator::TestTerminator),
{
let lock = Arc::new(OnceLock::<Result<(), Failure>>::new());

Expand All @@ -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<OnceLock<Result<(), Failure>>>,
handle: crate::libuv::AsyncHandle,
}

#[cfg(feature = "test-terminator")]
impl TestTerminator {
/// Terminates the test and consumes the `TestTerminator`.
pub fn terminate<E: Display>(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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -267,7 +221,7 @@ fn run_nvim_command(
}

#[derive(Clone)]
struct PanicInfo {
pub(super) struct PanicInfo {
msg: String,
thread: String,
file: Option<String>,
Expand Down Expand Up @@ -376,7 +330,7 @@ impl From<&panic::PanicHookInfo<'_>> for PanicInfo {
}

#[derive(Clone)]
enum Failure {
pub(super) enum Failure {
Error(String),
Panic(PanicInfo),
}
Expand All @@ -402,11 +356,15 @@ impl FromStr for Failure {
}

#[cfg(feature = "test-terminator")]
impl<E: Display> From<TestFailure<'_, E>> for Failure {
fn from(err: TestFailure<'_, E>) -> Self {
impl<E: Display> From<super::terminator::TestFailure<'_, E>> 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())
},
}
}
}
Expand Down

0 comments on commit 4835699

Please sign in to comment.