Skip to content

Commit

Permalink
add arbitrary + snapshots
Browse files Browse the repository at this point in the history
  • Loading branch information
cameron1024 committed Jul 12, 2024
1 parent 43ac2d0 commit 5e12c3e
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 0 deletions.
112 changes: 112 additions & 0 deletions proptest-macro/src/property_test/codegen/arbitrary.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use super::*;
use quote::quote_spanned;

/// Generate the arbitrary impl for the struct
pub(super) fn gen_arbitrary_impl(
fn_name: &Ident,
args: &[Argument],
) -> TokenStream {
if args.iter().all(|arg| arg.strategy.is_none()) {
no_custom_strategies(fn_name, args)
} else {
custom_strategies(fn_name, args)
}
}

// we can avoid boxing strategies if there are no custom strategies, since we have types written
// out in function args
//
// If there are custom strategies, we can't write the type, because we're only provided the
// expression for the strategy (e.g. `#[strategy = my_custom_strategy()]` doesn't tell us the
// return type of `my_custom_strategy`). In these cases, we just use `BoxedStrategy<Self>`
fn no_custom_strategies(fn_name: &Ident, args: &[Argument]) -> TokenStream {
let arg_types = args.iter().map(|arg| {
let ty = &arg.pat_ty.ty;
quote!(#ty,)
});

let arg_types = quote! { #(#arg_types)* };

let arg_names = args.iter().enumerate().map(|(index, arg)| {
let name = nth_field_name(arg.pat_ty.span(), index);
quote!(#name,)
});

let arg_names = quote! { #(#arg_names)* };

let strategy_type = quote! {
::proptest::strategy::Map<::proptest::arbitrary::StrategyFor<(#arg_types)>, fn((#arg_types)) -> Self>
};

let strategy_expr = quote! {
use ::proptest::strategy::Strategy;
::proptest::prelude::any::<(#arg_types)>().prop_map(|(#arg_names)| Self { #arg_names })
};

arbitrary_shared(fn_name, strategy_type, strategy_expr)
}

// if we have `fn foo(#[strategy = x] a: i32, b: i32) {}`, we want to generate something like this:
// ```ignore
// impl Arbitrary for FooArgs {
// type Parameters = ();
// type Strategy = BoxedStrategy<Self>;
//
// fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
// (x, any::<i32>()).prop_map(|(a, b)| Self { a, b }).boxed()
// }
// }
// ```
fn custom_strategies(fn_name: &Ident, args: &[Argument]) -> TokenStream {
let arg_strategies = args.iter().map(|arg| {
arg.strategy
.as_ref()
.map(|s| s.to_token_stream())
.unwrap_or_else(|| {
let ty = &arg.pat_ty.ty;
quote_spanned! {
ty.span() => ::proptest::prelude::any::<#ty>()
}
})
});

let arg_names: TokenStream = args
.iter()
.enumerate()
.map(|(index, arg)| {
let name = nth_field_name(arg.pat_ty.span(), index);
quote!(#name,)
})
.collect();
let arg_names = &arg_names;

let strategy_expr = quote! {
use ::proptest::strategy::Strategy;
(#(#arg_strategies),*).prop_map(|(#arg_names)| Self { #arg_names }).boxed()
};

let strategy_type = quote! {
::proptest::strategy::BoxedStrategy<Self>
};
arbitrary_shared(fn_name, strategy_type, strategy_expr)
}

/// shared code between both boxed and unboxed paths
fn arbitrary_shared(
fn_name: &Ident,
strategy_type: TokenStream,
strategy_expr: TokenStream,
) -> TokenStream {
let struct_name = struct_name(fn_name);

quote! {
impl ::proptest::prelude::Arbitrary for #struct_name {
type Parameters = ();
type Strategy = #strategy_type;

fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
#strategy_expr
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: proptest-macro/src/property_test/codegen/mod.rs
expression: arb.to_string()
---
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 number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
source: proptest-macro/src/property_test/tests/snapshot_tests.rs
expression: formatted
---
#[test]
fn foo() {
#[derive(Debug)]
struct FooArgs {
field0: i32,
field1: String,
}
impl ::proptest::prelude::Arbitrary for FooArgs {
type Parameters = ();
type Strategy = ::proptest::strategy::BoxedStrategy<Self>;
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
use ::proptest::strategy::Strategy;
(123, a + more()("complex") - expression!())
.prop_map(|(field0, field1)| Self { field0, field1 })
}
}
let config = ::proptest::test_runner::Config {
test_name: Some(concat!(module_path!(), "::", stringify!($test_name))),
source_file: Some(file!()),
..::proptest::test_runner::Config::default()
};
let mut runner = ::proptest::test_runner::TestRunner::new(config);
let result = runner
.run(
&::proptest::strategy::Strategy::prop_map(
::proptest::prelude::any::<FooArgs>(),
|values| {
::proptest::sugar::NamedArguments(stringify!(FooArgs), values)
},
),
|::proptest::sugar::NamedArguments(_, FooArgs { field0: x, field1: y })| {
let result = {
let x = 1;
};
let _ = result;
Ok(())
},
);
match result {
Ok(()) => {}
Err(e) => panic!("{}", e),
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
source: proptest-macro/src/property_test/tests/snapshot_tests.rs
expression: formatted
---
#[test]
fn foo() {
#[derive(Debug)]
struct FooArgs {
field0: i32,
field1: String,
}
impl ::proptest::prelude::Arbitrary for FooArgs {
type Parameters = ();
type Strategy = ::proptest::strategy::BoxedStrategy<Self>;
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
use ::proptest::strategy::Strategy;
(::proptest::prelude::any::<i32>, a + more()("complex") - expression!())
.prop_map(|(field0, field1)| Self { field0, field1 })
}
}
let config = ::proptest::test_runner::Config {
test_name: Some(concat!(module_path!(), "::", stringify!($test_name))),
source_file: Some(file!()),
..::proptest::test_runner::Config::default()
};
let mut runner = ::proptest::test_runner::TestRunner::new(config);
let result = runner
.run(
&::proptest::strategy::Strategy::prop_map(
::proptest::prelude::any::<FooArgs>(),
|values| {
::proptest::sugar::NamedArguments(stringify!(FooArgs), values)
},
),
|::proptest::sugar::NamedArguments(_, FooArgs { field0: x, field1: y })| {
let result = {
let x = 1;
};
let _ = result;
Ok(())
},
);
match result {
Ok(()) => {}
Err(e) => panic!("{}", e),
}
}

0 comments on commit 5e12c3e

Please sign in to comment.