sieve_cache/sync.rs
1use crate::SieveCache;
2use std::borrow::Borrow;
3use std::fmt;
4use std::hash::Hash;
5use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
6
7/// A thread-safe wrapper around `SieveCache`.
8///
9/// This provides a thread-safe implementation of the SIEVE cache algorithm by wrapping
10/// the standard `SieveCache` in an `Arc<Mutex<>>`. It offers the same functionality as
11/// the underlying cache but with thread safety guarantees.
12///
13/// # Thread Safety
14///
15/// All operations acquire a lock on the entire cache, which provides strong consistency
16/// but may become a bottleneck under high contention. For workloads with high concurrency,
17/// consider using [`ShardedSieveCache`](crate::ShardedSieveCache) instead, which partitions
18/// the cache into multiple independently-locked segments.
19///
20/// # Examples
21///
22/// ```
23/// # use sieve_cache::SyncSieveCache;
24/// let cache = SyncSieveCache::new(100).unwrap();
25///
26/// // Multiple threads can safely access the cache
27/// cache.insert("key1".to_string(), "value1".to_string());
28/// assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
29/// ```
30#[derive(Clone)]
31pub struct SyncSieveCache<K, V>
32where
33 K: Eq + Hash + Clone + Send + Sync,
34 V: Send + Sync,
35{
36 inner: Arc<Mutex<SieveCache<K, V>>>,
37}
38
39unsafe impl<K, V> Sync for SyncSieveCache<K, V>
40where
41 K: Eq + Hash + Clone + Send + Sync,
42 V: Send + Sync,
43{
44}
45
46impl<K, V> Default for SyncSieveCache<K, V>
47where
48 K: Eq + Hash + Clone + Send + Sync,
49 V: Send + Sync,
50{
51 /// Creates a new cache with a default capacity of 100 entries.
52 ///
53 /// # Panics
54 ///
55 /// Panics if the underlying `SieveCache::new()` returns an error, which should never
56 /// happen for a non-zero capacity.
57 ///
58 /// # Examples
59 ///
60 /// ```
61 /// # use sieve_cache::SyncSieveCache;
62 /// # use std::default::Default;
63 /// let cache: SyncSieveCache<String, u32> = Default::default();
64 /// assert_eq!(cache.capacity(), 100);
65 /// ```
66 fn default() -> Self {
67 Self::new(100).expect("Failed to create cache with default capacity")
68 }
69}
70
71impl<K, V> fmt::Debug for SyncSieveCache<K, V>
72where
73 K: Eq + Hash + Clone + Send + Sync + fmt::Debug,
74 V: Send + Sync + fmt::Debug,
75{
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 let guard = self.locked_cache();
78 f.debug_struct("SyncSieveCache")
79 .field("capacity", &guard.capacity())
80 .field("len", &guard.len())
81 .finish()
82 }
83}
84
85impl<K, V> From<SieveCache<K, V>> for SyncSieveCache<K, V>
86where
87 K: Eq + Hash + Clone + Send + Sync,
88 V: Send + Sync,
89{
90 /// Creates a new thread-safe cache from an existing `SieveCache`.
91 ///
92 /// This allows for easily converting a single-threaded cache to a thread-safe version.
93 ///
94 /// # Examples
95 ///
96 /// ```
97 /// # use sieve_cache::{SieveCache, SyncSieveCache};
98 /// let mut single_threaded = SieveCache::new(100).unwrap();
99 /// single_threaded.insert("key".to_string(), "value".to_string());
100 ///
101 /// // Convert to thread-safe version
102 /// let thread_safe = SyncSieveCache::from(single_threaded);
103 /// assert_eq!(thread_safe.get(&"key".to_string()), Some("value".to_string()));
104 /// ```
105 fn from(cache: SieveCache<K, V>) -> Self {
106 Self {
107 inner: Arc::new(Mutex::new(cache)),
108 }
109 }
110}
111
112impl<K, V> IntoIterator for SyncSieveCache<K, V>
113where
114 K: Eq + Hash + Clone + Send + Sync,
115 V: Clone + Send + Sync,
116{
117 type Item = (K, V);
118 type IntoIter = std::vec::IntoIter<(K, V)>;
119
120 /// Converts the cache into an iterator over its key-value pairs.
121 ///
122 /// This collects all entries into a Vec and returns an iterator over that Vec.
123 ///
124 /// # Examples
125 ///
126 /// ```
127 /// # use sieve_cache::SyncSieveCache;
128 /// # use std::collections::HashMap;
129 /// let cache = SyncSieveCache::new(100).unwrap();
130 /// cache.insert("key1".to_string(), "value1".to_string());
131 /// cache.insert("key2".to_string(), "value2".to_string());
132 ///
133 /// // Collect into a HashMap
134 /// let map: HashMap<_, _> = cache.into_iter().collect();
135 /// assert_eq!(map.len(), 2);
136 /// assert_eq!(map.get("key1"), Some(&"value1".to_string()));
137 /// ```
138 fn into_iter(self) -> Self::IntoIter {
139 self.entries().into_iter()
140 }
141}
142
143impl<K, V> SyncSieveCache<K, V>
144where
145 K: Eq + Hash + Clone + Send + Sync,
146 V: Send + Sync,
147{
148 /// Creates a new thread-safe cache with the given capacity.
149 ///
150 /// # Errors
151 ///
152 /// Returns an error if the capacity is zero.
153 ///
154 /// # Examples
155 ///
156 /// ```
157 /// # use sieve_cache::SyncSieveCache;
158 /// let cache = SyncSieveCache::<String, String>::new(100).unwrap();
159 /// ```
160 pub fn new(capacity: usize) -> Result<Self, &'static str> {
161 let cache = SieveCache::new(capacity)?;
162 Ok(Self {
163 inner: Arc::new(Mutex::new(cache)),
164 })
165 }
166
167 /// Returns a locked reference to the underlying cache.
168 ///
169 /// This is an internal helper method to abstract away the lock handling.
170 /// If the mutex is poisoned due to a panic in another thread, the poison
171 /// error is recovered from by calling `into_inner()` to access the underlying data.
172 #[inline]
173 fn locked_cache(&self) -> MutexGuard<'_, SieveCache<K, V>> {
174 self.inner.lock().unwrap_or_else(PoisonError::into_inner)
175 }
176
177 /// Returns the capacity of the cache.
178 ///
179 /// # Examples
180 ///
181 /// ```
182 /// # use sieve_cache::SyncSieveCache;
183 /// let cache = SyncSieveCache::<String, u32>::new(100).unwrap();
184 /// assert_eq!(cache.capacity(), 100);
185 /// ```
186 pub fn capacity(&self) -> usize {
187 self.locked_cache().capacity()
188 }
189
190 /// Returns the number of cached values.
191 ///
192 /// # Examples
193 ///
194 /// ```
195 /// # use sieve_cache::SyncSieveCache;
196 /// let cache = SyncSieveCache::new(100).unwrap();
197 /// cache.insert("key".to_string(), "value".to_string());
198 /// assert_eq!(cache.len(), 1);
199 /// ```
200 pub fn len(&self) -> usize {
201 self.locked_cache().len()
202 }
203
204 /// Returns `true` when no values are currently cached.
205 ///
206 /// # Examples
207 ///
208 /// ```
209 /// # use sieve_cache::SyncSieveCache;
210 /// let cache = SyncSieveCache::<String, String>::new(100).unwrap();
211 /// assert!(cache.is_empty());
212 ///
213 /// cache.insert("key".to_string(), "value".to_string());
214 /// assert!(!cache.is_empty());
215 /// ```
216 pub fn is_empty(&self) -> bool {
217 self.locked_cache().is_empty()
218 }
219
220 /// Returns `true` if there is a value in the cache mapped to by `key`.
221 ///
222 /// # Examples
223 ///
224 /// ```
225 /// # use sieve_cache::SyncSieveCache;
226 /// let cache = SyncSieveCache::new(100).unwrap();
227 /// cache.insert("key".to_string(), "value".to_string());
228 ///
229 /// assert!(cache.contains_key(&"key".to_string()));
230 /// assert!(!cache.contains_key(&"missing".to_string()));
231 /// ```
232 pub fn contains_key<Q>(&self, key: &Q) -> bool
233 where
234 Q: Hash + Eq + ?Sized,
235 K: Borrow<Q>,
236 {
237 let mut guard = self.locked_cache();
238 guard.contains_key(key)
239 }
240
241 /// Gets a clone of the value in the cache mapped to by `key`.
242 ///
243 /// If no value exists for `key`, this returns `None`.
244 ///
245 /// # Note
246 ///
247 /// Unlike the unwrapped `SieveCache`, this returns a clone of the value
248 /// rather than a reference, since the mutex guard would be dropped after
249 /// this method returns. This means that `V` must implement `Clone`.
250 ///
251 /// # Examples
252 ///
253 /// ```
254 /// # use sieve_cache::SyncSieveCache;
255 /// let cache = SyncSieveCache::new(100).unwrap();
256 /// cache.insert("key".to_string(), "value".to_string());
257 ///
258 /// assert_eq!(cache.get(&"key".to_string()), Some("value".to_string()));
259 /// assert_eq!(cache.get(&"missing".to_string()), None);
260 /// ```
261 pub fn get<Q>(&self, key: &Q) -> Option<V>
262 where
263 Q: Hash + Eq + ?Sized,
264 K: Borrow<Q>,
265 V: Clone,
266 {
267 let mut guard = self.locked_cache();
268 guard.get(key).cloned()
269 }
270
271 /// Gets a mutable reference to the value in the cache mapped to by `key` via a callback function.
272 ///
273 /// If no value exists for `key`, the callback will not be invoked and this returns `false`.
274 /// Otherwise, the callback is invoked with a mutable reference to the value and this returns `true`.
275 ///
276 /// This operation marks the entry as "visited" in the SIEVE algorithm,
277 /// which affects eviction decisions.
278 ///
279 /// # Examples
280 ///
281 /// ```
282 /// # use sieve_cache::SyncSieveCache;
283 /// let cache = SyncSieveCache::new(100).unwrap();
284 /// cache.insert("key".to_string(), "value".to_string());
285 ///
286 /// // Modify the value in-place
287 /// cache.get_mut(&"key".to_string(), |value| {
288 /// *value = "new_value".to_string();
289 /// });
290 ///
291 /// assert_eq!(cache.get(&"key".to_string()), Some("new_value".to_string()));
292 /// ```
293 pub fn get_mut<Q, F>(&self, key: &Q, f: F) -> bool
294 where
295 Q: Hash + Eq + ?Sized,
296 K: Borrow<Q>,
297 F: FnOnce(&mut V),
298 {
299 let mut guard = self.locked_cache();
300 if let Some(value) = guard.get_mut(key) {
301 f(value);
302 true
303 } else {
304 false
305 }
306 }
307
308 /// Maps `key` to `value` in the cache, possibly evicting old entries.
309 ///
310 /// This method returns `true` when this is a new entry, and `false` if an existing entry was
311 /// updated.
312 ///
313 /// # Examples
314 ///
315 /// ```
316 /// # use sieve_cache::SyncSieveCache;
317 /// let cache = SyncSieveCache::new(100).unwrap();
318 ///
319 /// // Insert a new key
320 /// assert!(cache.insert("key1".to_string(), "value1".to_string()));
321 ///
322 /// // Update an existing key
323 /// assert!(!cache.insert("key1".to_string(), "updated_value".to_string()));
324 /// ```
325 pub fn insert(&self, key: K, value: V) -> bool {
326 let mut guard = self.locked_cache();
327 guard.insert(key, value)
328 }
329
330 /// Removes the cache entry mapped to by `key`.
331 ///
332 /// This method returns the value removed from the cache. If `key` did not map to any value,
333 /// then this returns `None`.
334 ///
335 /// # Examples
336 ///
337 /// ```
338 /// # use sieve_cache::SyncSieveCache;
339 /// let cache = SyncSieveCache::new(100).unwrap();
340 /// cache.insert("key".to_string(), "value".to_string());
341 ///
342 /// // Remove an existing key
343 /// assert_eq!(cache.remove(&"key".to_string()), Some("value".to_string()));
344 ///
345 /// // Attempt to remove a missing key
346 /// assert_eq!(cache.remove(&"key".to_string()), None);
347 /// ```
348 pub fn remove<Q>(&self, key: &Q) -> Option<V>
349 where
350 K: Borrow<Q>,
351 Q: Eq + Hash + ?Sized,
352 {
353 let mut guard = self.locked_cache();
354 guard.remove(key)
355 }
356
357 /// Removes and returns a value from the cache that was not recently accessed.
358 ///
359 /// This implements the SIEVE eviction algorithm to select an entry for removal.
360 /// If no suitable value exists, this returns `None`.
361 ///
362 /// # Examples
363 ///
364 /// ```
365 /// # use sieve_cache::SyncSieveCache;
366 /// let cache = SyncSieveCache::new(2).unwrap();
367 /// cache.insert("key1".to_string(), "value1".to_string());
368 /// cache.insert("key2".to_string(), "value2".to_string());
369 ///
370 /// // Accessing key1 marks it as recently used
371 /// assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
372 ///
373 /// // Insert a new key, which should evict key2
374 /// cache.insert("key3".to_string(), "value3".to_string());
375 ///
376 /// // key2 should have been evicted
377 /// assert_eq!(cache.get(&"key2".to_string()), None);
378 /// ```
379 pub fn evict(&self) -> Option<V> {
380 let mut guard = self.locked_cache();
381 guard.evict()
382 }
383
384 /// Removes all entries from the cache.
385 ///
386 /// This operation clears all stored values and resets the cache to an empty state,
387 /// while maintaining the original capacity.
388 ///
389 /// # Examples
390 ///
391 /// ```
392 /// # use sieve_cache::SyncSieveCache;
393 /// let cache = SyncSieveCache::new(100).unwrap();
394 /// cache.insert("key1".to_string(), "value1".to_string());
395 /// cache.insert("key2".to_string(), "value2".to_string());
396 ///
397 /// assert_eq!(cache.len(), 2);
398 ///
399 /// cache.clear();
400 /// assert_eq!(cache.len(), 0);
401 /// assert!(cache.is_empty());
402 /// ```
403 pub fn clear(&self) {
404 let mut guard = self.locked_cache();
405 guard.clear();
406 }
407
408 /// Returns an iterator over all keys in the cache.
409 ///
410 /// The order of keys is not specified and should not be relied upon.
411 ///
412 /// # Examples
413 ///
414 /// ```
415 /// # use sieve_cache::SyncSieveCache;
416 /// # use std::collections::HashSet;
417 /// let cache = SyncSieveCache::new(100).unwrap();
418 /// cache.insert("key1".to_string(), "value1".to_string());
419 /// cache.insert("key2".to_string(), "value2".to_string());
420 ///
421 /// let keys: HashSet<_> = cache.keys().into_iter().collect();
422 /// assert_eq!(keys.len(), 2);
423 /// assert!(keys.contains(&"key1".to_string()));
424 /// assert!(keys.contains(&"key2".to_string()));
425 /// ```
426 pub fn keys(&self) -> Vec<K> {
427 let guard = self.locked_cache();
428 guard.keys().cloned().collect()
429 }
430
431 /// Returns all values in the cache.
432 ///
433 /// The order of values is not specified and should not be relied upon.
434 ///
435 /// # Examples
436 ///
437 /// ```
438 /// # use sieve_cache::SyncSieveCache;
439 /// # use std::collections::HashSet;
440 /// let cache = SyncSieveCache::new(100).unwrap();
441 /// cache.insert("key1".to_string(), "value1".to_string());
442 /// cache.insert("key2".to_string(), "value2".to_string());
443 ///
444 /// let values: HashSet<_> = cache.values().into_iter().collect();
445 /// assert_eq!(values.len(), 2);
446 /// assert!(values.contains(&"value1".to_string()));
447 /// assert!(values.contains(&"value2".to_string()));
448 /// ```
449 pub fn values(&self) -> Vec<V>
450 where
451 V: Clone,
452 {
453 let guard = self.locked_cache();
454 guard.values().cloned().collect()
455 }
456
457 /// Returns all key-value pairs in the cache.
458 ///
459 /// The order of pairs is not specified and should not be relied upon.
460 ///
461 /// # Examples
462 ///
463 /// ```
464 /// # use sieve_cache::SyncSieveCache;
465 /// # use std::collections::HashMap;
466 /// let cache = SyncSieveCache::new(100).unwrap();
467 /// cache.insert("key1".to_string(), "value1".to_string());
468 /// cache.insert("key2".to_string(), "value2".to_string());
469 ///
470 /// let entries: HashMap<_, _> = cache.entries().into_iter().collect();
471 /// assert_eq!(entries.len(), 2);
472 /// assert_eq!(entries.get(&"key1".to_string()), Some(&"value1".to_string()));
473 /// assert_eq!(entries.get(&"key2".to_string()), Some(&"value2".to_string()));
474 /// ```
475 pub fn entries(&self) -> Vec<(K, V)>
476 where
477 V: Clone,
478 {
479 let guard = self.locked_cache();
480 guard.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
481 }
482
483 /// Applies a function to all values in the cache.
484 ///
485 /// This method marks all entries as visited.
486 ///
487 /// # Examples
488 ///
489 /// ```
490 /// # use sieve_cache::SyncSieveCache;
491 /// let cache = SyncSieveCache::new(100).unwrap();
492 /// cache.insert("key1".to_string(), "value1".to_string());
493 /// cache.insert("key2".to_string(), "value2".to_string());
494 ///
495 /// // Update all values by appending text
496 /// cache.for_each_value(|value| {
497 /// *value = format!("{}_updated", value);
498 /// });
499 ///
500 /// assert_eq!(cache.get(&"key1".to_string()), Some("value1_updated".to_string()));
501 /// assert_eq!(cache.get(&"key2".to_string()), Some("value2_updated".to_string()));
502 /// ```
503 pub fn for_each_value<F>(&self, f: F)
504 where
505 F: FnMut(&mut V),
506 {
507 let mut guard = self.locked_cache();
508 guard.values_mut().for_each(f);
509 }
510
511 /// Applies a function to all key-value pairs in the cache.
512 ///
513 /// This method marks all entries as visited.
514 ///
515 /// # Examples
516 ///
517 /// ```
518 /// # use sieve_cache::SyncSieveCache;
519 /// let cache = SyncSieveCache::new(100).unwrap();
520 /// cache.insert("key1".to_string(), "value1".to_string());
521 /// cache.insert("key2".to_string(), "value2".to_string());
522 ///
523 /// // Update all values associated with keys containing '1'
524 /// cache.for_each_entry(|(key, value)| {
525 /// if key.contains('1') {
526 /// *value = format!("{}_special", value);
527 /// }
528 /// });
529 ///
530 /// assert_eq!(cache.get(&"key1".to_string()), Some("value1_special".to_string()));
531 /// assert_eq!(cache.get(&"key2".to_string()), Some("value2".to_string()));
532 /// ```
533 pub fn for_each_entry<F>(&self, f: F)
534 where
535 F: FnMut((&K, &mut V)),
536 {
537 let mut guard = self.locked_cache();
538 guard.iter_mut().for_each(f);
539 }
540
541 /// Gets exclusive access to the underlying cache to perform multiple operations atomically.
542 ///
543 /// This is useful when you need to perform a series of operations that depend on each other
544 /// and you want to ensure that no other thread can access the cache between operations.
545 ///
546 /// # Examples
547 ///
548 /// ```
549 /// # use sieve_cache::SyncSieveCache;
550 /// let cache = SyncSieveCache::new(100).unwrap();
551 ///
552 /// cache.with_lock(|inner_cache| {
553 /// // Perform multiple operations atomically
554 /// inner_cache.insert("key1".to_string(), "value1".to_string());
555 /// inner_cache.insert("key2".to_string(), "value2".to_string());
556 ///
557 /// // We can check internal state mid-transaction
558 /// assert_eq!(inner_cache.len(), 2);
559 /// });
560 /// ```
561 pub fn with_lock<F, T>(&self, f: F) -> T
562 where
563 F: FnOnce(&mut SieveCache<K, V>) -> T,
564 {
565 let mut guard = self.locked_cache();
566 f(&mut guard)
567 }
568
569 /// Retains only the elements specified by the predicate.
570 ///
571 /// Removes all entries for which the provided function returns `false`.
572 /// The elements are visited in arbitrary, unspecified order.
573 /// This operation acquires a lock on the entire cache.
574 ///
575 /// # Examples
576 ///
577 /// ```
578 /// # use sieve_cache::SyncSieveCache;
579 /// let cache = SyncSieveCache::new(100).unwrap();
580 /// cache.insert("key1".to_string(), 100);
581 /// cache.insert("key2".to_string(), 200);
582 /// cache.insert("key3".to_string(), 300);
583 ///
584 /// // Keep only entries with values greater than 150
585 /// cache.retain(|_, value| *value > 150);
586 ///
587 /// assert_eq!(cache.len(), 2);
588 /// assert!(!cache.contains_key(&"key1".to_string()));
589 /// assert!(cache.contains_key(&"key2".to_string()));
590 /// assert!(cache.contains_key(&"key3".to_string()));
591 /// ```
592 pub fn retain<F>(&self, f: F)
593 where
594 F: FnMut(&K, &V) -> bool,
595 {
596 let mut guard = self.locked_cache();
597 guard.retain(f);
598 }
599}
600
601#[cfg(test)]
602mod tests {
603 use super::*;
604 use std::thread;
605
606 #[test]
607 fn test_sync_cache() {
608 let cache = SyncSieveCache::new(100).unwrap();
609
610 // Insert a value
611 assert!(cache.insert("key1".to_string(), "value1".to_string()));
612
613 // Read back the value
614 assert_eq!(cache.get(&"key1".to_string()), Some("value1".to_string()));
615
616 // Check contains_key
617 assert!(cache.contains_key(&"key1".to_string()));
618
619 // Check capacity and length
620 assert_eq!(cache.capacity(), 100);
621 assert_eq!(cache.len(), 1);
622
623 // Remove a value
624 assert_eq!(
625 cache.remove(&"key1".to_string()),
626 Some("value1".to_string())
627 );
628 assert_eq!(cache.len(), 0);
629 assert!(cache.is_empty());
630 }
631
632 #[test]
633 fn test_multithreaded_access() {
634 let cache = SyncSieveCache::new(100).unwrap();
635 let cache_clone = cache.clone();
636
637 // Add some initial data
638 cache.insert("shared".to_string(), "initial".to_string());
639
640 // Spawn a thread that updates the cache
641 let thread = thread::spawn(move || {
642 cache_clone.insert("shared".to_string(), "updated".to_string());
643 cache_clone.insert("thread_only".to_string(), "thread_value".to_string());
644 });
645
646 // Main thread operations
647 cache.insert("main_only".to_string(), "main_value".to_string());
648
649 // Wait for thread to complete
650 thread.join().unwrap();
651
652 // Verify results
653 assert_eq!(
654 cache.get(&"shared".to_string()),
655 Some("updated".to_string())
656 );
657 assert_eq!(
658 cache.get(&"thread_only".to_string()),
659 Some("thread_value".to_string())
660 );
661 assert_eq!(
662 cache.get(&"main_only".to_string()),
663 Some("main_value".to_string())
664 );
665 assert_eq!(cache.len(), 3);
666 }
667
668 #[test]
669 fn test_with_lock() {
670 let cache = SyncSieveCache::new(100).unwrap();
671
672 // Perform multiple operations atomically
673 cache.with_lock(|inner_cache| {
674 inner_cache.insert("key1".to_string(), "value1".to_string());
675 inner_cache.insert("key2".to_string(), "value2".to_string());
676 inner_cache.insert("key3".to_string(), "value3".to_string());
677 });
678
679 assert_eq!(cache.len(), 3);
680 }
681
682 #[test]
683 fn test_get_mut() {
684 let cache = SyncSieveCache::new(100).unwrap();
685 cache.insert("key".to_string(), "value".to_string());
686
687 // Modify the value in-place
688 let modified = cache.get_mut(&"key".to_string(), |value| {
689 *value = "new_value".to_string();
690 });
691 assert!(modified);
692
693 // Verify the value was updated
694 assert_eq!(cache.get(&"key".to_string()), Some("new_value".to_string()));
695
696 // Try to modify a non-existent key
697 let modified = cache.get_mut(&"missing".to_string(), |_| {
698 panic!("This should not be called");
699 });
700 assert!(!modified);
701 }
702
703 #[test]
704 fn test_clear() {
705 let cache = SyncSieveCache::new(10).unwrap();
706 cache.insert("key1".to_string(), "value1".to_string());
707 cache.insert("key2".to_string(), "value2".to_string());
708
709 assert_eq!(cache.len(), 2);
710 assert!(!cache.is_empty());
711
712 cache.clear();
713
714 assert_eq!(cache.len(), 0);
715 assert!(cache.is_empty());
716 assert_eq!(cache.get(&"key1".to_string()), None);
717 assert_eq!(cache.get(&"key2".to_string()), None);
718 }
719
720 #[test]
721 fn test_keys_values_entries() {
722 let cache = SyncSieveCache::new(10).unwrap();
723 cache.insert("key1".to_string(), "value1".to_string());
724 cache.insert("key2".to_string(), "value2".to_string());
725
726 // Test keys
727 let keys = cache.keys();
728 assert_eq!(keys.len(), 2);
729 assert!(keys.contains(&"key1".to_string()));
730 assert!(keys.contains(&"key2".to_string()));
731
732 // Test values
733 let values = cache.values();
734 assert_eq!(values.len(), 2);
735 assert!(values.contains(&"value1".to_string()));
736 assert!(values.contains(&"value2".to_string()));
737
738 // Test entries
739 let entries = cache.entries();
740 assert_eq!(entries.len(), 2);
741 assert!(entries.contains(&("key1".to_string(), "value1".to_string())));
742 assert!(entries.contains(&("key2".to_string(), "value2".to_string())));
743 }
744
745 #[test]
746 fn test_for_each_methods() {
747 let cache = SyncSieveCache::new(10).unwrap();
748 cache.insert("key1".to_string(), "value1".to_string());
749 cache.insert("key2".to_string(), "value2".to_string());
750
751 // Test for_each_value
752 cache.for_each_value(|value| {
753 *value = format!("{}_updated", value);
754 });
755
756 assert_eq!(
757 cache.get(&"key1".to_string()),
758 Some("value1_updated".to_string())
759 );
760 assert_eq!(
761 cache.get(&"key2".to_string()),
762 Some("value2_updated".to_string())
763 );
764
765 // Test for_each_entry
766 cache.for_each_entry(|(key, value)| {
767 if key == "key1" {
768 *value = format!("{}_special", value);
769 }
770 });
771
772 assert_eq!(
773 cache.get(&"key1".to_string()),
774 Some("value1_updated_special".to_string())
775 );
776 assert_eq!(
777 cache.get(&"key2".to_string()),
778 Some("value2_updated".to_string())
779 );
780 }
781
782 #[test]
783 fn test_retain() {
784 let cache = SyncSieveCache::new(10).unwrap();
785
786 // Add some entries
787 cache.insert("even1".to_string(), 2);
788 cache.insert("even2".to_string(), 4);
789 cache.insert("odd1".to_string(), 1);
790 cache.insert("odd2".to_string(), 3);
791
792 assert_eq!(cache.len(), 4);
793
794 // Keep only entries with even values
795 cache.retain(|_, v| v % 2 == 0);
796
797 assert_eq!(cache.len(), 2);
798 assert!(cache.contains_key(&"even1".to_string()));
799 assert!(cache.contains_key(&"even2".to_string()));
800 assert!(!cache.contains_key(&"odd1".to_string()));
801 assert!(!cache.contains_key(&"odd2".to_string()));
802
803 // Keep only entries with keys containing '1'
804 cache.retain(|k, _| k.contains('1'));
805
806 assert_eq!(cache.len(), 1);
807 assert!(cache.contains_key(&"even1".to_string()));
808 assert!(!cache.contains_key(&"even2".to_string()));
809 }
810}