-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
use proptest to fuzz the resolver #5921
Conversation
r? @matklad (rust_highfive has picked a reviewer for you, use r? to override) |
cc @AltSysrq wrt. advice. |
Sounds like a great idea to me! I don't have many thoughts in terms of how best to approach this or how to solve the open issues, but when you're comfortable merging I'm game for that :) |
When the core functionality is working, I may decide to merge before all the things are checked off transferring the remainder to new issues. The code is far to inscrutable for it to be worth doing now, but before merge it needs comments so that other programers can understand what it is doing. I will need help finding the confusing parts, and making me explain them. |
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.
Added a couple suggestions as requested.
In general, I recommend trying to avoid prop_flat_map
when reasonably possible since it does cause shrinking to be a lot slower and less likely to find a minimal case.
Readability-wise, my only suggestion would be to split the big chain of combinators into smaller functions so that the strategy parts have names. It also makes it easier to reuse parts later, e.g. should you find a need to just generate a list of crate names in isolation.
tests/testsuite/resolve.rs
Outdated
const MAX_VERSIONS: usize = 10; | ||
|
||
fn range(max: usize) -> impl Strategy<Value = (usize, usize)> { | ||
(0..max).prop_flat_map(move |low| (Just(low), low..=max)) |
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 believe you could rewrite this as
(0..max, 0..max).prop_map(|(a, b)| min(a, b)..=max(a,b))
which could substantially improve shrinking performance
tests/testsuite/resolve.rs
Outdated
let data_len = data.len(); | ||
( | ||
Just(data), | ||
vec(subsequence(names, 0..names_len), data_len..=data_len), |
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.
There's impl From<usize> for SizeRange
so you should be able to just write data_len
by itself instead of data_len..=data_len
.
tests/testsuite/resolve.rs
Outdated
( | ||
Just(data), | ||
Just(deps), | ||
vec( |
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 think you could lift this out to a top-level strategy by just generating a vector of size MAX_CRATES*MAX_CRATES
since the code below only looks at the elements it needs. That would remove another layer of flat mapping.
tests/testsuite/resolve.rs
Outdated
/// Unlike vec((Name, Ver, vec((Name, VerRq), ..), ..) | ||
/// This strategy has a high probability of having valid dependencies | ||
fn registry_strategy() -> impl Strategy<Value=Vec<Summary>> { | ||
const VALID_NAME_STRATEGY: &str = "[A-Za-z_-][A-Za-z0-9_-]*"; |
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.
Since it sounds like runtime performance is a problem right now, I'd suggest precompiling the regex so it doesn't get reparsed every time.
@AltSysrq Thanks for the suggestions! Locally, that seems to have made a big difference. Thanks for the pointer on The current performance on my computer is almost eceptibal, at least for the trivial tests I have been working with so far. If the test passes, like the one committed, then even in debug it runs in just a few seconds. If I write a test that fails (like, |
I was working on generating only version requirements that are possible to meet by the named crate. I was having difficulties with the limitations of When I thought of another Implementation, but I don't know if it will be shrinking frently. Every time the proposed strategy uses @AltSysrq, @Centril what do you think of the heavy use of |
Cargo.toml
Outdated
@@ -93,6 +93,8 @@ features = [ | |||
|
|||
[dev-dependencies] | |||
bufstream = "0.1" | |||
proptest = "0.8.4" | |||
wait-timeout = "0.1.4" # required only for minimal-versions. brought in by proptest. |
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 believe this particular issue should be fixed in proptest 0.8.6
I think the |
@AltSysrq I was just reading through the changes in |
…meet by the crate named.
What is going on with appveyor!? That one timed out on |
@bors: retry |
use proptest to fuzz the resolver This has been a long time goal. This uses proptest to generate random registry indexes and throws them at the resolver. It would be simple to generate a registry by, 1. make a list of name and version number each picked at random 2. for each pick a list of dependencies by making a list of name and version requirements at random. Unfortunately, it would be extremely unlikely to generate any interesting cases, as the chance that the random name you depend on was also generated as the name of a crate is vanishingly small. So this implementation works very hard to ensure that it only generates valid dependency requirements. This is still a WIP as it has many problems: - [x] The current strategy is very convoluted. It is hard to see that it is correct, and harder to see how it can be expanded. Thanks to @Centril for working with me on IRC to get this far. Do you have advice for improving it? - [X] It is slow as molasses when run without release. I looked with a profilere and we seem to spend 2/3 of the time in `to_url`. Maybe we can special case `example.com` for test, like we do for `crates.io` or something? Edit: Done. `lazy_static` did its magic. - [x] `proptest` does not yet work with `minimal-versions`, a taste of my own medicine. - [x] I have not verified that, if I remove the fixes for other test that this regenerates them. The current strategy does not: - [x] generate interesting version numbers, it just dose 1.0.0, 2.0.0 ... - [x] guarantee that the version requirements are possible to meet by the crate named. - [ ] generate features. - [ ] generate dev-dependencies. - [x] build deep dependency trees, it seems to prefer to generate crates with 0 or 1 dependents so that on average the tree is 1 or 2 layers deep. And last but not least, there are no interesting properties being tested. Like: - [ ] If resolution was successful, then all the transitive requirements are met. - [x] If resolution was successful, then unpublishing a version of a crate that was not selected should not change that. - [x] If resolution was unsuccessful, then it should stay unsuccessful even if any version of a crate is unpublished. - [ ] @maurer suggested testing for consistency. Same registry, same cargo version, same lockfile, every time. - [ ] @maurer suggested a pareto optimality property (if all else stays the same, but new package versions are released, we don't get a new lockfile where every version is <= the old one, and at least one is < the old one)
💔 Test failed - status-travis |
maybe the test is just too slow in debug mode? |
It is randomize testing of NP complete code, so it is possible that some random portion of the time it finds an example the take so long to complete that we don't see the error message. Locally I have had this test running repeatedly in a loop for a couple of days trying to find an example that might be causing this. So far no luck. I know proptest has some fancy features specifically for dealing with this issue, when I get a chance I will investigate using them. |
The |
Thanks for the sample! It is randomized so it may take different amounts of time on different runs. Also it is setup to run a smaller number of cases on CI then locally. Possible solutions include:
I am experimenting. |
So it turn out that the distribution created by the edge list was heavily skewed to unresolvable registries. Srinking the number of edges made everything work faster and better. |
@bors: r=alexcrichton |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
@bors: retry |
use proptest to fuzz the resolver This has been a long time goal. This uses proptest to generate random registry indexes and throws them at the resolver. It would be simple to generate a registry by, 1. make a list of name and version number each picked at random 2. for each pick a list of dependencies by making a list of name and version requirements at random. Unfortunately, it would be extremely unlikely to generate any interesting cases, as the chance that the random name you depend on was also generated as the name of a crate is vanishingly small. So this implementation works very hard to ensure that it only generates valid dependency requirements. This is still a WIP as it has many problems: - [x] The current strategy is very convoluted. It is hard to see that it is correct, and harder to see how it can be expanded. Thanks to @Centril for working with me on IRC to get this far. Do you have advice for improving it? - [X] It is slow as molasses when run without release. I looked with a profilere and we seem to spend 2/3 of the time in `to_url`. Maybe we can special case `example.com` for test, like we do for `crates.io` or something? Edit: Done. `lazy_static` did its magic. - [x] `proptest` does not yet work with `minimal-versions`, a taste of my own medicine. - [x] I have not verified that, if I remove the fixes for other test that this regenerates them. The current strategy does not: - [x] generate interesting version numbers, it just dose 1.0.0, 2.0.0 ... - [x] guarantee that the version requirements are possible to meet by the crate named. - [ ] generate features. - [ ] generate dev-dependencies. - [x] build deep dependency trees, it seems to prefer to generate crates with 0 or 1 dependents so that on average the tree is 1 or 2 layers deep. And last but not least, there are no interesting properties being tested. Like: - [ ] If resolution was successful, then all the transitive requirements are met. - [x] If resolution was successful, then unpublishing a version of a crate that was not selected should not change that. - [x] If resolution was unsuccessful, then it should stay unsuccessful even if any version of a crate is unpublished. - [ ] @maurer suggested testing for consistency. Same registry, same cargo version, same lockfile, every time. - [ ] @maurer suggested a pareto optimality property (if all else stays the same, but new package versions are released, we don't get a new lockfile where every version is <= the old one, and at least one is < the old one)
☀️ Test successful - status-appveyor, status-travis |
@@ -240,6 +240,18 @@ fn activate_deps_loop( | |||
config.shell().status("Resolving", "dependency graph...")?; | |||
} | |||
} | |||
// The largest test in our sweet takes less then 5000 ticks |
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.
s/sweet/sweet test suite
(same below)
Proptest/Resolver move test helper functions to support This moves all the code in `tests/testsuite/resolve.rs` that is not tests of cargo to ` tests/testsuite/support/resolve.rs`. (follow up to #5921) This also clears up some inconsistencies in naming between local variables in `activate_deps_loop` and `BacktrackFrame`. (follow up to #6097) This is a true refactoring, nothing about the executable has changed.
This has been a long time goal. This uses proptest to generate random registry indexes and throws them at the resolver.
It would be simple to generate a registry by,
Unfortunately, it would be extremely unlikely to generate any interesting cases, as the chance that the random name you depend on was also generated as the name of a crate is vanishingly small. So this implementation works very hard to ensure that it only generates valid dependency requirements.
This is still a WIP as it has many problems:
to_url
. Maybe we can special caseexample.com
for test, like we do forcrates.io
or something? Edit: Done.lazy_static
did its magic.proptest
does not yet work withminimal-versions
, a taste of my own medicine.The current strategy does not:
And last but not least, there are no interesting properties being tested. Like:
If resolution was successful, then all the transitive requirements are met.
If resolution was successful, then unpublishing a version of a crate that was not selected should not change that.
If resolution was unsuccessful, then it should stay unsuccessful even if any version of a crate is unpublished.
@maurer suggested testing for consistency. Same registry, same cargo version, same lockfile, every time.
@maurer suggested a pareto optimality property (if all else stays the same, but new package versions are released, we don't get a new lockfile where every version is <= the old one, and at least one is < the old one)