After some discussion with PVH I realise that the repo structure in the last reorg was very rust-centric. In an attempt to put each language on a level footing move the rust code and project files into ./rust
178 lines
6.8 KiB
Rust
178 lines
6.8 KiB
Rust
//! Helpers for property tests.
|
|
|
|
use std::{fmt::Debug, ops::Range};
|
|
|
|
use proptest::prelude::*;
|
|
use smol_str::SmolStr;
|
|
|
|
use crate::{
|
|
columnar::Key,
|
|
types::{ElemId, OpId, ScalarValue},
|
|
};
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) struct SpliceScenario<T> {
|
|
pub(crate) initial_values: Vec<T>,
|
|
pub(crate) replace_range: Range<usize>,
|
|
pub(crate) replacements: Vec<T>,
|
|
}
|
|
|
|
impl<T: Debug + PartialEq + Clone> SpliceScenario<T> {
|
|
pub(crate) fn check(&self, results: Vec<T>) {
|
|
let mut expected = self.initial_values.clone();
|
|
expected.splice(self.replace_range.clone(), self.replacements.clone());
|
|
assert_eq!(expected, results)
|
|
}
|
|
}
|
|
|
|
impl<T: Debug + PartialEq + Clone> SpliceScenario<Option<T>> {
|
|
/// Checks that `results` are the same as `SpliceScenario::initial_values.splice(replace_range,
|
|
/// replacements)`, with two slight changes:
|
|
///
|
|
/// * If all of `initial_values` are `None` then this returns true if the output is just
|
|
/// `replacements`
|
|
/// * If the result of `Vec::splice` would return a vector of all `None` then this checks the
|
|
/// result is actually an empty vector
|
|
///
|
|
/// This is to accomodate the fact that the RLE encoder can encode a sequence of all `None` as
|
|
/// an empty sequence, in which case we decode it as an empty sequence.
|
|
pub(crate) fn check_optional(&self, results: Vec<Option<T>>) {
|
|
if self.initial_values.iter().all(|v| v.is_none()) {
|
|
if self.replacements.iter().all(|v| v.is_none()) {
|
|
assert!(results.is_empty());
|
|
} else {
|
|
assert_eq!(results, self.replacements);
|
|
}
|
|
} else {
|
|
let mut expected = self.initial_values.clone();
|
|
expected.splice(self.replace_range.clone(), self.replacements.clone());
|
|
if expected.iter().all(|e| e.is_none()) {
|
|
assert!(results.is_empty())
|
|
} else {
|
|
assert_eq!(expected, results)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn splice_scenario<S: Strategy<Value = T> + Clone, T: Debug + Clone + 'static>(
|
|
item_strat: S,
|
|
) -> impl Strategy<Value = SpliceScenario<T>> {
|
|
(
|
|
proptest::collection::vec(item_strat.clone(), 0..100),
|
|
proptest::collection::vec(item_strat, 0..10),
|
|
)
|
|
.prop_flat_map(move |(values, to_splice)| {
|
|
if values.is_empty() {
|
|
Just(SpliceScenario {
|
|
initial_values: values,
|
|
replace_range: 0..0,
|
|
replacements: to_splice,
|
|
})
|
|
.boxed()
|
|
} else {
|
|
// This is somewhat awkward to write because we have to carry the `values` and
|
|
// `to_splice` through as `Just(..)` to please the borrow checker.
|
|
(0..values.len(), Just(values), Just(to_splice))
|
|
.prop_flat_map(move |(replace_range_start, values, to_splice)| {
|
|
(
|
|
0..(values.len() - replace_range_start),
|
|
Just(values),
|
|
Just(to_splice),
|
|
)
|
|
.prop_map(
|
|
move |(replace_range_len, values, to_splice)| SpliceScenario {
|
|
initial_values: values,
|
|
replace_range: replace_range_start
|
|
..(replace_range_start + replace_range_len),
|
|
replacements: to_splice,
|
|
},
|
|
)
|
|
})
|
|
.boxed()
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Like splice scenario except that if the initial values we generate are all `None` then the
|
|
/// replace range is 0..0.
|
|
pub(crate) fn option_splice_scenario<
|
|
S: Strategy<Value = Option<T>> + Clone,
|
|
T: Debug + Clone + 'static,
|
|
>(
|
|
item_strat: S,
|
|
) -> impl Strategy<Value = SpliceScenario<Option<T>>> {
|
|
(
|
|
proptest::collection::vec(item_strat.clone(), 0..100),
|
|
proptest::collection::vec(item_strat, 0..10),
|
|
)
|
|
.prop_flat_map(move |(values, to_splice)| {
|
|
if values.is_empty() || values.iter().all(|v| v.is_none()) {
|
|
Just(SpliceScenario {
|
|
initial_values: values,
|
|
replace_range: 0..0,
|
|
replacements: to_splice,
|
|
})
|
|
.boxed()
|
|
} else {
|
|
// This is somewhat awkward to write because we have to carry the `values` and
|
|
// `to_splice` through as `Just(..)` to please the borrow checker.
|
|
(0..values.len(), Just(values), Just(to_splice))
|
|
.prop_flat_map(move |(replace_range_start, values, to_splice)| {
|
|
(
|
|
0..(values.len() - replace_range_start),
|
|
Just(values),
|
|
Just(to_splice),
|
|
)
|
|
.prop_map(
|
|
move |(replace_range_len, values, to_splice)| SpliceScenario {
|
|
initial_values: values,
|
|
replace_range: replace_range_start
|
|
..(replace_range_start + replace_range_len),
|
|
replacements: to_splice,
|
|
},
|
|
)
|
|
})
|
|
.boxed()
|
|
}
|
|
})
|
|
}
|
|
|
|
pub(crate) fn opid() -> impl Strategy<Value = OpId> + Clone {
|
|
(0..(i64::MAX as usize), 0..(i64::MAX as u64)).prop_map(|(actor, ctr)| OpId(ctr, actor))
|
|
}
|
|
|
|
pub(crate) fn elemid() -> impl Strategy<Value = ElemId> + Clone {
|
|
opid().prop_map(ElemId)
|
|
}
|
|
|
|
pub(crate) fn key() -> impl Strategy<Value = Key> + Clone {
|
|
prop_oneof! {
|
|
elemid().prop_map(Key::Elem),
|
|
any::<String>().prop_map(|s| Key::Prop(s.into())),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn encodable_int() -> impl Strategy<Value = i64> + Clone {
|
|
let bounds = i64::MAX / 2;
|
|
-bounds..bounds
|
|
}
|
|
|
|
pub(crate) fn scalar_value() -> impl Strategy<Value = ScalarValue> + Clone {
|
|
prop_oneof! {
|
|
Just(ScalarValue::Null),
|
|
any::<bool>().prop_map(ScalarValue::Boolean),
|
|
any::<u64>().prop_map(ScalarValue::Uint),
|
|
encodable_int().prop_map(ScalarValue::Int),
|
|
any::<f64>().prop_map(ScalarValue::F64),
|
|
smol_str().prop_map(ScalarValue::Str),
|
|
any::<Vec<u8>>().prop_map(ScalarValue::Bytes),
|
|
encodable_int().prop_map(|i| ScalarValue::Counter(i.into())),
|
|
encodable_int().prop_map(ScalarValue::Timestamp),
|
|
(10..15_u8, any::<Vec<u8>>()).prop_map(|(c, b)| ScalarValue::Unknown { type_code: c, bytes: b }),
|
|
}
|
|
}
|
|
|
|
fn smol_str() -> impl Strategy<Value = SmolStr> + Clone {
|
|
any::<String>().prop_map(SmolStr::from)
|
|
}
|