use std::fmt::{Debug, Write as _};
use std::ops::RangeInclusive;
use proptest::collection::vec;
use proptest::prelude::{Just, Strategy, prop_oneof};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ModelSequence<Step> {
steps: Vec<Step>,
}
impl<Step> ModelSequence<Step> {
pub fn new(steps: Vec<Step>) -> Self {
Self { steps }
}
pub fn steps(&self) -> &[Step] {
&self.steps
}
pub fn into_steps(self) -> Vec<Step> {
self.steps
}
pub fn is_empty(&self) -> bool {
self.steps.is_empty()
}
pub fn len(&self) -> usize {
self.steps.len()
}
}
impl<Step: Debug> ModelSequence<Step> {
pub fn to_replay_debug_string(&self) -> String {
let mut output = String::new();
for (index, step) in self.steps.iter().enumerate() {
let _ = writeln!(output, "{index}: {step:?}");
}
output
}
}
impl<Step> IntoIterator for ModelSequence<Step> {
type Item = Step;
type IntoIter = std::vec::IntoIter<Step>;
fn into_iter(self) -> Self::IntoIter {
self.steps.into_iter()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum InputChange<I> {
Set(I),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ScopeChange<S> {
Open(S),
Close(S),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CollectionChange<K, V> {
Add {
key: K,
value: V,
},
Remove {
key: K,
},
Update {
key: K,
value: V,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ResourceStatusChange<R, E> {
Succeeded(R),
Failed {
resource: R,
error: E,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TransactionChange<E> {
ExpectFailure(E),
RetryLastFailure,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum OutputChange<O> {
Rebaseline(O),
}
pub fn model_sequence_strategy<Step>(
step: impl Strategy<Value = Step>,
len: RangeInclusive<usize>,
) -> impl Strategy<Value = ModelSequence<Step>>
where
Step: Debug,
{
vec(step, len).prop_map(ModelSequence::new)
}
pub fn canonical_input_change<I>(
value: impl Strategy<Value = I>,
) -> impl Strategy<Value = InputChange<I>>
where
I: Debug,
{
value.prop_map(InputChange::Set)
}
pub fn canonical_input_sequence<I>(
value: impl Strategy<Value = I>,
len: RangeInclusive<usize>,
) -> impl Strategy<Value = ModelSequence<InputChange<I>>>
where
I: Debug,
{
model_sequence_strategy(canonical_input_change(value), len)
}
pub fn scope_change<S>(
open_scope: impl Strategy<Value = S>,
close_scope: impl Strategy<Value = S>,
) -> impl Strategy<Value = ScopeChange<S>>
where
S: Debug,
{
prop_oneof![
open_scope.prop_map(ScopeChange::Open),
close_scope.prop_map(ScopeChange::Close),
]
}
pub fn scope_sequence<S>(
open_scope: impl Strategy<Value = S>,
close_scope: impl Strategy<Value = S>,
len: RangeInclusive<usize>,
) -> impl Strategy<Value = ModelSequence<ScopeChange<S>>>
where
S: Debug,
{
model_sequence_strategy(scope_change(open_scope, close_scope), len)
}
pub fn collection_change<K, V>(
add: impl Strategy<Value = (K, V)>,
remove: impl Strategy<Value = K>,
update: impl Strategy<Value = (K, V)>,
) -> impl Strategy<Value = CollectionChange<K, V>>
where
K: Debug,
V: Debug,
{
prop_oneof![
add.prop_map(|(key, value)| CollectionChange::Add { key, value }),
remove.prop_map(|key| CollectionChange::Remove { key }),
update.prop_map(|(key, value)| CollectionChange::Update { key, value }),
]
}
pub fn collection_sequence<K, V>(
add: impl Strategy<Value = (K, V)>,
remove: impl Strategy<Value = K>,
update: impl Strategy<Value = (K, V)>,
len: RangeInclusive<usize>,
) -> impl Strategy<Value = ModelSequence<CollectionChange<K, V>>>
where
K: Debug,
V: Debug,
{
model_sequence_strategy(collection_change(add, remove, update), len)
}
pub fn resource_status_change<R, E>(
success: impl Strategy<Value = R>,
failure: impl Strategy<Value = (R, E)>,
) -> impl Strategy<Value = ResourceStatusChange<R, E>>
where
R: Debug,
E: Debug,
{
prop_oneof![
success.prop_map(ResourceStatusChange::Succeeded),
failure.prop_map(|(resource, error)| ResourceStatusChange::Failed { resource, error }),
]
}
pub fn resource_status_sequence<R, E>(
success: impl Strategy<Value = R>,
failure: impl Strategy<Value = (R, E)>,
len: RangeInclusive<usize>,
) -> impl Strategy<Value = ModelSequence<ResourceStatusChange<R, E>>>
where
R: Debug,
E: Debug,
{
model_sequence_strategy(resource_status_change(success, failure), len)
}
pub fn transaction_change<E>(
failure: impl Strategy<Value = E>,
) -> impl Strategy<Value = TransactionChange<E>>
where
E: Clone + Debug,
{
prop_oneof![
failure.prop_map(TransactionChange::ExpectFailure),
Just(TransactionChange::RetryLastFailure),
]
}
pub fn transaction_sequence<E>(
failure: impl Strategy<Value = E>,
len: RangeInclusive<usize>,
) -> impl Strategy<Value = ModelSequence<TransactionChange<E>>>
where
E: Clone + Debug,
{
model_sequence_strategy(transaction_change(failure), len)
}
pub fn output_rebaseline<O>(
output: impl Strategy<Value = O>,
) -> impl Strategy<Value = OutputChange<O>>
where
O: Debug,
{
output.prop_map(OutputChange::Rebaseline)
}
pub fn output_rebaseline_sequence<O>(
output: impl Strategy<Value = O>,
len: RangeInclusive<usize>,
) -> impl Strategy<Value = ModelSequence<OutputChange<O>>>
where
O: Debug,
{
model_sequence_strategy(output_rebaseline(output), len)
}