Files
improvise/crates/improvise-incremental/tests/random.rs
T

102 lines
3.6 KiB
Rust

//! `Incremental` against `Eager` over random edit sequences.
//! Evidence: `context/test-evidence/improvise-incremental.md`.
mod support;
use improvise_core::formula::ast::AggFunc;
use improvise_core::model::Model;
use improvise_core::model::cell::{CellKey, CellValue};
use improvise_core::view::View;
use improvise_incremental::{Edit, Incremental, differential_in};
use proptest::prelude::*;
use support::{
grouped_model, hand_model, hidden_counted_workbook, hidden_workbook, none_of, ratio_workbook,
};
fn pool() -> impl Strategy<Value = CellValue> {
prop_oneof![
(-3i32..4).prop_map(|n| CellValue::Number(f64::from(n))),
Just(CellValue::Number(-0.0)),
Just(CellValue::Number(f64::NAN)),
Just(CellValue::Number(-f64::NAN)),
Just(CellValue::Number(f64::INFINITY)),
prop::sample::select(vec![0.1, 1e16, -1e16, f64::MAX, 5e-324]).prop_map(CellValue::Number),
prop::sample::select(vec!["n", "x"]).prop_map(|t| CellValue::Text(t.into())),
Just(CellValue::Error("e".into())),
]
}
/// Edits built from a document's own cells: overwrite, a new record in a made-up item,
/// clear, and a new aggregation for one of its measures.
fn edits_over(model: &Model) -> BoxedStrategy<Edit> {
let keys: Vec<CellKey> = model.data.iter_cells().map(|(k, _)| k).take(24).collect();
let measures: Vec<String> = model.measure_item_names();
let funcs = vec![
AggFunc::Sum,
AggFunc::Avg,
AggFunc::Min,
AggFunc::Max,
AggFunc::Count,
];
let existing = prop::sample::select(keys.clone());
let fresh = (prop::sample::select(keys), 0usize..3).prop_map(|(k, which)| {
let (cat, _) = k.0[which % k.0.len()].clone();
k.with(cat, "Zz-new")
});
prop_oneof![
(existing.clone(), pool()).prop_map(|(k, v)| Edit::SetCell(k, v)),
(fresh, pool()).prop_map(|(k, v)| Edit::SetCell(k, v)),
existing.prop_map(Edit::ClearCell),
(prop::sample::select(measures), prop::sample::select(funcs))
.prop_map(|(m, func)| Edit::SetMeasureAgg { measure: m, func }),
]
.boxed()
}
fn small_documents() -> Vec<(String, Model, Vec<String>, Vec<View>)> {
let hidden = hidden_workbook();
let ratio = ratio_workbook();
let counted = hidden_counted_workbook();
let views = |wb: &improvise_core::workbook::Workbook| wb.views.values().cloned().collect();
vec![
("hand".into(), hand_model(), vec!["Region".into()], vec![]),
("grouped".into(), grouped_model(), vec![], vec![]),
(
"hidden".into(),
hidden.model.clone(),
none_of(&hidden),
views(&hidden),
),
(
"ratio".into(),
ratio.model.clone(),
none_of(&ratio),
views(&ratio),
),
(
"counted".into(),
counted.model.clone(),
none_of(&counted),
views(&counted),
),
]
}
fn any_document_edits() -> impl Strategy<Value = (usize, Vec<Edit>)> {
let docs = small_documents();
(0..docs.len()).prop_flat_map(move |i| {
let edits = prop::collection::vec(edits_over(&docs[i].1), 0..5);
(Just(i), edits)
})
}
proptest! {
#[test]
fn incremental_equals_eager_over_random_edit_sequences((i, edits) in any_document_edits()) {
let (name, model, none, views) = small_documents().swap_remove(i);
let candidate = Incremental::new(model.clone(), none.clone());
let ran = differential_in(&model, &views, &none, candidate, &edits);
prop_assert!(ran.is_ok(), "{name}: {}", ran.unwrap_err());
}
}