use std::{collections::HashMap, ops::Range};
use indexmap::IndexMap;
use crate::{Builder, Reader};
use bytes::Bytes;
use proptest::{
arbitrary::Arbitrary,
strategy::{BoxedStrategy, Just, MapInto, Strategy},
};
#[derive(Clone, Debug)]
pub struct TestEntryData(pub IndexMap<String, Bytes>);
#[macro_export]
macro_rules! test_entry_data {
($($key:literal: $value:literal),* $(,)?) => {
{
const CAP: usize = <[()]>::len(&[$({ stringify!($key); }),*]);
let mut result = $crate::proptest::TestEntryData(indexmap::IndexMap::with_capacity(CAP));
$(
result.0.insert($key.into(), bytes::Bytes::from_static($value));
)*
result
}
};
}
#[derive(Clone, Debug)]
pub struct ReaderAndData {
pub reader: Reader<Bytes>,
pub data: TestEntryData,
}
#[derive(Clone, Debug)]
pub struct ArbitraryTestEntryDataParams {
pub count_range: Range<usize>,
pub max_size: usize,
pub entry_name_pattern: &'static str,
}
#[derive(Clone, Debug, Default)]
pub struct ArbitraryReaderParams {
pub entries: ArbitraryTestEntryDataParams,
pub seek: bool,
}
impl Arbitrary for TestEntryData {
type Parameters = ArbitraryTestEntryDataParams;
type Strategy = BoxedStrategy<TestEntryData>;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
let content_strategy = if args.max_size == 0 {
Just(Bytes::new()).boxed()
} else {
proptest::collection::vec(proptest::bits::u8::ANY, 0..args.max_size)
.prop_map_into::<Bytes>()
.boxed()
};
proptest::collection::hash_map(args.entry_name_pattern, content_strategy, args.count_range)
.prop_map_into()
.boxed()
}
}
impl Arbitrary for ReaderAndData {
type Parameters = ArbitraryReaderParams;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
let fresh_reader_strategy =
TestEntryData::arbitrary_with(args.clone().into()).prop_map_into();
if args.seek {
fresh_reader_strategy
.prop_flat_map(|reader_and_data: ReaderAndData| {
let len = reader_and_data.reader.size();
(Just(reader_and_data), 0..len)
})
.prop_map(|(mut reader_and_data, seek_offset)| {
reader_and_data.reader.seek_from_start_mut(seek_offset);
reader_and_data
})
.boxed()
} else {
fresh_reader_strategy.boxed()
}
}
}
impl Arbitrary for Reader<Bytes> {
type Parameters = ArbitraryReaderParams;
type Strategy = MapInto<BoxedStrategy<ReaderAndData>, Self>;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
ReaderAndData::arbitrary_with(args).prop_map_into()
}
}
impl Default for ArbitraryTestEntryDataParams {
fn default() -> Self {
ArbitraryTestEntryDataParams {
count_range: 0..64,
max_size: 512,
entry_name_pattern: ".{0,30}",
}
}
}
impl From<ReaderAndData> for Reader<Bytes> {
fn from(value: ReaderAndData) -> Self {
value.reader
}
}
impl From<HashMap<String, Bytes>> for TestEntryData {
fn from(value: HashMap<String, Bytes>) -> Self {
TestEntryData(value.into_iter().collect())
}
}
impl From<TestEntryData> for Builder<Bytes> {
fn from(value: TestEntryData) -> Self {
let mut builder: Builder<Bytes> = Builder::new();
value.0.into_iter().for_each(|(name, content)| {
builder
.add_entry_with_size(name.clone(), content.clone(), content.len() as u64)
.expect("Adding entries from hash map should never fail");
});
builder
}
}
impl From<TestEntryData> for Reader<Bytes> {
fn from(value: TestEntryData) -> Self {
Into::<Builder<Bytes>>::into(value).build()
}
}
impl From<ArbitraryReaderParams> for ArbitraryTestEntryDataParams {
fn from(value: ArbitraryReaderParams) -> Self {
value.entries
}
}
impl From<TestEntryData> for ReaderAndData {
fn from(value: TestEntryData) -> Self {
let builder: Builder<Bytes> = value.clone().into();
ReaderAndData {
reader: builder.build(),
data: value,
}
}
}