radicle/test/arbitrary: Make BoundedVec shared

A `BoundedVec` that implements `Arbitrary` is useful for all property testing.
Move it from the `sigrefs` module so that it can be reused by other components.
This commit is contained in:
Fintan Halpenny 2026-06-12 10:40:22 +01:00
parent 94a24a7283
commit aae8938c01
2 changed files with 55 additions and 16 deletions

View File

@ -14,24 +14,11 @@ use crate::storage::refs::sigrefs::VerifiedCommit;
use crate::storage::refs::sigrefs::read::{SignedRefsReader, Tip}; use crate::storage::refs::sigrefs::read::{SignedRefsReader, Tip};
use crate::storage::refs::sigrefs::write::{SignedRefsWriter, Update}; use crate::storage::refs::sigrefs::write::{SignedRefsWriter, Update};
use crate::storage::refs::{IDENTITY_ROOT, Refs}; use crate::storage::refs::{IDENTITY_ROOT, Refs};
use crate::test::arbitrary;
use super::Committer; use super::Committer;
/// Newtype wrapper around [`Vec`] to keep the [`Arbitrary`] implementation type BoundedVec<T> = arbitrary::BoundedVec<T, 24>;
/// bounded to a smaller size.
#[derive(Clone, Debug)]
struct BoundedVec<T>(Vec<T>);
impl<T: qcheck::Arbitrary> qcheck::Arbitrary for BoundedVec<T> {
fn arbitrary(g: &mut qcheck::Gen) -> Self {
let size = usize::arbitrary(g) % 24;
BoundedVec((0..size).map(|_| T::arbitrary(g)).collect())
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(self.0.shrink().map(BoundedVec))
}
}
struct Verifier { struct Verifier {
key: PublicKey, key: PublicKey,
@ -176,7 +163,7 @@ fn initial_commit_roundtrip(mut refs: Refs) -> bool {
#[quickcheck] #[quickcheck]
fn chain_roundtrip(chain: BoundedVec<Refs>) -> TestResult { fn chain_roundtrip(chain: BoundedVec<Refs>) -> TestResult {
let chain = chain.0; let chain = chain.to_vec();
if chain.is_empty() { if chain.is_empty() {
return TestResult::discard(); return TestResult::discard();
} }

View File

@ -322,3 +322,55 @@ impl Arbitrary for UserAgent {
.unwrap() .unwrap()
} }
} }
/// Newtype wrapper around [`Vec`] to keep the [`Arbitrary`] implementation
/// bounded to a smaller size.
#[derive(Clone, Debug)]
pub struct BoundedVec<T, const N: usize> {
inner: Vec<T>,
}
impl<T, const N: usize> BoundedVec<T, N> {
pub fn to_vec(self) -> Vec<T> {
self.inner
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn len(&self) -> usize {
self.inner.len()
}
}
impl<T, const N: usize> IntoIterator for BoundedVec<T, N> {
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter()
}
}
impl<'a, T, const N: usize> IntoIterator for &'a BoundedVec<T, N> {
type Item = &'a T;
type IntoIter = std::slice::Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.inner.iter()
}
}
impl<T: qcheck::Arbitrary, const N: usize> qcheck::Arbitrary for BoundedVec<T, N> {
fn arbitrary(g: &mut qcheck::Gen) -> Self {
let size = usize::arbitrary(g) % N;
Self {
inner: (0..size).map(|_| T::arbitrary(g)).collect(),
}
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(self.inner.shrink().map(|inner| Self { inner }))
}
}