use super::Handle;
use std::collections::HashMap;
use std::sync::{
LazyLock, Mutex,
atomic::{AtomicUsize, Ordering},
};
use std::time::{Duration, Instant};
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StoreType {
Generic = 0,
Font = 1,
Image = 2,
Colorspace = 3,
Path = 4,
Shade = 5,
Glyph = 6,
DisplayList = 7,
Document = 8,
Page = 9,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EvictionPolicy {
LRU = 0,
LFU = 1,
FIFO = 2,
Random = 3,
}
#[derive(Debug, Clone)]
pub struct StoreItem {
pub item_type: StoreType,
pub handle: Handle,
pub size: usize,
pub last_access: Instant,
pub access_count: u64,
pub created: Instant,
pub key: Vec<u8>,
pub evictable: bool,
pub refs: u32,
}
impl Default for StoreItem {
fn default() -> Self {
Self {
item_type: StoreType::Generic,
handle: 0,
size: 0,
last_access: Instant::now(),
access_count: 0,
created: Instant::now(),
key: Vec::new(),
evictable: true,
refs: 1,
}
}
}
#[derive(Debug)]
pub struct Store {
pub max_size: usize,
pub current_size: usize,
pub items: HashMap<u64, StoreItem>,
pub key_map: HashMap<Vec<u8>, u64>,
pub policy: EvictionPolicy,
pub total_stored: u64,
pub total_evicted: u64,
pub hits: u64,
pub misses: u64,
pub type_limits: HashMap<StoreType, usize>,
pub type_sizes: HashMap<StoreType, usize>,
}
impl Default for Store {
fn default() -> Self {
Self {
max_size: 256 * 1024 * 1024, current_size: 0,
items: HashMap::new(),
key_map: HashMap::new(),
policy: EvictionPolicy::LRU,
total_stored: 0,
total_evicted: 0,
hits: 0,
misses: 0,
type_limits: HashMap::new(),
type_sizes: HashMap::new(),
}
}
}
pub static STORE: LazyLock<Mutex<Store>> = LazyLock::new(|| Mutex::new(Store::default()));
static STORE_ID_COUNTER: AtomicUsize = AtomicUsize::new(1);
fn new_store_id() -> u64 {
STORE_ID_COUNTER.fetch_add(1, Ordering::SeqCst) as u64
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_store(_ctx: Handle, max_size: usize) -> i32 {
if let Ok(mut store) = STORE.lock() {
store.max_size = max_size;
store.items.clear();
store.key_map.clear();
store.current_size = 0;
store.total_stored = 0;
store.total_evicted = 0;
store.hits = 0;
store.misses = 0;
return 1;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_set_max_size(_ctx: Handle, max_size: usize) {
if let Ok(mut store) = STORE.lock() {
store.max_size = max_size;
evict_to_size(&mut store, max_size);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_max_size(_ctx: Handle) -> usize {
if let Ok(store) = STORE.lock() {
return store.max_size;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_current_size(_ctx: Handle) -> usize {
if let Ok(store) = STORE.lock() {
return store.current_size;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_set_policy(_ctx: Handle, policy: i32) {
let p = match policy {
1 => EvictionPolicy::LFU,
2 => EvictionPolicy::FIFO,
3 => EvictionPolicy::Random,
_ => EvictionPolicy::LRU,
};
if let Ok(mut store) = STORE.lock() {
store.policy = p;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_set_type_limit(_ctx: Handle, item_type: i32, max_size: usize) {
let t = match item_type {
1 => StoreType::Font,
2 => StoreType::Image,
3 => StoreType::Colorspace,
4 => StoreType::Path,
5 => StoreType::Shade,
6 => StoreType::Glyph,
7 => StoreType::DisplayList,
8 => StoreType::Document,
9 => StoreType::Page,
_ => StoreType::Generic,
};
if let Ok(mut store) = STORE.lock() {
if max_size > 0 {
store.type_limits.insert(t, max_size);
} else {
store.type_limits.remove(&t);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_item(
_ctx: Handle,
item_type: i32,
handle: Handle,
size: usize,
key: *const u8,
key_len: usize,
) -> u64 {
let t = match item_type {
1 => StoreType::Font,
2 => StoreType::Image,
3 => StoreType::Colorspace,
4 => StoreType::Path,
5 => StoreType::Shade,
6 => StoreType::Glyph,
7 => StoreType::DisplayList,
8 => StoreType::Document,
9 => StoreType::Page,
_ => StoreType::Generic,
};
let key_data = if key.is_null() || key_len == 0 {
Vec::new()
} else {
unsafe { std::slice::from_raw_parts(key, key_len) }.to_vec()
};
if let Ok(mut store) = STORE.lock() {
if store.current_size + size > store.max_size {
let target_size = store.max_size.saturating_sub(size);
evict_to_size(&mut store, target_size);
}
if let Some(&limit) = store.type_limits.get(&t) {
let current = store.type_sizes.get(&t).copied().unwrap_or(0);
if current + size > limit {
evict_type_to_size(&mut store, t, limit.saturating_sub(size));
}
}
let id = new_store_id();
let item = StoreItem {
item_type: t,
handle,
size,
last_access: Instant::now(),
access_count: 0,
created: Instant::now(),
key: key_data.clone(),
evictable: true,
refs: 1,
};
store.current_size += size;
*store.type_sizes.entry(t).or_insert(0) += size;
store.items.insert(id, item);
if !key_data.is_empty() {
store.key_map.insert(key_data, id);
}
store.total_stored += 1;
return id;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_find(_ctx: Handle, key: *const u8, key_len: usize) -> Handle {
if key.is_null() || key_len == 0 {
return 0;
}
let key_data = unsafe { std::slice::from_raw_parts(key, key_len) };
if let Ok(mut store) = STORE.lock() {
if let Some(&id) = store.key_map.get(key_data) {
let result = if let Some(item) = store.items.get_mut(&id) {
item.last_access = Instant::now();
item.access_count += 1;
Some(item.handle)
} else {
None
};
if let Some(handle) = result {
store.hits += 1;
return handle;
}
}
store.misses += 1;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_find_by_id(_ctx: Handle, id: u64) -> Handle {
if let Ok(mut store) = STORE.lock() {
let result = if let Some(item) = store.items.get_mut(&id) {
item.last_access = Instant::now();
item.access_count += 1;
Some(item.handle)
} else {
None
};
if let Some(handle) = result {
store.hits += 1;
return handle;
}
store.misses += 1;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_remove(_ctx: Handle, id: u64) -> Handle {
if let Ok(mut store) = STORE.lock() {
if let Some(item) = store.items.remove(&id) {
if !item.key.is_empty() {
store.key_map.remove(&item.key);
}
store.current_size = store.current_size.saturating_sub(item.size);
if let Some(type_size) = store.type_sizes.get_mut(&item.item_type) {
*type_size = type_size.saturating_sub(item.size);
}
return item.handle;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_remove_by_key(_ctx: Handle, key: *const u8, key_len: usize) -> Handle {
if key.is_null() || key_len == 0 {
return 0;
}
let key_data = unsafe { std::slice::from_raw_parts(key, key_len) };
if let Ok(mut store) = STORE.lock() {
if let Some(id) = store.key_map.remove(key_data) {
if let Some(item) = store.items.remove(&id) {
store.current_size = store.current_size.saturating_sub(item.size);
if let Some(type_size) = store.type_sizes.get_mut(&item.item_type) {
*type_size = type_size.saturating_sub(item.size);
}
return item.handle;
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_keep(_ctx: Handle, id: u64) -> u64 {
if let Ok(mut store) = STORE.lock() {
if let Some(item) = store.items.get_mut(&id) {
item.refs = item.refs.saturating_add(1);
return id;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_drop(_ctx: Handle, id: u64) {
if let Ok(mut store) = STORE.lock() {
let should_remove = {
if let Some(item) = store.items.get_mut(&id) {
item.refs = item.refs.saturating_sub(1);
item.refs == 0
} else {
false
}
};
if should_remove {
if let Some(item) = store.items.remove(&id) {
if !item.key.is_empty() {
store.key_map.remove(&item.key);
}
store.current_size = store.current_size.saturating_sub(item.size);
if let Some(type_size) = store.type_sizes.get_mut(&item.item_type) {
*type_size = type_size.saturating_sub(item.size);
}
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_set_evictable(_ctx: Handle, id: u64, evictable: i32) {
if let Ok(mut store) = STORE.lock() {
if let Some(item) = store.items.get_mut(&id) {
item.evictable = evictable != 0;
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_item_size(_ctx: Handle, id: u64) -> usize {
if let Ok(store) = STORE.lock() {
if let Some(item) = store.items.get(&id) {
return item.size;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_item_type(_ctx: Handle, id: u64) -> i32 {
if let Ok(store) = STORE.lock() {
if let Some(item) = store.items.get(&id) {
return item.item_type as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_item_access_count(_ctx: Handle, id: u64) -> u64 {
if let Ok(store) = STORE.lock() {
if let Some(item) = store.items.get(&id) {
return item.access_count;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_item_age(_ctx: Handle, id: u64) -> u64 {
if let Ok(store) = STORE.lock() {
if let Some(item) = store.items.get(&id) {
return item.created.elapsed().as_millis() as u64;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_count(_ctx: Handle) -> usize {
if let Ok(store) = STORE.lock() {
return store.items.len();
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_hits(_ctx: Handle) -> u64 {
if let Ok(store) = STORE.lock() {
return store.hits;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_misses(_ctx: Handle) -> u64 {
if let Ok(store) = STORE.lock() {
return store.misses;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_hit_rate(_ctx: Handle) -> f32 {
if let Ok(store) = STORE.lock() {
let total = store.hits + store.misses;
if total == 0 {
return 0.0;
}
return store.hits as f32 / total as f32;
}
0.0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_total_stored(_ctx: Handle) -> u64 {
if let Ok(store) = STORE.lock() {
return store.total_stored;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_total_evicted(_ctx: Handle) -> u64 {
if let Ok(store) = STORE.lock() {
return store.total_evicted;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_type_size(_ctx: Handle, item_type: i32) -> usize {
let t = match item_type {
1 => StoreType::Font,
2 => StoreType::Image,
3 => StoreType::Colorspace,
4 => StoreType::Path,
5 => StoreType::Shade,
6 => StoreType::Glyph,
7 => StoreType::DisplayList,
8 => StoreType::Document,
9 => StoreType::Page,
_ => StoreType::Generic,
};
if let Ok(store) = STORE.lock() {
return store.type_sizes.get(&t).copied().unwrap_or(0);
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_type_count(_ctx: Handle, item_type: i32) -> usize {
let t = match item_type {
1 => StoreType::Font,
2 => StoreType::Image,
3 => StoreType::Colorspace,
4 => StoreType::Path,
5 => StoreType::Shade,
6 => StoreType::Glyph,
7 => StoreType::DisplayList,
8 => StoreType::Document,
9 => StoreType::Page,
_ => StoreType::Generic,
};
if let Ok(store) = STORE.lock() {
return store.items.values().filter(|i| i.item_type == t).count();
}
0
}
fn evict_to_size(store: &mut Store, target_size: usize) {
while store.current_size > target_size && !store.items.is_empty() {
let victim_id = select_victim(store);
if victim_id == 0 {
break;
}
if let Some(item) = store.items.remove(&victim_id) {
if !item.key.is_empty() {
store.key_map.remove(&item.key);
}
store.current_size = store.current_size.saturating_sub(item.size);
if let Some(type_size) = store.type_sizes.get_mut(&item.item_type) {
*type_size = type_size.saturating_sub(item.size);
}
store.total_evicted += 1;
}
}
}
fn evict_type_to_size(store: &mut Store, item_type: StoreType, target_size: usize) {
let current = store.type_sizes.get(&item_type).copied().unwrap_or(0);
if current <= target_size {
return;
}
let mut victims: Vec<u64> = store
.items
.iter()
.filter(|(_, item)| item.item_type == item_type && item.evictable && item.refs <= 1)
.map(|(&id, _)| id)
.collect();
victims.sort_by(|&a, &b| {
let item_a = store.items.get(&a).unwrap();
let item_b = store.items.get(&b).unwrap();
match store.policy {
EvictionPolicy::LRU => item_a.last_access.cmp(&item_b.last_access),
EvictionPolicy::LFU => item_a.access_count.cmp(&item_b.access_count),
EvictionPolicy::FIFO => item_a.created.cmp(&item_b.created),
EvictionPolicy::Random => std::cmp::Ordering::Equal,
}
});
let mut evicted_size = 0;
let needed = current.saturating_sub(target_size);
for victim_id in victims {
if evicted_size >= needed {
break;
}
if let Some(item) = store.items.remove(&victim_id) {
if !item.key.is_empty() {
store.key_map.remove(&item.key);
}
evicted_size += item.size;
store.current_size = store.current_size.saturating_sub(item.size);
if let Some(type_size) = store.type_sizes.get_mut(&item.item_type) {
*type_size = type_size.saturating_sub(item.size);
}
store.total_evicted += 1;
}
}
}
fn select_victim(store: &Store) -> u64 {
let evictable: Vec<_> = store
.items
.iter()
.filter(|(_, item)| item.evictable && item.refs <= 1)
.collect();
if evictable.is_empty() {
return 0;
}
match store.policy {
EvictionPolicy::LRU => evictable
.iter()
.min_by_key(|(_, item)| item.last_access)
.map(|(id, _)| **id)
.unwrap_or(0),
EvictionPolicy::LFU => evictable
.iter()
.min_by_key(|(_, item)| item.access_count)
.map(|(id, _)| **id)
.unwrap_or(0),
EvictionPolicy::FIFO => evictable
.iter()
.min_by_key(|(_, item)| item.created)
.map(|(id, _)| **id)
.unwrap_or(0),
EvictionPolicy::Random => {
evictable.first().map(|(id, _)| **id).unwrap_or(0)
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_evict(_ctx: Handle, target_size: usize) -> usize {
if let Ok(mut store) = STORE.lock() {
let before = store.items.len();
evict_to_size(&mut store, target_size);
return before - store.items.len();
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_evict_type(_ctx: Handle, item_type: i32) -> usize {
let t = match item_type {
1 => StoreType::Font,
2 => StoreType::Image,
3 => StoreType::Colorspace,
4 => StoreType::Path,
5 => StoreType::Shade,
6 => StoreType::Glyph,
7 => StoreType::DisplayList,
8 => StoreType::Document,
9 => StoreType::Page,
_ => StoreType::Generic,
};
if let Ok(mut store) = STORE.lock() {
let before = store.items.len();
evict_type_to_size(&mut store, t, 0);
return before - store.items.len();
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_evict_old(_ctx: Handle, max_age_ms: u64) -> usize {
if let Ok(mut store) = STORE.lock() {
let max_age = Duration::from_millis(max_age_ms);
let now = Instant::now();
let victims: Vec<u64> = store
.items
.iter()
.filter(|(_, item)| {
item.evictable && item.refs <= 1 && now.duration_since(item.last_access) > max_age
})
.map(|(&id, _)| id)
.collect();
let count = victims.len();
for id in victims {
if let Some(item) = store.items.remove(&id) {
if !item.key.is_empty() {
store.key_map.remove(&item.key);
}
store.current_size = store.current_size.saturating_sub(item.size);
if let Some(type_size) = store.type_sizes.get_mut(&item.item_type) {
*type_size = type_size.saturating_sub(item.size);
}
store.total_evicted += 1;
}
}
return count;
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_clear(_ctx: Handle) {
if let Ok(mut store) = STORE.lock() {
let count = store.items.len() as u64;
store.items.clear();
store.key_map.clear();
store.current_size = 0;
store.type_sizes.clear();
store.total_evicted += count;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_reset_stats(_ctx: Handle) {
if let Ok(mut store) = STORE.lock() {
store.hits = 0;
store.misses = 0;
store.total_stored = 0;
store.total_evicted = 0;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_store_debug(_ctx: Handle) {
if let Ok(store) = STORE.lock() {
eprintln!(
"Store: {} items, {} / {} bytes",
store.items.len(),
store.current_size,
store.max_size
);
eprintln!(
" Hits: {}, Misses: {}, Rate: {:.1}%",
store.hits,
store.misses,
fz_store_hit_rate(0) * 100.0
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
use std::sync::atomic::{AtomicU64, Ordering};
static TEST_COUNTER: AtomicU64 = AtomicU64::new(0);
fn unique_key(prefix: &str) -> Vec<u8> {
let id = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
format!("{}_{}", prefix, id).into_bytes()
}
#[test]
#[serial(store)]
fn test_store_item() {
let key = unique_key("store_item");
let id = fz_store_item(0, 2, 100, 1024, key.as_ptr(), key.len());
assert!(id > 0);
let found = fz_store_find(0, key.as_ptr(), key.len());
assert_eq!(found, 100);
fz_store_remove(0, id);
}
#[test]
#[serial(store)]
fn test_store_find() {
let key = unique_key("find_test");
let handle: Handle = 42424242;
let id = fz_store_item(0, 1, handle, 100, key.as_ptr(), key.len());
let found = fz_store_find(0, key.as_ptr(), key.len());
assert_eq!(found, handle);
fz_store_remove(0, id);
}
#[test]
#[serial(store)]
fn test_store_miss() {
let key = unique_key("nonexistent_key_that_was_never_added");
let found = fz_store_find(0, key.as_ptr(), key.len());
assert_eq!(found, 0);
}
#[test]
#[serial(store)]
fn test_store_eviction() {
let prefix = unique_key("evict");
let prefix_str = String::from_utf8_lossy(&prefix);
let original_max = if let Ok(store) = STORE.lock() {
store.max_size
} else {
return;
};
let mut ids = Vec::new();
for i in 0..10 {
let key = format!("{}_{}", prefix_str, i).into_bytes();
let id = fz_store_item(0, 2, (i + 1000) as Handle, 100, key.as_ptr(), key.len());
ids.push(id);
}
assert!(!ids.iter().all(|&id| id == 0));
for id in ids {
if id > 0 {
fz_store_remove(0, id);
}
}
fz_store_set_max_size(0, original_max);
}
#[test]
#[serial(store)]
fn test_store_remove() {
let key = unique_key("remove_test");
let handle: Handle = 99999999;
let id = fz_store_item(0, 1, handle, 50, key.as_ptr(), key.len());
assert!(id > 0);
let found_before = fz_store_find(0, key.as_ptr(), key.len());
assert_eq!(found_before, handle);
let removed = fz_store_remove(0, id);
assert_eq!(removed, handle);
let found_after = fz_store_find(0, key.as_ptr(), key.len());
assert_eq!(found_after, 0);
}
#[test]
#[serial(store)]
fn test_store_type_tracking() {
let key1 = unique_key("font");
let key2 = unique_key("image");
let font_type = 1;
let image_type = 2;
let id1 = fz_store_item(0, font_type, 1, 100, key1.as_ptr(), key1.len());
let id2 = fz_store_item(0, image_type, 2, 200, key2.as_ptr(), key2.len());
assert!(fz_store_type_size(0, font_type) >= 100);
assert!(fz_store_type_size(0, image_type) >= 200);
assert!(fz_store_type_count(0, font_type) >= 1);
assert!(fz_store_type_count(0, image_type) >= 1);
fz_store_remove(0, id1);
fz_store_remove(0, id2);
}
#[test]
#[serial(store)]
fn test_store_clear() {
let prefix = unique_key("clear");
let prefix_str = String::from_utf8_lossy(&prefix);
let mut ids = Vec::new();
for i in 0..5 {
let key = format!("{}_{}", prefix_str, i).into_bytes();
let id = fz_store_item(0, 0, (i + 2000) as Handle, 10, key.as_ptr(), key.len());
ids.push((id, key));
}
let count_before = fz_store_count(0);
for (id, key) in &ids {
if *id > 0 {
let found = fz_store_find(0, key.as_ptr(), key.len());
assert!(found > 0);
}
}
fz_store_clear(0);
let count_after = fz_store_count(0);
assert!(count_after <= count_before, "clear should remove items");
for (_id, key) in &ids {
let found = fz_store_find(0, key.as_ptr(), key.len());
assert_eq!(found, 0, "item should be removed after clear");
}
}
#[test]
#[serial(store)]
fn test_hit_rate() {
if let Ok(mut store) = STORE.lock() {
store.hits = 0;
store.misses = 0;
}
let key = unique_key("hit_rate");
let id = fz_store_item(0, 0, 1, 10, key.as_ptr(), key.len());
fz_store_find(0, key.as_ptr(), key.len());
fz_store_find(0, key.as_ptr(), key.len());
let miss_key = unique_key("miss_key_not_stored");
fz_store_find(0, miss_key.as_ptr(), miss_key.len());
let rate = fz_store_hit_rate(0);
assert!(rate >= 0.0 && rate <= 1.0);
fz_store_remove(0, id);
}
#[test]
#[serial(store)]
fn test_non_evictable() {
let key1 = unique_key("pinned");
let handle1: Handle = 88888888;
let id1 = fz_store_item(0, 0, handle1, 150, key1.as_ptr(), key1.len());
if id1 == 0 {
return; }
fz_store_set_evictable(0, id1, 0);
let found = fz_store_find(0, key1.as_ptr(), key1.len());
if found == handle1 {
fz_store_set_evictable(0, id1, 1);
}
fz_store_remove(0, id1);
}
#[test]
#[serial(store)]
fn test_store_find_null_key() {
assert_eq!(fz_store_find(0, std::ptr::null(), 0), 0);
assert_eq!(fz_store_find(0, b"x".as_ptr(), 0), 0);
}
#[test]
#[serial(store)]
fn test_store_remove_by_key() {
let key = unique_key("remove_by_key");
let handle: Handle = 77777777;
let id = fz_store_item(0, 0, handle, 50, key.as_ptr(), key.len());
if id > 0 {
let removed = fz_store_remove_by_key(0, key.as_ptr(), key.len());
assert_eq!(removed, handle);
}
}
#[test]
#[serial(store)]
fn test_store_remove_by_key_null() {
assert_eq!(fz_store_remove_by_key(0, std::ptr::null(), 0), 0);
}
#[test]
#[serial(store)]
fn test_store_find_by_id() {
let key = unique_key("find_by_id");
let handle: Handle = 33333333;
let id = fz_store_item(0, 0, handle, 50, key.as_ptr(), key.len());
if id > 0 {
let found = fz_store_find_by_id(0, id);
assert_eq!(found, handle);
fz_store_remove(0, id);
}
}
#[test]
#[serial(store)]
fn test_store_find_by_id_miss() {
assert_eq!(fz_store_find_by_id(0, 999999999999), 0);
}
#[test]
#[serial(store)]
fn test_store_keep() {
let key = unique_key("keep_test");
let id = fz_store_item(0, 0, 1, 10, key.as_ptr(), key.len());
if id > 0 {
let kept = fz_store_keep(0, id);
assert_eq!(kept, id);
fz_store_remove(0, id);
}
}
#[test]
#[serial(store)]
fn test_store_keep_invalid() {
assert_eq!(fz_store_keep(0, 999999999999), 0);
}
#[test]
#[serial(store)]
fn test_store_drop() {
let key = unique_key("drop_test");
let id = fz_store_item(0, 0, 1, 10, key.as_ptr(), key.len());
if id > 0 {
fz_store_drop(0, id);
}
}
#[test]
#[serial(store)]
fn test_store_item_size() {
let key = unique_key("size_test");
let id = fz_store_item(0, 0, 1, 123, key.as_ptr(), key.len());
if id > 0 {
assert_eq!(fz_store_item_size(0, id), 123);
fz_store_remove(0, id);
}
}
#[test]
#[serial(store)]
fn test_store_item_type() {
let key = unique_key("type_test");
let id = fz_store_item(0, 2, 1, 10, key.as_ptr(), key.len());
if id > 0 {
assert_eq!(fz_store_item_type(0, id), 2);
fz_store_remove(0, id);
}
}
#[test]
#[serial(store)]
fn test_store_item_access_count() {
let key = unique_key("access_test");
let id = fz_store_item(0, 0, 1, 10, key.as_ptr(), key.len());
if id > 0 {
fz_store_find(0, key.as_ptr(), key.len());
fz_store_find(0, key.as_ptr(), key.len());
assert!(fz_store_item_access_count(0, id) >= 2);
fz_store_remove(0, id);
}
}
#[test]
#[serial(store)]
fn test_store_new_and_max_size() {
let result = fz_new_store(0, 1024 * 1024);
assert_eq!(result, 1);
assert!(fz_store_max_size(0) > 0);
fz_store_set_max_size(0, 2048 * 1024);
assert!(fz_store_max_size(0) >= 2048 * 1024);
}
#[test]
#[serial(store)]
fn test_store_set_policy() {
fz_store_set_policy(0, 1);
fz_store_set_policy(0, 2);
fz_store_set_policy(0, 3);
fz_store_set_policy(0, 99);
}
#[test]
#[serial(store)]
fn test_store_set_type_limit() {
fz_store_set_type_limit(0, 2, 1000);
fz_store_set_type_limit(0, 2, 0);
}
#[test]
#[serial(store)]
fn test_store_evict() {
fz_store_evict(0, 0);
}
#[test]
#[serial(store)]
fn test_store_evict_type() {
fz_store_evict_type(0, 2);
}
#[test]
#[serial(store)]
fn test_store_evict_old() {
fz_store_evict_old(0, 0);
}
#[test]
#[serial(store)]
fn test_store_reset_stats() {
fz_store_reset_stats(0);
}
#[test]
#[serial(store)]
fn test_store_total_stored_evicted() {
let _ = fz_store_total_stored(0);
let _ = fz_store_total_evicted(0);
}
#[test]
#[serial(store)]
fn test_store_item_null_key() {
let id = fz_store_item(0, 0, 1, 10, std::ptr::null(), 0);
if id > 0 {
fz_store_remove(0, id);
}
}
}