reth_codecs/test_utils.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
//! Test utilities for `Compact` derive macro
/// Macro to ensure that derived `Compact` types can be extended with new fields while maintaining
/// backwards compatibility.
///
/// Verifies that the unused bits in the bitflag struct remain as expected: `Zero` or `NotZero`. For
/// more on bitflag struct: [`reth_codecs_derive::Compact`].
///
/// Possible failures:
/// ### 1. `NotZero` -> `Zero`
/// This wouldn't allow new fields to be added in the future. Instead, the new field of `T`
/// should be `Option<TExtension>` to allow for new fields. The new user field should be included
/// in `TExtension` type. **Only then, update the test to expect `Zero` for `T` and
/// add a new test for `TExtension`.**
///
/// **Goal:**
///
/// ```rust,ignore
/// {
/// struct T {
/// // ... other fields
/// ext: Option<TExtension>
/// }
///
/// // Use an extension type for new fields:
/// struct TExtension {
/// new_field_b: Option<u8>,
/// }
///
/// // Change tests
/// validate_bitflag_backwards_compat!(T, UnusedBits::Zero);
/// validate_bitflag_backwards_compat!(TExtension, UnusedBits::NotZero);
/// }
/// ```
///
/// ### 2. `Zero` -> `NotZero`
/// If it becomes `NotZero`, it would break backwards compatibility, so there is not an action item,
/// and should be handled with care in a case by case scenario.
#[macro_export]
macro_rules! validate_bitflag_backwards_compat {
($type:ty, $expected_unused_bits:expr) => {
let actual_unused_bits = <$type>::bitflag_unused_bits();
match $expected_unused_bits {
UnusedBits::NotZero => {
assert_ne!(
actual_unused_bits,
0,
"Assertion failed: `bitflag_unused_bits` for type `{}` unexpectedly went from non-zero to zero!",
stringify!($type)
);
}
UnusedBits::Zero => {
assert_eq!(
actual_unused_bits,
0,
"Assertion failed: `bitflag_unused_bits` for type `{}` unexpectedly went from zero to non-zero!",
stringify!($type)
);
}
}
};
}
/// Whether there are zero or more unused bits on `Compact` bitflag struct.
///
/// To be used with [`validate_bitflag_backwards_compat`].
#[derive(Debug)]
pub enum UnusedBits {
/// Zero bits available for a new field.
Zero,
/// Bits available for a new field.
NotZero,
}
impl UnusedBits {
/// Returns true if the variant is [`Self::NotZero`].
pub const fn not_zero(&self) -> bool {
matches!(self, Self::NotZero)
}
}
/// Tests decoding and re-encoding to ensure correctness.
pub fn test_decode<T: crate::Compact>(buf: &[u8]) {
let (decoded, _) = T::from_compact(buf, buf.len());
let mut encoded = Vec::with_capacity(buf.len());
decoded.to_compact(&mut encoded);
assert_eq!(buf, &encoded[..]);
}