extern crate alloc;
use alloc::vec::Vec;
use proptest::prelude::*;
use crate::kairos::Kairos;
use crate::metis::{
Anchor, Locus, Metatheses, MetathesesDecodeBudget, MetathesesDecodeError, Metathesis,
};
#[derive(Clone, Debug)]
struct Row {
station: u32,
index: u64,
target: (u32, u64),
anchor: Option<((u32, u64), bool)>,
tick: u64,
}
fn arb_rows() -> impl Strategy<Value = Vec<Row>> {
prop::collection::vec(
(
1u32..=3,
1u64..=8,
(1u32..=3, 0u64..=8),
prop::option::of(((1u32..=3, 0u64..=8), any::<bool>())),
0u64..64,
)
.prop_map(|(station, index, target, anchor, tick)| Row {
station,
index,
target,
anchor,
tick,
}),
0..24,
)
}
fn build(rows: &[Row]) -> Metatheses {
let mut record = Metatheses::new();
for (ordinal, row) in rows.iter().enumerate() {
let anchor = match row.anchor {
None => Anchor::Origin,
Some((dot, false)) => Anchor::After(dot.into()),
Some((dot, true)) => Anchor::Before(dot.into()),
};
let _ = record.insert(
super::d(row.station, row.index),
Metathesis {
target: row.target.into(),
to: Locus {
anchor,
rank: Kairos::new(row.tick * 64 + ordinal as u64, 0u16, row.station, 0u16),
},
},
);
}
record
}
proptest! {
#[test]
fn prop_metatheses_bytes_round_trip(rows in arb_rows()) {
let record = build(&rows);
let bytes = record.to_bytes();
let decoded = Metatheses::from_bytes(&bytes).expect("own frame decodes");
prop_assert_eq!(&decoded, &record);
prop_assert_eq!(decoded.to_bytes(), bytes);
}
#[test]
fn prop_metatheses_encoded_len_is_exact(rows in arb_rows()) {
let record = build(&rows);
prop_assert_eq!(record.encoded_len(), record.to_bytes().len());
}
#[test]
fn prop_metatheses_bytes_are_canonical(rows in arb_rows()) {
let forward = build(&rows);
let mut reversed = rows;
reversed.reverse();
let backward = build(&reversed);
if forward == backward {
prop_assert_eq!(forward.to_bytes(), backward.to_bytes());
}
}
#[test]
fn prop_metatheses_budget_is_exact(rows in arb_rows()) {
let record = build(&rows);
let bytes = record.to_bytes();
let len = record.len();
prop_assert_eq!(
Metatheses::from_bytes_with_budget(&bytes, MetathesesDecodeBudget::new(len)),
Ok(record)
);
if len > 0 {
prop_assert_eq!(
Metatheses::from_bytes_with_budget(
&bytes,
MetathesesDecodeBudget::new(len - 1)
),
Err(MetathesesDecodeError::TooManyTestimonies {
count: len as u64,
budget: len as u64 - 1,
})
);
}
}
#[test]
fn prop_metatheses_from_bytes_never_panics(
bytes in prop::collection::vec(any::<u8>(), 0..192),
) {
if let Ok(record) = Metatheses::from_bytes(&bytes) {
prop_assert_eq!(record.to_bytes(), bytes);
}
}
#[test]
fn prop_metatheses_truncation_never_panics(rows in arb_rows(), keep in any::<usize>()) {
let bytes = build(&rows).to_bytes();
let cut = keep % (bytes.len() + 1);
let prefix = &bytes[..cut];
if let Ok(record) = Metatheses::from_bytes(prefix) {
let reencoded = record.to_bytes();
prop_assert_eq!(reencoded.as_slice(), prefix);
}
}
#[test]
fn prop_metatheses_flip_byte_stays_total(
rows in arb_rows(),
offset in any::<usize>(),
xor in 1u8..=u8::MAX,
) {
let mut bytes = build(&rows).to_bytes();
let len = bytes.len();
bytes[offset % len] ^= xor;
if let Ok(record) = Metatheses::from_bytes(&bytes) {
prop_assert_eq!(&record.to_bytes(), &bytes);
}
}
}