use crate::cell::GraphemeId;
use std::collections::HashMap;
pub const MAX_POOL_ID: u32 = 0x00FF_FFFF;
pub const DEFAULT_SOFT_LIMIT: usize = 1_000_000;
pub const HIGH_UTILIZATION_THRESHOLD: usize = 80;
pub const COMPACTION_FRAGMENTATION_THRESHOLD: f32 = 0.5;
pub const COMPACTION_MIN_SLOTS: usize = 1000;
#[derive(Clone, Debug, Default)]
pub struct CompactionResult {
pub old_to_new: HashMap<u32, u32>,
pub slots_freed: usize,
pub bytes_saved: usize,
}
impl CompactionResult {
#[must_use]
pub fn has_remappings(&self) -> bool {
!self.old_to_new.is_empty()
}
#[must_use]
pub fn remap(&self, old_id: u32) -> Option<u32> {
self.old_to_new.get(&old_id).copied()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct PoolStats {
pub total_slots: usize,
pub active_slots: usize,
pub free_slots: usize,
pub soft_limit: usize,
pub utilization_percent: usize,
pub peak_usage: usize,
pub total_allocations: u64,
pub total_frees: u64,
}
impl PoolStats {
#[must_use]
pub fn is_above_threshold(&self, threshold_percent: usize) -> bool {
self.utilization_percent >= threshold_percent
}
}
#[derive(Clone, Debug)]
struct Slot {
bytes: String,
refcount: u32,
width: u8,
}
impl Slot {
fn new(bytes: String, width: u8) -> Self {
Self {
bytes,
refcount: 1,
width,
}
}
fn is_free(&self) -> bool {
self.refcount == 0
}
}
#[derive(Clone, Debug)]
pub struct GraphemePool {
slots: Vec<Slot>,
free_list: Vec<u32>,
index: HashMap<String, u32>,
soft_limit: usize,
peak_usage: usize,
total_allocations: u64,
total_frees: u64,
compact_threshold: f32,
}
impl Default for GraphemePool {
fn default() -> Self {
Self::new()
}
}
impl GraphemePool {
#[must_use]
pub fn new() -> Self {
Self {
slots: vec![Slot {
bytes: String::new(),
refcount: 0,
width: 0,
}],
free_list: Vec::new(),
index: HashMap::new(),
soft_limit: DEFAULT_SOFT_LIMIT,
peak_usage: 0,
total_allocations: 0,
total_frees: 0,
compact_threshold: COMPACTION_FRAGMENTATION_THRESHOLD,
}
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
let mut slots = Vec::with_capacity(capacity + 1);
slots.push(Slot {
bytes: String::new(),
refcount: 0,
width: 0,
});
Self {
slots,
free_list: Vec::new(),
index: HashMap::with_capacity(capacity),
soft_limit: DEFAULT_SOFT_LIMIT,
peak_usage: 0,
total_allocations: 0,
total_frees: 0,
compact_threshold: COMPACTION_FRAGMENTATION_THRESHOLD,
}
}
#[must_use]
pub fn with_soft_limit(soft_limit: usize) -> Self {
Self {
slots: vec![Slot {
bytes: String::new(),
refcount: 0,
width: 0,
}],
free_list: Vec::new(),
index: HashMap::new(),
soft_limit,
peak_usage: 0,
total_allocations: 0,
total_frees: 0,
compact_threshold: COMPACTION_FRAGMENTATION_THRESHOLD,
}
}
pub fn set_soft_limit(&mut self, limit: usize) -> &mut Self {
self.soft_limit = limit;
self
}
#[must_use]
pub fn soft_limit(&self) -> usize {
self.soft_limit
}
#[must_use]
pub fn alloc(&mut self, grapheme: &str) -> GraphemeId {
let width = crate::unicode::display_width(grapheme);
let width_u8 = width.min(u8::MAX as usize) as u8;
let grapheme_owned = grapheme.to_owned();
let slot = Slot::new(grapheme_owned.clone(), width_u8);
let pool_id = if let Some(free_id) = self.free_list.pop() {
self.slots[free_id as usize] = slot;
free_id
} else {
let id = self.slots.len() as u32;
assert!(
id <= MAX_POOL_ID,
"GraphemePool exceeded 16M entry limit (id={id})"
);
self.slots.push(slot);
id
};
self.index.insert(grapheme_owned, pool_id);
self.total_allocations = self.total_allocations.saturating_add(1);
let active = self.active_count();
if active > self.peak_usage {
self.peak_usage = active;
}
GraphemeId::new(pool_id, width_u8)
}
#[must_use]
pub fn intern(&mut self, grapheme: &str) -> GraphemeId {
if let Some(&pool_id) = self.index.get(grapheme) {
if let Some(slot) = self.slots.get(pool_id as usize) {
if !slot.is_free() {
let width = slot.width; self.incref_by_pool_id(pool_id);
return GraphemeId::new(pool_id, width);
}
}
self.index.remove(grapheme);
}
self.alloc(grapheme)
}
pub fn incref(&mut self, id: GraphemeId) {
self.incref_by_pool_id(id.pool_id());
}
fn incref_by_pool_id(&mut self, pool_id: u32) {
if let Some(slot) = self.slots.get_mut(pool_id as usize) {
if slot.refcount > 0 {
slot.refcount = slot.refcount.saturating_add(1);
}
}
}
pub fn decref(&mut self, id: GraphemeId) -> bool {
self.decref_by_pool_id(id.pool_id())
}
fn decref_by_pool_id(&mut self, pool_id: u32) -> bool {
if let Some(slot) = self.slots.get_mut(pool_id as usize) {
if slot.refcount > 0 {
slot.refcount -= 1;
if slot.refcount == 0 {
self.index.remove(&slot.bytes);
slot.bytes.clear();
self.free_list.push(pool_id);
self.total_frees = self.total_frees.saturating_add(1);
return false;
}
return true;
}
}
false
}
#[must_use]
pub fn get(&self, id: GraphemeId) -> Option<&str> {
self.get_by_pool_id(id.pool_id())
}
#[must_use]
pub fn get_by_pool_id(&self, pool_id: u32) -> Option<&str> {
self.slots.get(pool_id as usize).and_then(|slot| {
if slot.is_free() {
None
} else {
Some(slot.bytes.as_str())
}
})
}
#[must_use]
pub fn refcount(&self, id: GraphemeId) -> u32 {
self.slots
.get(id.pool_id() as usize)
.map_or(0, |slot| slot.refcount)
}
#[must_use]
pub fn is_valid(&self, id: GraphemeId) -> bool {
self.slots
.get(id.pool_id() as usize)
.is_some_and(|slot| !slot.is_free())
}
#[must_use]
pub fn active_count(&self) -> usize {
self.slots.iter().skip(1).filter(|s| !s.is_free()).count()
}
#[must_use]
pub fn total_slots(&self) -> usize {
self.slots.len().saturating_sub(1)
}
#[must_use]
pub fn free_count(&self) -> usize {
self.free_list.len()
}
#[must_use]
pub fn is_full(&self) -> bool {
self.free_list.is_empty() && self.slots.len() > MAX_POOL_ID as usize
}
#[must_use]
pub fn capacity_remaining(&self) -> usize {
let free_slots = self.free_list.len();
let allocatable = (MAX_POOL_ID as usize + 1).saturating_sub(self.slots.len());
free_slots + allocatable
}
pub fn clear(&mut self) {
self.slots.truncate(1);
self.free_list.clear();
self.index.clear();
}
#[must_use]
pub fn peak_usage(&self) -> usize {
self.peak_usage
}
#[must_use]
pub fn total_allocations(&self) -> u64 {
self.total_allocations
}
#[must_use]
pub fn total_frees(&self) -> u64 {
self.total_frees
}
#[must_use]
pub fn stats(&self) -> PoolStats {
let total_slots = self.total_slots();
let free_slots = self.free_count();
let active_slots = total_slots.saturating_sub(free_slots);
let utilization_percent = (active_slots * 100)
.checked_div(self.soft_limit)
.unwrap_or(0);
PoolStats {
total_slots,
active_slots,
free_slots,
soft_limit: self.soft_limit,
utilization_percent,
peak_usage: self.peak_usage,
total_allocations: self.total_allocations,
total_frees: self.total_frees,
}
}
#[must_use]
pub fn utilization_percent(&self) -> usize {
self.stats().utilization_percent
}
#[must_use]
pub fn is_high_utilization(&self) -> bool {
self.utilization_percent() >= HIGH_UTILIZATION_THRESHOLD
}
#[must_use]
pub fn is_above_utilization(&self, threshold_percent: usize) -> bool {
self.utilization_percent() >= threshold_percent
}
#[must_use]
pub fn get_fragmentation_ratio(&self) -> f32 {
let total = self.total_slots();
if total == 0 {
return 0.0;
}
self.free_count() as f32 / total as f32
}
pub fn iter_active(&self) -> impl Iterator<Item = (u32, &str)> {
self.slots
.iter()
.enumerate()
.skip(1) .filter_map(|(idx, slot)| {
if slot.is_free() {
None
} else {
Some((idx as u32, slot.bytes.as_str()))
}
})
}
#[must_use]
pub fn should_compact(&self) -> bool {
let ratio = self.get_fragmentation_ratio();
let size = self.total_slots();
ratio > self.compact_threshold && size > COMPACTION_MIN_SLOTS
}
pub fn set_compact_threshold(&mut self, ratio: f32) -> &mut Self {
self.compact_threshold = ratio.clamp(0.0, 1.0);
self
}
#[must_use]
pub fn compact_threshold(&self) -> f32 {
self.compact_threshold
}
pub fn clone_batch(&mut self, ids: &[u32]) {
for &pool_id in ids {
self.incref_by_pool_id(pool_id);
}
}
pub fn free_batch(&mut self, ids: &[u32]) -> usize {
let mut freed_count = 0;
for &pool_id in ids {
let was_valid = self
.slots
.get(pool_id as usize)
.is_some_and(|slot| slot.refcount > 0);
if was_valid && !self.decref_by_pool_id(pool_id) {
freed_count += 1;
}
}
freed_count
}
#[must_use]
pub fn alloc_batch(&mut self, graphemes: &[&str]) -> Vec<GraphemeId> {
let mut result = Vec::with_capacity(graphemes.len());
for &grapheme in graphemes {
result.push(self.alloc(grapheme));
}
result
}
#[must_use]
pub fn get_memory_usage(&self) -> usize {
let slot_size = std::mem::size_of::<Slot>();
let slots_heap = self.slots.capacity() * slot_size;
let string_heap: usize = self.slots.iter().map(|slot| slot.bytes.capacity()).sum();
let free_list_heap = self.free_list.capacity() * std::mem::size_of::<u32>();
let index_overhead = self.index.len() * 64;
let index_key_heap: usize = self.index.keys().map(String::len).sum();
let stack_size = std::mem::size_of::<GraphemePool>();
stack_size + slots_heap + string_heap + free_list_heap + index_overhead + index_key_heap
}
#[must_use]
pub fn try_alloc(&mut self, grapheme: &str) -> Option<GraphemeId> {
let active = self.active_count();
if self.free_list.is_empty() && active >= self.soft_limit {
return None;
}
Some(self.alloc(grapheme))
}
#[must_use]
pub fn try_intern(&mut self, grapheme: &str) -> Option<GraphemeId> {
if let Some(&pool_id) = self.index.get(grapheme) {
if let Some(slot) = self.slots.get(pool_id as usize) {
if !slot.is_free() {
let width = slot.width;
self.incref_by_pool_id(pool_id);
return Some(GraphemeId::new(pool_id, width));
}
}
self.index.remove(grapheme);
}
self.try_alloc(grapheme)
}
#[must_use]
pub fn compact(&mut self) -> CompactionResult {
if self.free_list.is_empty() {
return CompactionResult::default();
}
let slots_freed = self.free_list.len();
let avg_slot_heap: usize = self
.slots
.iter()
.skip(1)
.filter(|s| !s.is_free())
.map(|s| s.bytes.capacity())
.sum::<usize>()
.checked_div(self.active_count())
.unwrap_or(0);
let bytes_saved = slots_freed * (std::mem::size_of::<Slot>() + avg_slot_heap);
let active_count = self.active_count();
let mut new_slots = Vec::with_capacity(active_count + 1);
let mut old_to_new = HashMap::with_capacity(active_count);
let mut new_index = HashMap::with_capacity(active_count);
new_slots.push(Slot {
bytes: String::new(),
refcount: 0,
width: 0,
});
for (old_id, slot) in self.slots.iter().enumerate().skip(1) {
if slot.is_free() {
continue;
}
let new_id = new_slots.len() as u32;
old_to_new.insert(old_id as u32, new_id);
new_index.insert(slot.bytes.clone(), new_id);
new_slots.push(slot.clone());
}
self.slots = new_slots;
self.free_list.clear();
self.index = new_index;
CompactionResult {
old_to_new,
slots_freed,
bytes_saved,
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::let_underscore_must_use)] use super::*;
#[test]
fn test_pool_new() {
let pool = GraphemePool::new();
assert_eq!(pool.total_slots(), 0);
assert_eq!(pool.active_count(), 0);
assert_eq!(pool.free_count(), 0);
}
#[test]
fn test_alloc_and_get() {
let mut pool = GraphemePool::new();
let id = pool.alloc("👨👩👧");
assert_eq!(pool.get(id), Some("👨👩👧"));
assert_eq!(pool.refcount(id), 1);
assert!(pool.is_valid(id));
}
#[test]
fn test_grapheme_id_width_encoding() {
let mut pool = GraphemePool::new();
let id = pool.alloc("👨👩👧");
assert_eq!(id.width(), 2);
let id2 = pool.alloc("👍");
assert_eq!(id2.width(), 2);
}
#[test]
fn test_incref_decref() {
let mut pool = GraphemePool::new();
let id = pool.alloc("test");
assert_eq!(pool.refcount(id), 1);
pool.incref(id);
assert_eq!(pool.refcount(id), 2);
pool.incref(id);
assert_eq!(pool.refcount(id), 3);
assert!(pool.decref(id)); assert_eq!(pool.refcount(id), 2);
assert!(pool.decref(id)); assert_eq!(pool.refcount(id), 1);
assert!(!pool.decref(id)); assert_eq!(pool.refcount(id), 0);
assert!(!pool.is_valid(id));
assert_eq!(pool.get(id), None);
}
#[test]
fn test_slot_reuse() {
let mut pool = GraphemePool::new();
let id1 = pool.alloc("first");
let pool_id1 = id1.pool_id();
pool.decref(id1);
let id2 = pool.alloc("second");
assert_eq!(id2.pool_id(), pool_id1);
assert_eq!(pool.get(id2), Some("second"));
}
#[test]
fn test_multiple_allocations() {
let mut pool = GraphemePool::new();
let ids: Vec<_> = (0..10).map(|i| pool.alloc(&format!("item{i}"))).collect();
assert_eq!(pool.active_count(), 10);
assert_eq!(pool.total_slots(), 10);
for (i, id) in ids.iter().enumerate() {
assert_eq!(pool.get(*id), Some(format!("item{i}").as_str()));
}
}
#[test]
fn test_intern_deduplication() {
let mut pool = GraphemePool::new();
let id1 = pool.intern("duplicate");
let id2 = pool.intern("duplicate");
assert_eq!(id1, id2);
assert_eq!(pool.refcount(id1), 2);
assert_eq!(pool.active_count(), 1);
}
#[test]
fn test_intern_different_graphemes() {
let mut pool = GraphemePool::new();
let id1 = pool.intern("first");
let id2 = pool.intern("second");
assert_ne!(id1, id2);
assert_eq!(pool.active_count(), 2);
}
#[test]
fn test_invalid_id_handling() {
let pool = GraphemePool::new();
let invalid = GraphemeId::new(0, 1);
assert_eq!(pool.get(invalid), None);
assert!(!pool.is_valid(invalid));
let beyond = GraphemeId::new(9999, 1);
assert_eq!(pool.get(beyond), None);
assert!(!pool.is_valid(beyond));
}
#[test]
fn test_invalid_id_incref_decref() {
let mut pool = GraphemePool::new();
let invalid = GraphemeId::new(0, 1);
pool.incref(invalid);
assert!(!pool.decref(invalid));
}
#[test]
fn test_clear() {
let mut pool = GraphemePool::new();
let _ = pool.alloc("a");
let _ = pool.alloc("b");
let _ = pool.alloc("c");
assert_eq!(pool.active_count(), 3);
pool.clear();
assert_eq!(pool.active_count(), 0);
assert_eq!(pool.total_slots(), 0);
assert_eq!(pool.free_count(), 0);
}
#[test]
fn test_freed_slot_not_found_by_intern() {
let mut pool = GraphemePool::new();
let id = pool.alloc("ephemeral");
pool.decref(id);
let id2 = pool.intern("ephemeral");
assert_eq!(id2.pool_id(), id.pool_id());
assert_eq!(pool.refcount(id2), 1);
}
#[test]
fn test_refcount_saturation() {
let mut pool = GraphemePool::new();
let id = pool.alloc("test");
for _ in 0..100 {
pool.incref(id);
}
assert_eq!(pool.refcount(id), 101);
}
#[test]
fn test_with_capacity() {
let pool = GraphemePool::with_capacity(100);
assert_eq!(pool.total_slots(), 0);
assert_eq!(pool.active_count(), 0);
}
#[test]
fn test_grapheme_id_roundtrip() {
let mut pool = GraphemePool::new();
let id = pool.alloc("🎉");
assert_eq!(pool.get(id), Some("🎉"));
assert_eq!(id.width(), 2);
assert_eq!(id.pool_id(), 1);
}
#[test]
fn test_capacity_remaining() {
let mut pool = GraphemePool::new();
let initial_capacity = pool.capacity_remaining();
assert_eq!(initial_capacity, MAX_POOL_ID as usize);
let id = pool.alloc("test");
assert_eq!(pool.capacity_remaining(), initial_capacity - 1);
pool.decref(id);
assert_eq!(pool.capacity_remaining(), initial_capacity);
}
#[test]
fn test_is_full_empty_pool() {
let pool = GraphemePool::new();
assert!(!pool.is_full(), "empty pool should not be full");
}
#[test]
fn test_index_consistency_many_graphemes() {
let mut pool = GraphemePool::new();
let graphemes: Vec<String> = (0..1000).map(|i| format!("g{i}")).collect();
let ids: Vec<_> = graphemes.iter().map(|g| pool.alloc(g)).collect();
for (i, id) in ids.iter().enumerate() {
assert_eq!(pool.get(*id), Some(graphemes[i].as_str()));
}
for (i, g) in graphemes.iter().enumerate() {
let interned = pool.intern(g);
assert_eq!(interned.pool_id(), ids[i].pool_id());
assert_eq!(pool.refcount(interned), 2); }
for id in &ids {
pool.decref(*id);
pool.decref(*id);
}
assert_eq!(pool.active_count(), 0);
assert_eq!(pool.free_count(), 1000);
for g in &graphemes {
let fresh = pool.intern(g);
assert_eq!(pool.refcount(fresh), 1);
}
assert_eq!(pool.active_count(), 1000);
assert_eq!(pool.free_count(), 0);
assert_eq!(pool.total_slots(), 1000);
}
#[test]
fn test_index_cleared_on_clear() {
let mut pool = GraphemePool::new();
let _ = pool.alloc("a");
let _ = pool.alloc("b");
let _ = pool.alloc("c");
pool.clear();
let id = pool.intern("a");
assert_eq!(id.pool_id(), 1); assert_eq!(pool.refcount(id), 1);
}
#[test]
fn test_with_soft_limit() {
let pool = GraphemePool::with_soft_limit(100);
assert_eq!(pool.soft_limit(), 100);
assert_eq!(pool.total_slots(), 0);
}
#[test]
fn test_set_soft_limit() {
let mut pool = GraphemePool::new();
assert_eq!(pool.soft_limit(), DEFAULT_SOFT_LIMIT);
pool.set_soft_limit(500);
assert_eq!(pool.soft_limit(), 500);
}
#[test]
fn test_pool_stats() {
let mut pool = GraphemePool::with_soft_limit(100);
let stats = pool.stats();
assert_eq!(stats.total_slots, 0);
assert_eq!(stats.active_slots, 0);
assert_eq!(stats.free_slots, 0);
assert_eq!(stats.soft_limit, 100);
assert_eq!(stats.utilization_percent, 0);
for i in 0..50 {
let _ = pool.alloc(&format!("g{i}"));
}
let stats = pool.stats();
assert_eq!(stats.total_slots, 50);
assert_eq!(stats.active_slots, 50);
assert_eq!(stats.free_slots, 0);
assert_eq!(stats.utilization_percent, 50);
}
#[test]
fn test_utilization_percent() {
let mut pool = GraphemePool::with_soft_limit(100);
assert_eq!(pool.utilization_percent(), 0);
for i in 0..10 {
let _ = pool.alloc(&format!("g{i}"));
}
assert_eq!(pool.utilization_percent(), 10);
for i in 10..80 {
let _ = pool.alloc(&format!("g{i}"));
}
assert_eq!(pool.utilization_percent(), 80);
}
#[test]
fn test_is_high_utilization() {
let mut pool = GraphemePool::with_soft_limit(100);
for i in 0..79 {
let _ = pool.alloc(&format!("g{i}"));
}
assert!(!pool.is_high_utilization());
let _ = pool.alloc("g79");
assert!(pool.is_high_utilization());
let _ = pool.alloc("g80");
assert!(pool.is_high_utilization());
}
#[test]
fn test_is_above_utilization() {
let mut pool = GraphemePool::with_soft_limit(100);
for i in 0..90 {
let _ = pool.alloc(&format!("g{i}"));
}
assert!(pool.is_above_utilization(80));
assert!(pool.is_above_utilization(90));
assert!(!pool.is_above_utilization(91));
assert!(!pool.is_above_utilization(95));
}
#[test]
fn test_try_alloc_respects_soft_limit() {
let mut pool = GraphemePool::with_soft_limit(10);
for i in 0..10 {
let result = pool.try_alloc(&format!("g{i}"));
assert!(result.is_some(), "should be able to allocate g{i}");
}
let result = pool.try_alloc("overflow");
assert!(result.is_none(), "should fail when at soft limit");
let id = pool.intern("g0");
pool.decref(id); pool.decref(id);
let result = pool.try_alloc("reuse");
assert!(result.is_some(), "should reuse freed slot");
}
#[test]
fn test_try_intern_existing_always_succeeds() {
let mut pool = GraphemePool::with_soft_limit(5);
for i in 0..5 {
let _ = pool.alloc(&format!("g{i}"));
}
assert!(pool.try_alloc("new").is_none());
let existing = pool.try_intern("g0");
assert!(existing.is_some());
assert_eq!(pool.refcount(existing.unwrap()), 2);
}
#[test]
fn test_try_intern_new_respects_limit() {
let mut pool = GraphemePool::with_soft_limit(5);
for i in 0..5 {
let _ = pool.alloc(&format!("g{i}"));
}
let new = pool.try_intern("totally_new");
assert!(new.is_none());
}
#[test]
fn test_pool_stats_is_above_threshold() {
let stats = PoolStats {
total_slots: 100,
active_slots: 85,
free_slots: 15,
soft_limit: 100,
utilization_percent: 85,
peak_usage: 90,
total_allocations: 100,
total_frees: 15,
};
assert!(stats.is_above_threshold(80));
assert!(stats.is_above_threshold(85));
assert!(!stats.is_above_threshold(86));
assert!(!stats.is_above_threshold(90));
}
#[test]
fn test_utilization_can_exceed_100_percent() {
let mut pool = GraphemePool::with_soft_limit(10);
for i in 0..15 {
let _ = pool.alloc(&format!("g{i}"));
}
assert_eq!(pool.utilization_percent(), 150);
assert!(pool.is_high_utilization());
}
#[test]
fn test_get_fragmentation_ratio_empty_pool() {
let pool = GraphemePool::new();
assert!(pool.get_fragmentation_ratio().abs() < f32::EPSILON);
}
#[test]
fn test_get_fragmentation_ratio_no_freed_slots() {
let mut pool = GraphemePool::new();
let _ = pool.alloc("a");
let _ = pool.alloc("b");
let _ = pool.alloc("c");
assert!(pool.get_fragmentation_ratio().abs() < f32::EPSILON);
}
#[test]
fn test_get_fragmentation_ratio_half_freed() {
let mut pool = GraphemePool::new();
let id1 = pool.alloc("a");
let _ = pool.alloc("b");
pool.decref(id1);
assert!((pool.get_fragmentation_ratio() - 0.5).abs() < f32::EPSILON);
}
#[test]
fn test_get_fragmentation_ratio_all_freed() {
let mut pool = GraphemePool::new();
let id1 = pool.alloc("a");
let id2 = pool.alloc("b");
let id3 = pool.alloc("c");
pool.decref(id1);
pool.decref(id2);
pool.decref(id3);
assert!((pool.get_fragmentation_ratio() - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_get_fragmentation_ratio_after_reuse() {
let mut pool = GraphemePool::new();
let id1 = pool.alloc("a");
let _ = pool.alloc("b");
pool.decref(id1);
assert!((pool.get_fragmentation_ratio() - 0.5).abs() < f32::EPSILON);
let _ = pool.alloc("c");
assert!(pool.get_fragmentation_ratio().abs() < f32::EPSILON);
}
#[test]
fn test_iter_active_empty_pool() {
let pool = GraphemePool::new();
let active: Vec<_> = pool.iter_active().collect();
assert!(active.is_empty());
}
#[test]
fn test_iter_active_all_entries() {
let mut pool = GraphemePool::new();
let _ = pool.alloc("alpha");
let _ = pool.alloc("beta");
let _ = pool.alloc("gamma");
let active: Vec<_> = pool.iter_active().collect();
assert_eq!(active.len(), 3);
let ids: Vec<_> = active.iter().map(|(id, _)| *id).collect();
assert!(ids.contains(&1));
assert!(ids.contains(&2));
assert!(ids.contains(&3));
let graphemes: Vec<_> = active.iter().map(|(_, s)| *s).collect();
assert!(graphemes.contains(&"alpha"));
assert!(graphemes.contains(&"beta"));
assert!(graphemes.contains(&"gamma"));
}
#[test]
fn test_iter_active_skips_freed() {
let mut pool = GraphemePool::new();
let id1 = pool.alloc("alpha");
let id2 = pool.alloc("beta");
let id3 = pool.alloc("gamma");
pool.decref(id2);
let active: Vec<_> = pool.iter_active().collect();
assert_eq!(active.len(), 2);
let graphemes: Vec<_> = active.iter().map(|(_, s)| *s).collect();
assert!(graphemes.contains(&"alpha"));
assert!(!graphemes.contains(&"beta"));
assert!(graphemes.contains(&"gamma"));
assert!(active.iter().any(|(id, _)| *id == id1.pool_id()));
assert!(active.iter().any(|(id, _)| *id == id3.pool_id()));
}
#[test]
fn test_iter_active_ids_match_get() {
let mut pool = GraphemePool::new();
let _ = pool.alloc("one");
let _ = pool.alloc("two");
let id3 = pool.alloc("three");
pool.decref(id3);
for (id, grapheme) in pool.iter_active() {
assert_eq!(pool.get_by_pool_id(id), Some(grapheme));
}
}
#[test]
fn test_iter_active_after_reuse() {
let mut pool = GraphemePool::new();
let id1 = pool.alloc("old");
pool.decref(id1);
let _ = pool.alloc("new");
let active: Vec<_> = pool.iter_active().collect();
assert_eq!(active.len(), 1);
assert_eq!(active[0], (1, "new"));
}
#[test]
fn test_should_compact_empty_pool() {
let pool = GraphemePool::new();
assert!(!pool.should_compact());
}
#[test]
fn test_should_compact_small_fragmented_pool() {
let mut pool = GraphemePool::new();
for i in 0..10 {
let id = pool.alloc(&format!("g{i}"));
pool.decref(id);
}
assert!(!pool.should_compact());
assert!((pool.get_fragmentation_ratio() - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_should_compact_large_unfragmented_pool() {
let mut pool = GraphemePool::new();
for i in 0..2000 {
let _ = pool.alloc(&format!("g{i}"));
}
assert!(!pool.should_compact());
assert!(pool.get_fragmentation_ratio().abs() < f32::EPSILON);
}
#[test]
fn test_should_compact_large_fragmented_pool() {
let mut pool = GraphemePool::new();
let ids: Vec<_> = (0..2000).map(|i| pool.alloc(&format!("g{i}"))).collect();
for (i, id) in ids.iter().enumerate() {
if i % 3 != 0 {
pool.decref(*id);
}
}
assert!(pool.should_compact());
assert!(pool.get_fragmentation_ratio() > 0.5);
}
#[test]
fn test_should_compact_threshold_boundary() {
let mut pool = GraphemePool::new();
let mut ids = Vec::new();
for i in 0..1001 {
ids.push(pool.alloc(&format!("g{i}")));
}
for id in ids.iter().take(501) {
pool.decref(*id);
}
assert!(pool.should_compact());
assert!(pool.total_slots() > 1000);
assert!(pool.get_fragmentation_ratio() > 0.5);
}
#[test]
fn test_clone_batch_empty() {
let mut pool = GraphemePool::new();
let id = pool.alloc("test");
pool.clone_batch(&[]);
assert_eq!(pool.refcount(id), 1);
}
#[test]
fn test_clone_batch_valid_ids() {
let mut pool = GraphemePool::new();
let id1 = pool.alloc("alpha");
let id2 = pool.alloc("beta");
let id3 = pool.alloc("gamma");
pool.clone_batch(&[id1.pool_id(), id2.pool_id(), id3.pool_id()]);
assert_eq!(pool.refcount(id1), 2);
assert_eq!(pool.refcount(id2), 2);
assert_eq!(pool.refcount(id3), 2);
}
#[test]
fn test_clone_batch_skips_id_zero() {
let mut pool = GraphemePool::new();
let id = pool.alloc("test");
pool.clone_batch(&[0, id.pool_id()]);
assert_eq!(pool.refcount(id), 2);
}
#[test]
fn test_clone_batch_skips_invalid_ids() {
let mut pool = GraphemePool::new();
let id = pool.alloc("test");
pool.clone_batch(&[9999, id.pool_id(), 12345]);
assert_eq!(pool.refcount(id), 2);
}
#[test]
fn test_clone_batch_skips_freed_ids() {
let mut pool = GraphemePool::new();
let id1 = pool.alloc("alpha");
let id2 = pool.alloc("beta");
pool.decref(id1);
pool.clone_batch(&[id1.pool_id(), id2.pool_id()]);
assert_eq!(pool.refcount(id1), 0);
assert_eq!(pool.refcount(id2), 2);
}
#[test]
fn test_clone_batch_duplicate_ids() {
let mut pool = GraphemePool::new();
let id = pool.alloc("test");
pool.clone_batch(&[id.pool_id(), id.pool_id(), id.pool_id()]);
assert_eq!(pool.refcount(id), 4);
}
#[test]
fn test_free_batch_empty() {
let mut pool = GraphemePool::new();
let id = pool.alloc("test");
let freed = pool.free_batch(&[]);
assert_eq!(freed, 0);
assert_eq!(pool.refcount(id), 1);
}
#[test]
fn test_free_batch_valid_ids() {
let mut pool = GraphemePool::new();
let id1 = pool.alloc("alpha");
let id2 = pool.alloc("beta");
let id3 = pool.alloc("gamma");
let freed = pool.free_batch(&[id1.pool_id(), id2.pool_id(), id3.pool_id()]);
assert_eq!(freed, 3);
assert!(!pool.is_valid(id1));
assert!(!pool.is_valid(id2));
assert!(!pool.is_valid(id3));
}
#[test]
fn test_free_batch_skips_id_zero() {
let mut pool = GraphemePool::new();
let id = pool.alloc("test");
let freed = pool.free_batch(&[0, id.pool_id()]);
assert_eq!(freed, 1);
assert!(!pool.is_valid(id));
}
#[test]
fn test_free_batch_skips_invalid_ids() {
let mut pool = GraphemePool::new();
let id = pool.alloc("test");
let freed = pool.free_batch(&[9999, id.pool_id(), 12345]);
assert_eq!(freed, 1);
assert!(!pool.is_valid(id));
}
#[test]
fn test_free_batch_skips_freed_ids() {
let mut pool = GraphemePool::new();
let id1 = pool.alloc("alpha");
let id2 = pool.alloc("beta");
pool.decref(id1);
let freed = pool.free_batch(&[id1.pool_id(), id2.pool_id()]);
assert_eq!(freed, 1);
}
#[test]
fn test_free_batch_with_multiple_refs() {
let mut pool = GraphemePool::new();
let id = pool.alloc("test");
pool.incref(id);
pool.incref(id);
let freed = pool.free_batch(&[id.pool_id()]);
assert_eq!(freed, 0); assert_eq!(pool.refcount(id), 2);
let freed = pool.free_batch(&[id.pool_id(), id.pool_id()]);
assert_eq!(freed, 1); assert!(!pool.is_valid(id));
}
#[test]
fn test_alloc_batch_empty() {
let mut pool = GraphemePool::new();
let ids = pool.alloc_batch(&[]);
assert!(ids.is_empty());
assert_eq!(pool.active_count(), 0);
}
#[test]
fn test_alloc_batch_single() {
let mut pool = GraphemePool::new();
let ids = pool.alloc_batch(&["single"]);
assert_eq!(ids.len(), 1);
assert_eq!(pool.get(ids[0]), Some("single"));
}
#[test]
fn test_alloc_batch_multiple() {
let mut pool = GraphemePool::new();
let ids = pool.alloc_batch(&["alpha", "beta", "gamma", "delta"]);
assert_eq!(ids.len(), 4);
assert_eq!(pool.active_count(), 4);
assert_eq!(pool.get(ids[0]), Some("alpha"));
assert_eq!(pool.get(ids[1]), Some("beta"));
assert_eq!(pool.get(ids[2]), Some("gamma"));
assert_eq!(pool.get(ids[3]), Some("delta"));
}
#[test]
fn test_alloc_batch_with_duplicates() {
let mut pool = GraphemePool::new();
let ids = pool.alloc_batch(&["dup", "dup", "dup"]);
assert_eq!(ids.len(), 3);
assert_eq!(pool.active_count(), 3);
assert_ne!(ids[0].pool_id(), ids[1].pool_id());
assert_ne!(ids[1].pool_id(), ids[2].pool_id());
assert_eq!(pool.get(ids[0]), Some("dup"));
assert_eq!(pool.get(ids[1]), Some("dup"));
assert_eq!(pool.get(ids[2]), Some("dup"));
}
#[test]
fn test_alloc_batch_preserves_width() {
let mut pool = GraphemePool::new();
let ids = pool.alloc_batch(&["A", "👍", "世"]);
assert_eq!(ids[0].width(), 1);
assert_eq!(ids[1].width(), 2);
assert_eq!(ids[2].width(), 2);
}
#[test]
fn test_alloc_batch_reuses_freed_slots() {
let mut pool = GraphemePool::new();
let old_ids = pool.alloc_batch(&["old1", "old2", "old3"]);
for id in &old_ids {
pool.decref(*id);
}
let new_ids = pool.alloc_batch(&["new1", "new2"]);
assert_eq!(pool.total_slots(), 3);
assert_eq!(pool.active_count(), 2);
assert_eq!(pool.free_count(), 1);
assert_eq!(pool.get(new_ids[0]), Some("new1"));
assert_eq!(pool.get(new_ids[1]), Some("new2"));
}
#[test]
fn test_get_memory_usage_empty_pool() {
let pool = GraphemePool::new();
let usage = pool.get_memory_usage();
assert!(usage > 0);
}
#[test]
fn test_get_memory_usage_increases_with_data() {
let mut pool = GraphemePool::new();
let empty_usage = pool.get_memory_usage();
let _ = pool.alloc("hello");
let _ = pool.alloc("world");
let _ = pool.alloc("this is a longer string");
let usage_with_data = pool.get_memory_usage();
assert!(usage_with_data > empty_usage);
}
#[test]
fn test_get_memory_usage_accounts_for_string_length() {
let mut pool1 = GraphemePool::new();
let mut pool2 = GraphemePool::new();
for i in 0..10 {
let _ = pool1.alloc(&format!("{i}"));
}
for i in 0..10 {
let _ = pool2.alloc(&format!("this_is_a_much_longer_string_{i}"));
}
assert!(pool2.get_memory_usage() > pool1.get_memory_usage());
}
#[test]
fn test_get_memory_usage_includes_free_list() {
let mut pool = GraphemePool::new();
let ids: Vec<_> = (0..100).map(|i| pool.alloc(&format!("g{i}"))).collect();
let usage_before_free = pool.get_memory_usage();
for id in ids {
pool.decref(id);
}
let usage_after_free = pool.get_memory_usage();
assert!(usage_before_free > 0);
assert!(usage_after_free > 0);
}
#[test]
fn test_compact_empty_pool() {
let mut pool = GraphemePool::new();
let result = pool.compact();
assert!(result.old_to_new.is_empty());
assert_eq!(result.slots_freed, 0);
assert_eq!(result.bytes_saved, 0);
assert!(!result.has_remappings());
}
#[test]
fn test_compact_no_free_slots() {
let mut pool = GraphemePool::new();
let _ = pool.alloc("a");
let _ = pool.alloc("b");
let _ = pool.alloc("c");
let result = pool.compact();
assert!(result.old_to_new.is_empty());
assert_eq!(result.slots_freed, 0);
assert!(!result.has_remappings());
assert_eq!(pool.total_slots(), 3);
assert_eq!(pool.active_count(), 3);
}
#[test]
fn test_compact_single_gap() {
let mut pool = GraphemePool::new();
let id1 = pool.alloc("alpha");
let id2 = pool.alloc("beta");
let id3 = pool.alloc("gamma");
pool.decref(id2);
assert_eq!(pool.total_slots(), 3);
assert_eq!(pool.active_count(), 2);
assert_eq!(pool.free_count(), 1);
let result = pool.compact();
assert_eq!(result.slots_freed, 1);
assert!(result.has_remappings());
assert_eq!(pool.total_slots(), 2);
assert_eq!(pool.active_count(), 2);
assert_eq!(pool.free_count(), 0);
let new_id1 = result.remap(id1.pool_id()).unwrap_or_else(|| id1.pool_id());
let new_id3 = result.remap(id3.pool_id()).unwrap_or_else(|| id3.pool_id());
assert_eq!(new_id1, 1);
assert_eq!(new_id3, 2);
let remapped1 = GraphemeId::new(new_id1, id1.width() as u8);
let remapped3 = GraphemeId::new(new_id3, id3.width() as u8);
assert_eq!(pool.get(remapped1), Some("alpha"));
assert_eq!(pool.get(remapped3), Some("gamma"));
}
#[test]
fn test_compact_multiple_gaps() {
let mut pool = GraphemePool::new();
let ids: Vec<_> = (0..10).map(|i| pool.alloc(&format!("g{i}"))).collect();
for (i, id) in ids.iter().enumerate() {
if i % 2 == 1 {
pool.decref(*id);
}
}
assert_eq!(pool.total_slots(), 10);
assert_eq!(pool.active_count(), 5);
assert_eq!(pool.free_count(), 5);
let result = pool.compact();
assert_eq!(result.slots_freed, 5);
assert_eq!(pool.total_slots(), 5);
assert_eq!(pool.active_count(), 5);
assert_eq!(pool.free_count(), 0);
for (i, id) in ids.iter().enumerate() {
if i % 2 == 0 {
let new_pool_id = result.remap(id.pool_id()).unwrap_or_else(|| id.pool_id());
let remapped = GraphemeId::new(new_pool_id, id.width() as u8);
assert_eq!(pool.get(remapped), Some(format!("g{i}").as_str()));
}
}
}
#[test]
fn test_compact_all_freed() {
let mut pool = GraphemePool::new();
let ids: Vec<_> = (0..5).map(|i| pool.alloc(&format!("g{i}"))).collect();
for id in &ids {
pool.decref(*id);
}
assert_eq!(pool.total_slots(), 5);
assert_eq!(pool.active_count(), 0);
assert_eq!(pool.free_count(), 5);
let result = pool.compact();
assert_eq!(result.slots_freed, 5);
assert_eq!(pool.total_slots(), 0);
assert_eq!(pool.active_count(), 0);
assert_eq!(pool.free_count(), 0);
assert!(result.old_to_new.is_empty());
}
#[test]
fn test_compact_preserves_refcounts() {
let mut pool = GraphemePool::new();
let id1 = pool.alloc("alpha");
pool.incref(id1);
pool.incref(id1);
let id2 = pool.alloc("beta");
pool.decref(id2);
let id3 = pool.alloc("gamma");
assert_eq!(pool.refcount(id1), 3);
assert_eq!(pool.refcount(id3), 1);
let result = pool.compact();
let new_id1 = result.remap(id1.pool_id()).unwrap_or_else(|| id1.pool_id());
let new_id3 = result.remap(id3.pool_id()).unwrap_or_else(|| id3.pool_id());
let remapped1 = GraphemeId::new(new_id1, id1.width() as u8);
let remapped3 = GraphemeId::new(new_id3, id3.width() as u8);
assert_eq!(pool.refcount(remapped1), 3);
assert_eq!(pool.refcount(remapped3), 1);
}
#[test]
fn test_compact_preserves_widths() {
let mut pool = GraphemePool::new();
let id1 = pool.alloc("A"); let id2 = pool.alloc("👍"); let id3 = pool.alloc("世");
pool.decref(id2);
let result = pool.compact();
let new_id1 = result.remap(id1.pool_id()).unwrap_or_else(|| id1.pool_id());
let new_id3 = result.remap(id3.pool_id()).unwrap_or_else(|| id3.pool_id());
assert_eq!(pool.get_by_pool_id(new_id1), Some("A"));
assert_eq!(pool.get_by_pool_id(new_id3), Some("世"));
}
#[test]
fn test_compact_updates_index() {
let mut pool = GraphemePool::new();
let _id1 = pool.alloc("alpha");
let id2 = pool.alloc("beta");
let _id3 = pool.alloc("gamma");
pool.decref(id2);
let _ = pool.compact();
let interned1 = pool.intern("alpha");
let interned3 = pool.intern("gamma");
assert_eq!(pool.refcount(interned1), 2);
assert_eq!(pool.refcount(interned3), 2);
let interned2 = pool.intern("beta");
assert_eq!(pool.refcount(interned2), 1);
}
#[test]
fn test_compact_result_remap_helper() {
let mut pool = GraphemePool::new();
let id1 = pool.alloc("a");
let id2 = pool.alloc("b");
pool.decref(id1);
let result = pool.compact();
assert_eq!(result.remap(id2.pool_id()), Some(1));
assert_eq!(result.remap(id1.pool_id()), None);
assert_eq!(result.remap(9999), None);
}
#[test]
fn test_compact_result_has_remappings() {
let mut pool = GraphemePool::new();
let result1 = pool.compact();
assert!(!result1.has_remappings());
let id = pool.alloc("test");
pool.decref(id);
let result2 = pool.compact();
assert!(!result2.has_remappings());
let _ = pool.alloc("a");
let id_to_free = pool.alloc("b");
let _ = pool.alloc("c");
pool.decref(id_to_free);
let result3 = pool.compact();
assert!(result3.has_remappings());
}
#[test]
fn test_compact_bytes_saved_estimate() {
let mut pool = GraphemePool::new();
for i in 0..100 {
let _ = pool.alloc(&format!("grapheme_string_{i}"));
}
let ids: Vec<_> = pool.iter_active().map(|(id, _)| id).collect();
for (i, id) in ids.iter().enumerate() {
if i % 2 == 0 {
pool.decref_by_pool_id(*id);
}
}
let result = pool.compact();
assert!(result.bytes_saved > 0);
assert_eq!(result.slots_freed, 50);
}
#[test]
fn test_compact_large_pool() {
let mut pool = GraphemePool::new();
let ids: Vec<_> = (0..2000).map(|i| pool.alloc(&format!("g{i}"))).collect();
for (i, id) in ids.iter().enumerate() {
if i % 3 != 0 {
pool.decref(*id);
}
}
assert!(pool.should_compact());
let result = pool.compact();
assert!(result.slots_freed > 1300);
assert!(result.slots_freed < 1400);
assert!(!pool.should_compact());
assert_eq!(pool.free_count(), 0);
assert_eq!(pool.active_count(), pool.total_slots());
for (i, id) in ids.iter().enumerate() {
if i % 3 == 0 {
let new_pool_id = result.remap(id.pool_id()).unwrap_or_else(|| id.pool_id());
let remapped = GraphemeId::new(new_pool_id, id.width() as u8);
assert_eq!(pool.get(remapped), Some(format!("g{i}").as_str()));
}
}
}
#[test]
fn test_compact_then_alloc_reuses_correctly() {
let mut pool = GraphemePool::new();
let _ = pool.alloc("a");
let id2 = pool.alloc("b");
let _ = pool.alloc("c");
pool.decref(id2);
let _ = pool.compact();
assert_eq!(pool.total_slots(), 2);
assert_eq!(pool.free_count(), 0);
let id_new = pool.alloc("new");
assert_eq!(id_new.pool_id(), 3);
assert_eq!(pool.total_slots(), 3);
}
#[test]
fn test_compact_idempotent() {
let mut pool = GraphemePool::new();
let _ = pool.alloc("a");
let id2 = pool.alloc("b");
let _ = pool.alloc("c");
pool.decref(id2);
let result1 = pool.compact();
assert_eq!(result1.slots_freed, 1);
let result2 = pool.compact();
assert_eq!(result2.slots_freed, 0);
assert!(!result2.has_remappings());
}
#[test]
fn test_peak_usage_tracking() {
let mut pool = GraphemePool::new();
assert_eq!(pool.peak_usage(), 0);
let id1 = pool.alloc("a");
assert_eq!(pool.peak_usage(), 1);
let id2 = pool.alloc("b");
assert_eq!(pool.peak_usage(), 2);
let id3 = pool.alloc("c");
assert_eq!(pool.peak_usage(), 3);
pool.decref(id2);
assert_eq!(pool.peak_usage(), 3);
pool.decref(id1);
assert_eq!(pool.peak_usage(), 3);
let _ = pool.alloc("d");
assert_eq!(pool.peak_usage(), 3);
pool.decref(id3);
let _ = pool.alloc("e");
let _ = pool.alloc("f");
let _ = pool.alloc("g");
assert_eq!(pool.peak_usage(), 4);
}
#[test]
fn test_total_allocations_tracking() {
let mut pool = GraphemePool::new();
assert_eq!(pool.total_allocations(), 0);
let _ = pool.alloc("a");
assert_eq!(pool.total_allocations(), 1);
let _ = pool.alloc("b");
assert_eq!(pool.total_allocations(), 2);
let id = pool.alloc("c");
pool.decref(id);
let _ = pool.alloc("d");
assert_eq!(pool.total_allocations(), 4);
let _ = pool.alloc_batch(&["e", "f", "g"]);
assert_eq!(pool.total_allocations(), 7);
}
#[test]
fn test_total_frees_tracking() {
let mut pool = GraphemePool::new();
assert_eq!(pool.total_frees(), 0);
let id1 = pool.alloc("a");
let id2 = pool.alloc("b");
let id3 = pool.alloc("c");
pool.decref(id1);
assert_eq!(pool.total_frees(), 1);
pool.decref(id2);
assert_eq!(pool.total_frees(), 2);
pool.incref(id3);
pool.decref(id3);
assert_eq!(pool.total_frees(), 2);
pool.decref(id3);
assert_eq!(pool.total_frees(), 3);
pool.decref(id1);
assert_eq!(pool.total_frees(), 3);
}
#[test]
fn test_stats_includes_lifetime_fields() {
let mut pool = GraphemePool::new();
let id1 = pool.alloc("a");
let id2 = pool.alloc("b");
let _ = pool.alloc("c");
pool.decref(id1);
pool.decref(id2);
let stats = pool.stats();
assert_eq!(stats.peak_usage, 3);
assert_eq!(stats.total_allocations, 3);
assert_eq!(stats.total_frees, 2);
assert_eq!(stats.active_slots, 1);
assert_eq!(stats.free_slots, 2);
assert_eq!(stats.total_slots, 3);
}
#[test]
fn test_clear_preserves_lifetime_stats() {
let mut pool = GraphemePool::new();
let ids: Vec<_> = (0..10).map(|i| pool.alloc(&format!("g{i}"))).collect();
for id in &ids[0..5] {
pool.decref(*id);
}
let peak_before = pool.peak_usage();
let allocs_before = pool.total_allocations();
let frees_before = pool.total_frees();
assert_eq!(peak_before, 10);
assert_eq!(allocs_before, 10);
assert_eq!(frees_before, 5);
pool.clear();
assert_eq!(pool.peak_usage(), 10);
assert_eq!(pool.total_allocations(), 10);
assert_eq!(pool.total_frees(), 5);
assert_eq!(pool.active_count(), 0);
assert_eq!(pool.total_slots(), 0);
}
#[test]
fn test_lifetime_stats_in_stats_struct() {
let pool = GraphemePool::new();
let stats = pool.stats();
assert_eq!(stats.peak_usage, 0);
assert_eq!(stats.total_allocations, 0);
assert_eq!(stats.total_frees, 0);
}
#[test]
fn test_intern_counts_as_allocation() {
let mut pool = GraphemePool::new();
let _ = pool.intern("new");
assert_eq!(pool.total_allocations(), 1);
let _ = pool.intern("new");
assert_eq!(pool.total_allocations(), 1);
let _ = pool.alloc("new");
assert_eq!(pool.total_allocations(), 2);
}
#[test]
fn test_compact_threshold_default() {
let pool = GraphemePool::new();
assert!((pool.compact_threshold() - 0.5).abs() < f32::EPSILON);
}
#[test]
fn test_compact_threshold_set_get() {
let mut pool = GraphemePool::new();
pool.set_compact_threshold(0.3);
assert!((pool.compact_threshold() - 0.3).abs() < f32::EPSILON);
pool.set_compact_threshold(0.7);
assert!((pool.compact_threshold() - 0.7).abs() < f32::EPSILON);
}
#[test]
fn test_compact_threshold_clamped() {
let mut pool = GraphemePool::new();
pool.set_compact_threshold(-0.5);
assert!((pool.compact_threshold() - 0.0).abs() < f32::EPSILON);
pool.set_compact_threshold(1.5);
assert!((pool.compact_threshold() - 1.0).abs() < f32::EPSILON);
pool.set_compact_threshold(0.0);
assert!((pool.compact_threshold() - 0.0).abs() < f32::EPSILON);
pool.set_compact_threshold(1.0);
assert!((pool.compact_threshold() - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_compact_threshold_affects_should_compact() {
let mut pool = GraphemePool::new();
let ids: Vec<_> = (0..2000).map(|i| pool.alloc(&format!("g{i}"))).collect();
for (i, id) in ids.iter().enumerate() {
if i % 5 < 2 {
pool.decref(*id);
}
}
pool.set_compact_threshold(0.5);
assert!(!pool.should_compact());
pool.set_compact_threshold(0.3);
assert!(pool.should_compact());
pool.set_compact_threshold(0.6);
assert!(!pool.should_compact());
}
#[test]
fn test_compact_threshold_builder_pattern() {
let mut pool = GraphemePool::new();
pool.set_compact_threshold(0.4).set_soft_limit(500);
assert!((pool.compact_threshold() - 0.4).abs() < f32::EPSILON);
assert_eq!(pool.soft_limit(), 500);
}
}