pub use crate::{
DecoderBuffer, DecoderBufferMut, DecoderError, DecoderValue, DecoderValueMut, Encoder,
EncoderBuffer, EncoderLenEstimator, EncoderValue,
};
pub type Error = DecoderError;
#[macro_export]
macro_rules! assert_codec_round_trip_value {
($ty:ty, $value:expr) => {{
$crate::ensure_codec_round_trip_value!($ty, $value).unwrap()
}};
}
#[macro_export]
macro_rules! ensure_codec_round_trip_value {
($ty:ty, $value:expr) => {{
fn execute_round_trip(expected_value: &$ty) -> Result<Vec<u8>, $crate::testing::Error> {
let mut expected_bytes = $crate::testing::encode(expected_value)?;
$crate::testing::ensure_decoding_matches(expected_value, &expected_bytes)?;
$crate::testing::ensure_decoding_mut_matches(expected_value, &mut expected_bytes)?;
Ok(expected_bytes)
}
execute_round_trip(&$value)
}};
}
#[macro_export]
macro_rules! assert_codec_round_trip_value_mut {
($ty:ty, $value:expr) => {{
$crate::ensure_codec_round_trip_value_mut!($ty, $value).unwrap()
}};
}
#[macro_export]
macro_rules! ensure_codec_round_trip_value_mut {
($ty:ty, $value:expr) => {{
fn execute_round_trip(expected_value: &$ty) -> Result<Vec<u8>, $crate::testing::Error> {
let mut expected_bytes = $crate::testing::encode(expected_value)?;
$crate::testing::ensure_decoding_mut_matches(expected_value, &mut expected_bytes)?;
Ok(expected_bytes)
}
execute_round_trip(&$value)
}};
}
macro_rules! impl_round_trip_bytes {
($name:ident, $round_trip:ident, $buffer:ident, $slice:ty, $as_ref:ident) => {
#[macro_export]
macro_rules! $name {
($ty: ty,$bytes: expr) => {{
fn execute_all_round_trip(
buffer: $slice,
) -> Result<Vec<$ty>, $crate::testing::Error> {
let mut buffer = $crate::$buffer::new(buffer);
let mut values = vec![];
while let Ok((value, remaining)) = buffer.decode::<$ty>() {
$crate::$round_trip!($ty, &value)?;
values.push(value);
buffer = remaining;
if buffer.is_empty() {
break;
}
}
Ok(values)
}
execute_all_round_trip($bytes.$as_ref())
}};
}
};
}
#[macro_export]
macro_rules! assert_codec_round_trip_bytes {
($ty:ty, $bytes:expr) => {{
$crate::ensure_codec_round_trip_bytes!($ty, $bytes).unwrap()
}};
}
impl_round_trip_bytes!(
ensure_codec_round_trip_bytes,
ensure_codec_round_trip_value,
DecoderBuffer,
&[u8],
as_ref
);
#[macro_export]
macro_rules! assert_codec_round_trip_bytes_mut {
($ty:ty, $bytes:expr) => {{
$crate::ensure_codec_round_trip_bytes_mut!($ty, $bytes).unwrap()
}};
}
impl_round_trip_bytes!(
ensure_codec_round_trip_bytes_mut,
ensure_codec_round_trip_value_mut,
DecoderBufferMut,
&mut [u8],
as_mut
);
#[macro_export]
macro_rules! assert_codec_round_trip_sample_file {
($ty:ty, $path:expr) => {{
$crate::assert_codec_round_trip_sample_file!($ty, $path, |buffer| {
let (value, buffer) = buffer.decode::<$ty>().unwrap();
$crate::assert_codec_round_trip_value_mut!($ty, value);
(value, buffer)
});
}};
($ty:ty, $path:expr, | $buffer:ident | $decode:expr) => {{
#[cfg(not(miri))]
let mut expected = std::fs::read(concat!(env!("CARGO_MANIFEST_DIR"), "/", $path)).unwrap();
#[cfg(miri)]
let mut expected = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/", $path)).to_vec();
let mut $buffer = $crate::DecoderBufferMut::new(&mut expected);
let mut values = vec![];
while !$buffer.is_empty() {
let (value, remaining) = $decode;
values.push(value);
$buffer = remaining;
}
#[cfg(not(miri))]
insta::assert_debug_snapshot!(
std::path::Path::new($path)
.file_stem()
.unwrap()
.to_str()
.unwrap(),
values
);
}};
}
macro_rules! ensure {
($expr:expr, $message:expr $(,)?) => {
if !($expr) {
return Err($crate::testing::Error::InvariantViolation($message));
}
};
}
pub fn encode<T: EncoderValue>(expected_value: &T) -> Result<Vec<u8>, Error> {
let len = expected_value.encoding_size();
let mut buffer = vec![0; len];
EncoderBuffer::new(&mut buffer).encode(expected_value);
Ok(buffer)
}
pub fn ensure_encoding_matches<T: EncoderValue + PartialEq + core::fmt::Debug>(
expected_value: &T,
expected_bytes: &[u8],
) -> Result<Vec<u8>, Error> {
let actual_bytes = encode(expected_value)?;
ensure!(actual_bytes == expected_bytes, "encodings do not match");
Ok(actual_bytes)
}
pub fn decode<'a, T: DecoderValue<'a>>(
expected_bytes: &'a [u8],
) -> Result<(T, DecoderBuffer<'a>), Error> {
let (actual_value, remaining) = DecoderBuffer::new(expected_bytes).decode()?;
Ok((actual_value, remaining))
}
pub fn ensure_decoding_matches<'a, T: DecoderValue<'a> + PartialEq + core::fmt::Debug>(
expected_value: &T,
expected_bytes: &'a [u8],
) -> Result<(), Error> {
let (actual_value, remaining) = decode(expected_bytes)?;
ensure!(
expected_value == &actual_value,
"mut decodings do not match",
);
remaining.ensure_empty()?;
Ok(())
}
pub fn decode_mut<'a, T: DecoderValueMut<'a>>(
expected_bytes: &'a mut [u8],
) -> Result<(T, DecoderBufferMut<'a>), Error> {
let (actual_value, remaining) = DecoderBufferMut::new(expected_bytes).decode()?;
Ok((actual_value, remaining))
}
pub fn ensure_decoding_mut_matches<'a, T: DecoderValueMut<'a> + PartialEq + core::fmt::Debug>(
expected_value: &T,
expected_bytes: &'a mut [u8],
) -> Result<(), Error> {
let (actual_value, remaining) = decode_mut(expected_bytes)?;
ensure!(
expected_value == &actual_value,
"mut decodings do not match",
);
remaining.ensure_empty()?;
Ok(())
}
#[cfg(test)]
mod tests {
use crate::{i24, i48, u24, u48};
use bolero::check;
#[test]
fn u8_round_trip_value_test() {
for i in 0..=u8::MAX {
ensure_codec_round_trip_value!(u8, i).unwrap();
}
}
#[test]
fn u8_round_trip_bytes_test() {
let bytes = (0..=u8::MAX).collect::<Vec<_>>();
ensure_codec_round_trip_bytes!(u8, &bytes).unwrap();
}
#[test]
fn u16_round_trip_value_test() {
for i in 0..=u16::MAX {
ensure_codec_round_trip_value!(u16, i).unwrap();
}
}
#[test]
fn u16_round_trip_bytes_test() {
let mut bytes = vec![];
for v in 0..=u16::MAX {
bytes.extend_from_slice(&v.to_be_bytes());
}
ensure_codec_round_trip_bytes!(u16, &bytes).unwrap();
}
macro_rules! fuzz_tests {
($ty:ident, $value_test:ident, $bytes_test:ident) => {
#[test]
#[cfg_attr(kani, kani::proof, kani::solver(cadical))]
fn $value_test() {
check!().with_type().cloned().for_each(|v| {
ensure_codec_round_trip_value!($ty, v).unwrap();
let bytes = v.to_be_bytes();
let actual = $ty::from_be_bytes(bytes);
assert_eq!(v, actual);
});
}
#[test]
fn $bytes_test() {
check!().for_each(|bytes| {
let _ = ensure_codec_round_trip_bytes!($ty, &bytes);
});
}
};
}
fuzz_tests!(u24, u24_round_trip_value_test, u24_round_trip_bytes_test);
fuzz_tests!(i24, i24_round_trip_value_test, i24_round_trip_bytes_test);
fuzz_tests!(u32, u32_round_trip_value_test, u32_round_trip_bytes_test);
fuzz_tests!(i32, i32_round_trip_value_test, i32_round_trip_bytes_test);
fuzz_tests!(u48, u48_round_trip_value_test, u48_round_trip_bytes_test);
fuzz_tests!(i48, i48_round_trip_value_test, i48_round_trip_bytes_test);
fuzz_tests!(u64, u64_round_trip_value_test, u64_round_trip_bytes_test);
fuzz_tests!(i64, i64_round_trip_value_test, i64_round_trip_bytes_test);
}