-
Notifications
You must be signed in to change notification settings - Fork 163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#[proptest] Attribute Macro #153
Comments
cc #149 and #97 (comment). A We'll need to think about the details like:
Also, if you just write: #[proptest]
fn addition_is_commutative(x: u8, y: u8) {
...
} then presumably #[proptest(gen_function_arn())] The notation here for passing custom strategies seems suboptimal but serviceable in the interim. It could be improved with rust-lang/rust#60406 but that isn't stable yet (tho one some bugs are fixed it could be stabilized relatively quickly after baking for 6 weeks). This would allow e.g.: #[proptest]
fn function_arn(
#[strategy = gen_function_arn()]
arn: FunctionArn
) { We also cannot support the |
I'd be interested in hearing your thoughts on these! I've only used a minimal portion of proptest, so beyond what is in the Lambda runtime I linked, I don't really know how those other features of proptest ought be supported.
I like that approach!
Yeah, I'm not a fan of my current approach either. I think the correct solution/notation is, as you said, rust-lang/rust#60406. Would you be okay with a minimal, off-by-default feature flag that allows users to create & pass a custom strategy in the serviceable form I described above?
I don't really have a strong opinion on tackling this, as I wasn't a big fan of the |
Here are my thoughts: How to most ergonomically provide custom strategiesThis is mostly the discussion re. How configurations should be passedI figure we can take this design decision straight from #[proptest]
#[proptest_config(ProptestConfig { cases: 99, .. ProptestConfig::default() })]
fn foo(...) { ... } Tho we could make this "more first class": #[proptest]
#[proptest::cases = 99]
fn foo(...) { ... } or: #[proptest]
#[proptest_config(cases = 99)]
fn foo(...) { ... } How function return types should be dealt with in terms of
|
There's another possibility for how to provide custom strategies, which would be to encode them at the type level. Right now something like: #[proptest]
fn test_foo(x: u8) {
assert!(x > 0);
} is nice because it's syntactically a valid function (which is needed if you want to attach an attribute macro to it), but it only works if the type implements If I wanted to use a custom strategy that only produces even numbers, what if we could aim for a syntax like: #[proptest]
fn test_foo(x: Even<u8>) {
assert!(x > 0);
} Here we encode the strategy as the hypothetical What would be wonky here is that inside the body of the function, Another nice feature is that this is something I think we could do today with Rust stable: you'd just remove the requirement in Thoughts? |
This is standard practice in the Haskell community with QuickCheck; e.g. is_even :: Even -> bool
is_even (Even x) = x `mod` 2 == 0 but here standard pattern matching is used to extract out the
I do think this is surprising, not just initially, in that it breaks some basic things you expect about Rust. Consider also that someone not familiar with the codebase might be thrown into it.
This was a pretty intentional design choice. I expect that if something says Instead, taking inspiration from Haskell's QuickCheck, you can do something like: #[proptest]
fn is_even(Even(x): Even<u8>) {
assert!(x % 2 == 0);
} or if the older version of rust-lang/rfcs#2522 was implemented you could also write: #[proptest]
fn is_even(Even(x: u8)) {
assert!(x % 2 == 0);
} (this would only require syntactic support from Rust, semantic restrictions can still be in place) (It should also be relatively easy to |
Just a few notes on syntax that I ran accross when considering this. All of the below is syntactically valid today (i.e. it will work under It's possible to (ab)use const generics syntax to get something close to the current #[proptest]
fn my_test(bytes: In<{vec(any::<u8>(), 0..10_000)}>) {} Having the strategy as an argument attribute is also clean, and makes the actual type of the parameter obvious: #[proptest]
fn my_test(
#[proptest(strategy = vec(any::<u8>(), 0..10_000))]
bytes: Vec<u8>,
) {} Side note: alternate test runners (e.g. #179) aren't too hard to support with an attribute: #[proptest(tokio::test)]
async fn my_test(
#[proptest(strategy = vec(any::<u8>(), 0..10_000))]
bytes: Vec<u8>,
) {} |
I just came across the test-strategy crate, by @frozenlib. Quite frankly, it looks great, and it would be amazing to have this kind of functionality in |
I use test-strategy extensively - I significantly prefer the attribute macro to
As for syntax, I'd like to see something like: #[proptest]
fn simple(x: i32) { ... }
#[proptest(
cases = 50,
// any other field of `ProptestConfig`
async = tokio::test,
)]
async fn complex(
#[strategy = foo_strategy()]
foo: Foo,
#[strategy = bar_strategy()]
bar: Bar,
) { ... } Of course, exact details are up for bikeshedding |
The feature also would let rstest to integrate with proptest, see la10736/rstest#30 (comment) |
I've begun work on this, there's a WIP PR, I'll have a look at that issue, but if there's any specifics that you we should bear in mind while implementing it, that's the best place to do it 👍 |
Hi! I wrote a kinda rough implemenation of a
#[proptest]
attribute macro in awslabs/aws-lambda-rust-runtime#111. It accepts animpl Strategy<Value = T>
, and I wanted to see if there's interest in me sending a pull request to introduce it here. It looks a bit like this:The text was updated successfully, but these errors were encountered: