Skip to content

Commit fb4f8d8

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 fb4f8d8

File tree

3 files changed

+97
-8
lines changed

3 files changed

+97
-8
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: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,40 @@ 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+
const CANDIDATES_LEN: usize = 4;
58+
59+
let candidates: [[&str; CANDIDATES_LEN]; 2] = [
60+
["core", "prelude", "*", "test"],
61+
["std", "prelude", "*", "test"],
62+
];
63+
if path.leading_colon.is_none()
64+
&& path.segments.len() == 1
65+
&& path.segments[0].arguments.is_none()
66+
&& path.segments[0].ident == "test"
67+
{
68+
return true;
69+
} else if path.segments.len() != candidates[0].len() {
70+
return false;
71+
}
72+
candidates.into_iter().any(|segments| {
73+
path
74+
.segments
75+
.iter()
76+
.zip(segments)
77+
.all(|(segment, path)| segment.arguments.is_none() && (path == "*" || segment.ident == path))
78+
})
79+
}
5480

81+
fn try_test(attr: TokenStream, input: ItemFn) -> syn::Result<Tokens> {
5582
let ItemFn {
5683
attrs,
5784
vis,
@@ -63,9 +90,23 @@ fn try_test(attr: TokenStream, input: ItemFn) -> syn::Result<Tokens> {
6390
let logging_init = expand_logging_init(&attribute_args);
6491
let tracing_init = expand_tracing_init(&attribute_args);
6592

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

tests/mod.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55

66
use tokio::runtime::Builder;
77

8+
use rstest::rstest;
89
use tracing::debug;
910
use tracing::error;
1011
use tracing::info;
1112
use tracing::instrument;
1213

13-
1414
mod something {
1515
pub type Error = String;
1616
}
@@ -57,6 +57,53 @@ fn with_inner_test_attribute_and_test_args_and_panic(x: i8, _y: i8) {
5757
assert_eq!(x, 0);
5858
}
5959

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

0 commit comments

Comments
 (0)