From e0298831249188d37ffa67e4985c58ff8bfb14db Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sat, 30 Dec 2023 23:14:59 +0000 Subject: [PATCH 1/4] Allow applying serial to mod as well --- Cargo.lock | 27 +++++-- serial_test_derive/Cargo.toml | 1 + serial_test_derive/src/lib.rs | 143 ++++++++++++++++++++++++++++++---- serial_test_test/src/lib.rs | 10 +++ 4 files changed, 157 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 070a8f2..0b4cfed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -382,20 +382,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", ] @@ -473,9 +483,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]] @@ -540,9 +551,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", @@ -583,7 +594,7 @@ checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.4", + "syn 2.0.43", ] [[package]] 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..5adb6c5 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 { @@ -320,11 +321,69 @@ fn fs_parallel_core( 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()); + match fn_ast { + Ok(ast) => { + return fn_setup(ast, config, prefix, kind); + } + Err(_) => { + // Assume non-fn, skip + } + }; + let mod_ast: SynResult = syn::parse2(input); + match mod_ast { + Ok(mut ast) => { + let new_content = ast.content.clone().and_then(|(brace, items)| { + let new_items = items + .into_iter() + .map(|item| match item { + syn::Item::Fn(item_fn) + if item_fn + .attrs + .iter() + .find(|attr| { + attr.meta + .path() + .segments + .first() + .unwrap() + .ident + .to_string() + .contains("test") + }) + .is_some() => + { + syn::parse2(fn_setup(item_fn, config, prefix, kind)).unwrap() + } + other => other, + }) + .collect(); + Some((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().into() + } + 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 +396,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 +460,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 +468,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 +508,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 +525,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 +546,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 +603,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 +623,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 +643,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 +663,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 +683,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}; From ff2890d91b18e5d2b5e8ea546636a261a051ead2 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sat, 30 Dec 2023 23:19:47 +0000 Subject: [PATCH 2/4] Update docs for mod-level functionality --- README.md | 2 ++ serial_test/src/lib.rs | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) 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", From 67d22ff9eef867032031e30fc65a58154f588364 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sat, 30 Dec 2023 23:29:45 +0000 Subject: [PATCH 3/4] Fix some clippy issues --- serial_test_derive/src/lib.rs | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/serial_test_derive/src/lib.rs b/serial_test_derive/src/lib.rs index 5adb6c5..4a6440b 100644 --- a/serial_test_derive/src/lib.rs +++ b/serial_test_derive/src/lib.rs @@ -319,6 +319,7 @@ fn fs_parallel_core( parallel_setup(input, config, "fs") } +#[allow(clippy::cmp_owned)] fn core_setup( input: proc_macro2::TokenStream, config: &Config, @@ -337,32 +338,28 @@ fn core_setup( let mod_ast: SynResult = syn::parse2(input); match mod_ast { Ok(mut ast) => { - let new_content = ast.content.clone().and_then(|(brace, items)| { + 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() - .find(|attr| { - attr.meta - .path() - .segments - .first() - .unwrap() - .ident - .to_string() - .contains("test") - }) - .is_some() => + 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(); - Some((brace, new_items)) + (brace, new_items) }); if let Some(nc) = new_content { ast.content.replace(nc); @@ -370,7 +367,7 @@ fn core_setup( ast.attrs.retain(|attr| { attr.meta.path().segments.first().unwrap().ident.to_string() != "serial" }); - ast.into_token_stream().into() + ast.into_token_stream() } Err(_) => { panic!("Attribute applied to something other than mod or fn!"); From a8d985d9fde5aff6fe8822f71775f66c21a72a62 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sat, 30 Dec 2023 23:32:59 +0000 Subject: [PATCH 4/4] Replace a single-use match with if let --- serial_test_derive/src/lib.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/serial_test_derive/src/lib.rs b/serial_test_derive/src/lib.rs index 4a6440b..ba69ee0 100644 --- a/serial_test_derive/src/lib.rs +++ b/serial_test_derive/src/lib.rs @@ -327,13 +327,8 @@ fn core_setup( kind: &str, ) -> proc_macro2::TokenStream { let fn_ast: SynResult = syn::parse2(input.clone()); - match fn_ast { - Ok(ast) => { - return fn_setup(ast, config, prefix, kind); - } - Err(_) => { - // Assume non-fn, skip - } + if let Ok(ast) = fn_ast { + return fn_setup(ast, config, prefix, kind); }; let mod_ast: SynResult = syn::parse2(input); match mod_ast {