#[cfg(test)]
pub(crate) mod runner {
use crate::proptest_util::rng;
use proptest::test_runner::{Config, TestRunner};
pub(crate) fn new(source_file: &'static str, rng_seed: Option<[u8; 32]>) -> TestRunner {
let rng = rng::new_test_rng(rng_seed);
let mut config = proptest::test_runner::contextualize_config(Config::default().clone());
config.source_file = Some(source_file);
TestRunner::new_with_rng(config, rng)
}
}
#[cfg(test)]
pub(crate) mod arbitrary {
use crate::bytes_range;
use crate::bytes_range::BytesRange;
use crate::iter::IterationOrder;
use crate::proptest_util::{rng, sample};
use bytes::{BufMut, Bytes, BytesMut};
use proptest::arbitrary::any;
use proptest::collection::vec;
use proptest::prelude::{Just, Strategy};
use proptest::prop_oneof;
use proptest::test_runner::TestRng;
use std::ops::Bound::{Excluded, Included, Unbounded};
pub(crate) fn iteration_order() -> impl Strategy<Value = IterationOrder> {
prop_oneof![
Just(IterationOrder::Ascending),
Just(IterationOrder::Descending),
]
}
pub(crate) fn bytes(size: usize) -> impl Strategy<Value = Bytes> {
vec(any::<u8>(), 0..size).prop_map(Bytes::from)
}
pub(crate) fn nonempty_bytes(size: usize) -> impl Strategy<Value = Bytes> {
vec(any::<u8>(), 1..size)
.prop_filter("Filter out [0; 1]", |v| v != &[0; 1])
.prop_map(Bytes::from)
}
pub(crate) fn rng() -> impl Strategy<Value = TestRng> {
vec(any::<u8>(), 32).prop_map(|seed| {
let mut s: [u8; 32] = Default::default();
s.clone_from_slice(&seed);
rng::new_test_rng(Some(s))
})
}
fn nonempty_bounded_range(size: usize) -> impl Strategy<Value = BytesRange> {
(nonempty_bytes(size), nonempty_bytes(size))
.prop_filter_map("Filter non-empty ranges", nonempty_range_filter)
.prop_flat_map(|(start, end)| {
prop_oneof![
Just(BytesRange::new(
Included(start.clone()),
Included(end.clone())
)),
Just(BytesRange::new(
Excluded(start.clone()),
Excluded(end.clone())
)),
Just(BytesRange::new(
Included(start.clone()),
Excluded(end.clone())
)),
Just(BytesRange::new(
Excluded(start.clone()),
Included(end.clone())
)),
]
})
}
fn nonempty_partial_bounded_range(size: usize) -> impl Strategy<Value = BytesRange> {
nonempty_bytes(size).prop_flat_map(|a| {
prop_oneof![
Just(BytesRange::new(Unbounded, Included(a.clone()))),
Just(BytesRange::new(Unbounded, Excluded(a.clone()))),
Just(BytesRange::new(Included(a.clone()), Unbounded)),
Just(BytesRange::new(Excluded(a.clone()), Unbounded)),
]
})
}
fn extend_bytes(a: &Bytes, value: u8) -> Bytes {
let mut b = BytesMut::from(a.as_ref());
b.put_u8(value);
b.freeze()
}
fn arbitrary_single_point_bounded_range(size: usize) -> impl Strategy<Value = BytesRange> {
nonempty_bytes(size).prop_flat_map(|a| {
let a_extended = extend_bytes(&a, u8::MIN);
prop_oneof![
Just(BytesRange::new(Included(a.clone()), Included(a.clone()))),
Just(BytesRange::new(
Included(a.clone()),
Excluded(a_extended.clone())
)),
Just(BytesRange::new(
Excluded(a.clone()),
Included(a_extended.clone())
)),
]
})
}
fn min_and_max(a: Bytes, b: Bytes) -> (Bytes, Bytes) {
if a < b {
(a, b)
} else {
(b, a)
}
}
fn nonempty_range_filter(tuple: (Bytes, Bytes)) -> Option<(Bytes, Bytes)> {
let (a, b) = tuple;
if a == b {
None
} else {
let (start, end) = min_and_max(a, b);
if bytes_range::is_prefix_increment(&start, &end) {
None
} else {
Some((start, end))
}
}
}
pub(crate) fn empty_range(size: usize) -> impl Strategy<Value = BytesRange> {
nonempty_bytes(size).prop_flat_map(|a| {
prop_oneof![
Just(BytesRange::new(Excluded(a.clone()), Excluded(a.clone()))),
Just(BytesRange::new(Included(a.clone()), Excluded(a.clone()))),
Just(BytesRange::new(Excluded(a.clone()), Included(a.clone()))),
]
})
}
pub(crate) fn nonempty_range(size: usize) -> impl Strategy<Value = BytesRange> {
prop_oneof![
Just(BytesRange::new(Unbounded, Unbounded)),
arbitrary_single_point_bounded_range(size),
nonempty_partial_bounded_range(size),
nonempty_bounded_range(size),
]
}
pub(crate) fn nonempty_intersecting_ranges(
size: usize,
) -> impl Strategy<Value = (BytesRange, BytesRange)> {
(rng(), nonempty_range(size)).prop_flat_map(|(mut rng, range1)| {
let start = sample::bytes_in_range(&mut rng, &range1);
let end_range = BytesRange::new(Included(start.clone()), Unbounded);
let end = sample::bytes_in_range(&mut rng, &end_range);
prop_oneof![
(
Just(range1.clone()),
Just(BytesRange::new(
Included(start.clone()),
Included(end.clone())
))
),
(
Just(range1.clone()),
Just(BytesRange::new(Unbounded, Included(end.clone())))
),
(
Just(range1.clone()),
Just(BytesRange::new(Included(start.clone()), Unbounded))
),
(
Just(range1.clone()),
Just(BytesRange::new(Unbounded, Unbounded))
),
]
})
}
}
#[cfg(test)]
pub(crate) mod rng {
use proptest::test_runner::{RngAlgorithm, TestRng};
use rand::Rng;
pub(crate) fn new_test_rng(seed: Option<[u8; 32]>) -> TestRng {
let seed = seed.unwrap_or_else(|| {
let mut thread_rng = rand::rng();
let random_bytes: [u8; 32] = thread_rng.random();
random_bytes
});
TestRng::from_seed(RngAlgorithm::ChaCha, &seed)
}
}
#[cfg(test)]
pub(crate) mod sample {
use crate::bytes_range::BytesRange;
use crate::{bytes_range, test_utils};
use bytes::{BufMut, Bytes, BytesMut};
use proptest::test_runner::TestRng;
use rand::distr::uniform::SampleRange;
use rand::prelude::SliceRandom;
use rand::Rng;
use std::cmp::max;
use std::collections::BTreeMap;
use std::ops::Bound::{Excluded, Included, Unbounded};
use std::ops::{Bound, RangeBounds};
pub(crate) fn bytes<T: SampleRange<usize>>(rng: &mut TestRng, len_range: T) -> Bytes {
let len = rng.random_range(len_range);
let mut v: Vec<u8> = Vec::with_capacity(len);
for _ in 0..len {
v.push(rng.random());
}
v.into()
}
pub(crate) fn record(rng: &mut TestRng, max_bytes_len: usize) -> (Bytes, Bytes) {
let key = bytes(rng, 1..max_bytes_len);
let value = bytes(rng, 0..max_bytes_len);
(key, value)
}
pub(crate) fn table(
rng: &mut TestRng,
num_records: usize,
max_bytes_len: usize,
) -> BTreeMap<Bytes, Bytes> {
let mut table = BTreeMap::new();
while table.len() < num_records {
let (key, value) = record(rng, max_bytes_len);
table.insert(key, value);
}
table
}
fn bytes_between(rng: &mut TestRng, start: &Bytes, end: &Bytes) -> Bytes {
assert_eq!(start.len(), end.len());
let mut res = BytesMut::new();
let mut start_bound_satisfied = false;
let mut end_bound_satisfied = false;
let min_len = rng.random_range(0..=start.len());
for i in 0..start.len() {
let next_val_lb = if !start_bound_satisfied {
start[i]
} else {
u8::MIN
};
let next_val_ub = if !end_bound_satisfied {
end[i]
} else {
u8::MAX
};
let next_val = if next_val_lb == next_val_ub {
next_val_lb
} else {
let next_bound_range_diff = next_val_ub - next_val_lb;
next_val_lb + (rng.random::<u8>() % next_bound_range_diff)
};
res.put_u8(next_val);
if next_val > next_val_lb {
start_bound_satisfied = true;
}
if next_val < next_val_ub {
end_bound_satisfied = true;
}
if start_bound_satisfied && end_bound_satisfied && res.len() >= min_len {
break;
}
}
let res = res.freeze();
assert!(res >= start && res <= end);
res
}
fn increment_lex(b: &Bytes) -> Bytes {
let mut res = BytesMut::from(b.as_ref());
for i in (0..res.len()).rev() {
if res[i] < u8::MAX {
res[i] += 1;
return res.freeze();
} else {
res[i] = u8::MIN;
}
}
panic!("Overflow when incrementing bytes {b:?}")
}
fn decrement_lex(b: &Bytes) -> Bytes {
let mut res = BytesMut::from(b.as_ref());
for i in (0..res.len()).rev() {
if res[i] > u8::MIN {
res[i] -= 1;
return res.freeze();
} else {
res[i] = u8::MAX;
}
}
panic!("Overflow when decrementing bytes {b:?}")
}
fn minvalue_bytes(
rng: &mut TestRng,
min_bound: Bound<usize>,
max_bound: Bound<usize>,
) -> Bytes {
let min_len = match min_bound {
Unbounded => 0,
Included(len) => len,
Excluded(len) => len + 1,
};
let max_len = match max_bound {
Unbounded => min_len + 10,
Included(len) => len,
Excluded(len) => len - 1,
};
let len = rng.random_range(min_len..=max_len);
Bytes::from(vec![u8::MIN; len])
}
fn bound_len(bound: Bound<&Bytes>) -> usize {
match bound {
Unbounded => 0,
Included(b) | Excluded(b) => b.len(),
}
}
fn padded_bytes(b: &[u8], value: u8, len: usize) -> Bytes {
let mut padded = BytesMut::from(b);
while padded.len() < len {
padded.put_u8(value);
}
padded.freeze()
}
fn inclusive_end_bound(range: &BytesRange, len: usize) -> Bytes {
match range.end_bound() {
Unbounded => padded_bytes(&Bytes::new(), u8::MAX, len),
Included(b) | Excluded(b) => {
let b = padded_bytes(b, u8::MIN, len);
decrement_lex(&b)
}
}
}
fn inclusive_start_bound(range: &BytesRange, len: usize) -> Bytes {
match range.start_bound() {
Unbounded => padded_bytes(&Bytes::new(), u8::MIN, len),
Included(b) => padded_bytes(b, u8::MIN, len),
Excluded(b) => {
let padded = padded_bytes(b, u8::MIN, len);
if b.len() < len {
padded
} else {
increment_lex(&padded)
}
}
}
}
fn can_decrement_without_truncation(bytes: &Bytes) -> bool {
bytes.iter().any(|b| *b > u8::MIN)
}
pub(crate) fn bytes_in_range(rng: &mut TestRng, range: &BytesRange) -> Bytes {
assert!(
range.non_empty(),
"Cannot choose an arbitrary value from an empty range"
);
if let Some(end) = test_utils::bound_as_option(range.end_bound()) {
if !can_decrement_without_truncation(end) {
let min_len = match range.start_bound() {
Unbounded => Excluded(0),
Included(b) => Included(b.len()),
Excluded(b) => Excluded(b.len()),
};
let max_len = range.end_bound().map(|b| b.len());
let result = minvalue_bytes(rng, min_len, max_len);
assert!(
!result.is_empty(),
"calculated empty bytes for range {range:?}"
);
return result;
} else if range.start_bound() == Included(end) {
return end.clone();
} else {
let start = test_utils::bound_as_option(range.start_bound())
.cloned()
.unwrap_or_default();
if bytes_range::is_prefix_increment(&start, end) {
let mut tmp = Vec::new();
if matches!(range.start_bound(), Included(_) | Unbounded) {
tmp.push(start);
}
if matches!(range.end_bound(), Included(_)) {
tmp.push(end.clone());
}
tmp.shuffle(rng);
return tmp.pop().unwrap();
}
}
}
let len = max(bound_len(range.start_bound()), bound_len(range.end_bound())) + 3;
let start_bound = inclusive_start_bound(range, len);
let end_bound = inclusive_end_bound(range, len);
bytes_between(rng, &start_bound, &end_bound)
}
}
#[cfg(test)]
mod tests {
use std::ops::Bound;
use crate::bytes_range::BytesRange;
use crate::proptest_util::{arbitrary, sample};
use bytes::Bytes;
use proptest::proptest;
use proptest::test_runner::{RngAlgorithm, TestRng};
#[test]
fn test_arbitrary_bytes() {
proptest!(|(bytes in arbitrary::bytes(10))| {
assert!(bytes.len() <= 10);
});
proptest!(|(bytes in arbitrary::nonempty_bytes(10))| {
assert!(!bytes.is_empty());
assert!(bytes.len() <= 10);
});
}
#[test]
fn test_sample_table() {
let mut rng = TestRng::deterministic_rng(RngAlgorithm::ChaCha);
let num_records = 50;
let table = sample::table(&mut rng, num_records, 10);
assert_eq!(table.len(), num_records);
for (key, value) in table {
assert!(!key.is_empty());
assert!(key.len() <= 10);
assert!(value.len() <= 10);
}
}
#[test]
fn test_bytes_in_range_with_multiple_bytes() {
let mut rng = TestRng::deterministic_rng(RngAlgorithm::ChaCha);
let range = BytesRange::new(
Bound::Unbounded,
Bound::Included(Bytes::from_static(&[0; 2])),
);
sample::bytes_in_range(&mut rng, &range);
}
}