int-interval 0.8.1

A small, no_std half-open interval algebra library for primitive integer types.
Documentation
use super::*;

mod unit_tests {
    use super::*;

    fn span(lo: u8, hi: u8) -> U8CO {
        U8CO::try_new(lo, hi).unwrap()
    }

    #[test]
    fn disjoint() {
        let a = span(1, 3);
        let b = span(5, 7);
        assert_eq!(a.convex_hull(b), span(1, 7));
    }

    #[test]
    fn overlap() {
        let a = span(2, 6);
        let b = span(4, 9);
        assert_eq!(a.convex_hull(b), span(2, 9));
    }

    #[test]
    fn containment() {
        let a = span(2, 10);
        let b = span(4, 6);
        assert_eq!(a.convex_hull(b), a);
    }

    #[test]
    fn symmetric_case() {
        let a = span(4, 6);
        let b = span(2, 10);
        assert_eq!(a.convex_hull(b), b);
    }
}

mod prop_tests {
    use std::{vec, vec::Vec};

    use super::*;
    use proptest::prelude::*;

    fn span(a: u8, b: u8) -> Option<U8CO> {
        let lo = a.min(b);
        let hi = a.max(b);
        U8CO::try_new(lo, hi)
    }

    fn edge_values() -> Vec<u8> {
        let mut v = vec![u8::MIN, u8::MAX, 0, 1];

        if u8::MIN < u8::MAX {
            v.push(u8::MIN.saturating_add(1));
            v.push(u8::MAX.saturating_sub(1));
        }

        v.sort_unstable();
        v.dedup();
        v
    }

    fn edge_scalar() -> impl Strategy<Value = u8> {
        prop::sample::select(edge_values())
    }

    fn mixed_scalar() -> impl Strategy<Value = u8> {
        prop_oneof! {
            3 => edge_scalar(),
            7 => any::<u8>(),
        }
    }

    fn span_strategy() -> impl Strategy<Value = U8CO> {
        (mixed_scalar(), mixed_scalar()).prop_filter_map("non-empty interval", |(a, b)| span(a, b))
    }

    proptest! {
        #![proptest_config(ProptestConfig {
            cases: 64,
            .. ProptestConfig::default()
        })]

        #[test]
        fn hull_contains_inputs(
            x in span_strategy(),
            y in span_strategy(),
            p in mixed_scalar(),
            q in mixed_scalar(),
        ) {
            let h = x.convex_hull(y);

            if x.contains(p) {
                prop_assert!(h.contains(p));
            }

            if y.contains(q) {
                prop_assert!(h.contains(q));
            }
        }

        #[test]
        fn hull_bounds_correct(
            x in span_strategy(),
            y in span_strategy(),
        ) {
            let h = x.convex_hull(y);

            prop_assert_eq!(
                h,
                U8CO::try_new(
                    x.start().min(y.start()),
                    x.end_excl().max(y.end_excl())
                ).unwrap()
            );
        }

        #[test]
        fn commutative(
            x in span_strategy(),
            y in span_strategy(),
        ) {
            prop_assert_eq!(x.convex_hull(y), y.convex_hull(x));
        }

        #[test]
        fn idempotent(
            x in span_strategy(),
        ) {
            prop_assert_eq!(x.convex_hull(x), x);
        }

        #[test]
        fn hull_contains_interval_bounds(
            x in span_strategy(),
            y in span_strategy(),
        ) {
            let h = x.convex_hull(y);

            prop_assert!(h.start() <= x.start());
            prop_assert!(h.start() <= y.start());
            prop_assert!(h.end_excl() >= x.end_excl());
            prop_assert!(h.end_excl() >= y.end_excl());
        }
    }
}