diff --git a/src/tests/proptest/mod.rs b/src/tests/proptest/mod.rs index eaa9385..46d1116 100644 --- a/src/tests/proptest/mod.rs +++ b/src/tests/proptest/mod.rs @@ -35,6 +35,17 @@ pub fn arb_hash256() -> impl Strategy { proptest::array::uniform32(any::()).prop_map(Hash256::from) } +/// Strategy for generating initial values for a progressive list. +/// Unlike `arb_list`, this has no length limit, but we cap it at a reasonable +/// size for testing purposes. +pub fn arb_progressive_list(strategy: S, max_len: usize) -> impl Strategy> +where + S: Strategy, + T: std::fmt::Debug, +{ + proptest::collection::vec(strategy, 0..=max_len) +} + /// Struct with multiple fields shared by multiple proptests. #[derive(Debug, Clone, PartialEq, Encode, Decode, TreeHash)] pub struct Large { diff --git a/src/tests/proptest/operations.rs b/src/tests/proptest/operations.rs index bb3aaa6..6286eed 100644 --- a/src/tests/proptest/operations.rs +++ b/src/tests/proptest/operations.rs @@ -1,5 +1,5 @@ -use super::{Large, arb_hash256, arb_index, arb_large, arb_list, arb_vect}; -use crate::{Error, List, Value, Vector}; +use super::{Large, arb_hash256, arb_index, arb_large, arb_list, arb_progressive_list, arb_vect}; +use crate::{Error, List, ProgressiveList, Value, Vector}; use proptest::prelude::*; use ssz::{Decode, Encode}; use std::fmt::Debug; @@ -91,6 +91,30 @@ impl Spec { } } +/// Simple specification for `ProgressiveList` behaviour without a length limit. +#[derive(Debug, Clone)] +pub struct ProgressiveSpec { + values: Vec, +} + +impl ProgressiveSpec { + pub fn new(values: Vec) -> Self { + Self { values } + } + + pub fn len(&self) -> usize { + self.values.len() + } + + pub fn iter(&self) -> impl Iterator { + self.values.iter() + } + + pub fn push(&mut self, value: T) { + self.values.push(value); + } +} + #[derive(Debug, Clone)] pub enum Op { /// Check that `len` returns the correct length. @@ -173,6 +197,80 @@ where proptest::collection::vec(arb_op(strategy, n), 1..limit) } +/// Strategy for generating operations for ProgressiveList. +/// Only includes operations that are currently implemented for ProgressiveList. +fn arb_op_progressive<'a, T, S>(strategy: &'a S) -> impl Strategy> + 'a +where + T: Debug + Clone + 'a, + S: Strategy + 'a, +{ + // Only include operations that are currently implemented for ProgressiveList: + // Len, Push, Iter, TreeHash, and Debase (SSZ roundtrip) + prop_oneof![ + Just(Op::Len), + strategy.prop_map(Op::Push), + Just(Op::Iter), + Just(Op::TreeHash), + Just(Op::Debase), + ] +} + +fn arb_ops_progressive<'a, T, S>( + strategy: &'a S, + limit: usize, +) -> impl Strategy>> + 'a +where + T: Debug + Clone + 'a, + S: Strategy + 'a, +{ + proptest::collection::vec(arb_op_progressive(strategy), 1..limit) +} + +fn apply_ops_progressive_list( + list: &mut ProgressiveList, + spec: &mut ProgressiveSpec, + ops: Vec>, +) where + T: Value + Debug + Send + Sync, +{ + for op in ops { + match op { + Op::Len => { + assert_eq!(list.len(), spec.len()); + } + Op::Push(value) => { + list.push(value.clone()).expect("push should succeed"); + spec.push(value); + } + Op::Iter => { + assert!(list.iter().eq(spec.iter())); + } + Op::TreeHash => { + list.tree_hash_root(); + } + Op::Debase => { + let ssz_bytes = list.as_ssz_bytes(); + let new_list = ProgressiveList::from_ssz_bytes(&ssz_bytes).expect("SSZ decode"); + assert_eq!(new_list, *list); + *list = new_list; + } + // These operations are not implemented for ProgressiveList yet and + // are not generated by arb_op_progressive, but we handle them for completeness. + Op::Get(_) + | Op::Set(_, _) + | Op::SetCowWithIntoMut(_, _) + | Op::SetCowWithMakeMut(_, _) + | Op::IterFrom(_) + | Op::PopFront(_) + | Op::ApplyUpdates + | Op::Checkpoint + | Op::Rebase + | Op::FromIntoRoundtrip + | Op::IntraRebase => {} + } + } +} + fn apply_ops_list(list: &mut List, spec: &mut Spec, ops: Vec>) where T: Value + Debug + Send + Sync, @@ -492,3 +590,37 @@ mod vect { vect_test!(large_33, Large, U33, arb_large()); vect_test!(large_1024, Large, U1024, arb_large()); } + +/// Maximum initial length for progressive list tests. +/// This is used to cap the initial list size for reasonable test execution time. +/// Compare to list tests which can use up to 1024 elements (U1024). +const PROGRESSIVE_LIST_MAX_LEN: usize = 128; + +macro_rules! progressive_list_test { + ($name:ident, $T:ty) => { + // Use default strategy (assumes existence of an `Arbitrary` impl). + progressive_list_test!($name, $T, any::<$T>()); + }; + ($name:ident, $T:ty, $strat:expr) => { + proptest! { + #[test] + fn $name( + init in arb_progressive_list::<$T, _>(&$strat, PROGRESSIVE_LIST_MAX_LEN), + ops in arb_ops_progressive::<$T, _>(&$strat, OP_LIMIT) + ) { + let mut list = ProgressiveList::<$T>::try_from_iter(init.clone()).unwrap(); + let mut spec = ProgressiveSpec::<$T>::new(init); + apply_ops_progressive_list(&mut list, &mut spec, ops); + } + } + }; +} + +mod progressive_list { + use super::*; + + progressive_list_test!(u8, u8); + progressive_list_test!(u64, u64); + progressive_list_test!(hash256, Hash256, arb_hash256()); + progressive_list_test!(large, Large, arb_large()); +}