serial_test_derive-3.2.0/.cargo_vcs_info.json0000644000000001600000000000100147070ustar { "git": { "sha1": "3ac9744b019bee996c4d44afc72a0b1af8be1809" }, "path_in_vcs": "serial_test_derive" }serial_test_derive-3.2.0/Cargo.toml0000644000000026330000000000100127140ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "serial_test_derive" version = "3.2.0" authors = ["Tom Parker-Shemilt "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Helper crate for serial_test" readme = "README.md" categories = ["development-tools::testing"] license = "MIT" repository = "https://github.com/palfrey/serial_test/" [lib] name = "serial_test_derive" path = "src/lib.rs" proc-macro = true [dependencies.proc-macro2] version = "1.0.60" features = ["proc-macro"] default-features = false [dependencies.quote] version = "1" default-features = false [dependencies.syn] version = "2" features = [ "full", "printing", "parsing", "clone-impls", ] default-features = false [dev-dependencies.env_logger] version = ">=0.6.1" default-features = false [dev-dependencies.prettyplease] version = "0.2" default-features = false [features] async = [] default = [] test_logging = [] serial_test_derive-3.2.0/Cargo.toml.orig000064400000000000000000000015071046102023000163740ustar 00000000000000[package] name = "serial_test_derive" description = "Helper crate for serial_test" license = "MIT" version = "3.2.0" authors = ["Tom Parker-Shemilt "] edition = "2018" readme = "README.md" repository = "https://github.com/palfrey/serial_test/" categories = ["development-tools::testing"] [lib] proc-macro = true [dependencies] quote = { version="1", default-features = false} syn = { version="2", features=["full", "printing", "parsing", "clone-impls"], default-features = false} proc-macro2 = { version="1.0.60", features = ["proc-macro"], default-features = false} # Because of https://github.com/dtolnay/proc-macro2/issues/356 [dev-dependencies] env_logger = {version=">=0.6.1", default-features = false} prettyplease = {version="0.2", default-features = false} [features] default = [] async = [] test_logging = []serial_test_derive-3.2.0/LICENSE000064400000000000000000000020451046102023000145100ustar 00000000000000Copyright (c) 2018 Tom Parker-Shemilt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.serial_test_derive-3.2.0/README.md000064400000000000000000000045261046102023000147700ustar 00000000000000# serial_test [![Version](https://img.shields.io/crates/v/serial_test.svg)](https://crates.io/crates/serial_test) [![Downloads](https://img.shields.io/crates/d/serial_test)](https://crates.io/crates/serial_test) [![Docs](https://docs.rs/serial_test/badge.svg)](https://docs.rs/serial_test/) [![MIT license](https://img.shields.io/crates/l/serial_test.svg)](./LICENSE) [![Build Status](https://github.com/palfrey/serial_test/workflows/Continuous%20integration/badge.svg?branch=main)](https://github.com/palfrey/serial_test/actions) [![MSRV: 1.68.2](https://flat.badgen.net/badge/MSRV/1.68.2/purple)](https://blog.rust-lang.org/2023/03/28/Rust-1.68.2.html) `serial_test` allows for the creation of serialised Rust tests using the `serial` attribute e.g. ```rust #[test] #[serial] fn test_serial_one() { // Do things } #[test] #[serial] fn test_serial_another() { // Do things } #[tokio::test] #[serial] async fn test_serial_another() { // Do things asynchronously } ``` Multiple tests with the `serial` attribute are guaranteed to be executed in serial. Ordering of the tests is not guaranteed however. Other tests with the `parallel` attribute may run at the same time as each other, but not at the same time as a test with `serial`. Tests with neither attribute may run at any time and no guarantees are made about their timing! Both support optional keys for defining subsets of tests to run in serial together, see docs for more details. For cases like doctests and integration tests where the tests are run as separate processes, we also support `file_serial`, with 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. Add to your Cargo.toml ```toml [dev-dependencies] serial_test = "*" ``` plus `use serial_test::serial;` in your imports section. You can then either add `#[serial]` or `#[serial(some_key)]` to tests as required. serial_test_derive-3.2.0/src/lib.rs000064400000000000000000000624651046102023000154220ustar 00000000000000//! # serial_test_derive //! Helper crate for [serial_test](../serial_test/index.html) #![cfg_attr(docsrs, feature(doc_cfg))] #![deny(missing_docs)] extern crate proc_macro; 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 /// ````no_run /// #[test] /// #[serial] /// fn test_serial_one() { /// // Do things /// } /// /// #[test] /// #[serial] /// fn test_serial_another() { /// // Do things /// } /// ```` /// Multiple tests with the [serial](macro@serial) attribute are guaranteed to be executed in serial. Ordering /// of the tests is not guaranteed however. If you have other tests that can be run in parallel, but would clash /// if run at the same time as the [serial](macro@serial) tests, you can use the [parallel](macro@parallel) attribute. /// /// If you want different subsets of tests to be serialised with each /// other, but not depend on other subsets, you can add a key argument to [serial](macro@serial), and all calls /// with identical arguments will be called in serial. Multiple comma-separated keys will make a test run in serial with all of the sets with any of those keys. /// /// ````no_run /// #[test] /// #[serial(something)] /// fn test_serial_one() { /// // Do things /// } /// /// #[test] /// #[serial(something)] /// fn test_serial_another() { /// // Do things /// } /// /// #[test] /// #[serial(other)] /// fn test_serial_third() { /// // Do things /// } /// /// #[test] /// #[serial(other)] /// fn test_serial_fourth() { /// // Do things /// } /// /// #[test] /// #[serial(something, other)] /// fn test_serial_fifth() { /// // Do things, eventually /// } /// ```` /// `test_serial_one` and `test_serial_another` will be executed in serial, as will `test_serial_third` and `test_serial_fourth` /// but neither sequence will be blocked by the other. `test_serial_fifth` is blocked by tests in either sequence. /// /// Nested serialised tests (i.e. a [serial](macro@serial) tagged test calling another) are supported. #[proc_macro_attribute] pub fn serial(attr: TokenStream, input: TokenStream) -> TokenStream { local_serial_core(attr.into(), input.into()).into() } /// Allows for the creation of parallel Rust tests that won't clash with serial tests /// ````no_run /// #[test] /// #[serial] /// fn test_serial_one() { /// // Do things /// } /// /// #[test] /// #[parallel] /// fn test_parallel_one() { /// // Do things /// } /// /// #[test] /// #[parallel] /// fn test_parallel_two() { /// // Do things /// } /// ```` /// Multiple tests with the [parallel](macro@parallel) attribute may run in parallel, but not at the /// same time as [serial](macro@serial) tests. e.g. in the example code above, `test_parallel_one` /// and `test_parallel_two` may run at the same time, but `test_serial_one` is guaranteed not to run /// at the same time as either of them. [parallel](macro@parallel) also takes key arguments for groups /// of tests as per [serial](macro@serial). /// /// Note that this has zero effect on [file_serial](macro@file_serial) tests, as that uses a different /// serialisation mechanism. For that, you want [file_parallel](macro@file_parallel). #[proc_macro_attribute] pub fn parallel(attr: TokenStream, input: TokenStream) -> TokenStream { local_parallel_core(attr.into(), input.into()).into() } /// Allows for the creation of file-serialised Rust tests /// ````no_run /// #[test] /// #[file_serial] /// fn test_serial_one() { /// // Do things /// } /// /// #[test] /// #[file_serial] /// fn test_serial_another() { /// // Do things /// } /// ```` /// /// Multiple tests with the [file_serial](macro@file_serial) attribute are guaranteed to run in serial, as per the [serial](macro@serial) /// attribute. Note that there are no guarantees about one test with [serial](macro@serial) and another with [file_serial](macro@file_serial) /// as they lock using different methods, and [file_serial](macro@file_serial) does not support nested serialised tests, but otherwise acts /// like [serial](macro@serial). If you have other tests that can be run in parallel, but would clash /// if run at the same time as the [file_serial](macro@file_serial) tests, you can use the [file_parallel](macro@file_parallel) attribute. /// /// It also supports an optional `path` arg as well as key(s) as per [serial](macro@serial). /// ````no_run /// #[test] /// #[file_serial(key)] /// fn test_serial_one() { /// // Do things /// } /// /// #[test] /// #[file_serial(key, path => "/tmp/foo")] /// fn test_serial_another() { /// // Do things /// } /// ```` /// The path defaults to a reasonable temp directory for the OS if not specified. If the `path` is specified, you can only use one key. #[proc_macro_attribute] #[cfg_attr(docsrs, doc(cfg(feature = "file_locks")))] pub fn file_serial(attr: TokenStream, input: TokenStream) -> TokenStream { fs_serial_core(attr.into(), input.into()).into() } /// Allows for the creation of file-serialised parallel Rust tests that won't clash with file-serialised serial tests /// ````no_run /// #[test] /// #[file_serial] /// fn test_serial_one() { /// // Do things /// } /// /// #[test] /// #[file_parallel] /// fn test_parallel_one() { /// // Do things /// } /// /// #[test] /// #[file_parallel] /// fn test_parallel_two() { /// // Do things /// } /// ```` /// Effectively, this should behave like [parallel](macro@parallel) but for [file_serial](macro@file_serial). /// Note that as per [file_serial](macro@file_serial) this doesn't do anything for [serial](macro@serial)/[parallel](macro@parallel) tests. /// /// It also supports an optional `path` arg as well as key(s) as per [serial](macro@serial). /// ````no_run /// #[test] /// #[file_parallel(key, path => "/tmp/foo")] /// fn test_parallel_one() { /// // Do things /// } /// /// #[test] /// #[file_parallel(key, path => "/tmp/foo")] /// fn test_parallel_another() { /// // Do things /// } /// ```` /// The path defaults to a reasonable temp directory for the OS if not specified. If the `path` is specified, you can only use one key. #[proc_macro_attribute] #[cfg_attr(docsrs, doc(cfg(feature = "file_locks")))] pub fn file_parallel(attr: TokenStream, input: TokenStream) -> TokenStream { fs_parallel_core(attr.into(), input.into()).into() } // Based off of https://github.com/dtolnay/quote/issues/20#issuecomment-437341743 #[derive(Default, Debug, Clone)] struct QuoteOption(Option); impl ToTokens for QuoteOption { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { tokens.append_all(match self.0 { Some(ref t) => quote! { ::std::option::Option::Some(#t) }, None => quote! { ::std::option::Option::None }, }); } } #[derive(Default, Debug)] struct Config { names: Vec, path: QuoteOption, } fn string_from_literal(literal: Literal) -> String { let string_literal = literal.to_string(); if !string_literal.starts_with('\"') || !string_literal.ends_with('\"') { panic!("Expected a string literal, got '{}'", string_literal); } // Hacky way of getting a string without the enclosing quotes string_literal[1..string_literal.len() - 1].to_string() } fn get_config(attr: proc_macro2::TokenStream) -> Config { let mut attrs = attr.into_iter().collect::>(); let mut raw_args: Vec = Vec::new(); let mut in_path: bool = false; let mut path: Option = None; while !attrs.is_empty() { match attrs.remove(0) { TokenTree::Ident(id) if id.to_string().eq_ignore_ascii_case("path") => { in_path = true; } TokenTree::Ident(id) => { let name = id.to_string(); raw_args.push(name); } x => { panic!( "Expected literal as key args (or a 'path => '\"foo\"'), not {}", x ); } } if in_path { if attrs.len() < 3 { panic!("Expected a '=> ' after 'path'"); } match attrs.remove(0) { TokenTree::Punct(p) if p.as_char() == '=' => {} x => { panic!("Expected = after path, not {}", x); } } match attrs.remove(0) { TokenTree::Punct(p) if p.as_char() == '>' => {} x => { panic!("Expected > after path, not {}", x); } } match attrs.remove(0) { TokenTree::Literal(literal) => { path = Some(string_from_literal(literal)); } x => { panic!("Expected literals as path arg, not {}", x); } } in_path = false; } if !attrs.is_empty() { match attrs.remove(0) { TokenTree::Punct(p) if p.as_char() == ',' => {} x => { panic!("Expected , between args, not {}", x); } } } } if raw_args.is_empty() { raw_args.push(String::new()); } raw_args.sort(); // So the keys are always requested in the same order. Avoids dining philosopher issues. Config { names: raw_args, path: QuoteOption(path), } } fn local_serial_core( attr: proc_macro2::TokenStream, input: proc_macro2::TokenStream, ) -> proc_macro2::TokenStream { let config = get_config(attr); serial_setup(input, config, "local") } fn local_parallel_core( attr: proc_macro2::TokenStream, input: proc_macro2::TokenStream, ) -> proc_macro2::TokenStream { let config = get_config(attr); parallel_setup(input, config, "local") } fn fs_serial_core( attr: proc_macro2::TokenStream, input: proc_macro2::TokenStream, ) -> proc_macro2::TokenStream { let config = get_config(attr); serial_setup(input, config, "fs") } fn fs_parallel_core( attr: proc_macro2::TokenStream, input: proc_macro2::TokenStream, ) -> proc_macro2::TokenStream { let config = get_config(attr); parallel_setup(input, config, "fs") } #[allow(clippy::cmp_owned)] fn core_setup( input: proc_macro2::TokenStream, 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 .iter() .map(|s| s.ident.to_string()) .collect::>() .join("::") .contains("test") }) => { let tokens = fn_setup(item_fn, config, prefix, kind); let token_display = format!("tokens: {tokens}"); syn::parse2(tokens).expect(&token_display) } 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 asyncness = ast.sig.asyncness; if asyncness.is_some() && cfg!(not(feature = "async")) { panic!("async testing attempted with async feature disabled in serial_test!"); } let vis = ast.vis; let name = ast.sig.ident; #[cfg(all(feature = "test_logging", not(test)))] let print_name = { let print_str = format!("Starting {name}"); quote! { println!(#print_str); } }; #[cfg(any(not(feature = "test_logging"), test))] let print_name = quote! {}; let return_type = match ast.sig.output { syn::ReturnType::Default => None, syn::ReturnType::Type(_rarrow, ref box_type) => Some(box_type.deref()), }; let block = ast.block; let attrs: Vec = ast.attrs.into_iter().collect(); let names = config.names.clone(); let path = config.path.clone(); if let Some(ret) = return_type { match asyncness { Some(_) => { let fnname = format_ident!("{}_async_{}_core_with_return", prefix, kind); let temp_fn = format_ident!("_{}_internal", name); quote! { #(#attrs) * #vis async fn #name () -> #ret { async fn #temp_fn () -> #ret #block #print_name serial_test::#fnname(vec![#(#names ),*], #path, #temp_fn()).await } } } None => { let fnname = format_ident!("{}_{}_core_with_return", prefix, kind); quote! { #(#attrs) * #vis fn #name () -> #ret { #print_name serial_test::#fnname(vec![#(#names ),*], #path, || #block ) } } } } } else { match asyncness { Some(_) => { let fnname = format_ident!("{}_async_{}_core", prefix, kind); let temp_fn = format_ident!("_{}_internal", name); quote! { #(#attrs) * #vis async fn #name () { async fn #temp_fn () #block #print_name serial_test::#fnname(vec![#(#names ),*], #path, #temp_fn()).await; } } } None => { let fnname = format_ident!("{}_{}_core", prefix, kind); quote! { #(#attrs) * #vis fn #name () { #print_name serial_test::#fnname(vec![#(#names ),*], #path, || #block ); } } } } } } fn serial_setup( input: proc_macro2::TokenStream, config: Config, prefix: &str, ) -> proc_macro2::TokenStream { core_setup(input, &config, prefix, "serial") } fn parallel_setup( input: proc_macro2::TokenStream, config: Config, prefix: &str, ) -> proc_macro2::TokenStream { 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 init() { let _ = env_logger::builder().is_test(false).try_init(); } 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() { init(); let attrs = proc_macro2::TokenStream::new(); let input = quote! { #[test] fn foo() {} }; let stream = local_serial_core(attrs.into(), input); let compare = quote! { #[test] fn foo () { serial_test::local_serial_core(vec![""], ::std::option::Option::None, || {} ); } }; compare_streams(compare, stream); } #[test] fn test_serial_with_pub() { init(); let attrs = proc_macro2::TokenStream::new(); let input = quote! { #[test] pub fn foo() {} }; let stream = local_serial_core(attrs.into(), input); let compare = quote! { #[test] pub fn foo () { serial_test::local_serial_core(vec![""], ::std::option::Option::None, || {} ); } }; compare_streams(compare, stream); } #[test] fn test_other_attributes() { init(); let attrs = proc_macro2::TokenStream::new(); let input = quote! { #[test] #[ignore] #[should_panic(expected = "Testing panic")] #[something_else] fn foo() {} }; let stream = local_serial_core(attrs.into(), input); let compare = quote! { #[test] #[ignore] #[should_panic(expected = "Testing panic")] #[something_else] fn foo () { serial_test::local_serial_core(vec![""], ::std::option::Option::None, || {} ); } }; compare_streams(compare, stream); } #[test] #[cfg(feature = "async")] fn test_serial_async() { init(); let attrs = proc_macro2::TokenStream::new(); let input = quote! { async fn foo() {} }; let stream = local_serial_core(attrs.into(), input); let compare = quote! { async fn foo () { async fn _foo_internal () { } serial_test::local_async_serial_core(vec![""], ::std::option::Option::None, _foo_internal() ).await; } }; assert_eq!(format!("{}", compare), format!("{}", stream)); } #[test] #[cfg(feature = "async")] fn test_serial_async_return() { init(); let attrs = proc_macro2::TokenStream::new(); let input = quote! { async fn foo() -> Result<(), ()> { Ok(()) } }; let stream = local_serial_core(attrs.into(), input); let compare = quote! { async fn foo () -> Result<(), ()> { async fn _foo_internal () -> Result<(), ()> { Ok(()) } serial_test::local_async_serial_core_with_return(vec![""], ::std::option::Option::None, _foo_internal() ).await } }; assert_eq!(format!("{}", compare), format!("{}", stream)); } #[test] fn test_file_serial() { init(); let attrs: Vec<_> = quote! { foo }.into_iter().collect(); let input = quote! { #[test] fn foo() {} }; let stream = fs_serial_core( proc_macro2::TokenStream::from_iter(attrs.into_iter()), input, ); let compare = quote! { #[test] fn foo () { serial_test::fs_serial_core(vec!["foo"], ::std::option::Option::None, || {} ); } }; compare_streams(compare, stream); } #[test] fn test_file_serial_no_args() { init(); let attrs = proc_macro2::TokenStream::new(); let input = quote! { #[test] fn foo() {} }; let stream = fs_serial_core( proc_macro2::TokenStream::from_iter(attrs.into_iter()), input, ); let compare = quote! { #[test] fn foo () { serial_test::fs_serial_core(vec![""], ::std::option::Option::None, || {} ); } }; compare_streams(compare, stream); } #[test] fn test_file_serial_with_path() { init(); let attrs: Vec<_> = quote! { foo, path => "bar_path" }.into_iter().collect(); let input = quote! { #[test] fn foo() {} }; let stream = fs_serial_core( proc_macro2::TokenStream::from_iter(attrs.into_iter()), input, ); let compare = quote! { #[test] fn foo () { serial_test::fs_serial_core(vec!["foo"], ::std::option::Option::Some("bar_path"), || {} ); } }; compare_streams(compare, stream); } #[test] fn test_single_attr() { init(); let attrs: Vec<_> = quote! { one}.into_iter().collect(); let input = quote! { #[test] fn single() {} }; let stream = local_serial_core( proc_macro2::TokenStream::from_iter(attrs.into_iter()), input, ); let compare = quote! { #[test] fn single () { serial_test::local_serial_core(vec!["one"], ::std::option::Option::None, || {} ); } }; compare_streams(compare, stream); } #[test] fn test_multiple_attr() { init(); let attrs: Vec<_> = quote! { two, one }.into_iter().collect(); let input = quote! { #[test] fn multiple() {} }; let stream = local_serial_core( proc_macro2::TokenStream::from_iter(attrs.into_iter()), input, ); let compare = quote! { #[test] fn multiple () { serial_test::local_serial_core(vec!["one", "two"], ::std::option::Option::None, || {} ); } }; compare_streams(compare, stream); } #[test] fn test_mod() { init(); 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); } #[test] fn test_later_test_mod() { init(); let attrs = proc_macro2::TokenStream::new(); let input = quote! { #[cfg(test)] #[serial] mod serial_attr_tests { pub fn foo() { println!("Nothing"); } #[demo_library::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"); } #[demo_library::test] fn bar() { serial_test::local_serial_core(vec![""], ::std::option::Option::None, || {} ); } } }; compare_streams(compare, stream); } #[test] #[cfg(feature = "async")] fn test_mod_with_async() { init(); let attrs = proc_macro2::TokenStream::new(); let input = quote! { #[cfg(test)] #[serial] mod serial_attr_tests { #[demo_library::test] async fn foo() -> Result<(), ()> { Ok(()) } #[demo_library::test] #[ignore = "bla"] async fn bar() -> Result<(), ()> { Ok(()) } } }; let stream = local_serial_core( proc_macro2::TokenStream::from_iter(attrs.into_iter()), input, ); let compare = quote! { #[cfg(test)] mod serial_attr_tests { #[demo_library::test] async fn foo() -> Result<(), ()> { async fn _foo_internal() -> Result<(), ()> { Ok(())} serial_test::local_async_serial_core_with_return(vec![""], ::std::option::Option::None, _foo_internal() ).await } #[demo_library::test] #[ignore = "bla"] async fn bar() -> Result<(), ()> { async fn _bar_internal() -> Result<(), ()> { Ok(())} serial_test::local_async_serial_core_with_return(vec![""], ::std::option::Option::None, _bar_internal() ).await } } }; compare_streams(compare, stream); } }