use std::collections::BTreeMap;
use crate::common;
use common::{Value, to_hex};
use deser::ext::Timestamp;
use deser_msgpack::Ext;
const SUITE: &str = include_str!("data/msgpack-test-suite/msgpack-test-suite.json");
type Case = BTreeMap<String, Value>;
fn load() -> &'static BTreeMap<String, Vec<Case>> {
static CASES: std::sync::OnceLock<BTreeMap<String, Vec<Case>>> = std::sync::OnceLock::new();
CASES.get_or_init(|| deser_json::from_str(SUITE).unwrap())
}
fn hex(s: &str) -> Vec<u8> {
common::hex(&s.replace('-', ""))
}
fn as_str(value: &Value) -> &str {
match value {
Value::Str(value) => value,
other => panic!("expected string, got {:?}", other),
}
}
fn as_int(value: &Value) -> i128 {
match *value {
Value::U64(value) => value.into(),
Value::I64(value) => value.into(),
ref other => panic!("expected integer, got {:?}", other),
}
}
fn int(value: i128) -> Value {
match u64::try_from(value) {
Ok(value) => Value::U64(value),
Err(_) => Value::I64(value.try_into().unwrap()),
}
}
fn expected(case: &Case) -> Value {
if let Some(bignum) = case.get("bignum") {
return int(as_str(bignum).parse().unwrap());
}
let (kind, value) = case
.iter()
.find(|(key, _)| *key != "msgpack")
.expect("case without value");
match kind.as_str() {
"nil" | "bool" | "number" | "string" | "array" | "map" => value.clone(),
"binary" => Value::Bytes(hex(as_str(value))),
"timestamp" => match value {
Value::Array(items) => Value::ext(Timestamp {
seconds: as_int(&items[0]).try_into().unwrap(),
nanosecond: as_int(&items[1]).try_into().unwrap(),
}),
other => panic!("invalid timestamp {:?}", other),
},
"ext" => match value {
Value::Array(items) => Value::ext(Ext::new(
as_int(&items[0]).try_into().unwrap(),
hex(as_str(&items[1])),
)),
other => panic!("invalid extension {:?}", other),
},
other => panic!("unknown kind {}", other),
}
}
fn equivalent(a: &Value, b: &Value) -> bool {
fn number(value: &Value) -> Option<f64> {
match *value {
Value::U64(value) => Some(value as f64),
Value::I64(value) => Some(value as f64),
Value::F32(value) => Some(value.into()),
Value::F64(value) => Some(value),
_ => None,
}
}
match (a, b) {
(Value::Array(a), Value::Array(b)) => {
a.len() == b.len() && a.iter().zip(b).all(|(a, b)| equivalent(a, b))
}
(Value::Map(a), Value::Map(b)) => {
a.len() == b.len()
&& a.iter()
.zip(b)
.all(|(a, b)| equivalent(&a.0, &b.0) && equivalent(&a.1, &b.1))
}
(Value::U64(_) | Value::I64(_), Value::U64(_) | Value::I64(_)) => as_int(a) == as_int(b),
_ => match (number(a), number(b)) {
(Some(a), Some(b)) => a == b,
_ => a == b,
},
}
}
fn is_float(value: &Value) -> bool {
matches!(value, Value::F32(_) | Value::F64(_))
}
#[test]
#[cfg_attr(miri, ignore = "slow, no unsafe code under test")]
fn test_suite() {
let suite = load();
assert_eq!(suite.len(), 15);
let mut checked = 0;
for (group, cases) in suite {
for case in cases {
let value = expected(case);
let encodings: Vec<String> = match &case["msgpack"] {
Value::Array(items) => items
.iter()
.map(|item| as_str(item).replace('-', ""))
.collect(),
other => panic!("invalid encodings {:?}", other),
};
let context = format!("{}: {:?}", group, value);
for encoding in &encodings {
let bytes = common::hex(encoding);
let decoded: Value = deser_msgpack::from_slice(&bytes)
.unwrap_or_else(|err| panic!("{}: {} failed: {}", context, encoding, err));
assert!(
equivalent(&decoded, &value),
"{}: {} decoded as {:?}",
context,
encoding,
decoded
);
if !matches!(decoded, Value::U64(_) | Value::I64(_)) && encoding == &encodings[0] {
assert_eq!(
to_hex(&deser_msgpack::to_vec(&decoded).unwrap()),
*encoding,
"{}",
context
);
}
checked += 1;
}
let encoded = to_hex(&deser_msgpack::to_vec(&value).unwrap());
assert!(
encodings.contains(&encoded),
"{}: encoded as {}, expected one of {:?}",
context,
encoded,
encodings
);
if !is_float(&value) {
let shortest = encodings
.iter()
.filter(|x| !x.starts_with("ca") && !x.starts_with("cb"))
.map(|x| x.len())
.min()
.unwrap();
assert_eq!(encoded.len(), shortest, "{}: {}", context, encoded);
}
}
}
assert_eq!(checked, 233);
}
#[test]
#[cfg_attr(miri, ignore = "slow, no unsafe code under test")]
fn test_suite_typed() {
for (group, cases) in load() {
for case in cases {
let value = expected(case);
let Value::Array(ref encodings) = case["msgpack"] else {
panic!("invalid case");
};
for encoding in encodings {
let bytes = hex(as_str(encoding));
let context = format!("{}: {:?} from {:?}", group, value, encoding);
let is_float_encoding = matches!(bytes[0], 0xca | 0xcb);
match value {
Value::Null => deser_msgpack::from_slice::<()>(&bytes).unwrap(),
Value::Bool(expected) => {
assert_eq!(deser_msgpack::from_slice::<bool>(&bytes).unwrap(), expected)
}
Value::U64(_) | Value::I64(_) if !is_float_encoding => {
let expected = as_int(&value);
assert_eq!(
deser_msgpack::from_slice::<i128>(&bytes).unwrap(),
expected,
"{}",
context
);
if let Ok(expected) = i64::try_from(expected) {
assert_eq!(deser_msgpack::from_slice::<i64>(&bytes).unwrap(), expected);
}
if let Ok(expected) = u64::try_from(expected) {
assert_eq!(deser_msgpack::from_slice::<u64>(&bytes).unwrap(), expected);
}
}
Value::U64(_) | Value::I64(_) | Value::F64(_) => {
let expected = match value {
Value::F64(value) => value,
_ => as_int(&value) as f64,
};
assert_eq!(
deser_msgpack::from_slice::<f64>(&bytes).unwrap(),
expected,
"{}",
context
);
}
Value::Str(ref expected) => {
assert_eq!(
&deser_msgpack::from_slice::<String>(&bytes).unwrap(),
expected
);
assert_eq!(deser_msgpack::from_slice::<&str>(&bytes).unwrap(), expected);
}
Value::Bytes(ref expected) => {
assert_eq!(
&deser_msgpack::from_slice::<Vec<u8>>(&bytes).unwrap(),
expected
);
assert_eq!(
deser_msgpack::from_slice::<&[u8]>(&bytes).unwrap(),
expected
);
}
Value::Ext(ref ext) => {
if let Some(expected) = ext.downcast_ref::<Timestamp>() {
assert_eq!(
&deser_msgpack::from_slice::<Timestamp>(&bytes).unwrap(),
expected
);
let raw = deser_msgpack::from_slice::<Ext>(&bytes).unwrap();
assert_eq!(raw.kind, -1);
assert!(bytes.ends_with(&raw.data), "{}", context);
} else {
let expected = ext.downcast_ref::<Ext>().unwrap();
assert_eq!(
&deser_msgpack::from_slice::<Ext>(&bytes).unwrap(),
expected
);
assert_eq!(
deser_msgpack::from_slice::<Vec<u8>>(&bytes).unwrap(),
expected.data
);
}
}
Value::Array(ref items) if items.is_empty() => {
assert!(
deser_msgpack::from_slice::<Vec<u8>>(&bytes)
.unwrap()
.is_empty()
);
}
Value::Array(_) | Value::Map(_) => {}
ref other => panic!("unexpected value {:?}", other),
}
}
}
}
}
#[test]
#[cfg_attr(miri, ignore = "slow, no unsafe code under test")]
fn test_suite_as_stream() {
let mut bytes = Vec::new();
let mut values_expected = Vec::new();
for cases in load().values() {
for case in cases {
let Value::Array(ref encodings) = case["msgpack"] else {
panic!("invalid case");
};
for encoding in encodings {
bytes.extend(hex(as_str(encoding)));
values_expected.push(expected(case));
}
}
}
let mut de = deser_msgpack::Deserializer::from_slice(&bytes);
let values = de.iter::<Value>().collect::<Result<Vec<_>, _>>().unwrap();
assert_eq!(values.len(), values_expected.len());
for (value, expected) in values.iter().zip(&values_expected) {
assert!(equivalent(value, expected), "{:?} != {:?}", value, expected);
}
#[cfg(feature = "io")]
for &size in if cfg!(miri) {
&[7][..]
} else {
&[1, 2, 3, 7, 64][..]
} {
let mut reader = deser_msgpack::DeserializerConfig::new().reader(common::Chunked {
input: &bytes,
size,
});
let mut count = 0;
while let Some(value) = reader.read::<Value>().unwrap() {
assert!(equivalent(&value, &values_expected[count]), "size {}", size);
count += 1;
}
assert_eq!(count, values_expected.len());
}
}