GithubHelp home page GithubHelp logo

proptest-rs / proptest Goto Github PK

View Code? Open in Web Editor NEW
1.6K 1.6K 151.0 2.89 MB

Hypothesis-like property testing for Rust

License: Apache License 2.0

Rust 88.56% Shell 0.33% Batchfile 0.04% RenderScript 11.00% Nix 0.07%

proptest's People

Contributors

altsysrq avatar andlon avatar atouchet avatar cameron1024 avatar centril avatar cosmichorrordev avatar cuishuang avatar dependabot[bot] avatar dwijnand avatar flyingmutant avatar hamiltop avatar henriiik avatar ignatenkobrain avatar kraag-gorim avatar masonremaley avatar matthew-russo avatar mimoo avatar palfrey avatar paulgrandperrin avatar rex-remind101 avatar rexmas avatar sameer avatar sjackman avatar sunshowers avatar throwaway1037 avatar timstobal avatar tzemanovic avatar udoprog avatar waywardmonkeys avatar zackpierce avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

proptest's Issues

Empty ranges trigger a rand panic: "Uniform::new called with `low >= high"

I'm still reducing this problem, but this is my initial debugging output:

thread 'simd::test::works_as_find_does_for_up_to_and_including_16_bytes' panicked at 'Uniform::new called with `low >= high`', /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/rand-0.5.3/src/distributions/uniform.rs:408:1
    frame #0: 0x00000001001401c4 jetscii-0d882ad9a9b44f91`rust_panic at panicking.rs:559 [opt]
    frame #1: 0x00000001001401a8 jetscii-0d882ad9a9b44f91`std::panicking::rust_panic_with_hook::h32b65389d5fd573e at panicking.rs:531 [opt]
    frame #2: 0x000000010011ac78 jetscii-0d882ad9a9b44f91`std::panicking::begin_panic::hb60ded9f4661ea6e(msg=(data_ptr = "Uniform::new called with `low >= high`", length = 38), file_line_col=0x0000000100201b88) at panicking.rs:445
    frame #3: 0x00000001000a6b4a jetscii-0d882ad9a9b44f91`_$LT$rand..distributions..uniform..UniformInt$LT$usize$GT$$u20$as$u20$rand..distributions..uniform..UniformSampler$GT$::new::h1105c16f3cd996ea(low=0, high=0) at <panic macros>:3
    frame #4: 0x00000001000a998f jetscii-0d882ad9a9b44f91`_$LT$rand..distributions..uniform..Uniform$LT$X$GT$$GT$::new::h7218db1c8c892968(low=0, high=0) at uniform.rs:159
    frame #5: 0x00000001000bb578 jetscii-0d882ad9a9b44f91`proptest::num::sample_uniform::hcf0601c8bb3282a0(run=0x000070000e84b588, range=(start = 0, end = 0)) at num.rs:22
  * frame #6: 0x00000001000bde13 jetscii-0d882ad9a9b44f91`proptest::num::usize::_$LT$impl$u20$proptest..strategy..traits..Strategy$u20$for$u20$core..ops..range..Range$LT$usize$GT$$GT$::new_tree::h17d7a51577af5ebb(self=0x000070000e848f28, runner=0x000070000e84b588) at num.rs:61
    frame #7: 0x000000010003dd53 jetscii-0d882ad9a9b44f91`proptest::tuple::_$LT$impl$u20$proptest..strategy..traits..Strategy$u20$for$u20$$LP$A$C$$u20$B$RP$$GT$::new_tree::hc4c91ac45a92ae1f(self=0x000070000e848f10, runner=0x000070000e84b588) at tuple.rs:47
    frame #8: 0x00000001000558b2 jetscii-0d882ad9a9b44f91`_$LT$proptest..strategy..flatten..FlattenValueTree$LT$S$GT$$GT$::new::he814f2077a7684c7(runner=0x000070000e84b588, meta=Map<proptest::collection::VecValueTree<proptest::num::u8::BinarySearch>, closure> @ 0x000070000e849720) at flatten.rs:96
    frame #9: 0x0000000100054b22 jetscii-0d882ad9a9b44f91`_$LT$proptest..strategy..flatten..Flatten$LT$S$GT$$u20$as$u20$proptest..strategy..traits..Strategy$GT$::new_tree::h00782e56a8c2757f(self=0x0000000101e10130, runner=0x000070000e84b588) at flatten.rs:40
    frame #10: 0x0000000100037108 jetscii-0d882ad9a9b44f91`_$LT$proptest..strategy..map..Map$LT$S$C$$u20$F$GT$$u20$as$u20$proptest..strategy..traits..Strategy$GT$::new_tree::hd891f3b41258e3b5(self=0x0000000101e10130, runner=0x000070000e84b588) at map.rs:55
    frame #11: 0x000000010004f326 jetscii-0d882ad9a9b44f91`_$LT$proptest..strategy..traits..BoxedStrategyWrapper$LT$T$GT$$u20$as$u20$proptest..strategy..traits..Strategy$GT$::new_tree::h5d8e4495d15a20a8(self=0x0000000101e10130, runner=0x000070000e84b588) at traits.rs:710
    frame #12: 0x0000000100054812 jetscii-0d882ad9a9b44f91`_$LT$alloc..arc..Arc$LT$S$GT$$u20$as$u20$proptest..strategy..traits..Strategy$GT$::new_tree::hc1090be38d314e83(self=0x000070000e84b6f0, runner=0x000070000e84b588) at traits.rs:613
    frame #13: 0x000000010004f2b4 jetscii-0d882ad9a9b44f91`_$LT$proptest..strategy..traits..BoxedStrategy$LT$T$GT$$u20$as$u20$proptest..strategy..traits..Strategy$GT$::new_tree::hd82ddb3c7d839d8f(self=0x000070000e84b6f0, runner=0x000070000e84b588) at traits.rs:670
    frame #14: 0x000000010004fa54 jetscii-0d882ad9a9b44f91`proptest::tuple::_$LT$impl$u20$proptest..strategy..traits..Strategy$u20$for$u20$$LP$A$C$$u20$B$RP$$GT$::new_tree::h1f2b5bb7eb503449(self=0x000070000e84b6f0, runner=0x000070000e84b588) at tuple.rs:47
    frame #15: 0x000000010003700a jetscii-0d882ad9a9b44f91`_$LT$proptest..strategy..map..Map$LT$S$C$$u20$F$GT$$u20$as$u20$proptest..strategy..traits..Strategy$GT$::new_tree::h62770139579553af(self=0x000070000e84b6f0, runner=0x000070000e84b588) at map.rs:55
    frame #16: 0x0000000100023d00 jetscii-0d882ad9a9b44f91`proptest::test_runner::runner::TestRunner::gen_and_run_case::h4657340ea7a2fff3(self=0x000070000e84b588, strategy=0x000070000e84b6f0, f=0x000070000e84adb8, replay=0x000070000e84b3d0, fork_output=0x000070000e84adc0) at runner.rs:437
    frame #17: 0x000000010002b038 jetscii-0d882ad9a9b44f91`proptest::test_runner::runner::TestRunner::run_in_process_with_replay::ha71924b6efbc1553(self=0x000070000e84b588, strategy=0x000070000e84b6f0, test=closure @ 0x000070000e84adb8, replay=IntoIter<core::result::Result<(), proptest::test_runner::errors::TestCaseError>> @ 0x000070000e84b3d0, fork_output=ForkOutput @ 0x000070000e84adc0) at runner.rs:403
    frame #18: 0x00000001000233f0 jetscii-0d882ad9a9b44f91`proptest::test_runner::runner::TestRunner::run_in_process::h00e1f077443810a9(self=0x000070000e84b588, strategy=0x000070000e84b6f0, test=closure @ 0x000070000e84b388) at runner.rs:374
    frame #19: 0x000000010002b744 jetscii-0d882ad9a9b44f91`proptest::test_runner::runner::TestRunner::run::hb68d4dd3ce20ea87(self=0x000070000e84b588, strategy=0x000070000e84b6f0, test=closure @ 0x000070000e84b458) at runner.rs:262
    frame #20: 0x000000010001c6be jetscii-0d882ad9a9b44f91`jetscii::simd::test::works_as_find_does_for_up_to_and_including_16_bytes::haf9ad9dc7906f315 at <proptest macros>:9
    frame #21: 0x0000000100001001 jetscii-0d882ad9a9b44f91`jetscii::__test::TESTS::_$u7b$$u7b$closure$u7d$$u7d$::h3fbb323f4db9482b((null)=0x000070000e84b880) at <proptest macros>:6
    frame #22: 0x00000001000330f1 jetscii-0d882ad9a9b44f91`core::ops::function::FnOnce::call_once::h155e683191fe6a76((null)=closure @ 0x000070000e84b880, (null)=<unavailable>) at function.rs:223
    frame #23: 0x0000000100059522 jetscii-0d882ad9a9b44f91`_$LT$F$u20$as$u20$alloc..boxed..FnBox$LT$A$GT$$GT$::call_box::h382d9488cfbd3726 [inlined] test::run_test::_$u7b$$u7b$closure$u7d$$u7d$::h768eac7416ceebd2 at lib.rs:1451 [opt]
    frame #24: 0x000000010005951d jetscii-0d882ad9a9b44f91`_$LT$F$u20$as$u20$alloc..boxed..FnBox$LT$A$GT$$GT$::call_box::h382d9488cfbd3726 [inlined] core::ops::function::FnOnce::call_once::h711f3c6ca1799e17 at function.rs:223 [opt]
    frame #25: 0x000000010005951d jetscii-0d882ad9a9b44f91`_$LT$F$u20$as$u20$alloc..boxed..FnBox$LT$A$GT$$GT$::call_box::h382d9488cfbd3726 at boxed.rs:640 [opt]
    frame #26: 0x000000010014ca4f jetscii-0d882ad9a9b44f91`__rust_maybe_catch_panic at lib.rs:105 [opt]
    frame #27: 0x0000000100075b85 jetscii-0d882ad9a9b44f91`std::sys_common::backtrace::__rust_begin_short_backtrace::h5166b4c87ca4eda0 [inlined] std::panicking::try::h8c18147343ca4323 at panicking.rs:289 [opt]
    frame #28: 0x0000000100075b40 jetscii-0d882ad9a9b44f91`std::sys_common::backtrace::__rust_begin_short_backtrace::h5166b4c87ca4eda0 [inlined] std::panic::catch_unwind::h7484637124d0b46b at panic.rs:392 [opt]
    frame #29: 0x0000000100075b40 jetscii-0d882ad9a9b44f91`std::sys_common::backtrace::__rust_begin_short_backtrace::h5166b4c87ca4eda0 [inlined] test::run_test::run_test_inner::_$u7b$$u7b$closure$u7d$$u7d$::hc4f54e91368e6524 at lib.rs:1406 [opt]
    frame #30: 0x0000000100075a79 jetscii-0d882ad9a9b44f91`std::sys_common::backtrace::__rust_begin_short_backtrace::h5166b4c87ca4eda0 at backtrace.rs:136 [opt]
    frame #31: 0x000000010007aa58 jetscii-0d882ad9a9b44f91`std::panicking::try::do_call::h4d826826a954117e [inlined] std::thread::Builder::spawn::_$u7b$$u7b$closure$u7d$$u7d$::_$u7b$$u7b$closure$u7d$$u7d$::hf28c1a431173c3f6 at mod.rs:409 [opt]
    frame #32: 0x000000010007aa42 jetscii-0d882ad9a9b44f91`std::panicking::try::do_call::h4d826826a954117e [inlined] _$LT$std..panic..AssertUnwindSafe$LT$F$GT$$u20$as$u20$core..ops..function..FnOnce$LT$$LP$$RP$$GT$$GT$::call_once::h7efe71ce7225ad7a at panic.rs:313 [opt]
    frame #33: 0x000000010007aa42 jetscii-0d882ad9a9b44f91`std::panicking::try::do_call::h4d826826a954117e at panicking.rs:310 [opt]
    frame #34: 0x000000010014ca4f jetscii-0d882ad9a9b44f91`__rust_maybe_catch_panic at lib.rs:105 [opt]
    frame #35: 0x000000010006bc35 jetscii-0d882ad9a9b44f91`_$LT$F$u20$as$u20$alloc..boxed..FnBox$LT$A$GT$$GT$::call_box::ha654b6616bd8c988 [inlined] std::panicking::try::hcf4fc2684e911891 at panicking.rs:289 [opt]
    frame #36: 0x000000010006bbfc jetscii-0d882ad9a9b44f91`_$LT$F$u20$as$u20$alloc..boxed..FnBox$LT$A$GT$$GT$::call_box::ha654b6616bd8c988 [inlined] std::panic::catch_unwind::h975b34da0e3f5968 at panic.rs:392 [opt]
    frame #37: 0x000000010006bbf5 jetscii-0d882ad9a9b44f91`_$LT$F$u20$as$u20$alloc..boxed..FnBox$LT$A$GT$$GT$::call_box::ha654b6616bd8c988 [inlined] std::thread::Builder::spawn::_$u7b$$u7b$closure$u7d$$u7d$::hb440fe115ba08d89 at mod.rs:408 [opt]
    frame #38: 0x000000010006bbbe jetscii-0d882ad9a9b44f91`_$LT$F$u20$as$u20$alloc..boxed..FnBox$LT$A$GT$$GT$::call_box::ha654b6616bd8c988 at boxed.rs:640 [opt]
    frame #39: 0x000000010013f348 jetscii-0d882ad9a9b44f91`std::sys_common::thread::start_thread::h6398f2ef882d4027 [inlined] _$LT$alloc..boxed..Box$LT$$LP$dyn$u20$alloc..boxed..FnBox$LT$A$C$$u20$Output$u3d$R$GT$$u20$$u2b$$u20$$u27$a$RP$$GT$$u20$as$u20$core..ops..function..FnOnce$LT$A$GT$$GT$::call_once::heb40532b768013a5 at boxed.rs:650 [opt]
    frame #40: 0x000000010013f345 jetscii-0d882ad9a9b44f91`std::sys_common::thread::start_thread::h6398f2ef882d4027 at thread.rs:24 [opt]
    frame #41: 0x0000000100129a89 jetscii-0d882ad9a9b44f91`std::sys::unix::thread::Thread::new::thread_start::h3d4d679cf548ff17 at thread.rs:90 [opt]
    frame #42: 0x00007fff6fbed661 libsystem_pthread.dylib`_pthread_body + 340
    frame #43: 0x00007fff6fbed50d libsystem_pthread.dylib`_pthread_start + 377
    frame #44: 0x00007fff6fbecbf9 libsystem_pthread.dylib`thread_start + 13
frame #6: 0x00000001000bde13 jetscii-0d882ad9a9b44f91`proptest::num::usize::_$LT$impl$u20$proptest..strategy..traits..Strategy$u20$for$u20$core..ops..range..Range$LT$usize$GT$$GT$::new_tree::h17d7a51577af5ebb(self=0x000070000e848f28, runner=0x000070000e84b588) at num.rs:61
   58  	            fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
   59  	                Ok(BinarySearch::new_clamped(
   60  	                    self.start,
-> 61  	                    $crate::num::sample_uniform(runner, self.clone()),
   62  	                    self.end - $epsilon))
   63  	            }
   64  	        }
(lldb) p *self
(core::ops::range::Range<usize>) $4 = (start = 0, end = 0)

add `prop_rng_map<T>(Fn(r: Rng, T)` method

This relates to #13, and could probably be a way to just solve it outright.

The idea is that you would do:

impl_strategy
    .prop_rng_map(|rng, value| {
        // compose new value using the rng
    }

As far as I can tell, there is no way to get the Rng object in a mapped body. This is certainly a power feature, but in some case (like sampling) there is no reason why a simple Rng object is not sufficient -- no shrinking, etc could be gained anyway.

Obviously this could be misused to effectively make shrinking impossible and complicate a Strategy chain, so this feature may not be desired. However, I think it would help provide at least a workaround for any issue that a developer wants to address, even if the solution isn't 100% in compliance with this library.

Only use the parts of rand we actually need wrt. winapi

The rand crate recently started to use winapi for windows users which makes proptest slower to compile and in particular document. Since we only use XorShiftRng, we should not use the parts we don't need.

Also see rust-random/rand#241 which may be sufficient for us?
Might be a good idea to ask the rand folks if we can get XorShiftRng but but no other Rng we don't need =)

Unexpected results with "<var> in <regexp>"

Hi, I'm working through the proptest example/tutorial at https://altsysrq.github.io/rustdoc/proptest/0.8.2/proptest/ , and I got a failure when the example says the tests should pass. The minimal failing example is s = "00000000--", and s doesn't match the regexp "[0-9]{4}-[0-9]{2}-[0-9]{2}".

What is going on here? Is this a bug?

I'm using proptest 0.8.2.

Full code:


fn parse_date(s: &str) -> Option<(u32, u32, u32)> {
    if 10 != s.len() { return None; }

    // NEW: Ignore non-ASCII strings so we don't need to deal with Unicode.
    if !s.is_ascii() { return None; }

    if "-" != &s[4..5] || "-" != &s[7..8] { return None; }

    let year = &s[0..4];
    let month = &s[6..7];
    let day = &s[8..10];

    year.parse::<u32>().ok().and_then(
        |y| month.parse::<u32>().ok().and_then(
            |m| day.parse::<u32>().ok().map(
                |d| (y, m, d))))
}

fn main() {
    println!("Hello, world!");
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_parse_date() {
        assert_eq!(None, parse_date("2017-06-1"));
        assert_eq!(None, parse_date("2017-06-170"));
        assert_eq!(None, parse_date("2017006-17"));
        assert_eq!(None, parse_date("2017-06017"));
        assert_eq!(Some((2017, 06, 17)), parse_date("2017-06-17"));
    }

    proptest! {
        #[test]
        fn doesnt_crash(s in "\\PC*") {
            parse_date(&s);
        }

        #[test]
        fn parses_all_valid_dates(s in "[0-9]{4}-[0-9]{2}-[0-9]{2}") {
            parse_date(&s).unwrap();
        }
    }
    #[test]
    fn test_unicode_gibberish() {
        assert_eq!(None, parse_date("ල0A 0"));
    }
}

Closure style invocation doesn't return closure

Hey!

Follow up from #62 and the changes done after that was merged.

Now the proptest! when invoked like a closure, doesn't actually return a closure. This is a bit unexpected. For example, the move |..| {} variant of the macro doesn't do anything special even though it might seem like it does.

Basically it runs the property test in-place, like a block statement.

This makes the thing I initially wanted to accomplish in #62 a bit more awkward, instead of:

runner.test("some test", proptest!(|..| {
    /*  */
}));

I now have to define an outer closure:

runner.test("some test", || {
  proptest!(|..| {
    /*  */
  })
});

If possible I would like to have a way that permits me to define closures with as few brackets as possible.
If there's a need for block-style invocation, that should preferably have none-closure style syntax since it's a bit misleading.

Thanks!

Misleading example in README?

I'm not sure if this is intentional, but it seems to me that the example given in https://github.com/AltSysrq/proptest#limitations-of-property-testing will always pass, not "virtually always".

proptest! {
    #[test]
    fn i64_abs_is_never_negative(a in any::<i64>()) {
        assert!(a.abs() >= 0);
    }
}

If you want an example that misses a corner case, it should be > 0 so that in the rare event a is 0, the test fails. Otherwise, the example is kind of confusing.

Also, I think this library is pretty neat.

Add convenient way to set config limits

When using the propcheck! macro there doesn't appear to be an easy way to set the values in test_runner::Config.

It would be convenient if these could be inherited from the environment; if, say PROPCHECK_REQUIRED_SUCCESSES=1024 ran the tests with Config { cases : 1024, ...Config::default() }

(This may be already implemented; I haven't checked. If it is implemented then it's not documented 😄 )

Tracking issue: Feature parity with other libraries

This issue is intended to be used for tracking proptest's feature parity with other property based testing frameworks in other languages and Rust.

Python

Hypothesis

TODO

Haskell

QuickCheck

TODO

SmallCheck

TODO

Hedgehog

TODO

SessionCheck

TODO

Erlang

QuiviQ QuickCheck

TODO

PropEr

TODO

triq

TODO

Concuerror

TODO

Scala

ScalaCheck

TODO

C++

RapidCheck

TODO

CppQuickCheck

TODO

C

Theft

TODO

Clojure

test.check

TODO

Coq

TODO

QuickChick

TODO

F#

TODO

FsCheck

TODO

Go

gopter

TODO

Java

QuickTheories

TODO

JavaScript

jsverify

TODO

PHP

Eris

TODO

Ruby

Rantly

TODO

Swift

Swiftcheck

TODO

OCaml

OCaml QuickCheck

TODO

Strategy does not implement Sync

I'm not sure if there is a technical reason this is not possible, but I thought I would check.

I would like to create the regex strategy using lazy_static! but Strategies don't implement Sync.

Thanks!

error[E0277]: the trait bound `proptest::strategy::Strategy<Value=std::boxed::Box<proptest::strategy::ValueTree<Value=std::string::String> + 'static>> + 'static: std::marker::Sync` is not satisfied
  --> src/test_name.rs:38:1
   |
38 | / lazy_static!{
39 | |     static ref GEN_NAME_PROP: prop::string::RegexGeneratorStrategy<String> =
40 | |         prop::string::string_regex(GEN_NAME_RE).unwrap();
41 | | }
   | |_^ `proptest::strategy::Strategy<Value=std::boxed::Box<proptest::strategy::ValueTree<Value=std::string::String> + 'static>> + 'static` cannot be shared between threads safely
   |
   = help: the trait `std::marker::Sync` is not implemented for `proptest::strategy::Strategy<Value=std::boxed::Box<proptest::strategy::ValueTree<Value=std::string::String> + 'static>> + 'static`
   = note: required because of the requirements on the impl of `std::marker::Sync` for `std::ptr::Unique<proptest::strategy::Strategy<Value=std::boxed::Box<proptest::strategy::ValueTree<Value=std::string::String
> + 'static>> + 'static>`
   = note: required because it appears within the type `std::boxed::Box<proptest::strategy::Strategy<Value=std::boxed::Box<proptest::strategy::ValueTree<Value=std::string::String> + 'static>> + 'static>`
   = note: required because it appears within the type `proptest::string::RegexGeneratorStrategy<std::string::String>`
   = note: required by `lazy_static::lazy::Lazy`
   = note: this error originates in a macro outside of the current crate

error: aborting due to previous error

error: Could not compile `artifact-data`.

Incorrect documentation on float_to_weight

The documentation on float_to_weight states that it:

Panics if f is negative, greater than 1.0, or NaN.

But the code states:

assert!(f > 0.0 && f < 1.0, "Invalid probability: {}", f);

so either the documentation is wrong (i.e: it panics if greater than or equal to 1.0) or the code is wrong.

The union of state machines and property based testing

Idea from QC & the paper: classification of input to properties

The original QC paper: https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quick.pdf

Under 2.4. Monitoring test data, the paper talks about classification of trivial inputs which are then shown to the user. These mechanisms are exposed here today: https://hackage.haskell.org/package/QuickCheck-2.11.3/docs/Test-QuickCheck.html#g:22

I think the most useful function here is classify.
I believe these primitives can be offered either at the strategy level by calling methods of TestRunner or via the success/failure mechanism (we can overload the meaning of success such that it can be annotated with classification..)

However, cargo test does not currently expose extra info very nicely on success, so at best I think we can print the out the classification with a println!(..).

Feature: Strategy for Option<T> and Result<T, E>

It would be nice to add strategies for Option and Result<T, E>.

While it's doable with Union, leaner and more performant implementations are possible...

A possible implementation for Option is:

pub fn optional<T: Strategy>(strat: T) -> OptionStrategy<T> {
    optional_weighted(strat, 0.5)
}

pub fn optional_weighted<T: Strategy>(strat: T, prob: f64) -> OptionStrategy<T> {
    OptionStrategy { strat, prob }
}

#[derive(Clone, Debug)]
pub struct OptionStrategy<T: Strategy> {
    strat: T,
    prob: f64,
}

impl<T: Strategy> Strategy for OptionStrategy<T> {
    type Value = OptionValueTree<T::Value>;

    fn new_value(&self, runner: &mut TestRunner) -> Result<Self::Value, String> {
        Ok(OptionValueTree {
            prev: None,
            pick:
                if weighted(self.prob).new_value(runner)?.current() {
                    Some(self.strat.new_value(runner)?)
                } else {
                    None
                }
        })
    }
}

#[derive(Clone, Debug)]
pub struct OptionValueTree<T: ValueTree> {
    pick: Option<T>,
    prev: Option<T>,
}

impl<T: ValueTree> ValueTree for OptionValueTree<T> {
    type Value = Option<T::Value>;

    fn current(&self) -> Self::Value {
        self.pick.as_ref().map(ValueTree::current)
    }

    fn simplify(&mut self) -> bool {
        self.pick.take().map_or(false, |mut inner| {
            if inner.simplify() {
                self.pick = Some(inner);
                self.prev = None;
            } else {
                self.pick = None;
                self.prev = Some(inner);
            };
            true
        })
    }

    fn complicate(&mut self) -> bool {
        if let Some(pick) = self.prev.take() {
            self.pick = Some(pick);
            true
        } else {
            self.pick.as_mut().unwrap().complicate()
        }
    }
}

Feature: add Arbitrary. Allow proptest!(..) to omit strategy when T : Arbitrary

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 quickcheck Arbitrary trait.

The benefits of doing this are:

1.

Increased familiarity for quickcheck users by bridging the gap.

2.

The creation of the Strategy in proptest! can be omitted, and

proptest! {
    #[test]
    fn i64_abs_is_never_negative(a in prop::num::i64::ANY) {
        assert!(a.abs() >= 0);
    }
}

can be turned into:

proptest! {
    #[test]
    fn i64_abs_is_never_negative(a: i64) {
        assert!(a.abs() >= 0);
    }
}

or even the more standard in quickcheck:

proptest! {
    #[test]
    fn i64_abs_is_never_negative(a: i64) -> bool { a.abs() >= 0 }
}

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:

  • All unit types are Arbitrary by simply always returning the unit.
  • All product types are Arbitrary if their factors are Arbitrary.
  • All sum types are Arbitrary if each product type in each variant in a sum type is Arbitrary. 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:

/// `Arbitrary` determines a canonical `Strategy` for the implementing type.
///
/// It provides one function to, yield strategies for generating `Self`.
pub trait Arbitrary
where
    Self: Sized + Debug
{
    /// The type of `ValueTree` used for `Self`'s `Strategy`.
    type ValueTree: ValueTree<Value = Self>;

    /// The type of `Strategy` used to generate values of type `Self`.
    type Strategy: Strategy<Value = Self::ValueTree>;

    /// Yields a `Strategy` for generating values of type `Self`.
    fn arbitrary() -> Self::Strategy;
}

Or, in a future Rust version:

/// `Arbitrary` determines a canonical `Strategy` for the implementing type.
///
/// It provides one function to, yield strategies for generating `Self`.
pub trait Arbitrary
where
    Self: Sized + Debug
{
    /// Yields a `Strategy` for generating values of type `Self`.
    fn arbitrary() -> impl Strategy<Value = impl ValueTree<Value = Self>>;
}

does this replace quickcheck completely?

If the answer is yes, then I'd be happy to start directing users your way. In particular, I read your section on the differences between QuickCheck and Proptest and all of them seem like advantages in favor of proptest over quickcheck. Is this an exhaustive comparison? That is, does quickcheck have any advantages over proptest?

Apologies for the drive by comment. I haven't actually tried to use proptest yet, but figured I'd just get the ball rolling here. :)

Long term plan: Investigate strategies for non-owned types

Currently, it is impossible to have a S: Strategy where ValueOf<S> is non-owned.

Examples:

  1. We have a base strategy T where ValueOf<T> is owned, and then we use that to make a strategy where ValueOf<S> = &'a ValueOf<T> - This will not type check.
  2. Derived types, for example: Cow<'a, X>.

Generally, I think that, any type that has a lifetime can't be generated.

Being able to generate types with lifetimes in them is however useful when you have lifetimes somewhere deep in the hierarchy of a type. To be able to do this soundly, the ValueTree must perhaps have a notion of Owned (which defaults to Value) in addition to Value (which may be borrowed). Then TestRunner will return the Owned type instead of Value, but the function under test receives Value.
I tried to implement this and got TestRunner to compile, but no strategies would compile.
Solving this may require generic associated types (GAT). I'll experiment with it once we have GAT in nightly.

There's also *const T and *mut T. While it is possible to give a sound strategy for these by Box::leak, this may cause problems since tests for large data types may leak huge amounts of memory. This could perhaps be mitigated by letting the ValueTree store the previous value of .current(), and whenever .current() happens, the old value is deallocated. The last value needs to be persisted in the TestRunner somehow if the entire ValueTree did not fail so that it can be cleared.

I'd like your thought's on the desirability of these features.

Examples including Enums

Hi there, I just started using the proptest library for a network protocol crate that I'm working on. I've stared at the documentation a couple of times now, but I can't work out how I'm supposed to generate struct and enum values (even for what seem like simple cases).

I have something like:

#[derive(Debug, Copy, Clone)]
enum Field {
  A, B, C
}

struct Example {
  field: Field
}

I want to write a simple doesn't-crash property like:

proptest! {
  #[test]
  fn test_struct(field in any::<Field>()) {
    Example { field: field } .some_method()
  }
}

Where field in the test is going to be one of A, B, or C.

The ugly hack I've arrived at is roughly:

prop_compose! {
  fn field_strat()(int in 0..2) -> Field {
    match int {
      0 => Field::A,
      1 => Field::B,
      _ => Field::C // Otherwise I get a non-exhaustive match error
    }
  }
}

//and then...
proptest! {
  #[test]
  fn test_struct(field in field_strat()) {
    Example { field: field } .some_method()
  }
}

Is there a better way to do this? I would also suggest that whichever way you recommend, you consider adding an explicit "Generating Structs and Enums for Properties" documentation section (and maybe link it from prop_oneof!).

Thanks!

@composite strategy

As a huge fan of Hypothesis — thanks a lot for making this, fantastic work!

Am I correct that there is no support for @composite strategy yet?

Idea: String generation using Context Free Grammars

We can already generate Strings using RegexStrategy with type-3 grammars (regular expressions).
However; those are sometimes not enough. If you want to test natural language processing APIs, some programming language parser, or just some format that is more complex than regular expressions, you need something richer.

A fortunate property of type-2 grammars (context free grammars) is that they can still be generated, even when they are ambiguous. Being able to generate from ambiguous grammars is helpful for NLP. Another fortunate part of proptest is how alternation in CFGs translate nicely to union strategies... To allow the user to specify the probability of each alternation, we can use PCFGs.

We should consider some BNF formalism that is:

  1. Is understood by most people familiar with BNF
    There's:

    • LBNF which has redundant information which is useful for generation of parsers and ASTs, but which we don't care about for generation of strings.
    • ABNF
    • EBNF

    Some formalisms write production = definition ;, some write: production : definition ; and some write: production ::= definition ; .

  2. Which can be extended with probabilities such that the resulting formalism is a superset.

  3. Is ergonomic to write in (deals with tedious tasks such as repetition, optionality, separators, terminators well...).

We can then define a strategy producer:

fn cfg_strategy(depth: usize, grammar: &str) -> CfgStrategy { .. }

where CfgStrategy : Strategy<Value = String> (or Vec<u8>).
The depth parameter controls the recursion depth when generating from recursive CFGs,
which is necessary since CFGs can generate infinite words.

I have currently no idea how complex the implementation for this would be... It could reuse the logic for generating from regexes since regexes can often be embedded in CFGs...
I'd like to try out an implementation and find out... If it turns out to be too complex, maybe we could add a proptest-extras crate but keep it in tree for maintenance purposes?

Thoughts?

Contract breaking impl From<SizeRange> for Range<usize>

A while back I added the implementation:

/// Given a size range `[low, high]`, then a range`low..(high + 1)` is returned.
/// This will panic if `high == usize::MAX`.
impl From<SizeRange> for Range<usize> {
    fn from(sr: SizeRange) -> Self {
        let (start, end) = sr.extract();
        start..end + 1
    }
}

however, the trait From states that:

Note: this trait must not fail.

Therefore, the implementation above is contract breaking since it may panic in fringe circumstances.
As such, the implementation should be removed. This amounts to a breaking change (so we should probably label the issue as such...)

Strategy::prop_and_then for composing strategies

It would be nice to have the equivalent of {Option, Future}::and_then for composing Strategies. Composition can already be done via prop_compose! (which is probably more flexible), but for simply gluing together two strategies in a straight line and_then can be more straightforward and readable. Here's a sketch of what the API might look like:

    /// Returns a strategy which chains another strategy produced by `f` to this one.
    ///
    /// TODO: describe shrinking behavior
    fn prop_and_then<T, U, F>(self, f: F) -> AndThen<Self, U>
    where
        U: Strategy<Value=T>,
        F: FnOnce(Self::Value) -> U
    {
        ...
    }

Generating random closures and CoArbitrary

It would be neat to be able to generate random closures. Doing so will allow us to generate things like random std::iter::Filter.

To that end, I've started some work here:
https://github.com/Centril/proptest-arbitrary/blob/master/src/coarbitrary.rs

I essentially replicate QuickCheck's CoArbitrary class as:

/// `CoArbitrary` defines a method for perturbing an RNG
/// based on the value of `&self`.
pub trait CoArbitrary {
    /// Perturbs the given underlying RNG according to
    /// the structure of the given `&self` value.
    fn coarbitrary(&self, var: Varianter);
}

 // Varianter is just a wrapper around &mut XorShiftRng

To define impls we'll need a primitive operation:

/// Perturbs the RNG with an integer seed.
pub fn variant(rng: &mut XorShiftRng, seed: usize) {
    unimplemented!()
}

which I don't know how to define yet, but I've asked around at: rust-random/rand#231

Perhaps we could have CoStrategy instead of CoArbitrary - I'm not sure if that makes sense or is useful tho.

One problem I ran into when trying to define the ClosureStrategy was that I have to do .unwrap() as seen here: https://github.com/Centril/proptest-arbitrary/blob/master/src/coarbitrary.rs#L231
So it might be a good idea to have some sort of infallible strategy trait T: InfallibleStrategy |- T: Strategy that just returns Self::Value instead of NewTree<Self>. This is a bit akin to ExactSizeIterator and other schemes run in libstd.

Can you check my logic for ClosureStrategy and GenClosure for bugs btw?

input with `any::<bool>()` constraint causes test to run for a long time

Hey,

The following runs for a very long time, and seems to be unable to reduce the input:

#[macro_use]
extern crate proptest;

use proptest::prelude::*;

proptest! {
    fn run_for_a_long_time(v in any::<bool>()) {
        println!("v: {}", v);
        assert!(!v);
    }
}

fn main() {
    run_for_a_long_time();
}

Note that it works when the assert is just assert!(v).

Expanded example for prop_oneof macro

The documentation for generating recursive data shows the use of the prop_oneof macro in that generating scenario--thanks!

Could we have a stand-alone example of using prop_oneof as the idiomatic way to choose from a group of strategies at run time? As an example, suppose we have existing generators:

prop_compose! {
    [pub] fn small()(x in 0..100) -> u64 {
        // expression using x
    }
}

prop_compose! {
    [pub] fn medium()(x in 200..400) -> u64 {
        // expression using x
    }
}

prop_compose! {
    [pub] fn large()(x in 500..1000) -> u64 {
        // expression using x
    }
}

Then an example of the scaffolding needed to use

prop_oneof![small(), medium(), large()]

in test functions as a sized_any() generator would be very useful. Apologies if this is not the right way to go about choosing generators at run-time.

can not make functions public with `compose!`

I can't seem to make a function public

prop_compose! {
    pub fn arb_raw_name()(ref s in GEN_NAME_RE) -> String {                                                                                                                                                       
        s.to_string()                                                                                                                                                                                             
    }                                                                                                                                                                                                             
} 

has this error when compiled:

error: no rules expected the token `pub`
  --> src/test_name.rs:40:5
   |
40 |     pub fn arb_raw_name()(ref s in GEN_NAME_RE) -> String {

regex generation tests same case multiple times

With the regex pattern [a-z]{1, 5} and the assertion that the output length is even, proptest will repeatedly test the same (original) value over and over as it shrinks/expands:

"saanf"
"aanf"
"saanf"
"sanf"
"saanf"
"sanf"
"saanf"
"saaf"
"saanf"
"saan"
"saanf"
"jaanf"
"eaanf"
"caanf"
"baanf"
"aaanf"
"aaagf"
"aaadf"
"aaabf"
"aaaaf"
"aaaac"
"aaaab"
"aaaaa"

Here's the test for context:

        #[test]
        fn test_string_shrink(s in "[a-z]{1, 5}") {
            use env_logger;
            let _ = env_logger::try_init();
            info!("{:?}");
            assert!(s.len() % 2 != 0);
        }

This also demonstrates the shrinking method used by proptest can get 'stuck' at a local minimum (in this case aaaaa instead of a) because it doesn't try shrinking more than one character at once. This seems like a more difficult issue to fix, though. Perhaps there's some literature on effective string shrinking strategies out there?

Possible regression in 0.8.0

I just started using proptest in a brand new project, and after getting my first failure, subsquent runs were failing to parse the regressions file and new failures were not being added to the existing file:

During the test run, proptest output this during the test run:

failures:
 
---- tests::round_trip stdout ----
        proptest: /home/project/proptest-regressions/types/time/period.txt:1: unknown case type `` (corrupt file or newer proptest version?)
proptest: /home/project/proptest-regressions/types/time/period.txt:2: unknown case type `` (corrupt file or newer proptest version?)
proptest: /home/project/proptest-regressions/types/time/period.txt:3: unknown case type `` (corrupt file or newer proptest version?)
proptest: /home/project/proptest-regressions/types/time/period.txt:4: unknown case type `` (corrupt file or newer proptest version?)
proptest: /home/project/proptest-regressions/types/time/period.txt:5: unknown case type `` (corrupt file or newer proptest version?)
proptest: /home/project/proptest-regressions/types/time/period.txt:6: unknown case type `` (corrupt file or newer proptest version?)

The first six lines are the commented lines. It is likely due to the code here, as split will return one part with the entire string if there is nothing to split on.

While I had never used proptest 0.7.2, I downgraded to that version, and using the same file, was able to create a new failure that added cases to the regressions file.

Failure persistence file location for workspace member crates

I've just started using proptest. It looks great so far. The project I'm using it is organized as a Cargo workspace with several member crates. When using proptest in a member crate of a workspace the location of failure persistence file is not what I expected.

The project structure is like this:

my_workspace/
|-> Cargo.toml    #defines the workspace
|-> my_member_crate/
    |-> Cargo.toml
    |-> src/lib.rs
    |-> src/protocol.rs   #code with tests module

in this case the failure persistence file is located at:
my_workspace/my_member_crate/my_member_crate/protocol.protest-regressions

what I expected is:
my_workspace/my_member_crate/protest-regressions/protocol.txt

I have played around with the source code of test_runner. I found that the source_path is relative to the workspace root, e.g. my_member_crate/src/lib.rs. But the working directory when running the tests is the crate root, so my_member_crate in this case.

impl Strategy for &[T]

It would be great to have Strategy implemented for slices, with the same semantics as the current implementation for arrays.

In particular this could be handy for creating a strategy over data-less enum variants, e.g. here's a Stategy I've written for generating encoding types from an input data type. The domain here is prop testing a database -- not all data types the database has supports all encodings:

    impl EncodingType {
        pub fn arbitrary(data_type: DataType) -> impl Strategy<Value=EncodingType> {
            match data_type {
                DataType::Bool => prop_oneof![
                    Just(EncodingType::Auto),
                    Just(EncodingType::Plain),
                    Just(EncodingType::RunLength),
                ].sboxed(),
                DataType::Int8
                | DataType::Int16
                | DataType::Int32
                | DataType::Int64
                | DataType::Timestamp => prop_oneof![
                    Just(EncodingType::Auto),
                    Just(EncodingType::Plain),
                    Just(EncodingType::BitShuffle),
                    Just(EncodingType::RunLength),
                ].sboxed(),
                DataType::Float
                | DataType::Double => prop_oneof![
                    Just(EncodingType::Auto),
                    Just(EncodingType::Plain),
                    Just(EncodingType::RunLength),
                ].sboxed(),
                DataType::Binary
                | DataType::String => prop_oneof![
                    Just(EncodingType::Auto),
                    Just(EncodingType::Plain),
                    Just(EncodingType::Dictionary),
                    Just(EncodingType::RunLength),
                ].sboxed(),
            }
        }
    }

I've had to use prop_oneof! with sboxed, whereas if Strategy had been implemented for slices it would be simpler (and allocation free):

    impl EncodingType {
        pub fn arbitrary(data_type: DataType) -> impl Strategy<Value=EncodingType> {
            match data_type {
                DataType::Bool => &[EncodingType::Auto, EncodingType::Plain, EncodingType::RunLength],
                DataType::Int8
                | DataType::Int16
                | DataType::Int32
                | DataType::Int64
                | DataType::Timestamp => &[EncodingType::Auto, EncodingType::Plain, EncodingType::BitShuffle, EncodingType::RunLength],
                DataType::Float
                | DataType::Double => &[EncodingType::Auto, EncodingType::Plain, EncodingType::RunLength],
                DataType::Binary
                | DataType::String => &[EncodingType::Auto, EncodingType::Plain,EncodingType::Dictionary, EncodingType::RunLength],
            }
        }
    }

Had each sequence of encoding types been the same length I could have used a fixed sized array. I imagine this will be most useful for 'static slices (as above), but I don't see a reason it couldn't work for non-'static as well.

Idiomatic proptest approach for generating very similar repetitive properties?

I have a Rust trait that checks compatibility of two values of the same type:

pub trait Compatible {
  fn are_compatible(x: Self, y: Self) -> bool;
}

I've implemented Compatible for u8, u16, u32, u64, usize, as well as the signed integer types and f32, f64. I'd like to create proptest properties for are_compatible() for all fourteen implementations of Compatible.

To avoid a lot of repeated code my first thought was to create a macro to generate the properties but ran into problems with nested macro expansion in Rust. Another idea was to create just one test but let proptest use prop_oneof at runtime to choose the type of the test pair.

Is there an idiomatic proptest-oriented way of going about this kind of repetitive testing of similar properties?

Thanks!

Stu

`sample()` and `select()` functions are missing

My use case is a graph, where all nodes are randomly created, and then I want to select a subset for each node to connect to when I create the actual graph.

However, there doesn't seem to be either a sample() or select() api with which to do this.

exporting Rng may have caused other problems (sorry)

after updating proptest I'm getting this error:

error[E0277]: the trait bound `R: rand::Rng` is not satisfied
  --> src/test/implemented.rs:43:9
   |
43 |         regex_generate::Generator::parse(r"\.([a-zA-Z0-9_]{1,10})", rng.clone()).unwrap();
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `rand::Rng` is not implemented for `R`
   |
   = help: consider adding a `where R: rand::Rng` bound
   = note: required by `regex_generate::Generator`

I'm trying to trace down what is going wrong, but it seems like extern crate rand; rand::Rng is being treated differently than proptest::Rng, even though they are the same version. I'm not totally sure what's going on

Idea: Should we provide no-shrink standard definitions for `simplify` and `complicate`

Currently, ValueTree is defined as:

pub trait ValueTree {
    type Value: Debug;
    fn current(&self) -> Self::Value;
    fn simplify(&mut self) -> bool;
    fn complicate(&mut self) -> bool;
}

Should we redefine ValueTree as:

pub trait ValueTree {
    type Value: Debug;
    fn current(&self) -> Self::Value;
    fn simplify(&mut self) -> bool { false }
    fn complicate(&mut self) -> bool { false }
}

Pros:

  • Less work in a minority of cases.

Cons:

  • Risk of users not providing custom shrinking where they should do that. (I assume this is the reason why the methods are not provided already..?)

If we elect not to do this, I think it would be good to document the choice somehow.

Right now, the strategies that do not shrink are:

  • NoShrink
  • Just
  • a potential struct LazyJust<V: Debug, F: Fn() -> V>
  • a potential ClosureStrategy for generating random functions

This seems like a short list, so the case for providing default no-shrink semantics doesn't seem very well motivated. But I'd like to bring it up so that we can document it anyways =)

Recursive strategies do not shrink by depth as "expected"

While trying to improve the definition of Strategy for Recursive<..> in PR #40 I later realized that IndFlatten was being used, so the input strategy (bool::Weighted) is never shrunk, and so the depth of a recursive type is never shrunk as "expected" (at least by users of QuickCheck in asorted languages?). In other words, the following test holds:

#[test]
fn test_recursive_depth_shrink() {
    #[derive(Clone, Debug)]
    enum Nat { Z, S(Box<Nat>) }
    impl Nat {
        fn size(&self) -> usize {
            if let Nat::S(ref nat) = *self { 1 + nat.size() } else { 0 }
        }
    }

    let strat = Just(Nat::Z).prop_recursive(4, 1, 1,
        |strat| strat.prop_map(|nat| Nat::S(Box::new(nat)))
    );

    let mut runner = TestRunner::default();
    for _ in 0..::std::u16::MAX {
        let mut vt = strat.new_value(&mut runner).unwrap();
        let curr = vt.current().size();
        while vt.simplify() {}
        assert_eq!(curr, vt.current().size());
    }
}

while you could reasonably expect that the following should hold instead:

        let mut runner = TestRunner::default();
        for _ in 0..::std::u16::MAX {
            let mut vt = strat.new_value(&mut runner).unwrap();
            while vt.simplify() {}
            assert_eq!(0, vt.current().size());
        }

that is, if you shrink a Nat maximally, you should always end up with 0 as size (which is the same as the depth in this case).

We could of course change IndFlatten to Flatten in which case it does hold, but then you get into complicate_regen_remaining and such...

I think in any case, we should document the shrinking behavior that is used... But is the current behavior right?

Regex-based string generation is slower than it could be

Currently, the strategy for generating chars is fairly inefficient, plus the process of generating the string does a fairly large amount of allocation.

See conversation in #12 for more details. The example regex there averages around 100μs per string on my system.

Adding a quickcheck interoperability layer

I've created an interoperability layer between proptest and quickcheck, should I add this to proptest as a PR under a feature flag quickcheck or do you believe this belongs in a separate crate?

The benefit of the latter is that proptest is not then constrained in dependencies by quickcheck while the benefit of the former is that the user doesn't have to write in another dependency to their project.

What it amounts to is essentially (details omitted):

use quickcheck::Arbitrary;

pub fn from_qc<A: Arbitrary + Debug>() -> QCStrategy<A> {
    QCStrategy::new(qc_gen_size())
}

pub fn from_qc_sized<A: Arbitrary + Debug>(size: usize) -> QCStrategy<A> {
    QCStrategy::new(size)
}

consider re-exporting Rng trait

This is a pretty minor papercut.

As it is an extra dependency (rand) is required to make functions which can use the rng passed by Strategy::prop_perturb

It would be nice to not require this dependency. This also protects against (rare) cases where someone depends on multiple versions of rand. They could specify proptest::Rng specifically if they needed to.

Simplify usage with impl Trait

Now that impl Trait is on stable rust I started changing all the strategy functions to return impl Strategy instead of BoxedStrategy. The good news is that it just works but with a small caveat which makes it a bit awkward to use.

Since Strategy only indirectly specifies the Value through the associated ValueTree one must write the return type like below which is rather counter-intuitive and verbose.

impl Strategy<Value = impl ValueTree<Value = Vec<String>>>
// Nicer if this would be the type
impl Strategy<Value = Vec<String>>

I don't know enough about the internals of proptest to say how this could be fixed though but perhaps it would be possible to specify the Value on Strategy in a similar manner to how IntoIterator does it? Or maybe there is a simpler way to do it than that even.

Great library in either case though! :)

Version 0.8.0 checklist

Some proposed TODOs for version 0.8.0:

  • Raise minimum rustc version to 1.27; lands on 2018-06-21 (9 days).
    Benefits:
  • Implement Strategy for RangeInclusive<T> where T is a float (which requires .start() and .end()).
    • Can be done via #[cfg(MIN_1_127)] logic if we want to make v0.8.0 available for 1.26.0.
  • Arbitrary for SIMD stuff;
    • Can be done via #[cfg(MIN_1_127)] logic if we want to make v0.8.0 available for 1.26.0.
  • Land proptest_derive
    • Basic draft design is done
    • Need to update to latest quote, proc-macro2, syn crates.
    • Filtering of strategies
    • prob attribute on enum variants
    • mdbook documentation (serde style, https://serde.rs/)
    • Issues around recursive, esp. mutually recursive types remaining.
  • Land some form of "showable & shrinkable closures" a la [Test.QuickCheck.Function](https://hackage.haskell.org/package/QuickCheck-2.11.3/docs/Test-QuickCheck-Function.html)
    • Very much vaporware right now;
      • Some designs went nowhere some time ago...
      • I'll try some new designs in the coming days.

Support `no_std` Usage

It would be excellent to be able to use proptest in no_std environments like embedded development and wasm-land. Some features of proptest are not likely to be viable in no_std, but a meaningful subset should be possible.

In the long run, this would likely stratify into three tiers of capability: std, no_std + alloc, and finally no_std without access to a heap allocator. Feature flags should be sufficient to provide users a means of selecting the features available in their environment. Default feature flags would allow a no-effort transition for extant users.

Accomplishing this would likely require us to:

  • Identify which capabilities are feasible in which environment, and conditionally compile modules using feature flags. This may require reorganization of some files to avoid overly broad restrictions.
  • Reduce dependency on external crates which must use the standard library (e.g. quick-error) .
  • Use the no_std related feature flag options for crates that support such (e.g. lazy_static ) .
  • Replace current internal uses of std library bits with core-based alternatives or convert into optional capabilities.
  • Come up with a plan to conditionally export core-flavored variants of the arbitrary/_std submodules where the types in question are simple re-exposures of core types.

I'm sure there's more to do that I haven't yet pointed out, or mistakes in the above analysis.

From a practical standpoint, in order to show good faith and "skin in the game", I've started working on getting to the first additional tier of capability (no_std + alloc) working, beginning with adding or exposing no_std modes for dependencies ( see: contain-rs/bit-vec#49 and contain-rs/bit-set#15).

I'd be very happy to receive any guidance or corrections possible.

should_panic tests and generalizing run_one/run

Currently there's no easy way to assert that a function under test must panic for all inputs, which would be nice. Right now, the TestRunner has a built-in panic-guard which could perhaps be extracted.

I've thought about adding a trait:

pub trait Testable<V> {
    fn test(&mut self, &V) -> TestCaseResult; // perhaps not with mutability...?
}

And then changing run_one (and run) into:

    pub fn run_one<V : ValueTree, F : Testable<V::Value>>>
        (&mut self, mut case: V, mut test: F) -> Result<bool, TestError<V::Value>>

We can then provide these impls:

impl<'a, V, F: Testable<V>> Testable<V> for &'a mut F {
    fn test(&mut self, case: &V) -> TestCaseResult {
        use std::ops::DerefMut;
        self.deref_mut().test(case)
    }
}

pub struct ShouldPanic<F> {
    inner: F,
}

pub fn should_panic<V, F: Fn(&V) -> TestCaseResult>(inner: F) -> ShouldPanic<F> {
    ShouldPanic { inner }
}

impl<V, F: Fn(&V) -> TestCaseResult> Testable<V> for ShouldPanic<F> {
    fn test(&mut self, case: &V) -> TestCaseResult {
        match panic::catch_unwind(AssertUnwindSafe(|| (self.inner)(&case))) {
            Ok(_) => Err(fail_case("ShouldPanic test failed to panic.")),
            Err(_) => Ok(()),
        }
    }
}

pub struct PanicGuard<F> {
    inner: F,
}

pub fn panic_guard<V, F: Fn(&V) -> TestCaseResult>(inner: F) -> PanicGuard<F> {
    PanicGuard { inner }
}

impl<V, F: Fn(&V) -> TestCaseResult> Testable<V> for PanicGuard<F> {
    fn test(&mut self, case: &V) -> TestCaseResult {
        match panic::catch_unwind(AssertUnwindSafe(|| (self.inner)(&case))) {
            Ok(r) => r,
            Err(what) => {
                let msg = what.downcast::<&'static str>()
                    .map(|s| reject(*s))
                    .or_else(|what|
                        what.downcast::<String>().map(|b| reject(*b)))
                    .or_else(|what|
                        what.downcast::<Box<str>>().map(|b| reject(*b)))
                    .unwrap_or_else(|_| reject("<unknown panic value>"));
                Err(fail_case(msg))
            },
        }
    }
}

This might however incur a backwards-compatibility-break.

We could perhaps also say (and keep the signature of run_one as is):

fn panic_guard<V, F: Fn(&V) -> TestCaseResult>(test: F)
    -> impl Fn(&V) -> TestCaseResult {
    move |case| {
        match panic::catch_unwind(AssertUnwindSafe(|| test(&case))) {
            Ok(r) => r,
            Err(what) => {
                let msg = what.downcast::<&'static str>()
                    .map(|s| reject(*s))
                    .or_else(|what|
                        what.downcast::<String>().map(|b| reject(*b)))
                    .or_else(|what|
                        what.downcast::<Box<str>>().map(|b| reject(*b)))
                    .unwrap_or_else(|_| reject("<unknown panic value>"));
                Err(fail_case(msg))
            },
        }
    }
}

fn should_panic<V, F: Fn(&V) -> TestCaseResult>(test: F)
    -> impl Fn(&V) -> TestCaseResult {
    move |case| {
        match panic::catch_unwind(AssertUnwindSafe(|| test(&case))) {
            Ok(_) => Err(fail_case("ShouldPanic test failed to panic.")),
            Err(_) => Ok(()),
        }
    }
}

Which has the benefit of still allowing closures directly without first adding a wrapper.
I think it's impossible to do this without conservative_impl_trait tho, so should we wait with this?

What are your thoughts on this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.