-
Notifications
You must be signed in to change notification settings - Fork 161
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
Feature: add Arbitrary. Allow proptest!(..) to omit strategy when T : Arbitrary #7
Comments
I will start work on the |
There's a lot of facets here; hopefully I cover all of them.
I don't by any means disagree with this, but it would be nice to have some data for how often this applies in real code using things like Hypothesis/proptest. For what it's worth, here's some info for what I've worked on:
Maybe I'll keep calling it
I can't say I particularly care for this; I think maintaining the clarity that a I'd also question how useful it is to do this for things like integers; I'm not sure how many cases there are where testing with literally arbitrary integers is useful. IIRC, quickcheck defaults to the range 0..100.
This sounds like a very good idea, and would particularly eliminate a lot of boilerplate in ensync.
The rules don't take into account recursive types (e.g., I think any custom derive would also need a way to override the strategy for a particular field, since the type will often have its own constraints on internal state. Something akin to
IMO this is the single biggest weakness in proptest right now; the compiler can't help you if you forget a variant when defining the strategy.
I don't have anything to say about this as it pertains to the original proposal that I haven't already voiced. Overall, this is something I feel reasonably positively about. My biggest concern is that I'm not sure how practical this would be, but to illustrate: pub trait Arbitrary where Self : Sized + Debug {
// Parameters that can be passed to the implementation. The custom derive
// could default this to `()`.
type Parameters : Default;
// As in the issue description.
type ValueTree: ValueTree<Value = Self>;
type Strategy: Strategy<Value = Self::ValueTree>;
fn arbitrary(parameters: Self::Parameters) -> Self::Strategy;
}
// Simple use of auto-derive
#[derive(Debug, Arbitrary)]
struct SimpleStruct {
string: String,
int: i64,
}
// Generated impl
impl Arbitrary for SimpleStruct {
type Parameters = ();
type ValueTree = /* big type */;
type Strategy = /* big type */;
fn arbitrary(parameters: ()) -> Self::Strategy {
// NB This won't compile because the type is not nameable
(String::arbitrary(String::Parameters::default()),
i64::arbitrary(i64::Parameters::default()))
.prop_map(|string, int| SimpleStruct { string, int })
}
}
// Use custom parameters
#[derive(Debug, Arbitrary)]
#[proptest(paremeters = ComplexParms)]
struct ComplexStruct {
// Embedding Rust code in string literals is probably a bad idea, but
// doing it here for conciseness.
#[proptest(strategy = "parameters.string")]
string: String,
#[proptest(strategy = "0..parameters.int_max")]
int: i64,
}
struct ComplexParms {
string: &'static str,
int_max: i64,
}
impl Default for ComplexParms {
fn default() -> Self {
ComplexParms {
string: "[0-9]{4}",
int_max: 42,
}
}
}
// Generated impl
impl Arbitrary for ComplexStruct {
type Parameters = ComplexParms;
type ValueTree = /* big type */;
type Strategy = /* big type */;
fn arbitrary(parameters: ()) -> Self::Strategy {
(parameters.string, 0..parameters.int_max)
.prop_map(|string, int| ComplexStruct { string, int })
}
} In any case, thanks for being willing to take a stab at this yourself. I look forward to seeing what you come up with! |
I think it's not an all or nothing proposition - some types will be canonical, and some won't. For simpler types, I think this is more true, and since they are a pain point, it's nice to get rid of them. In my use case, I have a lexer and parser I'm testing for read/show identity. Edit: In any case where you have an AST (lots of enums) you will probably have a canonical strategy.
I think
I think stylistic choice is good here - I've so far added a function Edit: Some prefer type based approach with newtypes and some prefer the value based approach.
You can always use newtypes which is really a good idea if your data type really only handles a specific range - but sure, some types in the middle of deriving you'd want to customize it to use a different Strategy.
You could use sane defaults and allow depth and such to be customized with fields. Perhaps a
I agree completely. I thought of this a few hours ago but forgot to update the issue =) Tho, I'm not sure if it is currently possible to custom derive and have custom attributes as well in stable Rust - if not, perhaps it should be done when possible...?
Right =) This was the main impetus of this issue and the drive for the trait and custom derive proc macro.
That's a nice idea! I'll try this idea out and see how it works with type inference, etc. I think it should work out fine. |
That would be really useful! Currently I don't have a solid understanding of where to place strategies for my types, especially because I can't mirror |
@matklad =) I've done some initial work at the temporary crate |
So... most of the work is done I guess and lives here: https://github.com/Centril/proptest-arbitrary/tree/master/src How do you want to go about merging this into proptest? Should I make one large PR or several small ones? And where should I've done a bunch of work on deriving too, and it works - I haven't fixed the last bit for recursive types, but I have a good idea of how to do it. I'll also have to update proptest_derive given the new syn 0.12 update. |
Wow, that's.. quite exhaustive coverage. I'd like to read through everything over the course of the week or so to get the full picture, though my initial thought is to put it in If you want to go ahead and put this in one large PR, that'd be fine too. |
Cool =) I'll put this in a large PR under |
Looks like I forgot to close this. This feature became available in 0.5.0. |
Notes
This issue is a more long-term issue and may, to be really useful, depend on landing procedural macros in Rust stable, or provide a nightly only side-crate for some parts (3.).
Edit: Apparently procedural macros already allow for custom derive in stable
Motivation
In a lot of cases, there's a canonical way to generate an object of type
T
.In such cases, you could simply encode the canonical strategy for such a
T
in the standard quickcheckArbitrary
trait.The benefits of doing this are:
1.
Increased familiarity for quickcheck users by bridging the gap.
2.
The creation of the
Strategy
inproptest!
can be omitted, andcan be turned into:
or even the more standard in quickcheck:
3.
Custom "deriving"
Arbitrary
, by macro (to decouple testing from main logic), or by standalone derive if Rust ever gets that, becomes possible in a lot of cases.Rules for deriving by induction:
Arbitrary
by simply always returning the unit.Arbitrary
if their factors areArbitrary
.Arbitrary
if each product type in each variant in a sum type isArbitrary
. Variants with no inner product are just unit types.Ergonomics
It is considerably more ergonomic to use a macro to generate strategies compared to simply re-iterating the variants in the
enum
.The trait
The trait could look like:
Or, in a future Rust version:
The text was updated successfully, but these errors were encountered: