use lean_string::LeanString;
use std::borrow::Cow;
const INLINE_LIMIT: usize = size_of::<LeanString>();
#[cfg(target_pointer_width = "32")]
const MIN_CAPACITY_FOR_HEAP_LENGTH_LAYOUT_ON_32BIT: usize = (1 << 24) - 1;
#[test]
fn new_empty() {
assert_eq!(LeanString::new(), "");
let s = LeanString::new();
assert_eq!(s.as_str(), "");
assert!(s.is_empty());
assert_eq!(s.len(), 0);
assert!(!s.is_heap_allocated());
assert_eq!(s.capacity(), INLINE_LIMIT);
assert_eq!(LeanString::default(), "");
}
#[test]
fn with_capacity_on_inline() {
for c in 0..=INLINE_LIMIT {
let s = LeanString::with_capacity(c);
assert_eq!(s.len(), 0);
assert_eq!(s.capacity(), INLINE_LIMIT);
assert!(!s.is_heap_allocated());
}
}
#[test]
fn with_capacity_on_heap() {
let s = LeanString::with_capacity(INLINE_LIMIT + 1);
assert_eq!(s.len(), 0);
assert!(s.capacity() >= (INLINE_LIMIT + 1));
assert!(s.is_heap_allocated());
#[cfg(target_pointer_width = "32")]
for d in -1..=1 {
let capacity = (MIN_CAPACITY_FOR_HEAP_LENGTH_LAYOUT_ON_32BIT as isize + d) as usize;
let s = LeanString::with_capacity(capacity);
assert_eq!(s.len(), 0);
assert!(s.capacity() >= capacity);
assert!(s.is_heap_allocated());
}
}
#[test]
fn new_from_char() {
assert_eq!(LeanString::from('a'), "a");
assert_eq!(LeanString::from('👍'), "👍");
assert_eq!(LeanString::from(''), "");
}
#[test]
fn from_char_utf8_length_boundaries() {
let chars = [
'\0',
'a',
'\u{7F}',
'\u{80}',
'é',
'\u{7FF}',
'\u{800}',
'あ',
'\u{FFFF}',
'\u{10000}',
'🦀',
'\u{10FFFF}',
];
for ch in chars {
let expected = String::from(ch);
let s = LeanString::from(ch);
assert_eq!(s, expected);
assert_eq!(s.len(), ch.len_utf8());
assert!(!s.is_heap_allocated());
let mut pushed = s.clone();
pushed.push('x');
assert_eq!(pushed, expected + "x");
}
}
#[test]
fn from_around_inline_limit() {
let s = &String::from("0123456789abcdefg");
let inline = LeanString::from(&s[..INLINE_LIMIT - 1]);
assert_eq!(inline, s[..INLINE_LIMIT - 1]);
assert!(!inline.is_heap_allocated());
assert_eq!(inline.capacity(), INLINE_LIMIT);
let inline = LeanString::from(&s[..INLINE_LIMIT]);
assert_eq!(inline, s[..INLINE_LIMIT]);
assert!(!inline.is_heap_allocated());
assert_eq!(inline.capacity(), INLINE_LIMIT);
let heap = LeanString::from(&s[..INLINE_LIMIT + 1]);
assert_eq!(heap, s[..INLINE_LIMIT + 1]);
assert!(heap.is_heap_allocated());
assert_eq!(heap.capacity(), INLINE_LIMIT + 1);
}
#[test]
fn from_every_inline_length() {
let s = &String::from("0123456789abcdefg");
for len in 0..=INLINE_LIMIT {
let inline = LeanString::from(&s[..len]);
assert_eq!(inline, s[..len], "len: {len}");
assert_eq!(inline.len(), len, "len: {len}");
assert!(!inline.is_heap_allocated(), "len: {len}");
}
}
#[test]
fn from_static_str_around_inline_limit() {
let s: &'static str = "0123456789abcdefg";
let inline = LeanString::from_static_str(&s[..INLINE_LIMIT - 1]);
assert_eq!(inline, s[..INLINE_LIMIT - 1]);
assert!(!inline.is_heap_allocated());
assert_eq!(inline.capacity(), INLINE_LIMIT);
let inline = LeanString::from_static_str(&s[..INLINE_LIMIT]);
assert_eq!(inline, s[..INLINE_LIMIT]);
assert!(!inline.is_heap_allocated());
assert_eq!(inline.capacity(), INLINE_LIMIT);
let static_ = LeanString::from_static_str(&s[..INLINE_LIMIT + 1]);
assert_eq!(static_, s[..INLINE_LIMIT + 1]);
assert!(!static_.is_heap_allocated());
assert_eq!(static_.capacity(), INLINE_LIMIT + 1);
}
#[test]
fn into_cow_borrows_static() {
let s = LeanString::from_static_str("A static str that is longer than inline limit");
assert!(matches!(Cow::from(s), Cow::Borrowed(_)));
}
#[test]
fn reserve_on_inline() {
for c in 0..=INLINE_LIMIT {
let mut s = LeanString::new();
s.reserve(c);
assert_eq!(s.len(), 0);
assert_eq!(s.capacity(), INLINE_LIMIT);
assert!(!s.is_heap_allocated());
}
}
#[test]
fn reserve_on_heap() {
let mut s = LeanString::new();
s.reserve(INLINE_LIMIT + 1);
assert_eq!(s.len(), 0);
assert!(s.capacity() >= (INLINE_LIMIT + 1));
assert!(s.is_heap_allocated());
#[cfg(target_pointer_width = "32")]
{
for d in -1..=1 {
let capacity = (MIN_CAPACITY_FOR_HEAP_LENGTH_LAYOUT_ON_32BIT as isize + d) as usize;
let mut s = LeanString::new();
s.reserve(capacity);
assert_eq!(s.len(), 0);
assert!(s.capacity() >= capacity);
assert!(s.is_heap_allocated());
}
let mut s = LeanString::new();
s.reserve(MIN_CAPACITY_FOR_HEAP_LENGTH_LAYOUT_ON_32BIT);
assert_eq!(s.len(), 0);
assert!(s.capacity() >= MIN_CAPACITY_FOR_HEAP_LENGTH_LAYOUT_ON_32BIT);
assert!(s.is_heap_allocated());
s.reserve(MIN_CAPACITY_FOR_HEAP_LENGTH_LAYOUT_ON_32BIT + 1);
assert_eq!(s.len(), 0);
assert!(s.capacity() >= (MIN_CAPACITY_FOR_HEAP_LENGTH_LAYOUT_ON_32BIT + 1));
assert!(s.is_heap_allocated());
}
}
#[test]
fn reserve_exact_on_inline() {
for c in 0..=INLINE_LIMIT {
let mut s = LeanString::new();
s.reserve_exact(c);
assert_eq!(s.len(), 0);
assert_eq!(s.capacity(), INLINE_LIMIT);
assert!(!s.is_heap_allocated());
}
let mut s = LeanString::from("abc");
s.reserve_exact(INLINE_LIMIT);
assert_eq!(s, "abc");
assert_eq!(s.capacity(), 3 + INLINE_LIMIT);
assert!(s.is_heap_allocated());
}
#[test]
fn reserve_exact_on_heap() {
let text = "a".repeat(100);
let mut unique = LeanString::from(text.as_str());
unique.reserve_exact(1);
assert_eq!(unique, text);
assert_eq!(unique.capacity(), 101);
unique.reserve_exact(1);
assert_eq!(unique.capacity(), 101);
let mut shared = LeanString::from(text.as_str());
let original = shared.clone();
shared.reserve_exact(1);
assert_eq!(shared, text);
assert_eq!(shared.capacity(), 101);
assert_ne!(shared.as_ptr(), original.as_ptr());
assert_eq!(original, text);
assert_eq!(original.capacity(), 100);
}
#[test]
fn reserve_exact_on_static() {
let text = "a static text that is longer than the inline limit";
let mut long = LeanString::from_static_str(text);
long.reserve_exact(1);
assert_eq!(long, text);
assert_eq!(long.capacity(), text.len() + 1);
assert!(long.is_heap_allocated());
let mut short = LeanString::from_static_str(text);
short.truncate(3);
short.reserve_exact(1);
assert_eq!(short, "a s");
assert_eq!(short.capacity(), INLINE_LIMIT);
assert!(!short.is_heap_allocated());
}
#[test]
fn modifying_short_shared_heap_moves_to_inline() {
let text = "a text that is longer than the inline limit";
let mut removed = LeanString::from(text);
let _shared = removed.clone();
removed.truncate(3);
removed.remove(0);
assert_eq!(removed, " t");
assert!(!removed.is_heap_allocated());
let mut reserved = LeanString::from(text);
let _shared = reserved.clone();
reserved.truncate(3);
reserved.reserve(1);
assert_eq!(reserved, "a t");
assert!(!reserved.is_heap_allocated());
}
#[test]
fn shrink_to_inline_buffer() {
let mut inline = LeanString::from("Hello");
assert!(!inline.is_heap_allocated());
assert_eq!(inline.capacity(), INLINE_LIMIT);
inline.shrink_to(100);
assert_eq!(inline, "Hello");
assert_eq!(inline.capacity(), INLINE_LIMIT);
assert!(!inline.is_heap_allocated());
inline.shrink_to(3);
assert_eq!(inline, "Hello");
assert_eq!(inline.capacity(), INLINE_LIMIT);
assert!(!inline.is_heap_allocated());
}
#[test]
fn shrink_to_static_buffer() {
let text: &'static str = "A static str that is longer than inline limit";
let mut static_ = LeanString::from_static_str(text);
assert!(!static_.is_heap_allocated());
static_.shrink_to(30);
assert_eq!(static_.capacity(), text.len());
assert!(!static_.is_heap_allocated());
static_.shrink_to(10);
assert_eq!(static_.capacity(), text.len());
assert!(!static_.is_heap_allocated());
}
#[test]
fn shrink_to_heap_buffer() {
let text = "A heap allocated string that is longer than inline limit";
let mut heap = LeanString::from(text);
assert!(heap.is_heap_allocated());
let original_capacity = heap.capacity();
heap.shrink_to(100);
assert_eq!(heap.capacity(), original_capacity);
assert_eq!(heap, text);
assert!(heap.is_heap_allocated());
heap.shrink_to(30);
assert_eq!(heap.capacity(), original_capacity);
assert_eq!(heap, text);
assert!(heap.is_heap_allocated());
heap.shrink_to(0);
assert_eq!(heap.capacity(), heap.len());
assert_eq!(heap, text);
assert!(heap.is_heap_allocated());
}
#[test]
fn shrink_to_heap_to_inline() {
let mut heap = LeanString::with_capacity(100);
heap.push_str("Hello");
assert!(heap.is_heap_allocated());
assert_eq!(heap.capacity(), 100);
assert!(heap.len() < INLINE_LIMIT);
heap.shrink_to(100);
assert_eq!(heap.capacity(), 100);
assert_eq!(heap, "Hello");
heap.shrink_to(120);
assert_eq!(heap.capacity(), 100);
heap.shrink_to(50);
assert_eq!(heap.capacity(), 50);
assert_eq!(heap, "Hello");
assert!(heap.is_heap_allocated());
heap.shrink_to(INLINE_LIMIT);
assert_eq!(heap.capacity(), INLINE_LIMIT);
assert_eq!(heap, "Hello");
assert!(!heap.is_heap_allocated());
heap.shrink_to(0);
assert_eq!(heap.capacity(), INLINE_LIMIT);
assert_eq!(heap, "Hello");
assert!(!heap.is_heap_allocated());
}
#[test]
fn shrink_to_cow_shared_buffer() {
let mut heap1 = LeanString::with_capacity(100);
heap1.push_str("Shared");
let mut heap2 = heap1.clone();
assert_eq!(heap1.as_ptr(), heap2.as_ptr());
heap1.shrink_to(50);
assert_eq!(heap1.capacity(), 50);
assert_eq!(heap1, "Shared");
assert_eq!(heap2.capacity(), 100);
assert_eq!(heap2, "Shared");
assert_ne!(heap1.as_ptr(), heap2.as_ptr());
let heap3 = heap2.clone();
assert_eq!(heap2.as_ptr(), heap3.as_ptr());
heap2.shrink_to(0); assert_eq!(heap2.capacity(), INLINE_LIMIT);
assert_eq!(heap2, "Shared");
assert!(!heap2.is_heap_allocated());
assert_eq!(heap3.capacity(), 100);
assert_eq!(heap3, "Shared");
assert!(heap3.is_heap_allocated());
assert_ne!(heap2.as_ptr(), heap3.as_ptr());
}
#[test]
fn shrink_cow_shared_buffer_to_exact_capacity() {
let text = "a".repeat(100);
let mut shrink_to = LeanString::from(text.as_str());
shrink_to.reserve(50);
assert_eq!(shrink_to.capacity(), 150);
let mut shrink_to_fit = shrink_to.clone();
let original = shrink_to.clone();
shrink_to.shrink_to(120);
assert_eq!(shrink_to, text);
assert_eq!(shrink_to.capacity(), 120);
shrink_to_fit.shrink_to_fit();
assert_eq!(shrink_to_fit, text);
assert_eq!(shrink_to_fit.capacity(), 100);
assert_eq!(original, text);
assert_eq!(original.capacity(), 150);
}
#[test]
fn push_cow() {
let mut s = LeanString::new();
s.push('a');
s.push('b');
s.push_str("cdefgh");
assert_eq!(s, "abcdefgh");
assert_eq!(s.len(), 8);
s.push_str("12345678");
assert_eq!(s.len(), 16);
assert_eq!(s, "abcdefgh12345678");
let mut s1 = s.clone();
assert_eq!(s1, "abcdefgh12345678");
s1.push('0');
assert_eq!(s1, "abcdefgh123456780");
assert_eq!(s1.len(), 17);
let mut s2 = s.clone();
s2.push_str("90");
assert_eq!(s2, "abcdefgh1234567890");
assert_eq!(s2.len(), 18);
assert_eq!(s.len(), 16);
s.push_str("90");
assert!(s.is_heap_allocated());
assert_eq!(s.len(), 18);
let mut s3 = s.clone();
s3.push('');
assert_eq!(s3, "abcdefgh1234567890");
assert_eq!(s3.len(), 21);
let mut s4 = s.clone();
s4.push_str("👍👍");
assert_eq!(s4.len(), 26);
assert_eq!(s4, "abcdefgh1234567890👍👍");
}
#[test]
fn push_to_static() {
let mut inline = LeanString::from_static_str("abcdefgh");
assert_eq!(inline, "abcdefgh");
assert_eq!(inline.len(), 8);
assert!(!inline.is_heap_allocated());
assert_eq!(inline.capacity(), INLINE_LIMIT);
inline.push_str("12345678");
assert_eq!(inline, "abcdefgh12345678");
assert_eq!(inline.len(), 16);
if cfg!(target_pointer_width = "64") {
assert!(!inline.is_heap_allocated());
assert_eq!(inline.capacity(), 16);
} else {
assert!(inline.capacity() >= 16);
}
inline.push_str("90");
assert_eq!(inline, "abcdefgh1234567890");
assert_eq!(inline.len(), 18);
assert!(inline.is_heap_allocated());
let mut static_ = LeanString::from_static_str("abcdefghijklmnopqrstuvwxyz");
assert_eq!(static_, "abcdefghijklmnopqrstuvwxyz");
assert_eq!(static_.len(), 26);
assert!(!static_.is_heap_allocated());
static_.push('0');
assert_eq!(static_, "abcdefghijklmnopqrstuvwxyz0");
assert_eq!(static_.len(), 27);
assert!(static_.is_heap_allocated());
}
#[test]
fn pop_keep_capacity() {
let mut inline = LeanString::from("Hello!");
assert_eq!(inline.pop(), Some('!'));
assert_eq!(inline, "Hello");
assert_eq!(inline.len(), 5);
for _ in 0..4 {
inline.pop();
}
assert_eq!(inline, "H");
assert_eq!(inline.pop(), Some('H'));
assert_eq!(inline, "");
assert!(inline.is_empty());
assert_eq!(inline.capacity(), INLINE_LIMIT);
let mut heap = LeanString::from("abcdefghijklmnopqrstuvwxyz");
assert_eq!(heap.pop(), Some('z'));
assert_eq!(heap, "abcdefghijklmnopqrstuvwxy");
assert_eq!(heap.len(), 25);
for _ in 0..24 {
heap.pop();
}
assert_eq!(heap, "a");
assert_eq!(heap.pop(), Some('a'));
assert_eq!(heap, "");
assert!(heap.is_empty());
assert_eq!(heap.capacity(), 26);
}
#[test]
fn pop_share_buffer() {
let mut s = LeanString::from("abcdefgh");
assert_eq!(s.pop(), Some('h'));
assert_eq!(s.len(), 7);
let mut s1 = s.clone();
assert_eq!(s1.pop(), Some('g'));
assert_eq!(s1, "abcdef");
assert_eq!(s1.len(), 6);
assert_eq!(s, "abcdefg");
s.push_str("hijklmnopqrstuvwxyz");
let mut s2 = s.clone();
assert_eq!(s.as_ptr(), s2.as_ptr());
assert_eq!(s2.pop(), Some('z'));
assert_eq!(s2.len(), 25);
assert_eq!(s, "abcdefghijklmnopqrstuvwxyz");
assert_eq!(s.as_ptr(), s2.as_ptr());
s2.push('0');
assert_ne!(s.as_ptr(), s2.as_ptr());
}
#[test]
fn pop_from_static() {
let mut static_ = LeanString::from_static_str("abcdefghijklmnopqrstuvwxyz");
assert_eq!(static_.len(), 26);
assert_eq!(static_.pop(), Some('z'));
assert_eq!(static_, "abcdefghijklmnopqrstuvwxy");
assert_eq!(static_.len(), 25);
assert_eq!(static_.capacity(), static_.len());
assert!(!static_.is_heap_allocated());
}
#[test]
fn pop_from_static_cow() {
let mut static1 = LeanString::from_static_str("0123456789abcdef!");
assert_eq!(static1.pop(), Some('!'));
let static2 = static1.clone();
assert_eq!(static1.pop(), Some('f'));
assert_eq!(static1, "0123456789abcde");
assert_eq!(static1.capacity(), static1.len());
assert!(!static1.is_heap_allocated());
assert_eq!(static2, "0123456789abcdef");
assert_eq!(static2.capacity(), static2.len());
assert!(!static2.is_heap_allocated());
assert_eq!(static1.as_ptr(), static2.as_ptr());
}
#[test]
fn pop_from_empty() {
let mut inline = LeanString::new();
assert_eq!(inline, "");
assert_eq!(inline.pop(), None);
assert_eq!(inline, "");
let mut heap = LeanString::from("a".repeat(INLINE_LIMIT + 1));
for _ in 0..INLINE_LIMIT + 1 {
heap.pop();
}
assert_eq!(inline, "");
assert_eq!(heap.pop(), None);
assert_eq!(heap, "");
let mut static_ = LeanString::from_static_str("");
assert_eq!(static_.pop(), None);
assert_eq!(static_, "");
}
#[test]
fn remove_cow() {
let mut inline = LeanString::from("Hello");
assert_eq!(inline.remove(4), 'o');
assert_eq!(inline.remove(0), 'H');
assert_eq!(inline, "ell");
let mut heap = LeanString::from("abcdefghijklmnopqrstuvwxyz");
assert_eq!(heap.remove(0), 'a');
let cloned = heap.clone();
assert_eq!(heap.as_ptr(), cloned.as_ptr());
assert_eq!(heap.remove(24), 'z');
assert_eq!(heap, "bcdefghijklmnopqrstuvwxy");
assert_eq!(cloned, "bcdefghijklmnopqrstuvwxyz");
}
#[test]
#[should_panic(expected = "index out of bounds (index: 12, len: 12)")]
fn remove_fail() {
let mut s = LeanString::from("Hello World!");
assert_eq!(s.len(), 12);
s.remove(12);
}
#[test]
fn retain_f_apply_count() {
let mut inline = LeanString::from("012");
let mut count = 0;
inline.retain(|_| {
count += 1;
true
});
assert_eq!(count, 3);
let mut heap = LeanString::from("abcdefghijklmnopqrstuvwxyz");
let mut count = 0;
heap.retain(|_| {
count += 1;
true
});
assert_eq!(count, 26);
let mut shared = LeanString::from("abcdefghijklmnopqrstuvwxyz");
let _cloned = shared.clone();
let mut count = 0;
shared.retain(|c| {
count += 1;
c != 'm'
});
assert_eq!(count, 26);
assert_eq!(shared, "abcdefghijklnopqrstuvwxyz");
}
#[test]
fn retain_shared_matches_string() {
let text = "αbcdefghijklmnopqrstuvwxyζ";
for removed in ['α', 'm', 'ζ'] {
let mut expected = String::from(text);
expected.retain(|c| c != removed);
let mut shared = LeanString::from(text);
let cloned = shared.clone();
shared.retain(|c| c != removed);
assert_eq!(shared, expected, "removing {removed:?}");
assert_eq!(cloned, text, "removing {removed:?}");
}
}
#[test]
fn retain_cow() {
let mut heap = LeanString::from("qwer tyui opas dfgh jklz xcvb nm");
let cloned = heap.clone();
assert_eq!(heap.as_ptr(), cloned.as_ptr());
heap.retain(|c| c.is_alphabetic());
assert_eq!(heap, "qwertyuiopasdfghjklzxcvbnm");
assert_eq!(cloned, "qwer tyui opas dfgh jklz xcvb nm");
let mut static_ = LeanString::from_static_str("aBcDeFgHiJkLmNoPqRsTuVwXyZ");
let cloned = static_.clone();
static_.retain(|c| c.is_lowercase());
assert!(!cloned.is_heap_allocated());
assert_eq!(static_, "acegikmoqsuwy");
assert_eq!(cloned, "aBcDeFgHiJkLmNoPqRsTuVwXyZ");
}
#[test]
fn insert() {
let mut s = LeanString::from("01234");
s.insert(3, 'a');
assert_eq!(s, "012a34");
assert_eq!(s.len(), 6);
assert_eq!(s.capacity(), INLINE_LIMIT);
s.insert(0, 'b');
assert_eq!(s, "b012a34");
assert_eq!(s.len(), 7);
assert_eq!(s.capacity(), INLINE_LIMIT);
s.insert(7, 'c');
assert_eq!(s, "b012a34c");
assert_eq!(s.len(), 8);
assert_eq!(s.capacity(), INLINE_LIMIT);
s.insert_str(8, "12345678");
assert_eq!(s, "b012a34c12345678");
assert_eq!(s.len(), 16);
if cfg!(target_pointer_width = "64") {
assert_eq!(s.capacity(), INLINE_LIMIT);
assert!(!s.is_heap_allocated());
}
s.insert_str(0, "ABCDEFGH");
assert_eq!(s, "ABCDEFGHb012a34c12345678");
s.insert(20, '.');
assert_eq!(s, "ABCDEFGHb012a34c1234.5678");
}
#[test]
fn insert_to_static() {
let mut static_ = LeanString::from_static_str("01234567890123456789");
let cloned = static_.clone();
static_.insert(10, 'a');
assert_eq!(static_, "0123456789a0123456789");
assert!(static_.is_heap_allocated());
assert_eq!(cloned, "01234567890123456789");
assert!(!cloned.is_heap_allocated());
}
#[test]
#[should_panic(expected = "index is not a char boundary or out of bounds (index: 7)")]
fn insert_fail() {
let mut s = LeanString::from("012345");
s.insert(7, 'a');
}
#[test]
fn extend_from_within_in_place() {
let mut inline = LeanString::from("abc");
inline.extend_from_within(1..);
assert_eq!(inline, "abcbc");
assert!(!inline.is_heap_allocated());
let mut heap = LeanString::with_capacity(100);
heap.push_str("qwer tyui opas dfgh jklz xcvb nm");
let ptr = heap.as_ptr();
heap.extend_from_within(5..=8);
assert_eq!(heap, "qwer tyui opas dfgh jklz xcvb nmtyui");
assert_eq!(heap.as_ptr(), ptr);
}
#[test]
fn extend_from_within_inline_to_heap() {
let text = &"0123456789abcdef"[..INLINE_LIMIT];
let mut s = LeanString::from(text);
assert!(!s.is_heap_allocated());
s.extend_from_within(1..3);
assert_eq!(s, format!("{text}12"));
assert!(s.is_heap_allocated());
}
#[test]
fn extend_from_within_reallocate() {
let mut s = LeanString::from("qwer tyui opas dfgh jklz xcvb nm");
s.shrink_to_fit();
let capacity = s.capacity();
s.extend_from_within(..);
assert_eq!(s, "qwer tyui opas dfgh jklz xcvb nmqwer tyui opas dfgh jklz xcvb nm");
assert!(s.capacity() > capacity);
}
#[test]
fn extend_from_within_cow() {
let mut heap = LeanString::from("qwer tyui opas dfgh jklz xcvb nm");
let cloned = heap.clone();
heap.extend_from_within(..4);
assert_eq!(heap, "qwer tyui opas dfgh jklz xcvb nmqwer");
assert_eq!(cloned, "qwer tyui opas dfgh jklz xcvb nm");
let mut static_ = LeanString::from_static_str("01234567890123456789");
let cloned = static_.clone();
static_.extend_from_within(10..=12);
assert_eq!(static_, "01234567890123456789012");
assert!(static_.is_heap_allocated());
assert_eq!(cloned, "01234567890123456789");
assert!(!cloned.is_heap_allocated());
}
#[test]
fn extend_from_within_nothing_keeps_buffer() {
let mut heap = LeanString::from("qwer tyui opas dfgh jklz xcvb nm");
let cloned = heap.clone();
heap.extend_from_within(3..3);
assert_eq!(heap.as_ptr(), cloned.as_ptr());
let text = "01234567890123456789";
let mut static_ = LeanString::from_static_str(text);
static_.extend_from_within(3..3);
assert_eq!(static_.as_ptr(), text.as_ptr());
}
#[test]
#[should_panic(expected = "index is not a char boundary or out of bounds (index: 1)")]
fn extend_from_within_not_char_boundary() {
let mut s = LeanString::from("🦄");
s.extend_from_within(1..);
}
#[test]
#[should_panic(expected = "index is not a char boundary or out of bounds (index: 7)")]
fn extend_from_within_out_of_bounds() {
let mut s = LeanString::from("012345");
s.extend_from_within(..7);
}
#[test]
#[should_panic(expected = "range start is greater than end (start: 3, end: 2)")]
fn extend_from_within_start_greater_than_end() {
let mut s = LeanString::from("012345");
#[allow(clippy::reversed_empty_ranges)]
s.extend_from_within(3..2);
}
#[test]
fn replace_range_inline_to_heap() {
let mut s = LeanString::from("0123");
assert!(!s.is_heap_allocated());
s.replace_range(1..3, "abcdefghijklmnopqrstuvwxyz");
assert_eq!(s, "0abcdefghijklmnopqrstuvwxyz3");
assert!(s.is_heap_allocated());
}
#[test]
fn replace_range_cow() {
let mut heap = LeanString::from("qwer tyui opas dfgh jklz xcvb nm");
let cloned = heap.clone();
heap.replace_range(4.., "");
assert_eq!(heap, "qwer");
assert!(!heap.is_heap_allocated());
assert_eq!(cloned, "qwer tyui opas dfgh jklz xcvb nm");
assert!(cloned.is_heap_allocated());
let mut heap = cloned.clone();
heap.replace_range(..4, &cloned);
assert_eq!(heap, "qwer tyui opas dfgh jklz xcvb nm tyui opas dfgh jklz xcvb nm");
assert_eq!(cloned, "qwer tyui opas dfgh jklz xcvb nm");
let mut static_ = LeanString::from_static_str("01234567890123456789");
let cloned = static_.clone();
static_.replace_range(10..12, "a");
assert_eq!(static_, "0123456789a23456789");
assert!(static_.is_heap_allocated());
assert_eq!(cloned, "01234567890123456789");
assert!(!cloned.is_heap_allocated());
}
#[test]
fn replace_range_nothing_keeps_buffer() {
let mut heap = LeanString::from("qwer tyui opas dfgh jklz xcvb nm");
let cloned = heap.clone();
heap.replace_range(3..3, "");
assert_eq!(heap.as_ptr(), cloned.as_ptr());
let text = "01234567890123456789";
let mut static_ = LeanString::from_static_str(text);
static_.replace_range(3..3, "");
assert_eq!(static_.as_ptr(), text.as_ptr());
}
#[test]
#[should_panic(expected = "range start is greater than end (start: 3, end: 2)")]
fn replace_range_start_greater_than_end() {
let mut s = LeanString::from("012345");
#[allow(clippy::reversed_empty_ranges)]
s.replace_range(3..2, "a");
}
#[test]
fn repeat_around_inline() {
let s = LeanString::from("a");
assert!(!s.is_heap_allocated());
assert_eq!(s.repeat(1), "a");
let inline = s.repeat(INLINE_LIMIT);
assert_eq!(inline, "a".repeat(INLINE_LIMIT));
assert!(!inline.is_heap_allocated());
let heap = s.repeat(INLINE_LIMIT + 1);
assert_eq!(heap, "a".repeat(INLINE_LIMIT + 1));
assert!(heap.is_heap_allocated());
}
#[test]
fn repeat_zero_or_empty() {
let inline = LeanString::from("ab");
assert!(!inline.is_heap_allocated());
assert_eq!(inline.repeat(0), "");
assert!(!inline.repeat(0).is_heap_allocated());
let heap = LeanString::from("a".repeat(INLINE_LIMIT + 1).as_str());
assert!(heap.is_heap_allocated());
assert_eq!(heap.repeat(0), "");
assert!(!heap.repeat(0).is_heap_allocated());
let empty = LeanString::new();
assert_eq!(empty.repeat(0), "");
assert_eq!(empty.repeat(1), "");
assert_eq!(empty.repeat(100), "");
}
#[test]
fn repeat_heap() {
let s = LeanString::from("a".repeat(INLINE_LIMIT + 1).as_str());
assert!(s.is_heap_allocated());
let r1 = s.repeat(1);
assert_eq!(r1, s);
assert_eq!(r1.as_ptr(), s.as_ptr());
let r2 = s.repeat(2);
assert_eq!(r2.len(), s.len() * 2);
assert!(r2.is_heap_allocated());
let r5 = s.repeat(5);
assert_eq!(r5.len(), s.len() * 5);
assert!(r5.is_heap_allocated());
}
#[test]
fn repeat_static() {
let s = LeanString::from_static_str("0123456789abcdefghijklmnopqrstuvwxyz");
assert!(!s.is_heap_allocated());
assert!(s.as_static_str().is_some());
let r1 = s.repeat(1);
assert!(!r1.is_heap_allocated());
assert_eq!(r1.as_static_str(), s.as_static_str());
let r2 = s.repeat(2);
assert!(r2.is_heap_allocated());
assert_eq!(r2, s.as_str().repeat(2))
}
#[test]
fn try_repeat_overflow_is_err() {
let s = LeanString::from("ab");
assert!(s.try_repeat(usize::MAX).is_err());
}
#[test]
fn to_lowercase() {
let lower = |s: &str| LeanString::from(s).to_lowercase();
assert_eq!(lower(""), "");
assert_eq!(lower("AÉDžaé "), "aédžaé ");
assert_eq!(lower("ΑΣ"), "ας");
assert_eq!(lower("Α'Σ"), "α'ς");
assert_eq!(lower("Α''Σ"), "α''ς");
assert_eq!(lower("ΑΣ Α"), "ας α");
assert_eq!(lower("Α'Σ Α"), "α'ς α");
assert_eq!(lower("Α''Σ Α"), "α''ς α");
assert_eq!(lower("ΑΣ' Α"), "ας' α");
assert_eq!(lower("ΑΣ'' Α"), "ας'' α");
assert_eq!(lower("Α'Σ' Α"), "α'ς' α");
assert_eq!(lower("Α''Σ'' Α"), "α''ς'' α");
assert_eq!(lower("Α Σ"), "α σ");
assert_eq!(lower("Α 'Σ"), "α 'σ");
assert_eq!(lower("Α ''Σ"), "α ''σ");
assert_eq!(lower("Σ"), "σ");
assert_eq!(lower("'Σ"), "'σ");
assert_eq!(lower("''Σ"), "''σ");
assert_eq!(lower("ΑΣΑ"), "ασα");
assert_eq!(lower("ΑΣ'Α"), "ασ'α");
assert_eq!(lower("ΑΣ''Α"), "ασ''α");
assert_eq!(lower("abcdefghijklmnoΣ"), "abcdefghijklmnoς");
assert_eq!(lower("abcdefghijklmnopΣ"), "abcdefghijklmnopς");
assert_eq!(lower("abcdefghijklmnopqΣ"), "abcdefghijklmnopqς");
assert_eq!(lower(&"İ".repeat(512)), "i̇".repeat(512));
assert_eq!(lower(&"A".repeat(511)), "a".repeat(511));
}
#[test]
fn to_uppercase() {
let upper = |s: &str| LeanString::from(s).to_uppercase();
assert_eq!(upper(""), "");
assert_eq!(upper("aéDžßẞfiᾀ"), "AÉDŽSSẞFIἈΙ");
}
#[test]
fn to_lowercase_final_sigma_after_lowercase_letter() {
assert_eq!(LeanString::from("αΣ").to_lowercase(), "ας");
}
#[test]
fn case_conversion_fills_inline_buffer() {
let upper = "a".repeat(INLINE_LIMIT - 1) + "A";
assert_eq!(LeanString::from(upper.as_str()).to_lowercase(), "a".repeat(INLINE_LIMIT));
let lower = "A".repeat(INLINE_LIMIT - 1) + "a";
assert_eq!(LeanString::from(lower.as_str()).to_uppercase(), "A".repeat(INLINE_LIMIT));
}
#[test]
fn case_conversion_without_change_shares_buffer() {
let ascii = LeanString::from("already lowercase and heap allocated");
let non_ascii = LeanString::from("déjà lowercase and heap allocated");
let non_ascii_upper = LeanString::from("DÉJÀ UPPERCASE AND HEAP ALLOCATED");
assert!(ascii.is_heap_allocated());
assert!(non_ascii.is_heap_allocated());
assert!(non_ascii_upper.is_heap_allocated());
assert_eq!(ascii.to_ascii_lowercase().as_ptr(), ascii.as_ptr());
assert_eq!(ascii.to_lowercase().as_ptr(), ascii.as_ptr());
assert_eq!(non_ascii.to_ascii_lowercase().as_ptr(), non_ascii.as_ptr());
assert_eq!(non_ascii.to_lowercase().as_ptr(), non_ascii.as_ptr());
assert_eq!(non_ascii_upper.to_ascii_uppercase().as_ptr(), non_ascii_upper.as_ptr());
assert_eq!(non_ascii_upper.to_uppercase().as_ptr(), non_ascii_upper.as_ptr());
let static_ = LeanString::from_static_str("ALREADY UPPERCASE AND STATIC");
assert!(static_.to_ascii_uppercase().as_static_str().is_some());
assert!(static_.to_uppercase().as_static_str().is_some());
}
#[test]
fn make_ascii_case_without_change_keeps_buffer_shared() {
let mut heap = LeanString::from("already lowercase and heap allocated");
let cloned = heap.clone();
heap.make_ascii_lowercase();
assert_eq!(heap.as_ptr(), cloned.as_ptr());
let mut static_ = LeanString::from_static_str("ALREADY UPPERCASE AND STATIC");
static_.make_ascii_uppercase();
assert!(static_.as_static_str().is_some());
}
#[test]
fn make_ascii_case_inline() {
let mut inline = LeanString::from("Grüße");
assert!(!inline.is_heap_allocated());
inline.make_ascii_uppercase();
assert_eq!(inline, "GRüßE");
inline.make_ascii_lowercase();
assert_eq!(inline, "grüße");
}
#[test]
fn make_ascii_case_unique_heap_in_place() {
let mut heap = LeanString::from("Mixed Case And Heap Allocated");
let ptr = heap.as_ptr();
heap.make_ascii_uppercase();
assert_eq!(heap, "MIXED CASE AND HEAP ALLOCATED");
assert_eq!(heap.as_ptr(), ptr);
}
#[test]
fn make_ascii_case_shared_heap_detaches() {
let mut heap = LeanString::from("Mixed Case And Heap Allocated");
let cloned = heap.clone();
heap.make_ascii_lowercase();
assert_eq!(heap, "mixed case and heap allocated");
assert_eq!(cloned, "Mixed Case And Heap Allocated");
assert_ne!(heap.as_ptr(), cloned.as_ptr());
}
#[test]
fn make_ascii_case_static() {
let mut static_ = LeanString::from_static_str("Mixed Case And Static");
static_.make_ascii_uppercase();
assert_eq!(static_, "MIXED CASE AND STATIC");
assert!(static_.is_heap_allocated());
let mut static_ = LeanString::from_static_str("Mixed Case And Static");
static_.truncate(5);
static_.make_ascii_lowercase();
assert_eq!(static_, "mixed");
assert!(!static_.is_heap_allocated());
}
#[test]
fn truncate_keep_capacity() {
let mut inline = LeanString::from("abcdef");
assert!(!inline.is_heap_allocated());
inline.truncate(3);
assert_eq!(inline, "abc");
assert_eq!(inline.len(), 3);
assert!(!inline.is_heap_allocated());
assert_eq!(inline.capacity(), INLINE_LIMIT);
let mut heap = LeanString::from("a".repeat(INLINE_LIMIT + 10).as_str());
let original_capacity = heap.capacity();
assert!(heap.is_heap_allocated());
heap.truncate(INLINE_LIMIT + 1);
assert_eq!(heap, "a".repeat(INLINE_LIMIT + 1));
assert_eq!(heap.len(), INLINE_LIMIT + 1);
assert!(heap.is_heap_allocated());
assert_eq!(heap.capacity(), original_capacity);
heap.truncate(1);
assert_eq!(heap, "a");
assert_eq!(heap.len(), 1);
assert!(heap.is_heap_allocated());
assert_eq!(heap.capacity(), original_capacity);
}
#[test]
fn truncate_from_static() {
let mut static_ = LeanString::from_static_str("abcdefghijklmnopqrstuvwxyz");
assert_eq!(static_.len(), 26);
assert!(!static_.is_heap_allocated());
static_.truncate(20);
assert_eq!(static_, "abcdefghijklmnopqrst");
assert_eq!(static_.len(), 20);
assert_eq!(static_.capacity(), 20);
assert!(!static_.is_heap_allocated());
}
#[test]
fn truncate_share_buffer() {
let mut s = LeanString::from("abcdefgh");
assert_eq!(s.len(), 8);
let mut s1 = s.clone();
s1.truncate(4);
assert_eq!(s1, "abcd");
assert_eq!(s1.len(), 4);
assert_eq!(s, "abcdefgh");
s.push_str("ijklmnopqrstuvwxyz");
assert_eq!(s.len(), 26);
assert!(s.is_heap_allocated());
let mut s2 = s.clone();
s2.truncate(20);
assert_eq!(s.as_ptr(), s2.as_ptr());
s2.push('0');
assert_ne!(s.as_ptr(), s2.as_ptr());
}
#[test]
fn convert_static_to_inline_with_reserve() {
let s: &'static str = "1234567890ABCDEFGHIJ";
let mut static_ = LeanString::from_static_str(s);
for _ in 0..14 {
static_.pop();
}
assert_eq!(static_, "123456");
assert_eq!(static_.capacity(), static_.len());
static_.reserve(1);
assert_eq!(static_.capacity(), INLINE_LIMIT);
}
#[test]
fn clear_cow() {
let mut inline = LeanString::from("foo");
inline.clear();
assert_eq!(inline, "");
let mut heap: LeanString = core::iter::repeat_n('a', 100).collect();
let cloned = heap.clone();
heap.clear();
assert_eq!(heap, "");
assert_eq!(cloned.len(), 100);
assert_eq!(heap.capacity(), INLINE_LIMIT);
assert!(!heap.is_heap_allocated());
}
#[test]
fn extend_char() {
let mut s = LeanString::from("Hello, ");
s.extend("world!".chars());
assert_eq!(s, "Hello, world!");
}
#[test]
#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
fn from_iter_drops_reserved_buffer_on_panic() {
struct PanickingIterator;
impl Iterator for PanickingIterator {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
panic!("iterator panic");
}
fn size_hint(&self) -> (usize, Option<usize>) {
(17, Some(17))
}
}
let result = std::panic::catch_unwind(|| PanickingIterator.collect::<LeanString>());
assert!(result.is_err());
}
#[test]
#[cfg(target_pointer_width = "32")]
#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
fn from_iter_drops_prefixed_reserved_buffer_on_panic() {
struct PanickingIterator;
impl Iterator for PanickingIterator {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
panic!("iterator panic");
}
fn size_hint(&self) -> (usize, Option<usize>) {
let cap = MIN_CAPACITY_FOR_HEAP_LENGTH_LAYOUT_ON_32BIT;
(cap, Some(cap))
}
}
let result = std::panic::catch_unwind(|| PanickingIterator.collect::<LeanString>());
assert!(result.is_err());
}
#[test]
fn from_iter_owned_lean_strings_reuses_first() {
let text = "abcdefghijklmnopqrstuvwxyz012345";
let s = LeanString::from(text);
let s_ptr = s.as_ptr();
let one: LeanString = [s].into_iter().collect();
assert_eq!(one, text);
assert_eq!(one.as_ptr(), s_ptr);
let mut s = LeanString::with_capacity(128);
s.push_str(text);
let s_ptr = s.as_ptr();
let tow: LeanString = [s, LeanString::from("6789")].into_iter().collect();
assert_eq!(tow, text.to_owned() + "6789");
assert_eq!(tow.as_ptr(), s_ptr);
assert_eq!(tow.capacity(), 128);
}
#[test]
fn from_iter_owned_lean_strings_detaches_shared_first() {
let text = "abcdefghijklmnopqrstuvwxyz012345";
let s = LeanString::from(text);
let cloned = s.clone();
let clonded_ptr = cloned.as_ptr();
let collected: LeanString = [s, LeanString::from("6789")].into_iter().collect();
assert_eq!(cloned, text);
assert_eq!(cloned.as_ptr(), clonded_ptr);
assert_eq!(collected, text.to_owned() + "6789");
assert_ne!(collected.as_ptr(), clonded_ptr);
}
#[test]
fn from_iter_no_owned_lean_strings_is_empty_inline() {
let collected: LeanString = core::iter::empty::<LeanString>().collect();
assert!(collected.is_empty());
assert!(!collected.is_heap_allocated());
}