#![warn(rust_2018_idioms)]
use core::convert::Infallible;
use smol_bytes::{
Buf, Buffer, Bytes, BytesMut, INLINE_CAP, TryPutError, Utf8Buffer, Utf8Bytes, Utf8BytesMut,
Utf8Error, compact,
};
const LATIN_1: &str = "café"; const BMP: &str = "euro€"; const EMOJI: &str = "rust🦀"; const MIXED: &str = "a é € 🦀";
#[test]
fn utf8_buffer_from_multibyte_str() {
let b = Utf8Buffer::from(LATIN_1);
assert_eq!(b.as_str(), LATIN_1);
assert_eq!(b.len(), LATIN_1.len());
let b = Utf8Buffer::from(BMP);
assert_eq!(b.as_str(), BMP);
let b = Utf8Buffer::from(EMOJI);
assert_eq!(b.as_str(), EMOJI);
let b = Utf8Buffer::from(MIXED);
assert_eq!(b.as_str(), MIXED);
}
#[test]
fn utf8_buffer_from_str_respects_inline_capacity() {
let multibyte: Utf8Buffer = MIXED.parse().expect("multibyte string parses");
assert_eq!(multibyte.as_str(), MIXED);
let maximum = "x".repeat(INLINE_CAP);
let parsed: Utf8Buffer = maximum.parse().expect("inline string parses");
assert_eq!(parsed.as_str(), maximum);
let oversized = "x".repeat(INLINE_CAP + 1);
assert_eq!(
oversized.parse::<Utf8Buffer>(),
Err(TryPutError {
requested: INLINE_CAP + 1,
available: INLINE_CAP,
})
);
}
#[test]
fn utf8_buffer_push_multibyte_chars() {
let mut b = Utf8Buffer::new();
b.push('a');
b.push('é');
b.push('€');
b.push('🦀');
assert_eq!(b.as_str(), "aé€🦀");
assert_eq!(b.len(), 1 + 2 + 3 + 4);
}
#[test]
fn utf8_buffer_split_to_on_char_boundary() {
let mut b = Utf8Buffer::from("café");
let head = b.split_to(3);
assert_eq!(head.as_str(), "caf");
assert_eq!(b.as_str(), "é");
}
#[test]
#[should_panic]
fn utf8_buffer_split_to_in_middle_of_multibyte_char_panics() {
let mut b = Utf8Buffer::from("café");
let _ = b.split_to(4);
}
#[test]
fn utf8_buffer_try_split_to_mid_char_returns_err() {
let mut b = Utf8Buffer::from("café");
let r = b.try_split_to(4);
assert_eq!(r, Err(Utf8Error::InvalidCharBoundary { at: 4 }));
assert_eq!(b.as_str(), "café");
}
#[test]
fn utf8_buffer_try_split_off_out_of_bounds() {
let mut b = Utf8Buffer::from("hi");
let r = b.try_split_off(99);
assert!(matches!(r, Err(Utf8Error::OutOfBounds { at: 99, len: 2 })));
}
#[test]
fn utf8_buffer_slice_respects_char_boundaries() {
let b = Utf8Buffer::from("café euro");
let s = b.slice(3..5); assert_eq!(s.as_str(), "é");
}
#[test]
#[should_panic]
fn utf8_buffer_slice_mid_char_panics() {
let b = Utf8Buffer::from("café");
let _ = b.slice(3..4); }
#[test]
fn utf8_buffer_try_slice_mid_char_returns_err() {
let b = Utf8Buffer::from("café");
assert!(b.try_slice(3..4).is_err());
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
fn utf8_fallible_slices_reject_reversed_and_overflowing_ranges() {
let inline = Utf8Buffer::from("hello");
assert!(inline.try_slice(4..2).is_err());
assert!(inline.try_slice(..=usize::MAX).is_err());
let immutable = Utf8Bytes::from("a".repeat(100));
assert!(immutable.try_slice(80..20).is_err());
assert!(immutable.try_slice(..=usize::MAX).is_err());
let mutable = Utf8BytesMut::from("hello");
assert!(mutable.try_slice(4..2).is_err());
assert!(mutable.try_slice(..=usize::MAX).is_err());
}
#[test]
fn utf8_wrappers_forward_visible_lengths_and_split_bounds() {
let mut inline = Buffer::from(*b"hello");
inline.advance(1);
let mut inline = Utf8Buffer::try_from(inline).unwrap();
assert_eq!(inline.len(), 4);
assert_eq!(
inline.try_split_to(5),
Err(Utf8Error::OutOfBounds { at: 5, len: 4 })
);
assert_eq!(inline.split_to(4).as_str(), "ello");
assert!(inline.is_empty());
let mut immutable = Bytes::from_static(b"hello");
immutable.advance(1);
let mut immutable = Utf8Bytes::try_from(immutable).unwrap();
assert_eq!(immutable.len(), 4);
assert_eq!(
immutable.try_split_to(5),
Err(Utf8Error::OutOfBounds { at: 5, len: 4 })
);
assert_eq!(immutable.split_to(4).as_str(), "ello");
assert!(immutable.is_empty());
let mut inline_mut = BytesMut::from(&b"hello"[..]);
inline_mut.advance(1);
let mut inline_mut = Utf8BytesMut::try_from(inline_mut).unwrap();
assert_eq!(inline_mut.len(), 4);
assert_eq!(
inline_mut.try_split_to(5),
Err(Utf8Error::OutOfBounds { at: 5, len: 4 })
);
assert_eq!(inline_mut.split_to(4).as_str(), "ello");
assert!(inline_mut.is_empty());
let mut heap_mut = BytesMut::with_capacity(128);
heap_mut.extend_from_slice(b"hello");
heap_mut.advance(1);
let heap_mut = Utf8BytesMut::try_from(heap_mut).unwrap();
assert_eq!(heap_mut.len(), 4);
assert_eq!(heap_mut.as_str(), "ello");
}
#[test]
fn utf8_buffer_try_push_overflow() {
let mut b = Utf8Buffer::from("a".repeat(60).as_str());
assert!(b.try_push('🦀').is_err());
assert_eq!(b.as_str(), "a".repeat(60).as_str());
}
#[test]
#[should_panic]
fn utf8_buffer_push_overflow_panics() {
let mut b = Utf8Buffer::from("a".repeat(60).as_str());
b.push('🦀');
}
#[test]
fn utf8_buffer_try_from_bytes() {
let valid: &[u8] = "hello".as_bytes();
let b = Utf8Buffer::try_from(valid).unwrap();
assert_eq!(b.as_str(), "hello");
let invalid_utf8: &[u8] = &[0xff, 0xfe];
assert!(Utf8Buffer::try_from(invalid_utf8).is_err());
let too_large: Vec<u8> = "a".repeat(100).into_bytes();
assert!(Utf8Buffer::try_from(too_large.as_slice()).is_err());
}
#[test]
fn utf8_buffer_roundtrip_via_string() {
let original = Utf8Buffer::from(MIXED);
let s: String = String::from(original);
assert_eq!(s, MIXED);
let back = Utf8Buffer::try_from_str(&s).unwrap();
assert_eq!(back.as_str(), MIXED);
}
#[test]
fn utf8_buffer_try_truncate_checks_boundaries_and_preserves_errors() {
let mut value = Utf8Buffer::from("éx");
assert_eq!(
value.try_truncate(1),
Err(Utf8Error::InvalidCharBoundary { at: 1 })
);
assert_eq!(value.as_str(), "éx");
value.try_truncate(2).unwrap();
assert_eq!(value.as_str(), "é");
value.try_truncate(99).unwrap();
assert_eq!(value.as_str(), "é");
}
#[test]
fn utf8_bytes_from_multibyte_str() {
let b = Utf8Bytes::from(MIXED);
assert_eq!(b.as_str(), MIXED);
}
#[test]
fn heap_capable_utf8_types_parse_infallibly_and_preserve_multibyte_text() {
let value = "café € 🦀";
let shared: Result<Utf8Bytes, Infallible> = value.parse();
assert_eq!(shared.expect("infallible shared parse").as_str(), value);
let compact: Result<compact::Utf8Bytes, Infallible> = value.parse();
assert_eq!(compact.expect("infallible compact parse").as_str(), value);
let mutable: Result<Utf8BytesMut, Infallible> = value.parse();
assert_eq!(mutable.expect("infallible mutable parse").as_str(), value);
}
#[test]
fn utf8_bytes_inline_heap_boundary_ascii() {
let s62: String = "a".repeat(62);
let b = Utf8Bytes::from(s62.as_str());
assert!(b.is_inline());
assert_eq!(b.len(), 62);
let s63: String = "a".repeat(63);
let b = Utf8Bytes::from(s63.as_str());
assert!(b.is_heap());
assert_eq!(b.len(), 63);
}
#[test]
fn utf8_bytes_inline_heap_boundary_multibyte() {
let s = "€".repeat(30);
let b = Utf8Bytes::from(s.as_str());
assert!(b.is_heap());
assert_eq!(b.as_str(), s);
}
#[test]
fn utf8_bytes_split_to_on_char_boundary() {
let mut b = Utf8Bytes::from("café €uro");
let head = b.split_to(5); assert_eq!(head.as_str(), "café");
assert_eq!(b.as_str(), " €uro");
}
#[test]
#[should_panic]
fn utf8_bytes_split_off_mid_char_panics() {
let mut b = Utf8Bytes::from("café");
let _ = b.split_off(4);
}
#[test]
fn utf8_bytes_try_split_off_mid_char_errors() {
let mut b = Utf8Bytes::from("café");
assert!(b.try_split_off(4).is_err());
assert_eq!(b.as_str(), "café");
}
#[test]
fn utf8_bytes_slice_multibyte() {
let b = Utf8Bytes::from("hello café world");
let s = b.slice(6..11); assert_eq!(s.as_str(), "café");
}
#[test]
fn utf8_bytes_slice_mid_char_panics_not_silent() {
let b = Utf8Bytes::from("café");
assert!(b.try_slice(0..4).is_err()); assert!(b.try_slice(3..5).is_ok()); }
#[test]
fn utf8_bytes_try_from_bytes() {
let valid: &[u8] = "hello world".as_bytes();
let b = Utf8Bytes::try_from(valid).unwrap();
assert_eq!(b.as_str(), "hello world");
let invalid: &[u8] = &[0xff, 0xfe];
assert!(Utf8Bytes::try_from(invalid).is_err());
}
#[test]
fn utf8_bytes_from_static() {
let b = Utf8Bytes::from_static("static string");
assert_eq!(b.as_str(), "static string");
}
#[test]
fn utf8_bytes_roundtrip_via_string() {
let original = Utf8Bytes::from(MIXED);
let s: String = String::from(original);
let back = Utf8Bytes::from(s.as_str());
assert_eq!(back.as_str(), MIXED);
}
#[test]
fn utf8_bytes_clone_shares_storage() {
let b = Utf8Bytes::from("a".repeat(100).as_str()); let c = b.clone();
assert_eq!(b.as_str(), c.as_str());
assert_eq!(b.len(), 100);
}
#[test]
fn utf8_bytes_mut_push_multibyte() {
let mut b = Utf8BytesMut::new();
b.push('a');
b.push('é');
b.push('€');
b.push('🦀');
assert_eq!(b.as_str(), "aé€🦀");
assert_eq!(b.len(), 10);
}
#[test]
fn utf8_bytes_mut_push_str_grows_past_inline() {
let mut b = Utf8BytesMut::new();
for _ in 0..30 {
b.push_str("ab");
}
assert_eq!(b.len(), 60);
assert!(b.is_inline());
b.push_str("cd"); assert_eq!(b.len(), 62);
b.push_str("ef"); assert_eq!(b.len(), 64);
assert!(b.is_heap());
}
#[test]
fn utf8_bytes_mut_try_truncate_and_freeze_preserve_representation() {
let mut inline = Utf8BytesMut::from("éx");
assert_eq!(
inline.try_truncate(1),
Err(Utf8Error::InvalidCharBoundary { at: 1 })
);
assert_eq!(inline.as_str(), "éx");
inline.try_truncate(2).unwrap();
let shared = inline.freeze_shared();
assert!(shared.is_inline());
assert_eq!(shared.as_str(), "é");
let mut heap = Utf8BytesMut::with_capacity(128);
heap.push_str("short");
let compact = heap.freeze_compact();
assert!(compact.is_heap());
assert_eq!(compact.as_str(), "short");
}
#[test]
fn utf8_bytes_mut_split_to_on_char_boundary() {
let mut b = Utf8BytesMut::from("café euro");
let head = b.split_to(5); assert_eq!(head.as_str(), "café");
assert_eq!(b.as_str(), " euro");
}
#[test]
#[should_panic]
fn utf8_bytes_mut_split_off_mid_char_panics() {
let mut b = Utf8BytesMut::from("café");
let _ = b.split_off(4);
}
#[test]
fn utf8_bytes_mut_try_split_off_mid_char_errors() {
let mut b = Utf8BytesMut::from("café");
assert!(b.try_split_off(4).is_err());
assert_eq!(b.as_str(), "café");
}
#[test]
fn utf8_bytes_mut_slice_multibyte() {
let b = Utf8BytesMut::from("hello €uro");
let s = b.slice(6..9); assert_eq!(s.as_str(), "€");
}
#[test]
fn utf8_bytes_mut_split_and_unsplit_heap() {
let mut b = Utf8BytesMut::with_capacity(128);
b.push_str("hello café");
let tail = b.split_off(6); assert_eq!(b.as_str(), "hello ");
assert_eq!(tail.as_str(), "café");
assert!(b.unsplit(tail).is_none());
assert_eq!(b.as_str(), "hello café");
}
#[test]
fn utf8_bytes_mut_try_from_bytes() {
let valid: &[u8] = "hello world".as_bytes();
let b = Utf8BytesMut::try_from(valid).unwrap();
assert_eq!(b.as_str(), "hello world");
let invalid: &[u8] = &[0xff];
assert!(Utf8BytesMut::try_from(invalid).is_err());
}
#[test]
fn utf8_bytes_mut_clear() {
let mut b = Utf8BytesMut::from("café");
b.clear();
assert!(b.is_empty());
assert_eq!(b.as_str(), "");
}
#[test]
fn utf8_bytes_mut_inline_heap_boundary_with_multibyte() {
let s = "🦀".repeat(20);
let b = Utf8BytesMut::from(s.as_str());
assert_eq!(b.len(), 80);
assert!(b.is_heap());
assert_eq!(b.as_str(), s);
}
#[test]
fn cross_type_utf8_buffer_to_utf8_bytes_via_string() {
let a = Utf8Buffer::from("café");
let s: String = a.into();
let b = Utf8Bytes::from(s.as_str());
assert_eq!(b.as_str(), "café");
}
#[test]
fn cross_type_utf8_bytes_to_bytes_is_zero_validation() {
use smol_bytes::Bytes;
let a = Utf8Bytes::from("hello");
let b: Bytes = a.into();
assert_eq!(b.as_ref(), b"hello");
}
#[test]
fn cross_type_utf8_bytes_mut_to_bytes_mut() {
use smol_bytes::BytesMut;
let a = Utf8BytesMut::from("hello");
let b: BytesMut = a.into();
assert_eq!(b.as_slice(), b"hello");
}
#[test]
fn try_from_buffer_with_invalid_utf8() {
use smol_bytes::Buffer;
let buf = Buffer::try_from(&[0xffu8, 0xfe][..]).unwrap();
assert!(Utf8Buffer::try_from(buf).is_err());
}
#[test]
fn cross_type_eq_all_pairs() {
let a = Utf8Buffer::from("café");
let b = Utf8Bytes::from("café");
let c = Utf8BytesMut::from("café");
assert_eq!(a, b);
assert_eq!(b, a);
assert_eq!(a, c);
assert_eq!(c, a);
assert_eq!(b, c);
assert_eq!(c, b);
let d = Utf8Buffer::from("different");
assert_ne!(d, b);
assert_ne!(d, c);
}