use std::ops::{
Range, RangeBounds, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive,
};
#[derive(Clone, Debug, Default)]
pub struct Times(TimesEnum);
impl Times {
pub(crate) fn contains(&self, n_calls: u64) -> bool {
match &self.0 {
TimesEnum::Exact(e) => e == &n_calls,
TimesEnum::Unbounded(r) => r.contains(&n_calls),
TimesEnum::Range(r) => r.contains(&n_calls),
TimesEnum::RangeFrom(r) => r.contains(&n_calls),
TimesEnum::RangeTo(r) => r.contains(&n_calls),
TimesEnum::RangeToInclusive(r) => r.contains(&n_calls),
TimesEnum::RangeInclusive(r) => r.contains(&n_calls),
}
}
}
#[cfg(not(tarpaulin_include))]
impl std::fmt::Display for Times {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.0 {
TimesEnum::Exact(e) => write!(f, "== {}", e),
TimesEnum::Unbounded(_) => write!(f, "0 <= x"),
TimesEnum::Range(r) => write!(f, "{} <= x < {}", r.start, r.end),
TimesEnum::RangeFrom(r) => write!(f, "{} <= x", r.start),
TimesEnum::RangeTo(r) => write!(f, "0 <= x < {}", r.end),
TimesEnum::RangeToInclusive(r) => write!(f, "0 <= x <= {}", r.end),
TimesEnum::RangeInclusive(r) => write!(f, "{} <= x <= {}", r.start(), r.end()),
}
}
}
#[derive(Clone, Debug)]
pub(crate) enum TimesEnum {
Exact(u64),
Unbounded(RangeFull),
Range(Range<u64>),
RangeFrom(RangeFrom<u64>),
RangeTo(RangeTo<u64>),
RangeToInclusive(RangeToInclusive<u64>),
RangeInclusive(RangeInclusive<u64>),
}
impl Default for TimesEnum {
fn default() -> Self {
Self::Unbounded(RangeFull)
}
}
impl From<RangeFull> for Times {
fn from(r: RangeFull) -> Self {
Times(TimesEnum::Unbounded(r))
}
}
impl From<u64> for Times {
fn from(x: u64) -> Self {
Times(TimesEnum::Exact(x))
}
}
macro_rules! impl_from_for_range {
($type_name:ident) => {
impl From<$type_name<u64>> for Times {
fn from(r: $type_name<u64>) -> Self {
Times(TimesEnum::$type_name(r))
}
}
};
}
impl_from_for_range!(Range);
impl_from_for_range!(RangeTo);
impl_from_for_range!(RangeFrom);
impl_from_for_range!(RangeInclusive);
impl_from_for_range!(RangeToInclusive);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn range_sanity_test() {
let t: Times = 5.into();
assert!(t.contains(5));
assert!(!(t.contains(4) || t.contains(6)));
let t: Times = (0..5).into();
assert!(!t.contains(5));
assert!(t.contains(4) || t.contains(0));
let expected = [false, false, true, true, true, false, false];
let t1: Times = (2..5).into();
let t2: Times = (2..=4).into();
for (index, val) in expected.iter().enumerate() {
assert_eq!(t1.contains(index as u64), *val);
assert_eq!(t2.contains(index as u64), *val);
}
let t: Times = (..).into();
let t_d = Times::default();
for i in 0..100 {
assert!(t.contains(fastrand::u64(..)));
assert!(t_d.contains(fastrand::u64(..)));
}
let t1: Times = (..10).into();
let t2: Times = (..=9).into();
let t3: Times = (10..).into();
for i in 0..100 {
let first_segment = i < 10;
assert_eq!(t1.contains(i as u64), first_segment);
assert_eq!(t2.contains(i as u64), first_segment);
assert_eq!(t3.contains(i as u64), !first_segment);
}
}
}