use core::fmt;
#[cfg_attr(doc_cfg, doc(cfg(feature = "test")))]
#[repr(transparent)]
pub struct FormatBytes([u8]);
impl FormatBytes {
pub fn new(bytes: &[u8]) -> &Self {
unsafe { &*(bytes as *const [u8] as *const FormatBytes) }
}
}
impl fmt::Display for FormatBytes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "b\"")?;
for chunk in self.0.utf8_chunks() {
f.write_str(chunk.valid())?;
for &b in chunk.invalid() {
write!(f, "\\x{b:02x}")?;
}
}
write!(f, "\" (0-{})", self.0.len())?;
Ok(())
}
}
macro_rules! test_include_if {
(#[musli_value] => $($rest:tt)*) => { $($rest)* };
(=> $($_:tt)*) => {};
}
pub(crate) use test_include_if;
macro_rules! test_fns {
($mode:ident, $what:expr $(, $(#[$option:ident])*)?) => {
#[doc(hidden)]
#[track_caller]
#[cfg(feature = "alloc")]
pub fn rt<T, M>(value: T) -> T
where
T: $crate::en::Encode<M> + $crate::de::DecodeOwned<M, $crate::alloc::Global>,
T: ::core::fmt::Debug + ::core::cmp::PartialEq,
M: 'static,
{
use ::core::any::type_name;
let encoding = super::Encoding::new().with_mode::<M>();
let what = ::std::format!("{} ({})", $what, type_name::<M>());
let cx = $crate::context::new().with_trace().with_type();
let out = match encoding.to_vec_with(&cx, &value) {
Ok(out) => out,
Err(..) => {
let error = cx.report();
panic!("{what}: {}: failed to encode:\n{error}", type_name::<T>())
}
};
let decoded: T = match encoding.from_slice_with(&cx, out.as_slice()) {
Ok(decoded) => decoded,
Err(..) => {
let out = $crate::macros::FormatBytes::new(&out);
let error = cx.report();
panic!("{what}: {}: failed to decode:\nValue: {value:?}\nBytes: {out}\n{error}", type_name::<T>())
}
};
assert_eq!(decoded, value, "{what}: {}: roundtrip does not match\nValue: {value:?}", type_name::<T>());
$crate::macros::test_include_if! {
$($(#[$option])*)* =>
let value_encoding = crate::value::Encoding::new().with_mode::<M>();
let value_decode: $crate::value::Value<_> = match encoding.from_slice_with(&cx, out.as_slice()) {
Ok(decoded) => decoded,
Err(..) => {
let out = $crate::macros::FormatBytes::new(&out);
let error = cx.report();
panic!("{what}: {}: failed to decode to value type:\nValue: {value:?}\nBytes:{out}\n{error}", type_name::<T>())
}
};
let value_decoded: T = match value_encoding.decode_with(&cx, &value_decode) {
Ok(decoded) => decoded,
Err(..) => {
let out = $crate::macros::FormatBytes::new(&out);
let error = cx.report();
panic!("{what}: {}: failed to decode from value type:\nValue: {value:?}\nBytes: {out}\nBuffered value: {value_decode:?}\n{error}", type_name::<T>())
}
};
assert_eq!(value_decoded, value, "{what}: {}: musli-value roundtrip does not match\nValue: {value:?}", type_name::<T>());
}
decoded
}
#[doc(hidden)]
#[track_caller]
#[cfg(feature = "alloc")]
pub fn decode<'de, T, U, M>(value: T, out: &'de mut rust_alloc::vec::Vec<u8>, expected: &U) -> U
where
T: $crate::en::Encode<M>,
T: ::core::fmt::Debug + ::core::cmp::PartialEq,
U: $crate::de::Decode<'de, M, $crate::alloc::Global>,
U: ::core::fmt::Debug + ::core::cmp::PartialEq,
M: 'static,
{
let what = ::std::format!("{} ({})", $what, type_name::<M>());
let encoding = super::Encoding::new().with_mode::<M>();
use ::core::any::type_name;
let cx = $crate::context::new().with_trace().with_type();
out.clear();
match encoding.to_writer_with(&cx, &mut *out, &value) {
Ok(()) => (),
Err(..) => {
let error = cx.report();
panic!("{what}: {}: failed to encode:\n{error}", type_name::<T>())
}
};
let actual = match encoding.from_slice_with(&cx, &*out) {
Ok(decoded) => decoded,
Err(..) => {
let out = $crate::macros::FormatBytes::new(&*out);
let error = cx.report();
panic!("{what}: {}: failed to decode:\nValue: {value:?}\nBytes: {out}\n{error}", type_name::<U>())
}
};
assert_eq!(
actual,
*expected,
"{what}: decoded value does not match expected\nBytes: {}",
$crate::macros::FormatBytes::new(&*out),
);
actual
}
#[doc(hidden)]
#[track_caller]
#[cfg(feature = "alloc")]
pub fn to_vec<T, M>(value: T) -> rust_alloc::vec::Vec<u8>
where
T: $crate::en::Encode<M>,
M: 'static,
{
let what = ::std::format!("{} ({})", $what, type_name::<M>());
let encoding = super::Encoding::new().with_mode::<M>();
use ::core::any::type_name;
$crate::alloc::default(|alloc| {
let cx = $crate::context::new_in(alloc).with_trace().with_type();
match encoding.to_vec_with(&cx, &value) {
Ok(out) => out,
Err(..) => {
let error = cx.report();
panic!("{what}: {}: failed to encode:\n{error}", type_name::<T>())
}
}
})
}
}
}
pub(crate) use test_fns;
#[cfg_attr(doc_cfg, doc(cfg(feature = "test")))]
#[macro_export]
macro_rules! assert_roundtrip_eq {
($support:ident, $expr:expr $(, $($extra:tt)*)?) => {{
let expected = $expr;
macro_rules! inner {
($name:ident, $mode:ident) => {{
assert_eq!(
$crate::$name::test::rt::<_, $crate::mode::$mode>($expr),
expected,
"{}: roundtripped value does not match expected",
stringify!($name),
);
}}
}
$crate::macros::__test_matrix!($support, inner);
$crate::macros::support::musli_value_rt($expr);
$crate::macros::__test_extra!($expr $(, $($extra)*)*);
expected
}};
}
pub use assert_roundtrip_eq;
#[cfg_attr(doc_cfg, doc(cfg(feature = "test")))]
#[macro_export]
macro_rules! assert_roundtrip_borrowed_eq {
($support:ident, $name:ident $(::$variant:ident)? { $($body:tt)* } $(, $($extra:tt)*)?) => {{
macro_rules! inner {
($framework:ident, $mode:ident) => {{
let what = ::std::format!("{}/{}/{}", stringify!($framework), stringify!($name $(::$variant)*), stringify!($mode));
let encoding = musli::$framework::Encoding::new().with_mode::<musli::mode::$mode>();
let value = $name $(::$variant)* { $($body)* };
let cx = $crate::context::new().with_trace().with_type();
let bytes = match encoding.to_vec_with(&cx, &value) {
Ok(out) => out,
Err(..) => {
let error = cx.report();
::std::panic!("{what}: failed to encode:\n{error}")
}
};
let decoded: $name<'_> = match encoding.from_slice_with(&cx, &bytes) {
Ok(out) => out,
Err(..) => {
let out = $crate::macros::FormatBytes::new(&bytes);
let error = cx.report();
::std::panic!("{what}: failed to decode:\nValue: {value:?}\nBytes: {out}\n{error}")
}
};
assert_eq!(
value,
decoded,
"{what}: roundtripped value does not match expected",
);
}}
}
$crate::macros::__test_matrix!($support, inner);
}};
}
pub use assert_roundtrip_borrowed_eq;
#[cfg_attr(doc_cfg, doc(cfg(feature = "test")))]
#[macro_export]
macro_rules! assert_decode_eq {
($support:ident, $expr:expr, $expected:expr $(, $($extra:tt)*)?) => {{
let mut bytes = $crate::macros::support::Vec::<u8>::new();
macro_rules! decode {
($name:ident, $mode:ident) => {{
$crate::$name::test::decode::<_, _, $crate::mode::$mode>($expr, &mut bytes, &$expected);
}}
}
$crate::macros::__test_matrix!($support, decode);
$crate::macros::__test_extra!($expr $(, $($extra)*)*);
}};
}
pub use assert_decode_eq;
#[doc(hidden)]
#[macro_export]
macro_rules! __test_extra {
($expr:expr $(,)?) => {};
($expr:expr, json = $json_expected:expr $(, $($extra:tt)*)?) => {{
let json = $crate::json::test::to_vec::<_, $crate::mode::Text>($expr);
let string = ::std::string::String::from_utf8(json).expect("Encoded JSON is not valid utf-8");
assert_eq!(
string, $json_expected,
"json: encoded json does not match expected value"
);
$crate::macros::__test_extra!($expr $(, $($extra)*)*);
}};
($expr:expr, json_binary = $json_expected:expr $(, $($extra:tt)*)?) => {{
let json = $crate::json::test::to_vec::<_, $crate::mode::Binary>($expr);
let string = ::std::string::String::from_utf8(json).expect("Encoded JSON is not valid utf-8");
assert_eq!(
string, $json_expected,
"json: encoded json does not match expected value"
);
$crate::macros::__test_extra!($expr $(, $($extra)*)*);
}};
}
pub use __test_extra;
#[doc(hidden)]
#[macro_export]
macro_rules! __test_matrix {
(full, $call:path) => {
$call!(storage, Binary);
$call!(storage, Text);
$call!(packed, Binary);
$call!(wire, Binary);
$call!(wire, Text);
$call!(descriptive, Binary);
$call!(descriptive, Text);
$call!(json, Text);
};
(not_packed, $call:path) => {
$call!(storage, Binary);
$call!(wire, Binary);
$call!(descriptive, Binary);
$call!(json, Text);
};
(text_mode, $call:path) => {
$call!(storage, Text);
$call!(wire, Text);
$call!(descriptive, Text);
$call!(json, Text);
};
(storage, $call:path) => {
$call!(storage, Binary);
};
(wire, $call:path) => {
$call!(wire, Binary);
};
(binary_mode, $call:path) => {
$call!(storage, Binary);
$call!(packed, Binary);
$call!(wire, Binary);
$call!(descriptive, Binary);
$call!(json, Binary);
};
(no_json, $call:path) => {
$call!(storage, Binary);
$call!(packed, Binary);
$call!(wire, Binary);
$call!(descriptive, Binary);
};
(descriptive, $call:path) => {
$call!(descriptive, Binary);
$call!(json, Text);
};
(json, $call:path) => {
$call!(json, Text);
};
(packed, $call:path) => {
$call!(packed, Binary);
};
(upgrade_stable, $call:path) => {
$call!(wire, Binary);
$call!(descriptive, Binary);
$call!(json, Text);
};
}
pub use __test_matrix;
#[doc(hidden)]
#[cfg(feature = "alloc")]
pub mod support {
pub use rust_alloc::vec::Vec;
use crate::alloc::Global;
use crate::mode::Binary;
use crate::value::{self, Value};
use crate::{Decode, Encode};
#[track_caller]
pub fn musli_value_rt<T>(expected: T)
where
T: Encode<Binary> + for<'de> Decode<'de, Binary, Global>,
T: PartialEq + core::fmt::Debug,
{
let value: Value<_> = value::encode(&expected).expect("value: Encoding should succeed");
let actual: T = value::decode(&value).expect("value: Decoding should succeed");
assert_eq!(
actual, expected,
"value: roundtripped value does not match expected"
);
}
}