Skip to content
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

Merged
merged 33 commits into from
Sep 25, 2018
Merged

use proptest to fuzz the resolver #5921

merged 33 commits into from
Sep 25, 2018

Conversation

Eh2406
Copy link
Contributor

@Eh2406 Eh2406 commented Aug 21, 2018

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:

  • 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?
  • 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.
  • proptest does not yet work with minimal-versions, a taste of my own medicine.
  • I have not verified that, if I remove the fixes for other test that this regenerates them.

The current strategy does not:

  • generate interesting version numbers, it just dose 1.0.0, 2.0.0 ...
  • guarantee that the version requirements are possible to meet by the crate named.
  • generate features.
  • generate dev-dependencies.
  • 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.

  • 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)

@rust-highfive
Copy link

r? @matklad

(rust_highfive has picked a reviewer for you, use r? to override)

@Centril
Copy link

Centril commented Aug 22, 2018

cc @AltSysrq wrt. advice.

@alexcrichton
Copy link
Member

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 :)

@Eh2406
Copy link
Contributor Author

Eh2406 commented Aug 22, 2018

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.

Copy link

@AltSysrq AltSysrq left a 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.

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))

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

let data_len = data.len();
(
Just(data),
vec(subsequence(names, 0..names_len), data_len..=data_len),

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.

(
Just(data),
Just(deps),
vec(

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.

/// 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_-]*";

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.

@Eh2406
Copy link
Contributor Author

Eh2406 commented Aug 24, 2018

@AltSysrq Thanks for the suggestions! Locally, that seems to have made a big difference. Thanks for the pointer on prop_flat_map, I had somehow got in my head that it was the major form of composition. I will endeavor to dial its use back. :-)

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, prop_assert!(res.len() <= 4)) then it can shrink in ~10 min with release, after @AltSysrq suggestions.

@Eh2406
Copy link
Contributor Author

Eh2406 commented Aug 25, 2018

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 subsequence, and it is adding another layer of prop_flat_map.

When I thought of another Implementation, but I don't know if it will be shrinking frently. Every time the proposed strategy uses subsequence to ensure it gets a valid entry, (a dependency name that is in the index and smaller the the crate, and a version range end that are actually versions in the index) the new implementation would just generate a u16::any() then in the last prop_map I can use that u16 as an index into the vec of the corresponding thing. Like names[index as usize % names.len()].

@AltSysrq, @Centril what do you think of the heavy use of % to remove the use of prop_flat_map?

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.

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

@AltSysrq
Copy link

generate a u16::any() then in the last prop_map I can use that u16 as an index into the vec

I think the Index type added in proptest 0.8.6 would be usable for this purpose.

@Eh2406
Copy link
Contributor Author

Eh2406 commented Aug 27, 2018

@AltSysrq I was just reading through the changes in 0.8.6 one commit at a time. Each commit was more perfectly what I needed then the last! I nearly fell out of my seat with excitement for several of them! Thank you! Thank you! Thank you!

@Eh2406
Copy link
Contributor Author

Eh2406 commented Sep 21, 2018

What is going on with appveyor!? That one timed out on cargo check!?

@Eh2406
Copy link
Contributor Author

Eh2406 commented Sep 22, 2018

@bors: retry

@bors
Copy link
Collaborator

bors commented Sep 22, 2018

⌛ Testing commit 3e7192e with merge 702df0e...

bors added a commit that referenced this pull request Sep 22, 2018
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)
@bors
Copy link
Collaborator

bors commented Sep 22, 2018

💔 Test failed - status-travis

@matthiaskrgr
Copy link
Member

test resolve::limited_independence_of_irrelevant_alternatives ... test resolve::limited_independence_of_irrelevant_alternatives has been running for over 60 seconds
[....]


No output has been received in the last 10m0s, this potentially indicates a stalled build or something wrong with the build itself.
Check the details on how to adjust your build configuration on: https://docs.travis-ci.com/user/common-build-problems/#Build-times-out-because-no-output-was-received

The build has been terminated

maybe the test is just too slow in debug mode?

@Eh2406
Copy link
Contributor Author

Eh2406 commented Sep 22, 2018

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.

@matthiaskrgr
Copy link
Member

The limited_independence_of_irrelevant_alternatives is very slow in debug mode.
It took 16 minutes to complete on my system which I deem to be faster than the average travis virtual env.
So if it is launched somewhere at the end, travis will autokill the job because no output has been supplied for 15 minutes.

@Eh2406
Copy link
Contributor Author

Eh2406 commented Sep 22, 2018

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:

  • Have CI run fewer cases.
  • Have each case loop fewer times hear and hear
  • Make the test look at simpler input.
  • Find ways for the test to run more efficiently.
  • See if with the new debug the result_cache is worthwhile.
  • Find the slow casses and mark them as bugs, possibly with the timeout.
  • Split the test up into more test that each run faster.

I am experimenting.

@Eh2406
Copy link
Contributor Author

Eh2406 commented Sep 24, 2018

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.

@Eh2406
Copy link
Contributor Author

Eh2406 commented Sep 24, 2018

@bors: r=alexcrichton

@bors

This comment has been minimized.

@bors

This comment has been minimized.

@bors

This comment has been minimized.

@Eh2406
Copy link
Contributor Author

Eh2406 commented Sep 25, 2018

[35] SSL connect error (schannel: next InitializeSecurityContext failed: Unknown error (0x80092013) - The revocation function was unable to check revocation because the revocation server was offline.)

@bors: retry

@bors
Copy link
Collaborator

bors commented Sep 25, 2018

⌛ Testing commit a4da525 with merge 4e09634...

bors added a commit that referenced this pull request Sep 25, 2018
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)
@bors
Copy link
Collaborator

bors commented Sep 25, 2018

☀️ Test successful - status-appveyor, status-travis
Approved by: alexcrichton
Pushing 4e09634 to master...

@bors bors merged commit a4da525 into rust-lang:master Sep 25, 2018
@@ -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
Copy link
Member

@killercup killercup Sep 26, 2018

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

:trollface:

(same below)

bors added a commit that referenced this pull request Sep 27, 2018
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.
@ehuss ehuss added this to the 1.31.0 milestone Feb 6, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants