#[cfg(feature = "semver")]
pub mod semver;
use std::borrow::Borrow;
use std::cmp::Ordering;
use std::fmt::{Debug, Display, Formatter};
use std::ops::Bound::{self, Excluded, Included, Unbounded};
use std::ops::RangeBounds;
#[cfg(any(feature = "proptest", test))]
use proptest::prelude::*;
use smallvec::{smallvec, SmallVec};
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct Ranges<V> {
segments: SmallVec<[Interval<V>; 1]>,
}
type Interval<V> = (Bound<V>, Bound<V>);
impl<V> Ranges<V> {
pub fn empty() -> Self {
Self {
segments: SmallVec::new(),
}
}
pub fn full() -> Self {
Self {
segments: smallvec![(Unbounded, Unbounded)],
}
}
pub fn higher_than(v: impl Into<V>) -> Self {
Self {
segments: smallvec![(Included(v.into()), Unbounded)],
}
}
pub fn strictly_higher_than(v: impl Into<V>) -> Self {
Self {
segments: smallvec![(Excluded(v.into()), Unbounded)],
}
}
pub fn strictly_lower_than(v: impl Into<V>) -> Self {
Self {
segments: smallvec![(Unbounded, Excluded(v.into()))],
}
}
pub fn lower_than(v: impl Into<V>) -> Self {
Self {
segments: smallvec![(Unbounded, Included(v.into()))],
}
}
pub fn between(v1: impl Into<V>, v2: impl Into<V>) -> Self {
Self {
segments: smallvec![(Included(v1.into()), Excluded(v2.into()))],
}
}
pub fn is_empty(&self) -> bool {
self.segments.is_empty()
}
}
impl<V: Clone> Ranges<V> {
pub fn singleton(v: impl Into<V>) -> Self {
let v = v.into();
Self {
segments: smallvec![(Included(v.clone()), Included(v))],
}
}
pub fn complement(&self) -> Self {
match self.segments.first() {
None => Self::full(),
Some((Unbounded, Unbounded)) => Self::empty(),
Some((Included(v), Unbounded)) => Self::strictly_lower_than(v.clone()),
Some((Excluded(v), Unbounded)) => Self::lower_than(v.clone()),
Some((Unbounded, Included(v))) => {
Self::negate_segments(Excluded(v.clone()), &self.segments[1..])
}
Some((Unbounded, Excluded(v))) => {
Self::negate_segments(Included(v.clone()), &self.segments[1..])
}
Some((Included(_), Included(_)))
| Some((Included(_), Excluded(_)))
| Some((Excluded(_), Included(_)))
| Some((Excluded(_), Excluded(_))) => Self::negate_segments(Unbounded, &self.segments),
}
}
fn negate_segments(start: Bound<V>, segments: &[Interval<V>]) -> Self {
let mut complement_segments = SmallVec::new();
let mut start = start;
for (v1, v2) in segments {
complement_segments.push((
start,
match v1 {
Included(v) => Excluded(v.clone()),
Excluded(v) => Included(v.clone()),
Unbounded => unreachable!(),
},
));
start = match v2 {
Included(v) => Excluded(v.clone()),
Excluded(v) => Included(v.clone()),
Unbounded => Unbounded,
}
}
if !matches!(start, Unbounded) {
complement_segments.push((start, Unbounded));
}
Self {
segments: complement_segments,
}
}
}
impl<V: Ord> Ranges<V> {
pub fn as_singleton(&self) -> Option<&V> {
match self.segments.as_slice() {
[(Included(v1), Included(v2))] => {
if v1 == v2 {
Some(v1)
} else {
None
}
}
_ => None,
}
}
pub fn bounding_range(&self) -> Option<(Bound<&V>, Bound<&V>)> {
self.segments.first().map(|(start, _)| {
let end = self
.segments
.last()
.expect("if there is a first element, there must be a last element");
(start.as_ref(), end.1.as_ref())
})
}
pub fn contains<Q>(&self, version: &Q) -> bool
where
V: Borrow<Q>,
Q: ?Sized + PartialOrd,
{
self.segments
.binary_search_by(|segment| {
within_bounds(version, segment).reverse()
})
.is_ok()
}
pub fn contains_many<'s, I, BV>(&'s self, versions: I) -> impl Iterator<Item = bool> + 's
where
I: Iterator<Item = BV> + 's,
BV: Borrow<V> + 's,
{
#[cfg(debug_assertions)]
let mut last: Option<BV> = None;
versions.scan(0, move |i, v| {
#[cfg(debug_assertions)]
{
if let Some(l) = last.as_ref() {
assert!(
l.borrow() <= v.borrow(),
"`contains_many` `versions` argument incorrectly sorted"
);
}
}
while let Some(segment) = self.segments.get(*i) {
match within_bounds(v.borrow(), segment) {
Ordering::Less => return Some(false),
Ordering::Equal => return Some(true),
Ordering::Greater => *i += 1,
}
}
#[cfg(debug_assertions)]
{
last = Some(v);
}
Some(false)
})
}
pub fn from_range_bounds<R, IV>(bounds: R) -> Self
where
R: RangeBounds<IV>,
IV: Clone + Into<V>,
{
let start = match bounds.start_bound() {
Included(v) => Included(v.clone().into()),
Excluded(v) => Excluded(v.clone().into()),
Unbounded => Unbounded,
};
let end = match bounds.end_bound() {
Included(v) => Included(v.clone().into()),
Excluded(v) => Excluded(v.clone().into()),
Unbounded => Unbounded,
};
if valid_segment(&start, &end) {
Self {
segments: smallvec![(start, end)],
}
} else {
Self::empty()
}
}
fn check_invariants(self) -> Self {
if cfg!(debug_assertions) {
for p in self.segments.as_slice().windows(2) {
assert!(end_before_start_with_gap(&p[0].1, &p[1].0));
}
for (s, e) in self.segments.iter() {
assert!(valid_segment(s, e));
}
}
self
}
}
fn cmp_bounds_start<V: PartialOrd>(left: Bound<&V>, right: Bound<&V>) -> Option<Ordering> {
Some(match (left, right) {
(Unbounded, Unbounded) => Ordering::Equal,
(Included(_left), Unbounded) => Ordering::Greater,
(Excluded(_left), Unbounded) => Ordering::Greater,
(Unbounded, Included(_right)) => Ordering::Less,
(Included(left), Included(right)) => left.partial_cmp(right)?,
(Excluded(left), Included(right)) => match left.partial_cmp(right)? {
Ordering::Less => Ordering::Less,
Ordering::Equal => Ordering::Greater,
Ordering::Greater => Ordering::Greater,
},
(Unbounded, Excluded(_right)) => Ordering::Less,
(Included(left), Excluded(right)) => match left.partial_cmp(right)? {
Ordering::Less => Ordering::Less,
Ordering::Equal => Ordering::Less,
Ordering::Greater => Ordering::Greater,
},
(Excluded(left), Excluded(right)) => left.partial_cmp(right)?,
})
}
fn cmp_bounds_end<V: PartialOrd>(left: Bound<&V>, right: Bound<&V>) -> Option<Ordering> {
Some(match (left, right) {
(Unbounded, Unbounded) => Ordering::Equal,
(Included(_left), Unbounded) => Ordering::Less,
(Excluded(_left), Unbounded) => Ordering::Less,
(Unbounded, Included(_right)) => Ordering::Greater,
(Included(left), Included(right)) => left.partial_cmp(right)?,
(Excluded(left), Included(right)) => match left.partial_cmp(right)? {
Ordering::Less => Ordering::Less,
Ordering::Equal => Ordering::Less,
Ordering::Greater => Ordering::Greater,
},
(Unbounded, Excluded(_right)) => Ordering::Greater,
(Included(left), Excluded(right)) => match left.partial_cmp(right)? {
Ordering::Less => Ordering::Less,
Ordering::Equal => Ordering::Greater,
Ordering::Greater => Ordering::Greater,
},
(Excluded(left), Excluded(right)) => left.partial_cmp(right)?,
})
}
impl<V: PartialOrd> PartialOrd for Ranges<V> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
for (left, right) in self.segments.iter().zip(other.segments.iter()) {
let start_cmp = cmp_bounds_start(left.start_bound(), right.start_bound())?;
if start_cmp != Ordering::Equal {
return Some(start_cmp);
}
let end_cmp = cmp_bounds_end(left.end_bound(), right.end_bound())?;
if end_cmp != Ordering::Equal {
return Some(end_cmp);
}
}
Some(self.segments.len().cmp(&other.segments.len()))
}
}
impl<V: Ord> Ord for Ranges<V> {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other)
.expect("PartialOrd must be `Some(Ordering)` for types that implement `Ord`")
}
}
fn within_bounds<Q, V>(version: &Q, segment: &Interval<V>) -> Ordering
where
V: Borrow<Q>,
Q: ?Sized + PartialOrd,
{
let below_lower_bound = match segment {
(Excluded(start), _) => version <= start.borrow(),
(Included(start), _) => version < start.borrow(),
(Unbounded, _) => false,
};
if below_lower_bound {
return Ordering::Less;
}
let below_upper_bound = match segment {
(_, Unbounded) => true,
(_, Included(end)) => version <= end.borrow(),
(_, Excluded(end)) => version < end.borrow(),
};
if below_upper_bound {
return Ordering::Equal;
}
Ordering::Greater
}
fn valid_segment<T: PartialOrd>(start: &Bound<T>, end: &Bound<T>) -> bool {
match (start, end) {
(Included(s), Included(e)) => s <= e,
(Included(s), Excluded(e)) => s < e,
(Excluded(s), Included(e)) => s < e,
(Excluded(s), Excluded(e)) => s < e,
(Unbounded, _) | (_, Unbounded) => true,
}
}
fn end_before_start_with_gap<V: PartialOrd>(end: &Bound<V>, start: &Bound<V>) -> bool {
match (end, start) {
(_, Unbounded) => false,
(Unbounded, _) => false,
(Included(left), Included(right)) => left < right,
(Included(left), Excluded(right)) => left < right,
(Excluded(left), Included(right)) => left < right,
(Excluded(left), Excluded(right)) => left <= right,
}
}
fn left_start_is_smaller<V: PartialOrd>(left: Bound<V>, right: Bound<V>) -> bool {
match (left, right) {
(Unbounded, _) => true,
(_, Unbounded) => false,
(Included(l), Included(r)) => l <= r,
(Excluded(l), Excluded(r)) => l <= r,
(Included(l), Excluded(r)) => l <= r,
(Excluded(l), Included(r)) => l < r,
}
}
fn left_end_is_smaller<V: PartialOrd>(left: Bound<V>, right: Bound<V>) -> bool {
match (left, right) {
(_, Unbounded) => true,
(Unbounded, _) => false,
(Included(l), Included(r)) => l <= r,
(Excluded(l), Excluded(r)) => l <= r,
(Excluded(l), Included(r)) => l <= r,
(Included(l), Excluded(r)) => l < r,
}
}
fn group_adjacent_locations(
mut locations: impl Iterator<Item = Option<usize>>,
) -> impl Iterator<Item = (Option<usize>, Option<usize>)> {
let mut seg = locations.next().flatten().map(|ver| (None, Some(ver)));
std::iter::from_fn(move || {
for ver in locations.by_ref() {
if let Some(ver) = ver {
seg = Some((seg.map_or(Some(ver), |(s, _)| s), Some(ver)));
} else {
if seg.is_some() {
return seg.take();
}
}
}
seg.take().map(|(s, _)| (s, None))
})
}
impl<V: Ord + Clone> Ranges<V> {
pub fn union(&self, other: &Self) -> Self {
let mut output = SmallVec::new();
let mut accumulator: Option<(&Bound<_>, &Bound<_>)> = None;
let mut left_iter = self.segments.iter().peekable();
let mut right_iter = other.segments.iter().peekable();
loop {
let smaller_interval = match (left_iter.peek(), right_iter.peek()) {
(Some((left_start, left_end)), Some((right_start, right_end))) => {
if left_start_is_smaller(left_start.as_ref(), right_start.as_ref()) {
left_iter.next();
(left_start, left_end)
} else {
right_iter.next();
(right_start, right_end)
}
}
(Some((left_start, left_end)), None) => {
left_iter.next();
(left_start, left_end)
}
(None, Some((right_start, right_end))) => {
right_iter.next();
(right_start, right_end)
}
(None, None) => break,
};
if let Some(accumulator_) = accumulator {
if end_before_start_with_gap(accumulator_.1, smaller_interval.0) {
output.push((accumulator_.0.clone(), accumulator_.1.clone()));
accumulator = Some(smaller_interval);
} else {
let accumulator_end = match (accumulator_.1, smaller_interval.1) {
(_, Unbounded) | (Unbounded, _) => &Unbounded,
(Included(l), Excluded(r) | Included(r)) if l == r => accumulator_.1,
(Included(l) | Excluded(l), Included(r) | Excluded(r)) => {
if l > r {
accumulator_.1
} else {
smaller_interval.1
}
}
};
accumulator = Some((accumulator_.0, accumulator_end));
}
} else {
accumulator = Some(smaller_interval)
}
}
if let Some(accumulator) = accumulator {
output.push((accumulator.0.clone(), accumulator.1.clone()));
}
Self { segments: output }.check_invariants()
}
pub fn intersection(&self, other: &Self) -> Self {
let mut output = SmallVec::new();
let mut left_iter = self.segments.iter().peekable();
let mut right_iter = other.segments.iter().peekable();
while let Some(((left_start, left_end), (right_start, right_end))) =
left_iter.peek().zip(right_iter.peek())
{
let left_end_is_smaller = left_end_is_smaller(left_end.as_ref(), right_end.as_ref());
let (other_start, end) = if left_end_is_smaller {
left_iter.next();
(right_start, left_end)
} else {
right_iter.next();
(left_start, right_end)
};
if !valid_segment(other_start, end) {
continue;
}
let start = match (left_start, right_start) {
(Included(l), Included(r)) => Included(std::cmp::max(l, r)),
(Excluded(l), Excluded(r)) => Excluded(std::cmp::max(l, r)),
(Included(i), Excluded(e)) | (Excluded(e), Included(i)) => {
if i <= e {
Excluded(e)
} else {
Included(i)
}
}
(s, Unbounded) | (Unbounded, s) => s.as_ref(),
};
output.push((start.cloned(), end.clone()))
}
Self { segments: output }.check_invariants()
}
pub fn is_disjoint(&self, other: &Self) -> bool {
let mut left_iter = self.segments.iter().peekable();
let mut right_iter = other.segments.iter().peekable();
while let Some((left, right)) = left_iter.peek().zip(right_iter.peek()) {
if !valid_segment(&right.start_bound(), &left.end_bound()) {
left_iter.next();
} else if !valid_segment(&left.start_bound(), &right.end_bound()) {
right_iter.next();
} else {
return false;
}
}
true
}
pub fn subset_of(&self, other: &Self) -> bool {
let mut containing_iter = other.segments.iter();
let mut subset_iter = self.segments.iter();
let Some(mut containing_elem) = containing_iter.next() else {
return subset_iter.next().is_none();
};
for subset_elem in subset_iter {
while !valid_segment(&subset_elem.start_bound(), &containing_elem.end_bound()) {
if let Some(containing_elem_) = containing_iter.next() {
containing_elem = containing_elem_;
} else {
return false;
};
}
let start_contained =
left_start_is_smaller(containing_elem.start_bound(), subset_elem.start_bound());
if !start_contained {
return false;
}
let end_contained =
left_end_is_smaller(subset_elem.end_bound(), containing_elem.end_bound());
if !end_contained {
return false;
}
}
true
}
pub fn simplify<'s, I, BV>(&self, versions: I) -> Self
where
I: Iterator<Item = BV> + 's,
BV: Borrow<V> + 's,
{
if self.as_singleton().is_some() {
return self.clone();
}
#[cfg(debug_assertions)]
let mut last: Option<BV> = None;
let version_locations = versions.scan(0, move |i, v| {
#[cfg(debug_assertions)]
{
if let Some(l) = last.as_ref() {
assert!(
l.borrow() <= v.borrow(),
"`simplify` `versions` argument incorrectly sorted"
);
}
}
while let Some(segment) = self.segments.get(*i) {
match within_bounds(v.borrow(), segment) {
Ordering::Less => return Some(None),
Ordering::Equal => return Some(Some(*i)),
Ordering::Greater => *i += 1,
}
}
#[cfg(debug_assertions)]
{
last = Some(v);
}
Some(None)
});
let mut kept_segments = group_adjacent_locations(version_locations).peekable();
if kept_segments.peek().is_none() {
return self.clone();
}
self.keep_segments(kept_segments)
}
fn keep_segments(
&self,
kept_segments: impl Iterator<Item = (Option<usize>, Option<usize>)>,
) -> Ranges<V> {
let mut segments = SmallVec::new();
for (s, e) in kept_segments {
segments.push((
s.map_or(Unbounded, |s| self.segments[s].0.clone()),
e.map_or(Unbounded, |e| self.segments[e].1.clone()),
));
}
Self { segments }.check_invariants()
}
pub fn iter(&self) -> impl Iterator<Item = (&Bound<V>, &Bound<V>)> {
self.segments.iter().map(|(start, end)| (start, end))
}
}
pub struct RangesIter<V>(smallvec::IntoIter<[Interval<V>; 1]>);
impl<V> Iterator for RangesIter<V> {
type Item = Interval<V>;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.0.len(), Some(self.0.len()))
}
}
impl<V> ExactSizeIterator for RangesIter<V> {}
impl<V> DoubleEndedIterator for RangesIter<V> {
fn next_back(&mut self) -> Option<Self::Item> {
self.0.next_back()
}
}
impl<V> IntoIterator for Ranges<V> {
type Item = (Bound<V>, Bound<V>);
type IntoIter = RangesIter<V>;
fn into_iter(self) -> Self::IntoIter {
RangesIter(self.segments.into_iter())
}
}
impl<V: Ord> FromIterator<(Bound<V>, Bound<V>)> for Ranges<V> {
fn from_iter<T: IntoIterator<Item = (Bound<V>, Bound<V>)>>(iter: T) -> Self {
let mut segments: SmallVec<[Interval<V>; 1]> = SmallVec::new();
for segment in iter {
if !valid_segment(&segment.start_bound(), &segment.end_bound()) {
continue;
}
let insertion_point = segments.partition_point(|elem: &Interval<V>| {
cmp_bounds_start(elem.start_bound(), segment.start_bound())
.unwrap()
.is_lt()
});
let previous_overlapping = insertion_point > 0
&& !end_before_start_with_gap(
&segments[insertion_point - 1].end_bound(),
&segment.start_bound(),
);
let next_overlapping = insertion_point < segments.len()
&& !end_before_start_with_gap(
&segment.end_bound(),
&segments[insertion_point].start_bound(),
);
match (previous_overlapping, next_overlapping) {
(true, true) => {
let mut following = segments.remove(insertion_point);
while insertion_point < segments.len()
&& !end_before_start_with_gap(
&segment.end_bound(),
&segments[insertion_point].start_bound(),
)
{
following = segments.remove(insertion_point);
}
if cmp_bounds_end(segment.end_bound(), following.end_bound())
.unwrap()
.is_lt()
{
segments[insertion_point - 1].1 = following.1;
} else {
segments[insertion_point - 1].1 = segment.1;
}
}
(true, false) => {
if cmp_bounds_end(
segments[insertion_point - 1].end_bound(),
segment.end_bound(),
)
.unwrap()
.is_lt()
{
segments[insertion_point - 1].1 = segment.1;
}
}
(false, true) => {
while insertion_point + 1 < segments.len()
&& !end_before_start_with_gap(
&segment.end_bound(),
&segments[insertion_point + 1].start_bound(),
)
{
segments.remove(insertion_point);
}
if cmp_bounds_end(segments[insertion_point].end_bound(), segment.end_bound())
.unwrap()
.is_lt()
{
segments[insertion_point].1 = segment.1;
}
segments[insertion_point].0 = segment.0;
}
(false, false) => {
segments.insert(insertion_point, segment);
}
}
}
Self { segments }.check_invariants()
}
}
impl<V: Display + Eq> Display for Ranges<V> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.segments.is_empty() {
write!(f, "∅")?;
} else {
for (idx, segment) in self.segments.iter().enumerate() {
if idx > 0 {
write!(f, " | ")?;
}
match segment {
(Unbounded, Unbounded) => write!(f, "*")?,
(Unbounded, Included(v)) => write!(f, "<={v}")?,
(Unbounded, Excluded(v)) => write!(f, "<{v}")?,
(Included(v), Unbounded) => write!(f, ">={v}")?,
(Included(v), Included(b)) => {
if v == b {
write!(f, "{v}")?
} else {
write!(f, ">={v}, <={b}")?
}
}
(Included(v), Excluded(b)) => write!(f, ">={v}, <{b}")?,
(Excluded(v), Unbounded) => write!(f, ">{v}")?,
(Excluded(v), Included(b)) => write!(f, ">{v}, <={b}")?,
(Excluded(v), Excluded(b)) => write!(f, ">{v}, <{b}")?,
};
}
}
Ok(())
}
}
#[cfg(feature = "serde")]
impl<'de, V: serde::Deserialize<'de>> serde::Deserialize<'de> for Ranges<V> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(serde::Deserialize)]
#[serde(untagged)]
enum EitherInterval<V> {
B(Bound<V>, Bound<V>),
D(V, Option<V>),
}
let bounds: SmallVec<[EitherInterval<V>; 2]> =
serde::Deserialize::deserialize(deserializer)?;
let mut segments = SmallVec::new();
for i in bounds {
match i {
EitherInterval::B(l, r) => segments.push((l, r)),
EitherInterval::D(l, Some(r)) => segments.push((Included(l), Excluded(r))),
EitherInterval::D(l, None) => segments.push((Included(l), Unbounded)),
}
}
Ok(Ranges { segments })
}
}
#[cfg(any(feature = "proptest", test))]
pub fn proptest_strategy() -> impl Strategy<Value = Ranges<u32>> {
(
any::<bool>(),
prop::collection::vec(any::<(u32, bool)>(), 0..10),
)
.prop_map(|(start_unbounded, deltas)| {
let mut start = if start_unbounded {
Some(Unbounded)
} else {
None
};
let mut largest: u32 = 0;
let mut last_bound_was_inclusive = false;
let mut segments = SmallVec::new();
for (delta, inclusive) in deltas {
largest = match largest.checked_add(delta) {
Some(s) => s,
None => {
continue;
}
};
let current_bound = if inclusive {
Included(largest)
} else {
Excluded(largest)
};
if let Some(start_bound) = start.take() {
if delta == 0 && !(matches!(start_bound, Included(_)) && inclusive) {
start = Some(start_bound);
continue;
}
last_bound_was_inclusive = inclusive;
segments.push((start_bound, current_bound));
} else {
if delta == 0 && (last_bound_was_inclusive || inclusive) {
continue;
}
start = Some(current_bound);
}
}
if let Some(start_bound) = start {
segments.push((start_bound, Unbounded));
}
Ranges { segments }.check_invariants()
})
}
#[cfg(test)]
pub mod tests {
use proptest::prelude::*;
use super::*;
fn version_strat() -> impl Strategy<Value = u32> {
any::<u32>()
}
proptest! {
#[cfg(feature = "serde")]
#[test]
fn serde_round_trip(range in proptest_strategy()) {
let s = ron::ser::to_string(&range).unwrap();
let r = ron::de::from_str(&s).unwrap();
assert_eq!(range, r);
}
#[test]
fn negate_is_different(range in proptest_strategy()) {
assert_ne!(range.complement(), range);
}
#[test]
fn double_negate_is_identity(range in proptest_strategy()) {
assert_eq!(range.complement().complement(), range);
}
#[test]
fn negate_contains_opposite(range in proptest_strategy(), version in version_strat()) {
assert_ne!(range.contains(&version), range.complement().contains(&version));
}
#[test]
fn intersection_is_symmetric(r1 in proptest_strategy(), r2 in proptest_strategy()) {
assert_eq!(r1.intersection(&r2), r2.intersection(&r1));
}
#[test]
fn intersection_with_any_is_identity(range in proptest_strategy()) {
assert_eq!(Ranges::full().intersection(&range), range);
}
#[test]
fn intersection_with_none_is_none(range in proptest_strategy()) {
assert_eq!(Ranges::empty().intersection(&range), Ranges::empty());
}
#[test]
fn intersection_is_idempotent(r1 in proptest_strategy(), r2 in proptest_strategy()) {
assert_eq!(r1.intersection(&r2).intersection(&r2), r1.intersection(&r2));
}
#[test]
fn intersection_is_associative(r1 in proptest_strategy(), r2 in proptest_strategy(), r3 in proptest_strategy()) {
assert_eq!(r1.intersection(&r2).intersection(&r3), r1.intersection(&r2.intersection(&r3)));
}
#[test]
fn intesection_of_complements_is_none(range in proptest_strategy()) {
assert_eq!(range.complement().intersection(&range), Ranges::empty());
}
#[test]
fn intesection_contains_both(r1 in proptest_strategy(), r2 in proptest_strategy(), version in version_strat()) {
assert_eq!(r1.intersection(&r2).contains(&version), r1.contains(&version) && r2.contains(&version));
}
#[test]
fn union_of_complements_is_any(range in proptest_strategy()) {
assert_eq!(range.complement().union(&range), Ranges::full());
}
#[test]
fn union_contains_either(r1 in proptest_strategy(), r2 in proptest_strategy(), version in version_strat()) {
assert_eq!(r1.union(&r2).contains(&version), r1.contains(&version) || r2.contains(&version));
}
#[test]
fn is_disjoint_through_intersection(r1 in proptest_strategy(), r2 in proptest_strategy()) {
let disjoint_def = r1.intersection(&r2) == Ranges::empty();
assert_eq!(r1.is_disjoint(&r2), disjoint_def);
}
#[test]
fn subset_of_through_intersection(r1 in proptest_strategy(), r2 in proptest_strategy()) {
let disjoint_def = r1.intersection(&r2) == r1;
assert_eq!(r1.subset_of(&r2), disjoint_def);
}
#[test]
fn union_through_intersection(r1 in proptest_strategy(), r2 in proptest_strategy()) {
let union_def = r1
.complement()
.intersection(&r2.complement())
.complement()
.check_invariants();
assert_eq!(r1.union(&r2), union_def);
}
#[test]
fn always_contains_exact(version in version_strat()) {
assert!(Ranges::<u32>::singleton(version).contains(&version));
}
#[test]
fn contains_negation(range in proptest_strategy(), version in version_strat()) {
assert_ne!(range.contains(&version), range.complement().contains(&version));
}
#[test]
fn contains_intersection(range in proptest_strategy(), version in version_strat()) {
assert_eq!(range.contains(&version), range.intersection(&Ranges::singleton(version)) != Ranges::empty());
}
#[test]
fn contains_bounding_range(range in proptest_strategy(), version in version_strat()) {
if range.contains(&version) {
assert!(range.bounding_range().map(|b| b.contains(&version)).unwrap_or(false));
}
}
#[test]
fn from_range_bounds(range in any::<(Bound<u32>, Bound<u32>)>(), version in version_strat()) {
let rv: Ranges<_> = Ranges::<u32>::from_range_bounds(range);
assert_eq!(range.contains(&version), rv.contains(&version));
}
#[test]
fn from_range_bounds_round_trip(range in any::<(Bound<u32>, Bound<u32>)>()) {
let rv: Ranges<u32> = Ranges::from_range_bounds(range);
let rv2: Ranges<u32> = rv.bounding_range().map(Ranges::from_range_bounds::<_, u32>).unwrap_or_else(Ranges::empty);
assert_eq!(rv, rv2);
}
#[test]
fn contains(range in proptest_strategy(), versions in proptest::collection::vec(version_strat(), ..30)) {
for v in versions {
assert_eq!(range.contains(&v), range.segments.iter().any(|s| RangeBounds::contains(s, &v)));
}
}
#[test]
fn contains_many(range in proptest_strategy(), mut versions in proptest::collection::vec(version_strat(), ..30)) {
versions.sort();
assert_eq!(versions.len(), range.contains_many(versions.iter()).count());
for (a, b) in versions.iter().zip(range.contains_many(versions.iter())) {
assert_eq!(range.contains(a), b);
}
}
#[test]
fn simplify(range in proptest_strategy(), mut versions in proptest::collection::vec(version_strat(), ..30)) {
versions.sort();
let simp = range.simplify(versions.iter());
for v in versions {
assert_eq!(range.contains(&v), simp.contains(&v));
}
assert!(simp.segments.len() <= range.segments.len())
}
#[test]
fn from_iter_valid(segments in proptest::collection::vec(any::<(Bound<u32>, Bound<u32>)>(), ..30)) {
let mut expected = Ranges::empty();
for segment in &segments {
expected = expected.union(&Ranges::from_range_bounds(*segment));
}
let actual = Ranges::from_iter(segments.clone());
assert_eq!(expected, actual, "{segments:?}");
}
}
#[test]
fn contains_many_can_take_owned() {
let range: Ranges<u8> = Ranges::singleton(1);
let versions = vec![1, 2, 3];
assert_eq!(
range.contains_many(versions.iter()).count(),
range
.contains_many(versions.iter().map(std::borrow::Cow::Borrowed))
.count()
);
assert_eq!(
range.contains_many(versions.iter()).count(),
range.contains_many(versions.into_iter()).count()
);
}
#[test]
fn contains_can_take_owned() {
let range: Ranges<Box<u8>> = Ranges::singleton(1);
let version = 1;
assert_eq!(range.contains(&Box::new(version)), range.contains(&version));
let range: Ranges<String> = Ranges::singleton(1.to_string());
let version = 1.to_string();
assert_eq!(range.contains(&version), range.contains("1"));
}
#[test]
fn simplify_can_take_owned() {
let range: Ranges<u8> = Ranges::singleton(1);
let versions = vec![1, 2, 3];
assert_eq!(
range.simplify(versions.iter()),
range.simplify(versions.iter().map(std::borrow::Cow::Borrowed))
);
assert_eq!(
range.simplify(versions.iter()),
range.simplify(versions.into_iter())
);
}
#[test]
fn version_ord() {
let versions: &[Ranges<u32>] = &[
Ranges::strictly_lower_than(1u32),
Ranges::lower_than(1u32),
Ranges::singleton(1u32),
Ranges::between(1u32, 3u32),
Ranges::higher_than(1u32),
Ranges::strictly_higher_than(1u32),
Ranges::singleton(2u32),
Ranges::singleton(2u32).union(&Ranges::singleton(3u32)),
Ranges::singleton(2u32)
.union(&Ranges::singleton(3u32))
.union(&Ranges::singleton(4u32)),
Ranges::singleton(2u32).union(&Ranges::singleton(4u32)),
Ranges::singleton(3u32),
];
let mut versions_sorted = versions.to_vec();
versions_sorted.sort();
assert_eq!(versions_sorted, versions);
let mut version_reverse_sorted = versions.to_vec();
version_reverse_sorted.reverse();
version_reverse_sorted.sort();
assert_eq!(version_reverse_sorted, versions);
}
}