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
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
56a222c
a start on using proptest to fuzz the resolver
Eh2406 Aug 21, 2018
1761886
cache the example url to solve performance problem
Eh2406 Aug 21, 2018
ce1772c
get working with minimal-versions
Eh2406 Aug 21, 2018
4e619d8
better generation of version numbers
Eh2406 Aug 22, 2018
732aa10
small clean up
Eh2406 Aug 22, 2018
c26553a
small clean up
Eh2406 Aug 22, 2018
b7285e8
handle "bad" slightly better
Eh2406 Aug 23, 2018
5339e92
small clean up for the cache of the example url
Eh2406 Aug 23, 2018
f270dda
incorporate @AltSysrq suggestions
Eh2406 Aug 24, 2018
17fe190
update to the new version of proptest
Eh2406 Aug 27, 2018
85b1976
use the new `impl Strategy for Vec<S>` to only generate valid depende…
Eh2406 Aug 27, 2018
0ef43cb
use the new `result_cache`
Eh2406 Aug 27, 2018
0206cec
double down on `prop_flat_map` to guarantee version requirements are …
Eh2406 Aug 27, 2018
1f98871
stronger assert in the core
Eh2406 Aug 25, 2018
b0bbb6a
use args for scale
Eh2406 Aug 28, 2018
2628ec0
works a LOT better if the simple cases are at the beginning
Eh2406 Aug 29, 2018
509806e
small clean up
Eh2406 Aug 30, 2018
d6258e9
Merge remote-tracking branch 'origin/master' into proptest
Eh2406 Aug 30, 2018
1eaf543
small clean up
Eh2406 Aug 30, 2018
2b1c39f
more controllable bad dependencies, and use Index for ranges
Eh2406 Aug 30, 2018
05c6ce0
remove a `prop_flat_map` use @AltSysrq suggestion again
Eh2406 Aug 31, 2018
856964f
fail fast on CI, don't shrink the input
Eh2406 Aug 31, 2018
2b16156
If resolution was successful, then unpublishing a version of a crate …
Eh2406 Sep 5, 2018
c497851
If resolution was unsuccessful, then it should stay unsuccessful even…
Eh2406 Sep 5, 2018
1b15b15
give intermediate steps names
Eh2406 Sep 17, 2018
d8e19fb
pull the has bad dep before `prop_flat_map`
Eh2406 Sep 17, 2018
2015191
`edge list` is a much cleaner approach
Eh2406 Sep 17, 2018
7ad9f5e
add a test to test the tester
Eh2406 Sep 19, 2018
6bd2540
check each registry more thoroughly
Eh2406 Sep 18, 2018
40d9de4
Merge remote-tracking branch 'origin/master' into proptest
Eh2406 Sep 20, 2018
3e7192e
pretty print the registry made by proptest
Eh2406 Sep 21, 2018
6763ede
proptest 0.8.7 has a better `ci_no_shrink`
Eh2406 Sep 24, 2018
a4da525
In theory shrinkage should be 2, but in practice we get better trees …
Eh2406 Sep 24, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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


[[bin]]
name = "cargo"
Expand Down
4 changes: 4 additions & 0 deletions tests/testsuite/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ extern crate flate2;
extern crate git2;
extern crate glob;
extern crate hex;
#[macro_use]
extern crate lazy_static;
extern crate libc;
#[macro_use]
extern crate proptest;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate serde_json;
Expand Down
145 changes: 116 additions & 29 deletions tests/testsuite/resolve.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::collections::{BTreeMap, HashSet};
use std::cmp::{max, min};

use support::hamcrest::{assert_that, contains, is_not};

Expand All @@ -12,6 +13,11 @@ use support::ChannelChanger;
use support::{execs, project};
use support::registry::Package;

use proptest::collection::{btree_map, btree_set, vec};
use proptest::prelude::*;
use proptest::sample::subsequence;
use proptest::string::string_regex;

fn resolve(
pkg: &PackageId,
deps: Vec<Dependency>,
Expand Down Expand Up @@ -58,9 +64,7 @@ trait ToDep {

impl ToDep for &'static str {
fn to_dep(self) -> Dependency {
let url = "http://example.com".to_url().unwrap();
let source_id = SourceId::for_registry(&url).unwrap();
Dependency::parse_no_deprecated(self, Some("1.0.0"), &source_id).unwrap()
Dependency::parse_no_deprecated(self, Some("1.0.0"), &registry_loc()).unwrap()
}
}

Expand All @@ -74,54 +78,55 @@ trait ToPkgId {
fn to_pkgid(&self) -> PackageId;
}

impl<'a> ToPkgId for &'a str {
impl<'a> ToPkgId for PackageId {
fn to_pkgid(&self) -> PackageId {
PackageId::new(*self, "1.0.0", &registry_loc()).unwrap()
self.clone()
}
}

impl<'a> ToPkgId for (&'a str, &'a str) {
impl<'a> ToPkgId for &'a str {
fn to_pkgid(&self) -> PackageId {
let (name, vers) = *self;
PackageId::new(name, vers, &registry_loc()).unwrap()
PackageId::new(*self, "1.0.0", &registry_loc()).unwrap()
}
}

impl<'a> ToPkgId for (&'a str, String) {
impl<T: AsRef<str>, U: AsRef<str>> ToPkgId for (T, U) {
fn to_pkgid(&self) -> PackageId {
let (name, ref vers) = *self;
PackageId::new(name, vers, &registry_loc()).unwrap()
let (name, vers) = self;
PackageId::new(name.as_ref(), vers.as_ref(), &registry_loc()).unwrap()
}
}

macro_rules! pkg {
($pkgid:expr => [$($deps:expr),+]) => ({
let d: Vec<Dependency> = vec![$($deps.to_dep()),+];
let pkgid = $pkgid.to_pkgid();
let link = if pkgid.name().ends_with("-sys") {Some(pkgid.name().as_str())} else {None};

Summary::new(pkgid, d, &BTreeMap::<String, Vec<String>>::new(), link, false).unwrap()
pkg_dep($pkgid, d)
});

($pkgid:expr) => ({
let pkgid = $pkgid.to_pkgid();
let link = if pkgid.name().ends_with("-sys") {Some(pkgid.name().as_str())} else {None};
Summary::new(pkgid, Vec::new(), &BTreeMap::<String, Vec<String>>::new(), link, false).unwrap()
pkg($pkgid)
})
}

fn registry_loc() -> SourceId {
let remote = "http://example.com".to_url().unwrap();
SourceId::for_registry(&remote).unwrap()
lazy_static! {
static ref EXAMPLE_DOT_COM: SourceId = SourceId::for_registry(&"http://example.com".to_url().unwrap()).unwrap();
}
EXAMPLE_DOT_COM.clone()
}

fn pkg(name: &str) -> Summary {
let link = if name.ends_with("-sys") {
Some(name)
fn pkg<T: ToPkgId>(name: T) -> Summary {
pkg_dep(name, Vec::new())
}

fn pkg_dep<T: ToPkgId>(name: T, dep: Vec<Dependency>) -> Summary {
let pkgid = name.to_pkgid();
let link = if pkgid.name().ends_with("-sys") {
Some(pkgid.name().as_str())
} else {
None
};
Summary::new(pkg_id(name), Vec::new(), &BTreeMap::<String, Vec<String>>::new(), link, false).unwrap()
Summary::new(name.to_pkgid(), dep, &BTreeMap::<String, Vec<String>>::new(), link, false).unwrap()
}

fn pkg_id(name: &str) -> PackageId {
Expand Down Expand Up @@ -155,9 +160,7 @@ fn dep(name: &str) -> Dependency {
dep_req(name, "1.0.0")
}
fn dep_req(name: &str, req: &str) -> Dependency {
let url = "http://example.com".to_url().unwrap();
let source_id = SourceId::for_registry(&url).unwrap();
Dependency::parse_no_deprecated(name, Some(req), &source_id).unwrap()
Dependency::parse_no_deprecated(name, Some(req), &registry_loc()).unwrap()
}

fn dep_loc(name: &str, location: &str) -> Dependency {
Expand Down Expand Up @@ -185,6 +188,90 @@ fn loc_names(names: &[(&'static str, &'static str)]) -> Vec<PackageId> {
.collect()
}

/// This generates a random registry index.
/// 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 MAX_CRATES: usize = 10;
const MAX_VERSIONS: usize = 10;

let valid_name_strategy = string_regex("[A-Za-z_-][A-Za-z0-9_-]*").unwrap();

fn range(m: usize) -> impl Strategy<Value = (usize, usize)> {
(..m, ..m).prop_map(|(a, b)| (min(a, b), max(a,b)))
}

fn ver(max: usize) -> impl Strategy<Value=(usize, usize, usize)> {
(..=max, ..=max, ..=max)
}

btree_map(
valid_name_strategy,
btree_set(
ver(MAX_VERSIONS),
1..=MAX_VERSIONS,
),
1..=MAX_CRATES,
).prop_flat_map(|mut b| {
// root is the name of the thing being compiled
// so it would be confusing to have it in the index
b.remove("root");
let names = Some("bad".to_owned()) // if randomly pointing to a dependency that does not exist then call it `bad`
.iter()
.chain(b.iter().map(|(name, _)| name))
.map(|name| pkg_id(name).name())
.collect::<Vec<_>>();
let data = b.iter()
.flat_map(|(name, vers)| vers.iter().map(move |(a, b, c)| (name, format!("{}.{}.{}", a, b, c)).to_pkgid()))
.collect::<Vec<_>>();
let names_len = names.len();
let data_len = data.len();
(
Just(data),
vec(subsequence(names, ..names_len), data_len),
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.

range(MAX_VERSIONS).prop_map(|(b, e)| format!(">={}.0.0, <={}.0.0", b, e)),
names_len*data_len,
),
)
}).prop_map(|(data, deps, vers)| {
let mut i = 0;
data.into_iter()
.zip(deps.into_iter())
.map(|(pkgid, deps)| {
let d: Vec<Dependency> = deps.into_iter()
.filter(|n| pkgid.name() < *n || n.as_str() == "bad")
.map(|n| {
i += 1;
dep_req(&n, &vers[i - 1])
})
.collect();
pkg_dep(pkgid, d)
})
.collect()
})
}

proptest! {
#[test]
fn doesnt_crash(input in registry_strategy()) {
let reg = registry(input.clone());
// there is only a small chance that eny one
// crate will be interesting. So we try them all.
for this in input.iter().rev() {
let res = resolve(
&pkg_id("root"),
vec![dep_req(&this.name(), &format!("={}", this.version()))],
&reg,
);

if let Ok(r) = res {
prop_assert!(r.len() <= 4)
}
}
}
}

#[test]
#[should_panic(expected = "assertion failed: !name.is_empty()")]
fn test_dependency_with_empty_name() {
Expand All @@ -208,14 +295,14 @@ fn assert_same(a: &[PackageId], b: &[PackageId]) {

#[test]
fn test_resolving_only_package() {
let reg = registry(vec![pkg("foo")]);
let reg = registry(vec![pkg!("foo")]);
let res = resolve(&pkg_id("root"), vec![dep("foo")], &reg).unwrap();
assert_same(&res, &names(&["root", "foo"]));
}

#[test]
fn test_resolving_one_dep() {
let reg = registry(vec![pkg("foo"), pkg("bar")]);
let reg = registry(vec![pkg!("foo"), pkg!("bar")]);
let res = resolve(&pkg_id("root"), vec![dep("foo")], &reg).unwrap();
assert_same(&res, &names(&["root", "foo"]));
}
Expand Down