proptest-rs / proptest Goto Github PK
View Code? Open in Web Editor NEWHypothesis-like property testing for Rust
License: Apache License 2.0
Hypothesis-like property testing for Rust
License: Apache License 2.0
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)
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.
Some inspiration:
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 =)
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"));
}
}
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!
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.
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 😄 )
This issue is intended to be used for tracking proptest's feature parity with other property based testing frameworks in other languages and Rust.
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
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`.
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.
This is mostly a note to self to do research and implement this in some reasonable way, but also to @AltSysrq to see if they have some input. The design we end up with if any should be ergonomic and avoid repeating much of the logic from the actual state machine being tested.
The quickcheck crate request:
ScalaCheck:
Hypothesis:
QuickCheck:
Hedgehog:
A slide:
Erlang:
Rapidcheck:
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!(..)
.
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()
}
}
}
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
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:
Increased familiarity for quickcheck users by bridging the gap.
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 }
}
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.
Arbitrary
by simply always returning the unit.Arbitrary
if their factors are Arbitrary
.Arbitrary
if each product type in each variant in a sum type is Arbitrary
. Variants with no inner product are just unit types.It is considerably more ergonomic to use a macro to generate strategies compared to simply re-iterating the variants in the enum
.
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>>;
}
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. :)
Currently, it is impossible to have a S: Strategy
where ValueOf<S>
is non-owned.
Examples:
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.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.
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!
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?
We can already generate String
s 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:
Is understood by most people familiar with BNF
There's:
Some formalisms write production = definition ;
, some write: production : definition ;
and some write: production ::= definition ;
.
Which can be extended with probabilities such that the resulting formalism is a superset.
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?
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...)
I'm curious: what deficiencies did you see in the quickcheck crate to make you create this?
Usually, the only thing that you are interested in is the minimal failing test case. If possible, it would be great to omit all other output.
This may very well be more of a Cargo issue though.
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
{
...
}
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?
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)
.
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.
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 {
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?
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.
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.
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.
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
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.
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
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:
Cons:
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:
struct LazyJust<V: Debug, F: Fn() -> V>
ClosureStrategy
for generating random functionsThis 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 =)
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?
Due to changes in the alloc
crate.
Fix pending update to bit-vec
crate and some minor changes in this crate.
Crates not using the no_std support or unstable
/nightly
features are not affected.
Currently, the strategy for generating char
s 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.
Hi, I am really excited to see your work on this project!
I was looking forward to using a deadline type feature to look for accidental infinite loops in some code. I know that hypothesis as a deadline setting. How hard is it to add it to this project?
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)
}
Hypothesis has built-in support for recursive generation. Being able to do the same using proptest would be very valuable, given high emphasis on enums and ADTs in Rust.
Easiest explained with an example: pattern a[1]
results in 1a
instead of a1
.
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.
@killercup and I discussed a bit about using proptest
to test CLI apps which we've recorded at: https://github.com/rust-lang-nursery/cli-wg/issues/43
Filing this issue to make y'all aware of this in case you have ideas ;)
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! :)
This is being discussed over at quickcheck in issues (I'll let those discussions explain):
It would be neat to add something similar for proptest =)
Something like:
#[proptest_save(path/to/my/regressions)]
Obviously the type tested has to be serializable somehow.
#[proptest_explicit(regression1, regression2, ...)]
Some proposed TODOs for version 0.8.0:
Iterator::try_fold
RangeInclusive::{start, end}
become available.Strategy for RangeInclusive<T>
where T
is a float (which requires .start()
and .end()
).
#[cfg(MIN_1_127)]
logic if we want to make v0.8.0 available for 1.26.0.Arbitrary
for SIMD stuff;#[cfg(MIN_1_127)]
logic if we want to make v0.8.0 available for 1.26.0.proptest_derive
quote
, proc-macro2
, syn
crates.prob
attribute on enum variantsIt 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:
quick-error
) .no_std
related feature flag options for crates that support such (e.g. lazy_static
) .std
library bits with core-based alternatives or convert into optional capabilities.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.
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?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.