Skip to content

Commit ad47064

Browse files
committed
Append generated test macro so next test macros are aware of it
This is an attempt to improve capabilities among test macros to avoid duplicated test runs which is rare to be aware of. The rationale is simple: procedure of attribute macro see only attributes following it. Macros next to processing macro will not see generated macro if it is generated in-place. So, instead of generating test macro in-place, appending generated test macro will let macros next to processing macro have chance to react, for example, not generate test macro if there is already one. Without the fix, the new test will run twice. See also tokio-rs/tokio#6497, d-e-s-o/test-log#46, frondeus/test-case#143, la10736/rstest#291, google/googletest-rust#561, kezhuw/stuck#53.
1 parent b329f56 commit ad47064

File tree

3 files changed

+52
-3
lines changed

3 files changed

+52
-3
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ proptest = "1.6.0"
2828
trybuild = "1.0.96"
2929
tokio = { version = "1.38.0", features = ["rt-multi-thread"] }
3030
anyhow = "1.0.97"
31+
googletest = "0.14.0"

src/proptest_fn.rs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,41 @@ use crate::syn_utils::{Arg, Args};
44
use proc_macro2::{Span, TokenStream};
55
use quote::{quote, ToTokens};
66
use syn::{
7-
parse2, parse_quote, parse_str, spanned::Spanned, token, Block, Expr, Field, FieldMutability,
8-
FnArg, Ident, ItemFn, LitStr, Pat, Result, ReturnType, Visibility,
7+
parse2, parse_quote, parse_str, spanned::Spanned, token, Attribute, Block, Expr, Field,
8+
FieldMutability, FnArg, Ident, ItemFn, LitStr, Pat, Result, ReturnType, Visibility,
99
};
1010

11+
// Check whether given attribute is a test attribute of forms:
12+
// * `#[test]`
13+
// * `#[core::prelude::*::test]` or `#[::core::prelude::*::test]`
14+
// * `#[std::prelude::*::test]` or `#[::std::prelude::*::test]`
15+
fn is_test_attribute(attr: &Attribute) -> bool {
16+
let path = match &attr.meta {
17+
syn::Meta::Path(path) => path,
18+
_ => return false,
19+
};
20+
const CANDIDATES_LEN: usize = 4;
21+
22+
let candidates: [[&str; CANDIDATES_LEN]; 2] = [
23+
["core", "prelude", "*", "test"],
24+
["std", "prelude", "*", "test"],
25+
];
26+
if path.leading_colon.is_none()
27+
&& path.segments.len() == 1
28+
&& path.segments[0].arguments.is_none()
29+
&& path.segments[0].ident == "test"
30+
{
31+
return true;
32+
} else if path.segments.len() != candidates[0].len() {
33+
return false;
34+
}
35+
candidates.into_iter().any(|segments| {
36+
path.segments.iter().zip(segments).all(|(segment, path)| {
37+
segment.arguments.is_none() && (path == "*" || segment.ident == path)
38+
})
39+
})
40+
}
41+
1142
pub fn build_proptest(attr: TokenStream, mut item_fn: ItemFn) -> Result<TokenStream> {
1243
let mut attr_args = None;
1344
if !attr.is_empty() {
@@ -64,6 +95,12 @@ pub fn build_proptest(attr: TokenStream, mut item_fn: ItemFn) -> Result<TokenStr
6495
};
6596
item_fn.sig.inputs = parse_quote! { input: #args_type_ident };
6697
item_fn.block = Box::new(parse2(block)?);
98+
if !item_fn.attrs.iter().any(is_test_attribute) {
99+
let test_attr: Attribute = parse_quote! {
100+
#[::core::prelude::v1::test]
101+
};
102+
item_fn.attrs.push(test_attr);
103+
}
67104
let args_fields = args.iter().map(|arg| &arg.field);
68105
let config = to_proptest_config(config_args);
69106
let ts = quote! {
@@ -75,7 +112,6 @@ pub fn build_proptest(attr: TokenStream, mut item_fn: ItemFn) -> Result<TokenStr
75112
#[cfg(test)]
76113
proptest::proptest! {
77114
#config
78-
#[test]
79115
#item_fn
80116
}
81117
};

tests/proptest_fn.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ use proptest::{prelude::ProptestConfig, prop_assert};
66
use test_strategy::proptest;
77
use tokio::task::yield_now;
88

9+
use googletest::expect_that;
10+
use googletest::gtest;
11+
use googletest::matchers::*;
12+
use googletest::verify_that;
13+
914
#[proptest]
1015
fn example(_x: u32, #[strategy(1..10u32)] y: u32, #[strategy(0..#y)] z: u32) {
1116
assert!(1 <= y);
@@ -291,3 +296,10 @@ async fn anyhow_result_bail_async(#[strategy(1..10u8)] x: u8) -> anyhow::Result<
291296
yield_now().await;
292297
anyhow::bail!("error");
293298
}
299+
300+
#[proptest]
301+
#[gtest]
302+
fn googletest_result(#[strategy(1..10u8)] x: u8) -> googletest::Result<()> {
303+
expect_that!(x, ge(1));
304+
verify_that!(x, lt(10))
305+
}

0 commit comments

Comments
 (0)