From 5bb673a43670b620c13241702442c9882cb8ccf5 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Thu, 1 Apr 2021 12:22:03 +0200 Subject: [PATCH] Polish up --- README.md | 6 ++- rusty-fork-macro/Cargo.toml | 4 +- rusty-fork-macro/src/lib.rs | 89 +++++++++++++++++++++++++------------ src/lib.rs | 2 +- 4 files changed, 68 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index fc06ee1..1ed7d03 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ the test and forcibly terminate it and produce a normal test failure. as the current working directory, can do so without interfering with other tests. -This crate itself provides two things: +This crate itself provides three things: - The [`rusty_fork_test!`](macro.rusty_fork_test.html) macro, which is a simple way to wrap standard Rust tests to be run in subprocesses with @@ -29,6 +29,10 @@ optional timeouts. - The [`fork`](fn.fork.html) function which can be used as a building block to make other types of process isolation strategies. +- The [`#[fork_test]`] proc macro, which works exactly like `rusty_fork_test!`, + but with the additionaly feature of supporting `async` functions. It is gated + behind the [`macro`] crate feature flag. + ## Quick Start If you just want to run normal Rust tests in isolated processes, getting diff --git a/rusty-fork-macro/Cargo.toml b/rusty-fork-macro/Cargo.toml index 54e4a0b..8b014fc 100644 --- a/rusty-fork-macro/Cargo.toml +++ b/rusty-fork-macro/Cargo.toml @@ -2,8 +2,8 @@ name = "rusty-fork-macro" version = "0.1.0" authors = ["Jason Lingle"] -license = "MIT/Apache-2.0" -readme = "README.md" +license = "MIT OR Apache-2.0" +readme = "../README.md" repository = "https://github.com/altsysrq/rusty-fork" documentation = "https://docs.rs/rusty-fork-macro" keywords = ["testing", "process", "fork"] diff --git a/rusty-fork-macro/src/lib.rs b/rusty-fork-macro/src/lib.rs index 131187d..efc0f30 100644 --- a/rusty-fork-macro/src/lib.rs +++ b/rusty-fork-macro/src/lib.rs @@ -16,10 +16,10 @@ use syn::{AttributeArgs, Error, ItemFn, Lit, Meta, NestedMeta, ReturnType}; /// #[cfg(test)] /// # */ /// mod test { -/// use rusty_fork::test_fork; +/// use rusty_fork::fork_test; /// /// # /* -/// #[test_fork] +/// #[fork_test] /// # */ /// # pub /// fn my_test() { @@ -39,10 +39,10 @@ use syn::{AttributeArgs, Error, ItemFn, Lit, Meta, NestedMeta, ReturnType}; /// the block, like so: /// /// ``` -/// use rusty_fork::test_fork; +/// use rusty_fork::fork_test; /// /// # /* -/// #[test_fork(timeout_ms = 1000)] +/// #[fork_test(timeout_ms = 1000)] /// # */ /// fn my_test() { /// do_some_expensive_computation(); @@ -56,29 +56,55 @@ use syn::{AttributeArgs, Error, ItemFn, Lit, Meta, NestedMeta, ReturnType}; /// /// Using the timeout feature requires the `timeout` feature for this crate to /// be enabled (which it is by default). +/// +/// ``` +/// use rusty_fork::fork_test; +/// +/// # /* +/// #[fork_test(crate = rusty_fork)] +/// # */ +/// fn my_test() { +/// assert_eq!(2, 1 + 1); +/// } +/// # fn main() { my_test(); } +/// ``` +/// +/// Sometimes the crate dependency might be renamed, in cases like this use the `crate` attribute +/// to pass the new name to rusty-fork. #[proc_macro_attribute] -pub fn test_fork(args: TokenStream, item: TokenStream) -> TokenStream { +pub fn fork_test(args: TokenStream, item: TokenStream) -> TokenStream { let args = syn::parse_macro_input!(args as AttributeArgs); + // defaults let mut crate_name = quote::quote! { rusty_fork }; let mut timeout = quote::quote! { 0 }; + // may be changed by the user for arg in args { - if let NestedMeta::Meta(meta) = arg { - if let Meta::NameValue(name_value) = meta { - if let Some(ident) = name_value.path.get_ident() { - match ident.to_string().as_str() { - "timeout_ms" => { - if let Lit::Int(int) = name_value.lit { - timeout = int.to_token_stream(); - } + if let NestedMeta::Meta(Meta::NameValue(name_value)) = arg { + if let Some(ident) = name_value.path.get_ident() { + match ident.to_string().as_str() { + "timeout_ms" => { + if let Lit::Int(int) = name_value.lit { + timeout = int.to_token_stream(); } - "crate" => { - if let Lit::Str(str) = name_value.lit { - crate_name = str.to_token_stream(); - } + } + "crate" => { + if let Lit::Str(str) = name_value.lit { + crate_name = str.to_token_stream(); } - _ => (), + } + // we don't support using invalid attributes + attribute => { + return Error::new( + ident.span(), + format!( + "`{}` is not a valid attribute for `#[fork_test]`", + attribute + ), + ) + .to_compile_error() + .into() } } } @@ -92,8 +118,10 @@ pub fn test_fork(args: TokenStream, item: TokenStream) -> TokenStream { let fn_body = &item.block; let fn_name = &fn_sig.ident; + // the default is that we add the `#[test]` for the use let mut test = quote::quote! { #[test] }; + // we should still support a use case where the user adds it himself for attr in fn_attrs { if let Some(ident) = attr.path.get_ident() { if ident == "test" { @@ -102,15 +130,18 @@ pub fn test_fork(args: TokenStream, item: TokenStream) -> TokenStream { } } + // we don't support async functions, whatever library the user uses to support this, should + // process first if let Some(asyncness) = fn_sig.asyncness { return Error::new( asyncness.span, - "put `#[test_fork]` last to let other macros process first", + "put `#[fork_test]` after the macro that enables `async` support", ) .to_compile_error() .into(); } + // support returning `Result` let body_fn = if let ReturnType::Type(_, ret_ty) = &fn_sig.output { quote::quote! { fn body_fn() { @@ -156,45 +187,45 @@ pub fn test_fork(args: TokenStream, item: TokenStream) -> TokenStream { #[cfg(test)] mod test { - use rusty_fork::test_fork; + use rusty_fork::fork_test; use std::io::Result; - #[test_fork] + #[fork_test] fn trivials() {} - #[test_fork] + #[fork_test] #[should_panic] fn panicking_child() { panic!("just testing a panic, nothing to see here"); } - #[test_fork] + #[fork_test] #[should_panic] fn aborting_child() { ::std::process::abort(); } - #[test_fork] + #[fork_test] fn trivial_result() -> Result<()> { Ok(()) } - #[test_fork] + #[fork_test] #[should_panic] fn panicking_child_result() -> Result<()> { panic!("just testing a panic, nothing to see here"); } - #[test_fork] + #[fork_test] #[should_panic] fn aborting_child_result() -> Result<()> { ::std::process::abort(); } - #[test_fork(timeout_ms = 1000)] + #[fork_test(timeout_ms = 1000)] fn timeout_passes() {} - #[test_fork(timeout_ms = 1000)] + #[fork_test(timeout_ms = 1000)] #[should_panic] fn timeout_fails() { println!("hello from child"); @@ -203,7 +234,7 @@ mod test { } #[tokio::test] - #[test_fork] + #[fork_test] async fn my_test() { assert!(true); } diff --git a/src/lib.rs b/src/lib.rs index dbb9fbf..cdd1f6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,4 +127,4 @@ pub use crate::error::{Error, Result}; pub use crate::fork::fork; pub use crate::child_wrapper::{ChildWrapper, ExitStatusWrapper}; #[cfg(feature = "macro")] -pub use rusty_fork_macro::test_fork; +pub use rusty_fork_macro::fork_test;