use lean_string::{LeanString, ReserveError};
use std::{
alloc::{GlobalAlloc, Layout, System},
cell::Cell,
ptr,
};
thread_local! {
static FAIL_NEXT_ALLOCATION: Cell<bool> = const { Cell::new(false) };
static FAIL_NEXT_REALLOCATION: Cell<bool> = const { Cell::new(false) };
}
struct FailNextAllocation;
unsafe impl GlobalAlloc for FailNextAllocation {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
if FAIL_NEXT_ALLOCATION.replace(false) {
ptr::null_mut()
} else {
unsafe { System.alloc(layout) }
}
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
if FAIL_NEXT_REALLOCATION.replace(false) {
ptr::null_mut()
} else {
unsafe { System.realloc(ptr, layout, new_size) }
}
}
}
#[global_allocator]
static ALLOCATOR: FailNextAllocation = FailNextAllocation;
const TEXT: &str = "a string longer than the inline limit";
#[track_caller]
fn without_allocating<T>(f: impl FnOnce() -> T) -> T {
FAIL_NEXT_ALLOCATION.set(true);
FAIL_NEXT_REALLOCATION.set(true);
let value = f();
let allocated = !FAIL_NEXT_ALLOCATION.replace(false);
let reallocated = !FAIL_NEXT_REALLOCATION.replace(false);
assert!(!allocated, "an allocation was attempted");
assert!(!reallocated, "a reallocation was attempted");
value
}
#[test]
fn try_reserve_reports_allocation_failure() {
let mut string = LeanString::from("inline");
FAIL_NEXT_ALLOCATION.set(true);
let result = string.try_reserve(64);
FAIL_NEXT_ALLOCATION.set(false);
assert_eq!(result, Err(ReserveError));
assert_eq!(string, "inline");
assert!(!string.is_heap_allocated());
}
#[test]
fn try_reserve_zero_keeps_static_buffer() {
let mut string = LeanString::from_static_str(TEXT);
let static_ptr = string.as_ptr();
let result = without_allocating(|| string.try_reserve(0));
assert_eq!(result, Ok(()));
assert_eq!(string, TEXT);
assert_eq!(string.as_ptr(), static_ptr);
assert!(!string.is_heap_allocated());
string.try_reserve(1).unwrap();
assert!(string.is_heap_allocated());
string.push('!');
assert_eq!(string, "a string longer than the inline limit!");
}
#[test]
fn try_reserve_zero_keeps_heap_buffer_shared() {
let mut string = LeanString::from(TEXT);
let shared = string.clone();
let shared_ptr = string.as_ptr();
let result = without_allocating(|| string.try_reserve(0));
assert_eq!(result, Ok(()));
assert_eq!(string, TEXT);
assert_eq!(shared, TEXT);
assert_eq!(string.as_ptr(), shared_ptr);
assert_eq!(shared.as_ptr(), shared_ptr);
string.try_reserve(1).unwrap();
assert_ne!(string.as_ptr(), shared_ptr);
assert_eq!(shared.as_ptr(), shared_ptr);
string.push('!');
assert_eq!(string, "a string longer than the inline limit!");
assert_eq!(shared, TEXT);
}
#[test]
fn insert_empty_str_keeps_static_buffer() {
let mut string = LeanString::from_static_str(TEXT);
let static_ptr = string.as_ptr();
let result = without_allocating(|| string.try_insert_str(2, ""));
assert_eq!(result, Ok(()));
assert_eq!(string, TEXT);
assert_eq!(string.as_ptr(), static_ptr);
assert!(!string.is_heap_allocated());
}
#[test]
fn insert_empty_str_keeps_heap_buffer_shared() {
let mut string = LeanString::from(TEXT);
let shared = string.clone();
let shared_ptr = string.as_ptr();
let result = without_allocating(|| string.try_insert_str(2, ""));
assert_eq!(result, Ok(()));
assert_eq!(string, TEXT);
assert_eq!(shared, TEXT);
assert_eq!(string.as_ptr(), shared_ptr);
assert_eq!(shared.as_ptr(), shared_ptr);
}
#[test]
fn extend_with_empty_iterator_keeps_heap_buffer_shared() {
let mut string = LeanString::from(TEXT);
let shared = string.clone();
let shared_ptr = string.as_ptr();
without_allocating(|| string.extend(core::iter::empty::<char>()));
assert_eq!(string, TEXT);
assert_eq!(string.as_ptr(), shared_ptr);
assert_eq!(shared.as_ptr(), shared_ptr);
}