-
Notifications
You must be signed in to change notification settings - Fork 152
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make tests pass and improve shrinking for prop_flat_map #269
Open
nzeh
wants to merge
4
commits into
proptest-rs:master
Choose a base branch
from
nzeh:master
base: master
Could not load branches
Branch not found: {{ refName }}
Could not load tags
Nothing to show
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,20 @@ | ||
## Unreleased | ||
|
||
### Breaking Changes | ||
|
||
- `prop_flat_map` can only be used with closures that produce a strategy that | ||
implements `Copy`. | ||
|
||
- Half-bounded ranges (`x..`, `..x`, and `..=x`) are no longer supported for | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This problem was fixed in #261 . These changes aren't needed anymore. |
||
`f32` and `f64`. | ||
|
||
### New Features | ||
|
||
- The shrink strategy for `prop_flat_map` now shrinks the "outer" strategy | ||
first (the one that produces the values given to `prop_flat_map`) and | ||
shrinks the "inner" strategy" (the one produced by `prop_flat_map`) only | ||
when the outer strategy cannot be shrunk further. | ||
|
||
## 1.0.0 | ||
|
||
### Breaking Changes | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,11 +8,11 @@ | |
// except according to those terms. | ||
|
||
use crate::std_facade::{fmt, Arc}; | ||
use core::mem; | ||
|
||
use crate::strategy::fuse::Fuse; | ||
use crate::strategy::traits::*; | ||
use crate::test_runner::*; | ||
use std::mem; | ||
|
||
/// Adaptor that flattens a `Strategy` which produces other `Strategy`s into a | ||
/// `Strategy` that picks one of those strategies and then picks values from | ||
|
@@ -33,6 +33,7 @@ impl<S: Strategy> Flatten<S> { | |
impl<S: Strategy> Strategy for Flatten<S> | ||
where | ||
S::Value: Strategy, | ||
<S::Value as Strategy>::Tree: Clone, | ||
{ | ||
type Tree = FlattenValueTree<S::Tree>; | ||
type Value = <S::Value as Strategy>::Value; | ||
|
@@ -50,19 +51,7 @@ where | |
{ | ||
meta: Fuse<S>, | ||
current: Fuse<<S::Value as Strategy>::Tree>, | ||
// The final value to produce after successive calls to complicate() on the | ||
// underlying objects return false. | ||
final_complication: Option<Fuse<<S::Value as Strategy>::Tree>>, | ||
// When `simplify()` or `complicate()` causes a new `Strategy` to be | ||
// chosen, we need to find a new failing input for that case. To do this, | ||
// we implement `complicate()` by regenerating values up to a number of | ||
// times corresponding to the maximum number of test cases. A `simplify()` | ||
// which does not cause a new strategy to be chosen always resets | ||
// `complicate_regen_remaining` to 0. | ||
// | ||
// This does unfortunately depart from the direct interpretation of | ||
// simplify/complicate as binary search, but is still easier to think about | ||
// than other implementations of higher-order strategies. | ||
last_complication: Option<Fuse<<S::Value as Strategy>::Tree>>, | ||
runner: TestRunner, | ||
complicate_regen_remaining: u32, | ||
} | ||
|
@@ -77,7 +66,7 @@ where | |
FlattenValueTree { | ||
meta: self.meta.clone(), | ||
current: self.current.clone(), | ||
final_complication: self.final_complication.clone(), | ||
last_complication: self.last_complication.clone(), | ||
runner: self.runner.clone(), | ||
complicate_regen_remaining: self.complicate_regen_remaining, | ||
} | ||
|
@@ -94,7 +83,7 @@ where | |
f.debug_struct("FlattenValueTree") | ||
.field("meta", &self.meta) | ||
.field("current", &self.current) | ||
.field("final_complication", &self.final_complication) | ||
.field("last_complication", &self.last_complication) | ||
.field( | ||
"complicate_regen_remaining", | ||
&self.complicate_regen_remaining, | ||
|
@@ -112,7 +101,7 @@ where | |
Ok(FlattenValueTree { | ||
meta: Fuse::new(meta), | ||
current: Fuse::new(current), | ||
final_complication: None, | ||
last_complication: None, | ||
runner: runner.partial_clone(), | ||
complicate_regen_remaining: 0, | ||
}) | ||
|
@@ -122,6 +111,7 @@ where | |
impl<S: ValueTree> ValueTree for FlattenValueTree<S> | ||
where | ||
S::Value: Strategy, | ||
<S::Value as Strategy>::Tree: Clone, | ||
{ | ||
type Value = <S::Value as Strategy>::Value; | ||
|
||
|
@@ -130,33 +120,28 @@ where | |
} | ||
|
||
fn simplify(&mut self) -> bool { | ||
self.current.disallow_complicate(); | ||
|
||
if self.meta.simplify() { | ||
if let Ok(v) = self.meta.current().new_tree(&mut self.runner) { | ||
self.last_complication = Some(Fuse::new(v)); | ||
mem::swap( | ||
self.last_complication.as_mut().unwrap(), | ||
&mut self.current, | ||
); | ||
self.complicate_regen_remaining = self.runner.config().cases; | ||
return true; | ||
} else { | ||
self.meta.disallow_simplify(); | ||
} | ||
} | ||
|
||
self.complicate_regen_remaining = 0; | ||
let mut old_current = self.current.clone(); | ||
old_current.disallow_simplify(); | ||
|
||
if self.current.simplify() { | ||
// Now that we've simplified the derivative value, we can't | ||
// re-complicate the meta value unless it gets simplified again. | ||
// We also mustn't complicate back to whatever's in | ||
// `final_complication` since the new state of `self.current` is | ||
// the most complicated state. | ||
self.meta.disallow_complicate(); | ||
self.final_complication = None; | ||
true | ||
} else if !self.meta.simplify() { | ||
false | ||
} else if let Ok(v) = self.meta.current().new_tree(&mut self.runner) { | ||
// Shift current into final_complication and `v` into | ||
// `current`. We also need to prevent that value from | ||
// complicating beyond the current point in the future | ||
// since we're going to return `true` from `simplify()` | ||
// ourselves. | ||
self.current.disallow_complicate(); | ||
self.final_complication = Some(Fuse::new(v)); | ||
mem::swap( | ||
self.final_complication.as_mut().unwrap(), | ||
&mut self.current, | ||
); | ||
// Initially complicate by regenerating the chosen value. | ||
self.complicate_regen_remaining = self.runner.config().cases; | ||
self.last_complication = Some(old_current); | ||
true | ||
} else { | ||
false | ||
|
@@ -177,17 +162,20 @@ where | |
} | ||
} | ||
|
||
if self.current.complicate() { | ||
return true; | ||
} else if self.meta.complicate() { | ||
if self.meta.complicate() { | ||
if let Ok(v) = self.meta.current().new_tree(&mut self.runner) { | ||
self.complicate_regen_remaining = self.runner.config().cases; | ||
self.current = Fuse::new(v); | ||
self.complicate_regen_remaining = self.runner.config().cases; | ||
return true; | ||
} else { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dead code? |
||
} | ||
} | ||
|
||
if let Some(v) = self.final_complication.take() { | ||
if self.current.complicate() { | ||
return true; | ||
} | ||
|
||
if let Some(v) = self.last_complication.take() { | ||
self.current = v; | ||
true | ||
} else { | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm concerned about this, I often use Strategies over String, sometimes even
Just(String)
, which isClone
but notCopy
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that perhaps a typo? The initial issue mentions
Clone
.Regardless, it seems a little risky to require
Clone
as it might permanently break lots of proptests out there. Would it make sense to instead provide it under a new name, preserving the currentprop_flat_map
? Not ideal either, I know...I otherwise am happy to see some effort in trying to do something about the shrinking behavior for
prop_flat_map
. Like @nzeh I've run into the issue of poor shrinking many times.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh. Yes, that's a typo. The updated implementation in the pull request requires
Clone
able strategies, not ones that implementCopy
. I aggree with @Andlon that adding even the requirement that the strategy implementClone
is suboptimal. After spending quite some time thinking about how to avoid it at the time, I concluded that doing what my shrink implementation does (shrink the outer strategy first and then the inner one) is not possible with the current architecture of theValueTree
trait. I think it is possible if we completely overhaul the architecture of at least parts of proptest, but that was not something I intended to undertake at the time. Moreover, this overhaul in itself could lead to breakage of code that uses proptest because it would involve changing at least theValueTree
trait.