diff --git a/Cargo.lock b/Cargo.lock index e2887ff..907f9bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,20 +356,30 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn 2.0.43", +] + [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "5907a1b7c277254a8b15170f6e7c97cfa60ee7872a3217663bb81151e48184bb" dependencies = [ "proc-macro2", ] @@ -458,9 +468,10 @@ name = "serial_test_derive" version = "2.0.0" dependencies = [ "env_logger", + "prettyplease", "proc-macro2", "quote", - "syn 2.0.4", + "syn 2.0.43", ] [[package]] @@ -525,9 +536,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.4" +version = "2.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c622ae390c9302e214c31013517c2061ecb2699935882c60a9b37f82f8625ae" +checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" dependencies = [ "proc-macro2", "quote", @@ -568,7 +579,7 @@ checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.4", + "syn 2.0.43", ] [[package]] diff --git a/README.md b/README.md index 43efca1..545443f 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ For cases like doctests and integration tests where the tests are run as separat similar properties but based off file locking. Note that there are no guarantees about one test with `serial` and another with `file_serial` as they lock using different methods. +All of the attributes can also be applied at a `mod` level and will be automagically applied to all test functions in that block. + ## Usage The minimum supported Rust version here is 1.68.2. Note this is minimum _supported_, as it may well compile with lower versions, but they're not supported at all. Upgrades to this will require at a major version bump. 1.x supports 1.51 if you need a lower version than that. diff --git a/serial_test/src/lib.rs b/serial_test/src/lib.rs index 641a8b7..d6519f0 100644 --- a/serial_test/src/lib.rs +++ b/serial_test/src/lib.rs @@ -42,6 +42,23 @@ //! } //! ```` //! +//! All of the attributes can also be applied at a `mod` level and will be automagically applied to all test functions in that block +//! ```` +//! #[cfg(test)] +//! #[serial] +//! mod serial_attr_tests { +//! fn foo() { +//! // Won't have `serial` applied, because not a test function +//! println!("Nothing"); +//! } +//! +//! #[test] +//! fn test_bar() { +//! // Will be run serially +//! } +//!} +//! ```` +//! //! ## Feature flags #![cfg_attr( feature = "docsrs", diff --git a/serial_test_derive/Cargo.toml b/serial_test_derive/Cargo.toml index 5d6d772..830b940 100644 --- a/serial_test_derive/Cargo.toml +++ b/serial_test_derive/Cargo.toml @@ -19,6 +19,7 @@ proc-macro2 = "1.0.60" # Because of https://github.com/dtolnay/proc-macro2/issue [dev-dependencies] env_logger = "0.10" +prettyplease = "0.2" [features] async = [] \ No newline at end of file diff --git a/serial_test_derive/src/lib.rs b/serial_test_derive/src/lib.rs index 83a943b..ba69ee0 100644 --- a/serial_test_derive/src/lib.rs +++ b/serial_test_derive/src/lib.rs @@ -10,6 +10,7 @@ use proc_macro::TokenStream; use proc_macro2::{Literal, TokenTree}; use quote::{format_ident, quote, ToTokens, TokenStreamExt}; use std::ops::Deref; +use syn::Result as SynResult; /// Allows for the creation of serialised Rust tests /// ```` @@ -193,7 +194,7 @@ pub fn file_parallel(attr: TokenStream, input: TokenStream) -> TokenStream { } // Based off of https://github.com/dtolnay/quote/issues/20#issuecomment-437341743 -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone)] struct QuoteOption(Option); impl ToTokens for QuoteOption { @@ -318,13 +319,63 @@ fn fs_parallel_core( parallel_setup(input, config, "fs") } +#[allow(clippy::cmp_owned)] fn core_setup( input: proc_macro2::TokenStream, - config: Config, + config: &Config, + prefix: &str, + kind: &str, +) -> proc_macro2::TokenStream { + let fn_ast: SynResult = syn::parse2(input.clone()); + if let Ok(ast) = fn_ast { + return fn_setup(ast, config, prefix, kind); + }; + let mod_ast: SynResult = syn::parse2(input); + match mod_ast { + Ok(mut ast) => { + let new_content = ast.content.clone().map(|(brace, items)| { + let new_items = items + .into_iter() + .map(|item| match item { + syn::Item::Fn(item_fn) + if item_fn.attrs.iter().any(|attr| { + attr.meta + .path() + .segments + .first() + .unwrap() + .ident + .to_string() + .contains("test") + }) => + { + syn::parse2(fn_setup(item_fn, config, prefix, kind)).unwrap() + } + other => other, + }) + .collect(); + (brace, new_items) + }); + if let Some(nc) = new_content { + ast.content.replace(nc); + } + ast.attrs.retain(|attr| { + attr.meta.path().segments.first().unwrap().ident.to_string() != "serial" + }); + ast.into_token_stream() + } + Err(_) => { + panic!("Attribute applied to something other than mod or fn!"); + } + } +} + +fn fn_setup( + ast: syn::ItemFn, + config: &Config, prefix: &str, kind: &str, ) -> proc_macro2::TokenStream { - let ast: syn::ItemFn = syn::parse2(input).unwrap(); let asyncness = ast.sig.asyncness; if asyncness.is_some() && cfg!(not(feature = "async")) { panic!("async testing attempted with async feature disabled in serial_test!"); @@ -337,8 +388,8 @@ fn core_setup( }; let block = ast.block; let attrs: Vec = ast.attrs.into_iter().collect(); - let names = config.names; - let path = config.path; + let names = config.names.clone(); + let path = config.path.clone(); if let Some(ret) = return_type { match asyncness { Some(_) => { @@ -401,7 +452,7 @@ fn serial_setup( config: Config, prefix: &str, ) -> proc_macro2::TokenStream { - core_setup(input, config, prefix, "serial") + core_setup(input, &config, prefix, "serial") } fn parallel_setup( @@ -409,15 +460,32 @@ fn parallel_setup( config: Config, prefix: &str, ) -> proc_macro2::TokenStream { - core_setup(input, config, prefix, "parallel") + core_setup(input, &config, prefix, "parallel") } #[cfg(test)] mod tests { use super::{fs_serial_core, local_serial_core}; + use proc_macro2::TokenStream; use quote::quote; use std::iter::FromIterator; + fn unparse(input: TokenStream) -> String { + let item = syn::parse2(input).unwrap(); + let file = syn::File { + attrs: vec![], + items: vec![item], + shebang: None, + }; + + prettyplease::unparse(&file) + } + + fn compare_streams(first: TokenStream, second: TokenStream) { + let f = unparse(first); + assert_eq!(f, unparse(second)); + } + #[test] fn test_serial() { let attrs = proc_macro2::TokenStream::new(); @@ -432,7 +500,7 @@ mod tests { serial_test::local_serial_core(vec![""], ::std::option::Option::None, || {} ); } }; - assert_eq!(format!("{}", compare), format!("{}", stream)); + compare_streams(compare, stream); } #[test] @@ -449,7 +517,7 @@ mod tests { serial_test::local_serial_core(vec![""], ::std::option::Option::None, || {} ); } }; - assert_eq!(format!("{}", compare), format!("{}", stream)); + compare_streams(compare, stream); } #[test] @@ -470,10 +538,10 @@ mod tests { #[should_panic(expected = "Testing panic")] #[something_else] fn foo () { - serial_test::local_serial_core(vec![""], ::std::option::Option::None, || {} ); + serial_test::local_serial_core(vec![""], ::std::option::Option::None, || {} ); } }; - assert_eq!(format!("{}", compare), format!("{}", stream)); + compare_streams(compare, stream); } #[test] @@ -527,7 +595,7 @@ mod tests { serial_test::fs_serial_core(vec!["foo"], ::std::option::Option::None, || {} ); } }; - assert_eq!(format!("{}", compare), format!("{}", stream)); + compare_streams(compare, stream); } #[test] @@ -547,7 +615,7 @@ mod tests { serial_test::fs_serial_core(vec![""], ::std::option::Option::None, || {} ); } }; - assert_eq!(format!("{}", compare), format!("{}", stream)); + compare_streams(compare, stream); } #[test] @@ -567,7 +635,7 @@ mod tests { serial_test::fs_serial_core(vec!["foo"], ::std::option::Option::Some("bar_path"), || {} ); } }; - assert_eq!(format!("{}", compare), format!("{}", stream)); + compare_streams(compare, stream); } #[test] @@ -587,7 +655,7 @@ mod tests { serial_test::local_serial_core(vec!["one"], ::std::option::Option::None, || {} ); } }; - assert_eq!(format!("{}", compare), format!("{}", stream)); + compare_streams(compare, stream); } #[test] @@ -607,6 +675,41 @@ mod tests { serial_test::local_serial_core(vec!["one", "two"], ::std::option::Option::None, || {} ); } }; - assert_eq!(format!("{}", compare), format!("{}", stream)); + compare_streams(compare, stream); + } + + #[test] + fn test_mod() { + let attrs = proc_macro2::TokenStream::new(); + let input = quote! { + #[cfg(test)] + #[serial] + mod serial_attr_tests { + pub fn foo() { + println!("Nothing"); + } + + #[test] + fn bar() {} + } + }; + let stream = local_serial_core( + proc_macro2::TokenStream::from_iter(attrs.into_iter()), + input, + ); + let compare = quote! { + #[cfg(test)] + mod serial_attr_tests { + pub fn foo() { + println!("Nothing"); + } + + #[test] + fn bar() { + serial_test::local_serial_core(vec![""], ::std::option::Option::None, || {} ); + } + } + }; + compare_streams(compare, stream); } } diff --git a/serial_test_test/src/lib.rs b/serial_test_test/src/lib.rs index 974c99d..17385d3 100644 --- a/serial_test_test/src/lib.rs +++ b/serial_test_test/src/lib.rs @@ -39,6 +39,8 @@ //! ``` use lazy_static::lazy_static; +#[cfg(test)] +use serial_test::{parallel, serial}; use std::{ convert::TryInto, env, fs, @@ -82,6 +84,14 @@ pub fn fs_test_fn(count: usize) { assert_eq!(loaded, count); } +#[cfg(test)] +#[serial] +mod serial_attr_tests {} + +#[cfg(test)] +#[parallel] +mod parallel_attr_tests {} + #[cfg(test)] mod tests { use super::{init, test_fn};