Skip to content

Commit 5e12c3e

Browse files
committed
add arbitrary + snapshots
1 parent 43ac2d0 commit 5e12c3e

4 files changed

+211
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
use super::*;
2+
use quote::quote_spanned;
3+
4+
/// Generate the arbitrary impl for the struct
5+
pub(super) fn gen_arbitrary_impl(
6+
fn_name: &Ident,
7+
args: &[Argument],
8+
) -> TokenStream {
9+
if args.iter().all(|arg| arg.strategy.is_none()) {
10+
no_custom_strategies(fn_name, args)
11+
} else {
12+
custom_strategies(fn_name, args)
13+
}
14+
}
15+
16+
// we can avoid boxing strategies if there are no custom strategies, since we have types written
17+
// out in function args
18+
//
19+
// If there are custom strategies, we can't write the type, because we're only provided the
20+
// expression for the strategy (e.g. `#[strategy = my_custom_strategy()]` doesn't tell us the
21+
// return type of `my_custom_strategy`). In these cases, we just use `BoxedStrategy<Self>`
22+
fn no_custom_strategies(fn_name: &Ident, args: &[Argument]) -> TokenStream {
23+
let arg_types = args.iter().map(|arg| {
24+
let ty = &arg.pat_ty.ty;
25+
quote!(#ty,)
26+
});
27+
28+
let arg_types = quote! { #(#arg_types)* };
29+
30+
let arg_names = args.iter().enumerate().map(|(index, arg)| {
31+
let name = nth_field_name(arg.pat_ty.span(), index);
32+
quote!(#name,)
33+
});
34+
35+
let arg_names = quote! { #(#arg_names)* };
36+
37+
let strategy_type = quote! {
38+
::proptest::strategy::Map<::proptest::arbitrary::StrategyFor<(#arg_types)>, fn((#arg_types)) -> Self>
39+
};
40+
41+
let strategy_expr = quote! {
42+
use ::proptest::strategy::Strategy;
43+
::proptest::prelude::any::<(#arg_types)>().prop_map(|(#arg_names)| Self { #arg_names })
44+
};
45+
46+
arbitrary_shared(fn_name, strategy_type, strategy_expr)
47+
}
48+
49+
// if we have `fn foo(#[strategy = x] a: i32, b: i32) {}`, we want to generate something like this:
50+
// ```ignore
51+
// impl Arbitrary for FooArgs {
52+
// type Parameters = ();
53+
// type Strategy = BoxedStrategy<Self>;
54+
//
55+
// fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
56+
// (x, any::<i32>()).prop_map(|(a, b)| Self { a, b }).boxed()
57+
// }
58+
// }
59+
// ```
60+
fn custom_strategies(fn_name: &Ident, args: &[Argument]) -> TokenStream {
61+
let arg_strategies = args.iter().map(|arg| {
62+
arg.strategy
63+
.as_ref()
64+
.map(|s| s.to_token_stream())
65+
.unwrap_or_else(|| {
66+
let ty = &arg.pat_ty.ty;
67+
quote_spanned! {
68+
ty.span() => ::proptest::prelude::any::<#ty>()
69+
}
70+
})
71+
});
72+
73+
let arg_names: TokenStream = args
74+
.iter()
75+
.enumerate()
76+
.map(|(index, arg)| {
77+
let name = nth_field_name(arg.pat_ty.span(), index);
78+
quote!(#name,)
79+
})
80+
.collect();
81+
let arg_names = &arg_names;
82+
83+
let strategy_expr = quote! {
84+
use ::proptest::strategy::Strategy;
85+
(#(#arg_strategies),*).prop_map(|(#arg_names)| Self { #arg_names }).boxed()
86+
};
87+
88+
let strategy_type = quote! {
89+
::proptest::strategy::BoxedStrategy<Self>
90+
};
91+
arbitrary_shared(fn_name, strategy_type, strategy_expr)
92+
}
93+
94+
/// shared code between both boxed and unboxed paths
95+
fn arbitrary_shared(
96+
fn_name: &Ident,
97+
strategy_type: TokenStream,
98+
strategy_expr: TokenStream,
99+
) -> TokenStream {
100+
let struct_name = struct_name(fn_name);
101+
102+
quote! {
103+
impl ::proptest::prelude::Arbitrary for #struct_name {
104+
type Parameters = ();
105+
type Strategy = #strategy_type;
106+
107+
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
108+
#strategy_expr
109+
}
110+
}
111+
}
112+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
source: proptest-macro/src/property_test/codegen/mod.rs
3+
expression: arb.to_string()
4+
---
5+
impl :: proptest :: prelude :: Arbitrary for FooArgs { type Parameters = () ; type Strategy = :: proptest :: strategy :: Map < :: proptest :: arbitrary :: StrategyFor < (i32 , u8 ,) > , fn ((i32 , u8 ,)) -> Self > ; fn arbitrary_with (() : Self :: Parameters) -> Self :: Strategy { use :: proptest :: strategy :: Strategy ; :: proptest :: prelude :: any :: < (i32 , u8 ,) > () . prop_map (| (field0 , field1 ,) | Self { field0 , field1 , }) } }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
source: proptest-macro/src/property_test/tests/snapshot_tests.rs
3+
expression: formatted
4+
---
5+
#[test]
6+
fn foo() {
7+
#[derive(Debug)]
8+
struct FooArgs {
9+
field0: i32,
10+
field1: String,
11+
}
12+
impl ::proptest::prelude::Arbitrary for FooArgs {
13+
type Parameters = ();
14+
type Strategy = ::proptest::strategy::BoxedStrategy<Self>;
15+
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
16+
use ::proptest::strategy::Strategy;
17+
(123, a + more()("complex") - expression!())
18+
.prop_map(|(field0, field1)| Self { field0, field1 })
19+
}
20+
}
21+
let config = ::proptest::test_runner::Config {
22+
test_name: Some(concat!(module_path!(), "::", stringify!($test_name))),
23+
source_file: Some(file!()),
24+
..::proptest::test_runner::Config::default()
25+
};
26+
let mut runner = ::proptest::test_runner::TestRunner::new(config);
27+
let result = runner
28+
.run(
29+
&::proptest::strategy::Strategy::prop_map(
30+
::proptest::prelude::any::<FooArgs>(),
31+
|values| {
32+
::proptest::sugar::NamedArguments(stringify!(FooArgs), values)
33+
},
34+
),
35+
|::proptest::sugar::NamedArguments(_, FooArgs { field0: x, field1: y })| {
36+
let result = {
37+
let x = 1;
38+
};
39+
let _ = result;
40+
Ok(())
41+
},
42+
);
43+
match result {
44+
Ok(()) => {}
45+
Err(e) => panic!("{}", e),
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
source: proptest-macro/src/property_test/tests/snapshot_tests.rs
3+
expression: formatted
4+
---
5+
#[test]
6+
fn foo() {
7+
#[derive(Debug)]
8+
struct FooArgs {
9+
field0: i32,
10+
field1: String,
11+
}
12+
impl ::proptest::prelude::Arbitrary for FooArgs {
13+
type Parameters = ();
14+
type Strategy = ::proptest::strategy::BoxedStrategy<Self>;
15+
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
16+
use ::proptest::strategy::Strategy;
17+
(::proptest::prelude::any::<i32>, a + more()("complex") - expression!())
18+
.prop_map(|(field0, field1)| Self { field0, field1 })
19+
}
20+
}
21+
let config = ::proptest::test_runner::Config {
22+
test_name: Some(concat!(module_path!(), "::", stringify!($test_name))),
23+
source_file: Some(file!()),
24+
..::proptest::test_runner::Config::default()
25+
};
26+
let mut runner = ::proptest::test_runner::TestRunner::new(config);
27+
let result = runner
28+
.run(
29+
&::proptest::strategy::Strategy::prop_map(
30+
::proptest::prelude::any::<FooArgs>(),
31+
|values| {
32+
::proptest::sugar::NamedArguments(stringify!(FooArgs), values)
33+
},
34+
),
35+
|::proptest::sugar::NamedArguments(_, FooArgs { field0: x, field1: y })| {
36+
let result = {
37+
let x = 1;
38+
};
39+
let _ = result;
40+
Ok(())
41+
},
42+
);
43+
match result {
44+
Ok(()) => {}
45+
Err(e) => panic!("{}", e),
46+
}
47+
}

0 commit comments

Comments
 (0)