use crate::{
NameMatcher,
parsing::{
AndOperator, DifferenceOperator, GenericGlob, NotOperator, OrOperator, ParsedExpr,
ParsedLeaf,
},
};
use guppy::graph::cargo::BuildPlatform;
use proptest::prelude::*;
impl ParsedExpr<()> {
#[doc(hidden)]
pub fn strategy() -> impl Strategy<Value = Self> {
let leaf = ParsedLeaf::strategy().prop_map(Self::Set);
leaf.prop_recursive(8, 256, 10, |inner| {
prop_oneof![
1 => (any::<NotOperator>(), inner.clone()).prop_map(|(op, a)| {
Self::Not(op, a.parenthesize_not())
}),
1 => (any::<OrOperator>(), inner.clone(), inner.clone()).prop_map(|(op, a, b)| {
Self::Union(op, a.parenthesize_or_left(), b.parenthesize_or_right())
}),
1 => (any::<AndOperator>(), inner.clone(), inner.clone()).prop_map(|(op, a, b)| {
Self::Intersection(op, a.parenthesize_and_left(), b.parenthesize_and_right())
}),
1 => (any::<DifferenceOperator>(), inner.clone(), inner.clone()).prop_map(|(op, a, b)| {
Self::Difference(op, a.parenthesize_and_left(), b.parenthesize_and_right())
}),
1 => inner.prop_map(|a| Self::Parens(Box::new(a))),
]
})
}
fn parenthesize_not(self) -> Box<Self> {
match &self {
Self::Union(_, _, _) | Self::Intersection(_, _, _) | Self::Difference(_, _, _) => {
Box::new(Self::Parens(Box::new(self)))
}
Self::Set(_) | Self::Not(_, _) | Self::Parens(_) => Box::new(self),
}
}
fn parenthesize_or_left(self) -> Box<Self> {
Box::new(self)
}
fn parenthesize_or_right(self) -> Box<Self> {
match &self {
Self::Union(_, _, _) => Box::new(Self::Parens(Box::new(self))),
Self::Set(_)
| Self::Intersection(_, _, _)
| Self::Difference(_, _, _)
| Self::Not(_, _)
| Self::Parens(_) => Box::new(self),
}
}
fn parenthesize_and_left(self) -> Box<Self> {
match &self {
Self::Union(_, _, _) => Box::new(Self::Parens(Box::new(self))),
Self::Set(_)
| Self::Intersection(_, _, _)
| Self::Difference(_, _, _)
| Self::Not(_, _)
| Self::Parens(_) => Box::new(self),
}
}
fn parenthesize_and_right(self) -> Box<Self> {
match &self {
Self::Union(_, _, _) | Self::Intersection(_, _, _) | Self::Difference(_, _, _) => {
Box::new(Self::Parens(Box::new(self)))
}
Self::Set(_) | Self::Not(_, _) | Self::Parens(_) => Box::new(self),
}
}
}
impl ParsedLeaf<()> {
pub(crate) fn strategy() -> impl Strategy<Value = Self> {
prop_oneof![
1 => NameMatcher::default_glob_strategy().prop_map(|s| Self::Package(s, ())),
1 => NameMatcher::default_glob_strategy().prop_map(|s| Self::Deps(s, ())),
1 => NameMatcher::default_glob_strategy().prop_map(|s| Self::Rdeps(s, ())),
1 => NameMatcher::default_equal_strategy().prop_map(|s| Self::Kind(s, ())),
1 => NameMatcher::default_glob_strategy().prop_map(|s| Self::Binary(s, ())),
1 => NameMatcher::default_glob_strategy().prop_map(|s| Self::BinaryId(s, ())),
1 => build_platform_strategy().prop_map(|p| Self::Platform(p, ())),
1 => NameMatcher::default_contains_strategy().prop_map(|s| Self::Test(s, ())),
1 => NameMatcher::default_glob_strategy().prop_map(|s| Self::Group(s, ())),
1 => Just(Self::All),
1 => Just(Self::None),
]
}
}
impl NameMatcher {
pub(crate) fn default_equal_strategy() -> impl Strategy<Value = Self> {
prop_oneof![
1 => (name_strategy(), any::<bool>()).prop_filter_map(
"implicit = true can't begin with operators",
|(value, implicit)| {
let accept = match (implicit, begins_with_operator(&value)) {
(false, _) => true,
(true, false) => true,
(true, true) => false,
};
accept.then_some(Self::Equal { value, implicit })
},
),
1 => name_strategy().prop_map(|value| {
Self::Contains { value, implicit: false }
}),
1 => regex_strategy().prop_map(Self::Regex),
1 => glob_strategy().prop_map(|glob| { Self::Glob { glob, implicit: false }}),
]
}
pub(crate) fn default_contains_strategy() -> impl Strategy<Value = Self> {
prop_oneof![
1 => name_strategy().prop_map(|value| {
Self::Equal { value, implicit: false }
}),
1 => (name_strategy(), any::<bool>()).prop_filter_map(
"implicit = true can't begin with operators",
|(value, implicit)| {
let accept = match (implicit, begins_with_operator(&value)) {
(false, _) => true,
(true, false) => true,
(true, true) => false,
};
accept.then_some(Self::Contains { value, implicit })
}),
1 => regex_strategy().prop_map(Self::Regex),
1 => glob_strategy().prop_map(|glob| { Self::Glob { glob, implicit: false }}),
]
}
pub(crate) fn default_glob_strategy() -> impl Strategy<Value = Self> {
prop_oneof![
1 => name_strategy().prop_map(|value| {
Self::Equal { value, implicit: false }
}),
1 => name_strategy().prop_map(|value| {
Self::Contains { value, implicit: false }
}),
1 => regex_strategy().prop_map(Self::Regex),
1 => (glob_strategy(), any::<bool>()).prop_filter_map(
"implicit = true can't begin with operators",
|(glob, implicit)| {
let accept = match (implicit, begins_with_operator(glob.as_str())) {
(false, _) => true,
(true, false) => true,
(true, true) => false,
};
accept.then_some(Self::Glob { glob, implicit })
},
),
]
}
}
fn begins_with_operator(value: &str) -> bool {
value.starts_with('=')
|| value.starts_with('~')
|| value.starts_with('/')
|| value.starts_with('#')
}
pub(crate) fn build_platform_strategy() -> impl Strategy<Value = BuildPlatform> {
prop::sample::select(&[BuildPlatform::Host, BuildPlatform::Target][..])
}
pub(crate) fn name_strategy() -> impl Strategy<Value = String> {
prop_oneof![
4 => "[abcde]{0,10}",
1 => r"[abcde=/~#*?]{0,10}",
1 => r"[abcde=/~#*?\[\]\r\t\n\u{2055}\u{1fe4e}]{0,10}",
]
}
pub(crate) fn glob_strategy() -> impl Strategy<Value = GenericGlob> {
glob_str_strategy().prop_filter_map(
"some strings generated by the strategy are invalid globs",
|s| GenericGlob::new(s).ok(),
)
}
fn glob_str_strategy() -> impl Strategy<Value = String> {
prop_oneof![
4 => "[abcde]{0,10}",
1 => r"[abcde*?\[\]]{0,10}",
1 => r"[abcde=/~#*?\[\]\r\t\n\u{2055}\u{1fe4e}]{0,10}",
]
}
pub(crate) fn regex_strategy() -> impl Strategy<Value = regex::Regex> {
regex_str_strategy().prop_map(|s| {
regex::Regex::new(&s).expect("all regexes generated by the strategy are valid")
})
}
fn regex_str_strategy() -> impl Strategy<Value = String> {
let leaf = prop_oneof![
4 => "[abcde]{0,10}",
1 => r"([abcde]|(\\\?)|(\\\*)|){0,10}",
1 => r"[abcde/\r\t\n\u{2055}\u{1fe4e}]{0,10}",
];
leaf.prop_recursive(
3,
16,
3,
|inner| {
prop_oneof![
1 => (inner.clone(), inner.clone()).prop_map(|(a, b)| {
format!("{a}{b}")
}),
1 => (inner.clone(), inner.clone()).prop_map(|(a, b)| {
format!("({a})|({b})")
}),
1 => inner.clone().prop_map(|a| {
format!("({a})*")
}),
1 => inner.prop_map(|a| {
format!("({a})?")
}),
]
},
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test_strategy::proptest]
fn proptest_regex_valid(#[strategy(regex_str_strategy())] regex_str: String) {
println!("regex_str = {regex_str:?}");
regex::Regex::new(®ex_str).expect("all regexes generated by the strategy are valid");
}
}