formualizer-eval 0.7.0

High-performance Arrow-backed Excel formula engine with dependency graph and incremental recalculation
Documentation
use std::collections::HashSet;

use proptest::prelude::*;

use super::region_index::{AxisKind, AxisRange, Region, SheetRegionIndex};

fn any_axis_range() -> impl Strategy<Value = AxisRange> {
    prop_oneof![
        any::<u32>().prop_map(AxisRange::Point),
        (any::<u32>(), any::<u32>()).prop_map(|(a, b)| {
            let (lo, hi) = if a <= b { (a, b) } else { (b, a) };
            AxisRange::Span(lo, hi)
        }),
        any::<u32>().prop_map(AxisRange::From),
        any::<u32>().prop_map(AxisRange::To),
        Just(AxisRange::All),
    ]
}

fn axis_range_projection_stays_in_bounds(range: AxisRange, offset: i64) -> bool {
    fn coord_stays_in_bounds(coord: u32, offset: i64) -> bool {
        let shifted = i128::from(coord) + i128::from(offset);
        (0..=i128::from(u32::MAX)).contains(&shifted)
    }

    match range {
        AxisRange::Point(point) => coord_stays_in_bounds(point, offset),
        AxisRange::Span(start, end) => {
            coord_stays_in_bounds(start, offset) && coord_stays_in_bounds(end, offset)
        }
        AxisRange::From(start) => coord_stays_in_bounds(start, offset),
        AxisRange::To(end) => coord_stays_in_bounds(end, offset),
        AxisRange::All => true,
    }
}

fn any_currently_constructible_region() -> impl Strategy<Value = Region> {
    let small = 0u32..20;
    prop_oneof![
        (1u16..3, small.clone(), small.clone())
            .prop_map(|(sheet_id, row, col)| Region::point(sheet_id, row, col)),
        (1u16..3, small.clone(), small.clone(), small.clone()).prop_map(
            |(sheet_id, col, row_start, row_end)| {
                let (lo, hi) = if row_start <= row_end {
                    (row_start, row_end)
                } else {
                    (row_end, row_start)
                };
                Region::col_interval(sheet_id, col, lo, hi)
            },
        ),
        (1u16..3, small.clone(), small.clone(), small.clone()).prop_map(
            |(sheet_id, row, col_start, col_end)| {
                let (lo, hi) = if col_start <= col_end {
                    (col_start, col_end)
                } else {
                    (col_end, col_start)
                };
                Region::row_interval(sheet_id, row, lo, hi)
            },
        ),
        (
            1u16..3,
            small.clone(),
            small.clone(),
            small.clone(),
            small.clone(),
        )
            .prop_map(|(sheet_id, row_start, row_end, col_start, col_end)| {
                let (row_lo, row_hi) = if row_start <= row_end {
                    (row_start, row_end)
                } else {
                    (row_end, row_start)
                };
                let (col_lo, col_hi) = if col_start <= col_end {
                    (col_start, col_end)
                } else {
                    (col_end, col_start)
                };
                Region::rect(sheet_id, row_lo, row_hi, col_lo, col_hi)
            }),
        (1u16..3, small.clone()).prop_map(|(sheet_id, row)| Region::rows_from(sheet_id, row)),
        (1u16..3, small.clone()).prop_map(|(sheet_id, col)| Region::cols_from(sheet_id, col)),
        (1u16..3, small.clone()).prop_map(|(sheet_id, row)| Region::whole_row(sheet_id, row)),
        (1u16..3, small.clone()).prop_map(|(sheet_id, col)| Region::whole_col(sheet_id, col)),
        (1u16..3).prop_map(Region::whole_sheet),
    ]
}

proptest! {
    #[test]
    fn intersects_commutes(a in any_axis_range(), b in any_axis_range()) {
        prop_assert_eq!(a.intersects(b), b.intersects(a));
    }

    #[test]
    fn contains_iff_intersects_with_point(r in any_axis_range(), c: u32) {
        prop_assert_eq!(r.contains(c), r.intersects(AxisRange::Point(c)));
    }

    #[test]
    fn project_zero_offset_is_identity(r in any_axis_range()) {
        prop_assert_eq!(r.project_through_offset(0), Some(r));
    }

    #[test]
    fn from_projection_no_overflow(start: u32, offset in -10_000i64..10_000) {
        let r = AxisRange::From(start);
        let _ = r.project_through_offset(offset);
        let r = AxisRange::To(start);
        let _ = r.project_through_offset(offset);
        let r = AxisRange::Span(start, start.saturating_add(100));
        let _ = r.project_through_offset(offset);
    }

    #[test]
    fn projection_composition_is_offset_sum(
        r in any_axis_range(),
        o1 in -1_000_000i64..1_000_000,
        o2 in -1_000_000i64..1_000_000,
    ) {
        let Some(sum) = o1.checked_add(o2) else {
            return Ok(());
        };
        prop_assume!(axis_range_projection_stays_in_bounds(r, o1));
        prop_assume!(axis_range_projection_stays_in_bounds(r, sum));

        let first = r.project_through_offset(o1);
        if let Some(first_range) = first {
            prop_assume!(axis_range_projection_stays_in_bounds(first_range, o2));
        }

        let composed = first.and_then(|first_range| first_range.project_through_offset(o2));
        let direct = r.project_through_offset(sum);
        prop_assert_eq!(composed, direct);
    }

    #[test]
    fn projection_no_panic_for_any_axis_range_and_bounded_offset(
        r in any_axis_range(),
        offset in -2_147_483_648i64..=2_147_483_647i64,
    ) {
        let _ = r.project_through_offset(offset);
    }

    #[test]
    fn intersect_query_bounds_consistent(a in any_axis_range(), b in any_axis_range()) {
        if a.intersects(b) {
            let (a_lo, a_hi) = a.query_bounds();
            let (b_lo, b_hi) = b.query_bounds();
            prop_assert!(a_lo <= b_hi && b_lo <= a_hi);
        }
    }

    #[test]
    fn kind_matches_variant(r in any_axis_range()) {
        let expected = match r {
            AxisRange::Point(_) => AxisKind::Point,
            AxisRange::Span(_, _) => AxisKind::Span,
            AxisRange::From(_) => AxisKind::From,
            AxisRange::To(_) => AxisKind::To,
            AxisRange::All => AxisKind::All,
        };
        prop_assert_eq!(r.kind(), expected);
    }

    #[test]
    fn region_index_query_returns_all_intersecting(
        indexed in proptest::collection::vec(any_currently_constructible_region(), 0..50),
        query in any_currently_constructible_region(),
    ) {
        let mut index = SheetRegionIndex::new();
        for (value, region) in indexed.iter().enumerate() {
            index.insert(*region, value);
        }

        let result = index.query(query);
        let result_values: HashSet<usize> = result.matches.iter().map(|matched| matched.value).collect();
        let expected: HashSet<usize> = indexed
            .iter()
            .enumerate()
            .filter(|(_value, region)| region.intersects(&query))
            .map(|(value, _region)| value)
            .collect();

        prop_assert_eq!(expected, result_values);
    }

    #[test]
    fn shifted_region_intersects_consistently(
        region in any_currently_constructible_region(),
        row_delta in -1000i64..1000,
        col_delta in -1000i64..1000,
    ) {
        if let Some(shifted) = region.project_through_axis_shift(row_delta, col_delta) {
            prop_assert_eq!(shifted.sheet_id, region.sheet_id);
            if row_delta == 0 && col_delta == 0 {
                prop_assert_eq!(shifted, region);
            }
        }
    }
}