Skip to content

Commit c6ccb6c

Browse files
committed
Append #[::core::prelude::v1::test] only if no test macros exist
This way we could avoid duplicated test runs if `#[test]` variants already exist See also frondeus/test-case#101, frondeus/test-case#143 and tokio-rs/tokio#6497. Closes d-e-s-o#35.
1 parent 81e1696 commit c6ccb6c

File tree

3 files changed

+91
-7
lines changed

3 files changed

+91
-7
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ env_logger = {version = "0.11", default-features = false, optional = true}
5151

5252
[dev-dependencies]
5353
logging = {version = "0.4.8", package = "log"}
54+
rstest = "0.25.0"
5455
test-case = {version = "3.1"}
55-
tokio = {version = "1.0", default-features = false, features = ["rt-multi-thread", "macros"]}
56+
tokio = {version = "1.38", default-features = false, features = ["rt-multi-thread", "macros"]}
5657
tracing = {version = "0.1.20"}

macros/src/lib.rs

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,38 @@ fn parse_attrs(attrs: Vec<Attribute>) -> syn::Result<(AttributeArgs, Vec<Attribu
4545
}
4646
}
4747

48-
fn try_test(attr: TokenStream, input: ItemFn) -> syn::Result<Tokens> {
49-
let inner_test = if attr.is_empty() {
50-
quote! { ::core::prelude::v1::test }
51-
} else {
52-
attr.into()
48+
// Check whether given attribute is a test attribute of forms:
49+
// * `#[test]`
50+
// * `#[core::prelude::*::test]` or `#[::core::prelude::*::test]`
51+
// * `#[std::prelude::*::test]` or `#[::std::prelude::*::test]`
52+
fn is_test_attribute(attr: &Attribute) -> bool {
53+
let path = match &attr.meta {
54+
syn::Meta::Path(path) => path,
55+
_ => return false,
5356
};
57+
let candidates = [
58+
["core", "prelude", "*", "test"],
59+
["std", "prelude", "*", "test"],
60+
];
61+
if path.leading_colon.is_none()
62+
&& path.segments.len() == 1
63+
&& path.segments[0].arguments.is_none()
64+
&& path.segments[0].ident == "test"
65+
{
66+
return true;
67+
} else if path.segments.len() != candidates[0].len() {
68+
return false;
69+
}
70+
candidates.into_iter().any(|segments| {
71+
path
72+
.segments
73+
.iter()
74+
.zip(segments)
75+
.all(|(segment, path)| segment.arguments.is_none() && (path == "*" || segment.ident == path))
76+
})
77+
}
5478

79+
fn try_test(attr: TokenStream, input: ItemFn) -> syn::Result<Tokens> {
5580
let ItemFn {
5681
attrs,
5782
vis,
@@ -63,9 +88,23 @@ fn try_test(attr: TokenStream, input: ItemFn) -> syn::Result<Tokens> {
6388
let logging_init = expand_logging_init(&attribute_args);
6489
let tracing_init = expand_tracing_init(&attribute_args);
6590

91+
let (inner_test, generated_test) = if attr.is_empty() {
92+
let has_test = ignored_attrs.iter().any(is_test_attribute);
93+
let generated_test = if has_test {
94+
quote! {}
95+
} else {
96+
quote! { #[::core::prelude::v1::test]}
97+
};
98+
(quote! {}, generated_test)
99+
} else {
100+
let attr = Tokens::from(attr);
101+
(quote! { #[#attr] }, quote! {})
102+
};
103+
66104
let result = quote! {
67-
#[#inner_test]
105+
#inner_test
68106
#(#ignored_attrs)*
107+
#generated_test
69108
#vis #sig {
70109
// We put all initialization code into a separate module here in
71110
// order to prevent potential ambiguities that could result in

tests/mod.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
use tokio::runtime::Builder;
77

8+
use rstest::rstest;
89
use tracing::debug;
910
use tracing::error;
1011
use tracing::info;
@@ -57,6 +58,49 @@ fn with_inner_test_attribute_and_test_args_and_panic(x: i8, _y: i8) {
5758
assert_eq!(x, 0);
5859
}
5960

61+
#[test_log::test]
62+
#[test]
63+
fn with_existing_test_attribute() {}
64+
65+
#[test_log::test]
66+
#[::core::prelude::v1::test]
67+
fn with_existing_generated_test_attribute() {}
68+
69+
#[tokio::test]
70+
#[test_log::test]
71+
async fn with_append_test_attribute_and_async() {
72+
assert_eq!(async { 42 }.await, 42)
73+
}
74+
75+
#[rstest]
76+
#[case(-2, -4)]
77+
#[case(-2, -5)]
78+
#[test_log::test]
79+
fn with_append_test_attribute_and_test_args(#[case] x: i8, #[case] _y: i8) {
80+
assert_eq!(x, -2);
81+
}
82+
83+
#[rstest]
84+
#[case(-2, -4)]
85+
#[case(-3, -4)]
86+
#[test_log::test]
87+
// Applied to all cases, must not come before `rstest`, see https://github.com/la10736/rstest/issues/210
88+
#[should_panic] // https://docs.rs/rstest/0.25.0/rstest/attr.rstest.html#use-specific-case-attributes
89+
fn with_append_test_attribute_and_test_args_and_panic(#[case] x: i8, #[case] _y: i8) {
90+
assert_eq!(x, 0);
91+
}
92+
93+
#[rstest]
94+
#[case(-2, -4)]
95+
#[case(-3, -4)]
96+
#[tokio::test]
97+
#[test_log::test]
98+
// Applied to all cases, must not come before `rstest`, see https://github.com/la10736/rstest/issues/210
99+
#[should_panic] // https://docs.rs/rstest/0.25.0/rstest/attr.rstest.html#use-specific-case-attributes
100+
async fn with_append_test_attribute_and_test_args_and_panic_async(#[case] x: i8, #[case] _y: i8) {
101+
assert_eq!(x, 0);
102+
}
103+
60104
#[instrument]
61105
async fn instrumented(input: usize) -> usize {
62106
info!("input = {}", input);

0 commit comments

Comments
 (0)