use crate::resp::{
ARRAY_TAG, BULK_STRING_TAG, INTEGER_TAG, MAP_TAG, MAX_TAPE_PAYLOAD, PUSH_TAG, RespTape,
RespTapeMut, SET_TAG, SIMPLE_STRING_TAG, TAPE_LEN_TAG, TAPE_NODE_SIZE, TapeNode,
is_collection_tag,
};
#[test]
fn encode_decode_roundtrip() {
for &tag in &[
SIMPLE_STRING_TAG,
INTEGER_TAG,
ARRAY_TAG,
TAPE_LEN_TAG,
0xFF,
] {
for &payload in &[0u64, 1, 42, 0xABCD, MAX_TAPE_PAYLOAD / 2, MAX_TAPE_PAYLOAD] {
let node = TapeNode::new(tag, payload);
assert_eq!(tag, node.tag(), "tag lost for payload {payload:#x}");
assert_eq!(payload, node.payload(), "payload lost for tag {tag:#x}");
}
}
}
#[test]
fn tag_and_payload_do_not_bleed_into_each_other() {
let node = TapeNode::new(0xFF, MAX_TAPE_PAYLOAD);
assert_eq!(0xFF, node.tag());
assert_eq!(MAX_TAPE_PAYLOAD, node.payload());
let node = TapeNode::new(0x00, MAX_TAPE_PAYLOAD);
assert_eq!(0x00, node.tag());
assert_eq!(MAX_TAPE_PAYLOAD, node.payload());
}
#[test]
fn max_payload_is_56_bits() {
assert_eq!(MAX_TAPE_PAYLOAD, (1u64 << 56) - 1);
}
#[test]
fn node_size_is_a_power_of_two() {
assert_eq!(8, TAPE_NODE_SIZE);
}
#[test]
fn is_collection_tag_matches_only_collections() {
for &tag in &[ARRAY_TAG, MAP_TAG, SET_TAG, PUSH_TAG] {
assert!(
is_collection_tag(tag),
"{tag:#x} should be a collection tag"
);
assert!(TapeNode::new(tag, 0).is_collection());
}
for &tag in &[
SIMPLE_STRING_TAG,
INTEGER_TAG,
BULK_STRING_TAG,
TAPE_LEN_TAG,
b'_',
b'#',
b',',
b'(',
b'=',
b'!',
] {
assert!(
!is_collection_tag(tag),
"{tag:#x} should not be a collection tag"
);
assert!(!TapeNode::new(tag, 0).is_collection());
}
}
#[test]
fn push_returns_sequential_indices_and_reads_back() {
let mut builder = RespTapeMut::default();
assert_eq!(0, builder.node_count());
assert!(builder.is_empty());
let nodes = [
(ARRAY_TAG, 4u64),
(TAPE_LEN_TAG, 2),
(BULK_STRING_TAG, 100),
(INTEGER_TAG, 200),
];
for (i, &(tag, payload)) in nodes.iter().enumerate() {
assert_eq!(i, builder.push(tag, payload));
assert_eq!(i + 1, builder.node_count());
}
let tape = builder.split_freeze();
assert_eq!(nodes.len(), tape.node_count());
assert_eq!(nodes.len() * TAPE_NODE_SIZE, tape.byte_len());
assert!(builder.is_empty());
for (i, &(tag, payload)) in nodes.iter().enumerate() {
let node = tape.node(i);
assert_eq!(tag, node.tag());
assert_eq!(payload, node.payload());
}
}
#[test]
fn patch_overwrites_payload_and_keeps_tag() {
let mut builder = RespTapeMut::default();
let head = builder.push(ARRAY_TAG, 0);
builder.push(TAPE_LEN_TAG, 3);
builder.patch(head, ARRAY_TAG, 42);
let tape = builder.split_freeze();
let node = tape.node(head);
assert_eq!(ARRAY_TAG, node.tag());
assert_eq!(42, node.payload());
let len_node = tape.node(head + 1);
assert_eq!(TAPE_LEN_TAG, len_node.tag());
assert_eq!(3, len_node.payload());
}
#[test]
fn patch_can_change_the_tag_too() {
let mut builder = RespTapeMut::default();
let idx = builder.push(ARRAY_TAG, 1);
builder.patch(idx, MAP_TAG, 9);
let node = builder.split_freeze().node(idx);
assert_eq!(MAP_TAG, node.tag());
assert_eq!(9, node.payload());
}
#[test]
fn manual_flat_collection_layout_walks_correctly() {
let mut builder = RespTapeMut::default();
let head = builder.push(ARRAY_TAG, 0);
builder.push(TAPE_LEN_TAG, 2);
builder.push(INTEGER_TAG, 4);
builder.push(INTEGER_TAG, 9);
let next = builder.node_count() as u64;
builder.patch(head, ARRAY_TAG, next);
let tape = builder.split_freeze();
let head_node = tape.node(head);
assert!(head_node.is_collection());
assert_eq!(4, head_node.payload());
assert_eq!(2, tape.node(head + 1).payload());
assert_eq!(Some(2), tape.collection_len(head));
assert!(!tape.node(head + 2).is_collection());
assert!(!tape.node(head + 3).is_collection());
}
#[test]
fn collection_len_reports_unreadable_rather_than_panicking() {
let mut builder = RespTapeMut::default();
builder.push(ARRAY_TAG, 0);
let tape = builder.split_freeze();
assert_eq!(None, tape.collection_len(0));
assert_eq!(None, RespTape::default().collection_len(0));
}