diff --git a/Cargo.toml b/Cargo.toml index 88ae7c4..5f0c571 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ env_logger = {version = "0.11", default-features = false, optional = true} [dev-dependencies] logging = {version = "0.4.8", package = "log"} +rstest = "0.25.0" test-case = {version = "3.1"} -tokio = {version = "1.0", default-features = false, features = ["rt-multi-thread", "macros"]} +tokio = {version = "1.38", default-features = false, features = ["rt-multi-thread", "macros"]} tracing = {version = "0.1.20"} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index d0b3a64..fba479c 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -45,13 +45,38 @@ fn parse_attrs(attrs: Vec) -> syn::Result<(AttributeArgs, Vec syn::Result { - let inner_test = if attr.is_empty() { - quote! { ::core::prelude::v1::test } - } else { - attr.into() +// Check whether given attribute is a test attribute of forms: +// * `#[test]` +// * `#[core::prelude::*::test]` or `#[::core::prelude::*::test]` +// * `#[std::prelude::*::test]` or `#[::std::prelude::*::test]` +fn is_test_attribute(attr: &Attribute) -> bool { + let path = match &attr.meta { + syn::Meta::Path(path) => path, + _ => return false, }; + let candidates = [ + ["core", "prelude", "*", "test"], + ["std", "prelude", "*", "test"], + ]; + if path.leading_colon.is_none() + && path.segments.len() == 1 + && path.segments[0].arguments.is_none() + && path.segments[0].ident == "test" + { + return true; + } else if path.segments.len() != candidates[0].len() { + return false; + } + candidates.into_iter().any(|segments| { + path + .segments + .iter() + .zip(segments) + .all(|(segment, path)| segment.arguments.is_none() && (path == "*" || segment.ident == path)) + }) +} +fn try_test(attr: TokenStream, input: ItemFn) -> syn::Result { let ItemFn { attrs, vis, @@ -63,9 +88,23 @@ fn try_test(attr: TokenStream, input: ItemFn) -> syn::Result { let logging_init = expand_logging_init(&attribute_args); let tracing_init = expand_tracing_init(&attribute_args); + let (inner_test, generated_test) = if attr.is_empty() { + let has_test = ignored_attrs.iter().any(is_test_attribute); + let generated_test = if has_test { + quote! {} + } else { + quote! { #[::core::prelude::v1::test]} + }; + (quote! {}, generated_test) + } else { + let attr = Tokens::from(attr); + (quote! { #[#attr] }, quote! {}) + }; + let result = quote! { - #[#inner_test] + #inner_test #(#ignored_attrs)* + #generated_test #vis #sig { // We put all initialization code into a separate module here in // order to prevent potential ambiguities that could result in diff --git a/tests/mod.rs b/tests/mod.rs index ecc2a8a..bb9486d 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -5,6 +5,7 @@ use tokio::runtime::Builder; +use rstest::rstest; use tracing::debug; use tracing::error; use tracing::info; @@ -57,6 +58,49 @@ fn with_inner_test_attribute_and_test_args_and_panic(x: i8, _y: i8) { assert_eq!(x, 0); } +#[test_log::test] +#[test] +fn with_existing_test_attribute() {} + +#[test_log::test] +#[::core::prelude::v1::test] +fn with_existing_generated_test_attribute() {} + +#[tokio::test] +#[test_log::test] +async fn with_append_test_attribute_and_async() { + assert_eq!(async { 42 }.await, 42) +} + +#[rstest] +#[case(-2, -4)] +#[case(-2, -5)] +#[test_log::test] +fn with_append_test_attribute_and_test_args(#[case] x: i8, #[case] _y: i8) { + assert_eq!(x, -2); +} + +#[rstest] +#[case(-2, -4)] +#[case(-3, -4)] +#[test_log::test] +// Applied to all cases, must not come before `rstest`, see https://github.com/la10736/rstest/issues/210 +#[should_panic] // https://docs.rs/rstest/0.25.0/rstest/attr.rstest.html#use-specific-case-attributes +fn with_append_test_attribute_and_test_args_and_panic(#[case] x: i8, #[case] _y: i8) { + assert_eq!(x, 0); +} + +#[rstest] +#[case(-2, -4)] +#[case(-3, -4)] +#[tokio::test] +#[test_log::test] +// Applied to all cases, must not come before `rstest`, see https://github.com/la10736/rstest/issues/210 +#[should_panic] // https://docs.rs/rstest/0.25.0/rstest/attr.rstest.html#use-specific-case-attributes +async fn with_append_test_attribute_and_test_args_and_panic_async(#[case] x: i8, #[case] _y: i8) { + assert_eq!(x, 0); +} + #[instrument] async fn instrumented(input: usize) -> usize { info!("input = {}", input);