1#[cfg(feature = "metrics")]
146use crate::metrics::metrics_impl::SlruMetrics;
147#[cfg(feature = "metrics")]
148use crate::metrics::snapshot::SlruMetricsSnapshot;
149#[cfg(feature = "metrics")]
150use crate::metrics::traits::{CoreMetricsRecorder, MetricsSnapshotProvider, SlruMetricsRecorder};
151use crate::traits::Cache;
152use rustc_hash::FxHashMap;
153use std::hash::Hash;
154use std::iter::FusedIterator;
155use std::marker::PhantomData;
156use std::ptr::NonNull;
157
158#[derive(Copy, Clone, Debug, Eq, PartialEq)]
160enum Segment {
161 Probationary,
163 Protected,
165}
166
167#[repr(C)]
171struct Node<K, V> {
172 prev: Option<NonNull<Node<K, V>>>,
173 next: Option<NonNull<Node<K, V>>>,
174 segment: Segment,
175 key: K,
176 value: V,
177}
178
179pub struct SlruCore<K, V> {
227 map: FxHashMap<K, NonNull<Node<K, V>>>,
228
229 probationary_head: Option<NonNull<Node<K, V>>>,
231 probationary_tail: Option<NonNull<Node<K, V>>>,
232 probationary_len: usize,
233
234 protected_head: Option<NonNull<Node<K, V>>>,
236 protected_tail: Option<NonNull<Node<K, V>>>,
237 protected_len: usize,
238
239 probationary_cap: usize,
240 capacity: usize,
241
242 #[cfg(feature = "metrics")]
243 metrics: SlruMetrics,
244}
245
246unsafe impl<K: Send, V: Send> Send for SlruCore<K, V> {}
251
252unsafe impl<K: Sync, V: Sync> Sync for SlruCore<K, V> {}
257
258impl<K, V> SlruCore<K, V>
259where
260 K: Clone + Eq + Hash,
261{
262 #[inline]
281 #[must_use]
282 pub fn new(capacity: usize, probationary_frac: f64) -> Self {
283 assert!(
284 (0.0..=1.0).contains(&probationary_frac),
285 "probationary_frac must be in 0.0..=1.0, got {probationary_frac}"
286 );
287
288 let probationary_cap = (capacity as f64 * probationary_frac) as usize;
289 let total_cap = capacity + probationary_cap;
290
291 Self {
292 map: FxHashMap::with_capacity_and_hasher(total_cap, Default::default()),
293 probationary_head: None,
294 probationary_tail: None,
295 probationary_len: 0,
296 protected_head: None,
297 protected_tail: None,
298 protected_len: 0,
299 probationary_cap,
300 capacity,
301 #[cfg(feature = "metrics")]
302 metrics: SlruMetrics::default(),
303 }
304 }
305
306 #[inline(always)]
308 fn detach(&mut self, node_ptr: NonNull<Node<K, V>>) {
309 unsafe {
310 let node = node_ptr.as_ref();
311 let prev = node.prev;
312 let next = node.next;
313 let segment = node.segment;
314
315 let (head, tail, len) = match segment {
316 Segment::Probationary => (
317 &mut self.probationary_head,
318 &mut self.probationary_tail,
319 &mut self.probationary_len,
320 ),
321 Segment::Protected => (
322 &mut self.protected_head,
323 &mut self.protected_tail,
324 &mut self.protected_len,
325 ),
326 };
327
328 match prev {
329 Some(mut p) => p.as_mut().next = next,
330 None => *head = next,
331 }
332
333 match next {
334 Some(mut n) => n.as_mut().prev = prev,
335 None => *tail = prev,
336 }
337
338 *len -= 1;
339 }
340 }
341
342 #[inline(always)]
344 fn attach_probationary_head(&mut self, mut node_ptr: NonNull<Node<K, V>>) {
345 unsafe {
346 let node = node_ptr.as_mut();
347 node.prev = None;
348 node.next = self.probationary_head;
349 node.segment = Segment::Probationary;
350
351 match self.probationary_head {
352 Some(mut h) => h.as_mut().prev = Some(node_ptr),
353 None => self.probationary_tail = Some(node_ptr),
354 }
355
356 self.probationary_head = Some(node_ptr);
357 self.probationary_len += 1;
358 }
359 }
360
361 #[inline(always)]
363 fn attach_protected_head(&mut self, mut node_ptr: NonNull<Node<K, V>>) {
364 unsafe {
365 let node = node_ptr.as_mut();
366 node.prev = None;
367 node.next = self.protected_head;
368 node.segment = Segment::Protected;
369
370 match self.protected_head {
371 Some(mut h) => h.as_mut().prev = Some(node_ptr),
372 None => self.protected_tail = Some(node_ptr),
373 }
374
375 self.protected_head = Some(node_ptr);
376 self.protected_len += 1;
377 }
378 }
379
380 #[inline(always)]
382 fn pop_probationary_tail(&mut self) -> Option<Box<Node<K, V>>> {
383 self.probationary_tail.map(|tail_ptr| unsafe {
384 let node = Box::from_raw(tail_ptr.as_ptr());
385
386 self.probationary_tail = node.prev;
387 match self.probationary_tail {
388 Some(mut t) => t.as_mut().next = None,
389 None => self.probationary_head = None,
390 }
391 self.probationary_len -= 1;
392
393 node
394 })
395 }
396
397 #[inline(always)]
399 fn pop_protected_tail(&mut self) -> Option<Box<Node<K, V>>> {
400 self.protected_tail.map(|tail_ptr| unsafe {
401 let node = Box::from_raw(tail_ptr.as_ptr());
402
403 self.protected_tail = node.prev;
404 match self.protected_tail {
405 Some(mut t) => t.as_mut().next = None,
406 None => self.protected_head = None,
407 }
408 self.protected_len -= 1;
409
410 node
411 })
412 }
413
414 #[inline]
432 pub fn peek(&self, key: &K) -> Option<&V> {
433 self.map.get(key).map(|&ptr| unsafe { &ptr.as_ref().value })
434 }
435
436 #[inline]
460 pub fn get(&mut self, key: &K) -> Option<&V> {
461 let node_ptr = match self.map.get(key) {
462 Some(&ptr) => ptr,
463 None => {
464 #[cfg(feature = "metrics")]
465 self.metrics.record_get_miss();
466 return None;
467 },
468 };
469
470 #[cfg(feature = "metrics")]
471 self.metrics.record_get_hit();
472
473 let segment = unsafe { node_ptr.as_ref().segment };
474
475 match segment {
476 Segment::Probationary => {
477 self.detach(node_ptr);
478 self.attach_protected_head(node_ptr);
479
480 #[cfg(feature = "metrics")]
481 self.metrics.record_probationary_to_protected();
482 },
483 Segment::Protected => {
484 self.detach(node_ptr);
485 self.attach_protected_head(node_ptr);
486 },
487 }
488
489 unsafe { Some(&node_ptr.as_ref().value) }
490 }
491
492 #[inline]
515 pub fn insert(&mut self, key: K, value: V) -> Option<V> {
516 #[cfg(feature = "metrics")]
517 self.metrics.record_insert_call();
518
519 if self.capacity == 0 {
520 return None;
521 }
522
523 if let Some(&node_ptr) = self.map.get(&key) {
524 #[cfg(feature = "metrics")]
525 self.metrics.record_insert_update();
526
527 let old = unsafe { std::mem::replace(&mut (*node_ptr.as_ptr()).value, value) };
528 return Some(old);
529 }
530
531 #[cfg(feature = "metrics")]
532 self.metrics.record_insert_new();
533
534 self.evict_if_needed();
535
536 let node = Box::new(Node {
537 prev: None,
538 next: None,
539 segment: Segment::Probationary,
540 key: key.clone(),
541 value,
542 });
543 let node_ptr = NonNull::new(Box::into_raw(node)).unwrap();
544
545 self.map.insert(key, node_ptr);
546 self.attach_probationary_head(node_ptr);
547
548 #[cfg(debug_assertions)]
549 self.validate_invariants();
550 None
551 }
552
553 #[inline]
555 fn evict_if_needed(&mut self) {
556 while self.len() >= self.capacity {
557 #[cfg(feature = "metrics")]
558 self.metrics.record_evict_call();
559
560 if self.probationary_len > self.probationary_cap {
561 if let Some(node) = self.pop_probationary_tail() {
562 self.map.remove(&node.key);
563 #[cfg(feature = "metrics")]
564 self.metrics.record_evicted_entry();
565 continue;
566 }
567 }
568 if let Some(node) = self.pop_protected_tail() {
569 self.map.remove(&node.key);
570 #[cfg(feature = "metrics")]
571 {
572 self.metrics.record_evicted_entry();
573 self.metrics.record_protected_eviction();
574 }
575 continue;
576 }
577 if let Some(node) = self.pop_probationary_tail() {
578 self.map.remove(&node.key);
579 #[cfg(feature = "metrics")]
580 self.metrics.record_evicted_entry();
581 continue;
582 }
583 break;
584 }
585 }
586
587 #[inline]
602 pub fn len(&self) -> usize {
603 self.map.len()
604 }
605
606 #[inline]
620 pub fn is_empty(&self) -> bool {
621 self.map.is_empty()
622 }
623
624 #[inline]
635 pub fn capacity(&self) -> usize {
636 self.capacity
637 }
638
639 #[inline]
655 pub fn contains(&self, key: &K) -> bool {
656 self.map.contains_key(key)
657 }
658
659 #[inline]
661 pub fn probationary_len(&self) -> usize {
662 self.probationary_len
663 }
664
665 #[inline]
667 pub fn protected_len(&self) -> usize {
668 self.protected_len
669 }
670
671 pub fn clear(&mut self) {
687 #[cfg(feature = "metrics")]
688 self.metrics.record_clear();
689
690 while self.pop_probationary_tail().is_some() {}
691 while self.pop_protected_tail().is_some() {}
692 self.map.clear();
693
694 #[cfg(debug_assertions)]
695 self.validate_invariants();
696 }
697
698 pub fn remove(&mut self, key: &K) -> Option<V> {
714 let node_ptr = self.map.remove(key)?;
715 self.detach(node_ptr);
716 unsafe {
717 let node = Box::from_raw(node_ptr.as_ptr());
718 Some(node.value)
719 }
720 }
721
722 pub fn iter(&self) -> Iter<'_, K, V> {
726 Iter {
727 current: self.probationary_head,
728 protected_head: self.protected_head,
729 in_protected: false,
730 remaining: self.probationary_len + self.protected_len,
731 _marker: PhantomData,
732 }
733 }
734
735 pub fn iter_mut(&mut self) -> IterMut<'_, K, V> {
740 IterMut {
741 current: self.probationary_head,
742 protected_head: self.protected_head,
743 in_protected: false,
744 remaining: self.probationary_len + self.protected_len,
745 _marker: PhantomData,
746 }
747 }
748
749 #[cfg(debug_assertions)]
759 fn validate_invariants(&self) {
760 let mut prob_count = 0;
761 let mut current = self.probationary_head;
762 let mut visited = std::collections::HashSet::new();
763
764 while let Some(ptr) = current {
765 prob_count += 1;
766 assert!(visited.insert(ptr), "Cycle detected in probationary list");
767 assert!(
768 prob_count <= self.map.len(),
769 "Probationary count exceeds total entries"
770 );
771
772 unsafe {
773 let node = ptr.as_ref();
774 assert!(
775 matches!(node.segment, Segment::Probationary),
776 "Non-probationary node in probationary list"
777 );
778 current = node.next;
779 }
780 }
781
782 debug_assert_eq!(
783 prob_count, self.probationary_len,
784 "Probationary count mismatch"
785 );
786
787 let mut prot_count = 0;
788 let mut current = self.protected_head;
789 visited.clear();
790
791 while let Some(ptr) = current {
792 prot_count += 1;
793 assert!(visited.insert(ptr), "Cycle detected in protected list");
794 assert!(
795 prot_count <= self.map.len(),
796 "Protected count exceeds total entries"
797 );
798
799 unsafe {
800 let node = ptr.as_ref();
801 assert!(
802 matches!(node.segment, Segment::Protected),
803 "Non-protected node in protected list"
804 );
805 current = node.next;
806 }
807 }
808
809 debug_assert_eq!(prot_count, self.protected_len, "Protected count mismatch");
810
811 debug_assert_eq!(
812 prob_count + prot_count,
813 self.map.len(),
814 "List counts don't match map size"
815 );
816
817 for &node_ptr in self.map.values() {
818 unsafe {
819 let node = node_ptr.as_ref();
820 match node.segment {
821 Segment::Probationary => {
822 debug_assert!(prob_count > 0, "Node marked probationary but list empty");
823 },
824 Segment::Protected => {
825 debug_assert!(prot_count > 0, "Node marked protected but list empty");
826 },
827 }
828 }
829 }
830 }
831}
832
833#[cfg(feature = "metrics")]
834impl<K, V> SlruCore<K, V>
835where
836 K: Clone + Eq + Hash,
837{
838 pub fn metrics_snapshot(&self) -> SlruMetricsSnapshot {
839 SlruMetricsSnapshot {
840 get_calls: self.metrics.get_calls,
841 get_hits: self.metrics.get_hits,
842 get_misses: self.metrics.get_misses,
843 insert_calls: self.metrics.insert_calls,
844 insert_updates: self.metrics.insert_updates,
845 insert_new: self.metrics.insert_new,
846 evict_calls: self.metrics.evict_calls,
847 evicted_entries: self.metrics.evicted_entries,
848 probationary_to_protected: self.metrics.probationary_to_protected,
849 protected_evictions: self.metrics.protected_evictions,
850 cache_len: self.len(),
851 capacity: self.capacity(),
852 }
853 }
854}
855
856#[cfg(feature = "metrics")]
857impl<K, V> MetricsSnapshotProvider<SlruMetricsSnapshot> for SlruCore<K, V>
858where
859 K: Clone + Eq + Hash,
860{
861 fn snapshot(&self) -> SlruMetricsSnapshot {
862 self.metrics_snapshot()
863 }
864}
865
866impl<K, V> Drop for SlruCore<K, V> {
867 fn drop(&mut self) {
868 unsafe {
869 let mut current = self.probationary_head.take();
870 while let Some(ptr) = current {
871 current = ptr.as_ref().next;
872 drop(Box::from_raw(ptr.as_ptr()));
873 }
874 let mut current = self.protected_head.take();
875 while let Some(ptr) = current {
876 current = ptr.as_ref().next;
877 drop(Box::from_raw(ptr.as_ptr()));
878 }
879 }
880 }
881}
882
883impl<K, V> std::fmt::Debug for SlruCore<K, V>
884where
885 K: std::fmt::Debug,
886{
887 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
888 f.debug_struct("SlruCore")
889 .field("capacity", &self.capacity)
890 .field("probationary_cap", &self.probationary_cap)
891 .field("len", &self.map.len())
892 .field("probationary_len", &self.probationary_len)
893 .field("protected_len", &self.protected_len)
894 .finish_non_exhaustive()
895 }
896}
897
898impl<K, V> Default for SlruCore<K, V>
899where
900 K: Clone + Eq + Hash,
901{
902 fn default() -> Self {
906 Self::new(0, 0.25)
907 }
908}
909
910impl<K, V> Clone for SlruCore<K, V>
911where
912 K: Clone + Eq + Hash,
913 V: Clone,
914{
915 fn clone(&self) -> Self {
916 let mut new_cache = SlruCore::new(
917 self.capacity,
918 if self.capacity > 0 {
919 self.probationary_cap as f64 / self.capacity as f64
920 } else {
921 0.25
922 },
923 );
924
925 let mut prob_entries = Vec::with_capacity(self.probationary_len);
928 let mut current = self.probationary_tail;
929 while let Some(ptr) = current {
930 unsafe {
931 let node = ptr.as_ref();
932 prob_entries.push((node.key.clone(), node.value.clone()));
933 current = node.prev;
934 }
935 }
936 for (key, value) in prob_entries {
937 let node = Box::new(Node {
938 prev: None,
939 next: None,
940 segment: Segment::Probationary,
941 key: key.clone(),
942 value,
943 });
944 let ptr = NonNull::new(Box::into_raw(node)).unwrap();
945 new_cache.map.insert(key, ptr);
946 new_cache.attach_probationary_head(ptr);
947 }
948
949 let mut prot_entries = Vec::with_capacity(self.protected_len);
950 let mut current = self.protected_tail;
951 while let Some(ptr) = current {
952 unsafe {
953 let node = ptr.as_ref();
954 prot_entries.push((node.key.clone(), node.value.clone()));
955 current = node.prev;
956 }
957 }
958 for (key, value) in prot_entries {
959 let node = Box::new(Node {
960 prev: None,
961 next: None,
962 segment: Segment::Protected,
963 key: key.clone(),
964 value,
965 });
966 let ptr = NonNull::new(Box::into_raw(node)).unwrap();
967 new_cache.map.insert(key, ptr);
968 new_cache.attach_protected_head(ptr);
969 }
970
971 #[cfg(feature = "metrics")]
972 {
973 new_cache.metrics = self.metrics;
974 }
975
976 new_cache
977 }
978}
979
980impl<K, V> Cache<K, V> for SlruCore<K, V>
998where
999 K: Clone + Eq + Hash,
1000{
1001 #[inline]
1002 fn contains(&self, key: &K) -> bool {
1003 SlruCore::contains(self, key)
1004 }
1005
1006 #[inline]
1007 fn len(&self) -> usize {
1008 SlruCore::len(self)
1009 }
1010
1011 #[inline]
1012 fn capacity(&self) -> usize {
1013 SlruCore::capacity(self)
1014 }
1015
1016 #[inline]
1017 fn peek(&self, key: &K) -> Option<&V> {
1018 SlruCore::peek(self, key)
1019 }
1020
1021 #[inline]
1022 fn get(&mut self, key: &K) -> Option<&V> {
1023 SlruCore::get(self, key)
1024 }
1025
1026 #[inline]
1027 fn insert(&mut self, key: K, value: V) -> Option<V> {
1028 SlruCore::insert(self, key, value)
1029 }
1030
1031 #[inline]
1032 fn remove(&mut self, key: &K) -> Option<V> {
1033 SlruCore::remove(self, key)
1034 }
1035
1036 fn clear(&mut self) {
1037 SlruCore::clear(self);
1038 }
1039}
1040
1041pub struct Iter<'a, K, V> {
1050 current: Option<NonNull<Node<K, V>>>,
1051 protected_head: Option<NonNull<Node<K, V>>>,
1052 in_protected: bool,
1053 remaining: usize,
1054 _marker: PhantomData<&'a (K, V)>,
1055}
1056
1057impl<'a, K, V> Iterator for Iter<'a, K, V> {
1058 type Item = (&'a K, &'a V);
1059
1060 fn next(&mut self) -> Option<Self::Item> {
1061 loop {
1062 if let Some(node_ptr) = self.current {
1063 unsafe {
1064 let node = node_ptr.as_ref();
1065 self.current = node.next;
1066 self.remaining -= 1;
1067 return Some((&node.key, &node.value));
1068 }
1069 } else if !self.in_protected {
1070 self.in_protected = true;
1071 self.current = self.protected_head;
1072 } else {
1073 return None;
1074 }
1075 }
1076 }
1077
1078 fn size_hint(&self) -> (usize, Option<usize>) {
1079 (self.remaining, Some(self.remaining))
1080 }
1081}
1082
1083impl<K, V> ExactSizeIterator for Iter<'_, K, V> {}
1084impl<K, V> FusedIterator for Iter<'_, K, V> {}
1085
1086pub struct IterMut<'a, K, V> {
1090 current: Option<NonNull<Node<K, V>>>,
1091 protected_head: Option<NonNull<Node<K, V>>>,
1092 in_protected: bool,
1093 remaining: usize,
1094 _marker: PhantomData<&'a mut (K, V)>,
1095}
1096
1097impl<'a, K, V> Iterator for IterMut<'a, K, V> {
1098 type Item = (&'a K, &'a mut V);
1099
1100 fn next(&mut self) -> Option<Self::Item> {
1101 loop {
1102 if let Some(mut node_ptr) = self.current {
1103 unsafe {
1104 let node = node_ptr.as_mut();
1105 self.current = node.next;
1106 self.remaining -= 1;
1107 return Some((&node.key, &mut node.value));
1108 }
1109 } else if !self.in_protected {
1110 self.in_protected = true;
1111 self.current = self.protected_head;
1112 } else {
1113 return None;
1114 }
1115 }
1116 }
1117
1118 fn size_hint(&self) -> (usize, Option<usize>) {
1119 (self.remaining, Some(self.remaining))
1120 }
1121}
1122
1123impl<K, V> ExactSizeIterator for IterMut<'_, K, V> {}
1124impl<K, V> FusedIterator for IterMut<'_, K, V> {}
1125
1126pub struct IntoIter<K, V> {
1130 current: Option<NonNull<Node<K, V>>>,
1131 protected_head: Option<NonNull<Node<K, V>>>,
1132 in_protected: bool,
1133 remaining: usize,
1134}
1135
1136impl<K, V> Iterator for IntoIter<K, V> {
1137 type Item = (K, V);
1138
1139 fn next(&mut self) -> Option<Self::Item> {
1140 loop {
1141 if let Some(node_ptr) = self.current {
1142 unsafe {
1143 let node = Box::from_raw(node_ptr.as_ptr());
1144 self.current = node.next;
1145 self.remaining -= 1;
1146 return Some((node.key, node.value));
1147 }
1148 } else if !self.in_protected {
1149 self.in_protected = true;
1150 self.current = self.protected_head;
1151 } else {
1152 return None;
1153 }
1154 }
1155 }
1156
1157 fn size_hint(&self) -> (usize, Option<usize>) {
1158 (self.remaining, Some(self.remaining))
1159 }
1160}
1161
1162impl<K, V> ExactSizeIterator for IntoIter<K, V> {}
1163impl<K, V> FusedIterator for IntoIter<K, V> {}
1164
1165impl<K, V> Drop for IntoIter<K, V> {
1166 fn drop(&mut self) {
1167 while self.next().is_some() {}
1168 }
1169}
1170
1171unsafe impl<K: Send, V: Send> Send for IntoIter<K, V> {}
1173unsafe impl<K: Sync, V: Sync> Sync for IntoIter<K, V> {}
1174
1175impl<K, V> IntoIterator for SlruCore<K, V> {
1176 type Item = (K, V);
1177 type IntoIter = IntoIter<K, V>;
1178
1179 fn into_iter(mut self) -> IntoIter<K, V> {
1180 let iter = IntoIter {
1181 current: self.probationary_head,
1182 protected_head: self.protected_head,
1183 in_protected: false,
1184 remaining: self.probationary_len + self.protected_len,
1185 };
1186 self.probationary_head = None;
1188 self.probationary_tail = None;
1189 self.probationary_len = 0;
1190 self.protected_head = None;
1191 self.protected_tail = None;
1192 self.protected_len = 0;
1193 iter
1194 }
1195}
1196
1197impl<'a, K, V> IntoIterator for &'a SlruCore<K, V>
1198where
1199 K: Clone + Eq + Hash,
1200{
1201 type Item = (&'a K, &'a V);
1202 type IntoIter = Iter<'a, K, V>;
1203
1204 fn into_iter(self) -> Iter<'a, K, V> {
1205 self.iter()
1206 }
1207}
1208
1209impl<'a, K, V> IntoIterator for &'a mut SlruCore<K, V>
1210where
1211 K: Clone + Eq + Hash,
1212{
1213 type Item = (&'a K, &'a mut V);
1214 type IntoIter = IterMut<'a, K, V>;
1215
1216 fn into_iter(self) -> IterMut<'a, K, V> {
1217 self.iter_mut()
1218 }
1219}
1220
1221impl<K, V> FromIterator<(K, V)> for SlruCore<K, V>
1222where
1223 K: Clone + Eq + Hash,
1224{
1225 fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
1226 let iter = iter.into_iter();
1227 let (lower, _) = iter.size_hint();
1228 let mut cache = SlruCore::new(lower.max(16), 0.25);
1229 cache.extend(iter);
1230 cache
1231 }
1232}
1233
1234impl<K, V> Extend<(K, V)> for SlruCore<K, V>
1235where
1236 K: Clone + Eq + Hash,
1237{
1238 fn extend<I: IntoIterator<Item = (K, V)>>(&mut self, iter: I) {
1239 for (k, v) in iter {
1240 self.insert(k, v);
1241 }
1242 }
1243}
1244
1245#[cfg(test)]
1246mod tests {
1247 use super::*;
1248
1249 mod basic_operations {
1254 use super::*;
1255
1256 #[test]
1257 fn new_cache_is_empty() {
1258 let cache: SlruCore<&str, i32> = SlruCore::new(100, 0.25);
1259 assert!(cache.is_empty());
1260 assert_eq!(cache.len(), 0);
1261 assert_eq!(cache.capacity(), 100);
1262 }
1263
1264 #[test]
1265 fn insert_and_get() {
1266 let mut cache = SlruCore::new(100, 0.25);
1267 cache.insert("key1", "value1");
1268
1269 assert_eq!(cache.len(), 1);
1270 assert_eq!(cache.get(&"key1"), Some(&"value1"));
1271 }
1272
1273 #[test]
1274 fn insert_multiple_items() {
1275 let mut cache = SlruCore::new(100, 0.25);
1276 cache.insert("a", 1);
1277 cache.insert("b", 2);
1278 cache.insert("c", 3);
1279
1280 assert_eq!(cache.len(), 3);
1281 assert_eq!(cache.get(&"a"), Some(&1));
1282 assert_eq!(cache.get(&"b"), Some(&2));
1283 assert_eq!(cache.get(&"c"), Some(&3));
1284 }
1285
1286 #[test]
1287 fn get_missing_key_returns_none() {
1288 let mut cache: SlruCore<&str, i32> = SlruCore::new(100, 0.25);
1289 cache.insert("exists", 42);
1290
1291 assert_eq!(cache.get(&"missing"), None);
1292 }
1293
1294 #[test]
1295 fn update_existing_key() {
1296 let mut cache = SlruCore::new(100, 0.25);
1297 cache.insert("key", "initial");
1298 cache.insert("key", "updated");
1299
1300 assert_eq!(cache.len(), 1);
1301 assert_eq!(cache.get(&"key"), Some(&"updated"));
1302 }
1303
1304 #[test]
1305 fn contains_returns_correct_result() {
1306 let mut cache = SlruCore::new(100, 0.25);
1307 cache.insert("exists", 1);
1308
1309 assert!(cache.contains(&"exists"));
1310 assert!(!cache.contains(&"missing"));
1311 }
1312
1313 #[test]
1314 fn contains_does_not_promote() {
1315 let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
1316 cache.insert("a".to_string(), 1);
1317 cache.insert("b".to_string(), 2);
1318 cache.insert("c".to_string(), 3);
1319
1320 assert!(cache.contains(&"a".to_string()));
1321 assert!(cache.contains(&"b".to_string()));
1322 assert!(cache.contains(&"c".to_string()));
1323
1324 for i in 0..10 {
1325 cache.insert(format!("new{}", i), i);
1326 }
1327
1328 assert!(!cache.contains(&"a".to_string()));
1329 assert!(!cache.contains(&"b".to_string()));
1330 assert!(!cache.contains(&"c".to_string()));
1331 }
1332
1333 #[test]
1334 fn clear_removes_all_entries() {
1335 let mut cache = SlruCore::new(100, 0.25);
1336 cache.insert("a", 1);
1337 cache.insert("b", 2);
1338 cache.get(&"a");
1339
1340 cache.clear();
1341
1342 assert!(cache.is_empty());
1343 assert_eq!(cache.len(), 0);
1344 assert!(!cache.contains(&"a"));
1345 assert!(!cache.contains(&"b"));
1346 }
1347
1348 #[test]
1349 fn capacity_returns_correct_value() {
1350 let cache: SlruCore<i32, i32> = SlruCore::new(500, 0.25);
1351 assert_eq!(cache.capacity(), 500);
1352 }
1353 }
1354
1355 mod peek_tests {
1360 use super::*;
1361
1362 #[test]
1363 fn peek_returns_value_without_promotion() {
1364 let mut cache = SlruCore::new(100, 0.25);
1365 cache.insert("key", 42);
1366
1367 assert_eq!(cache.peek(&"key"), Some(&42));
1368 assert_eq!(cache.peek(&"missing"), None);
1369 }
1370
1371 #[test]
1372 fn peek_does_not_promote_to_protected() {
1373 let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
1374 cache.insert("key".to_string(), 1);
1375
1376 for _ in 0..10 {
1378 assert_eq!(cache.peek(&"key".to_string()), Some(&1));
1379 }
1380
1381 for i in 0..12 {
1383 cache.insert(format!("filler{}", i), i);
1384 }
1385
1386 assert!(!cache.contains(&"key".to_string()));
1387 }
1388 }
1389
1390 mod segment_behavior {
1395 use super::*;
1396
1397 #[test]
1398 fn new_insert_goes_to_probationary() {
1399 let mut cache = SlruCore::new(10, 0.3);
1400 cache.insert("key", "value");
1401
1402 assert!(cache.contains(&"key"));
1403 assert_eq!(cache.len(), 1);
1404 assert_eq!(cache.probationary_len(), 1);
1405 assert_eq!(cache.protected_len(), 0);
1406 }
1407
1408 #[test]
1409 fn get_promotes_from_probationary_to_protected() {
1410 let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
1411 cache.insert("key".to_string(), 0);
1412
1413 let _ = cache.get(&"key".to_string());
1414
1415 for i in 0..12 {
1416 cache.insert(format!("new{}", i), i);
1417 }
1418
1419 assert!(cache.contains(&"key".to_string()));
1420 }
1421
1422 #[test]
1423 fn item_in_protected_stays_in_protected() {
1424 let mut cache = SlruCore::new(10, 0.3);
1425 cache.insert("key", "value");
1426
1427 cache.get(&"key");
1428 cache.get(&"key");
1429 cache.get(&"key");
1430
1431 assert_eq!(cache.get(&"key"), Some(&"value"));
1432 }
1433
1434 #[test]
1435 fn multiple_accesses_keep_item_alive() {
1436 let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
1437
1438 cache.insert("hot".to_string(), 0);
1439 cache.get(&"hot".to_string());
1440
1441 for i in 0..15 {
1442 cache.insert(format!("cold{}", i), i);
1443 cache.get(&"hot".to_string());
1444 }
1445
1446 assert!(cache.contains(&"hot".to_string()));
1447 }
1448
1449 #[test]
1450 fn segment_len_accessors() {
1451 let mut cache = SlruCore::new(100, 0.25);
1452 cache.insert("a", 1);
1453 cache.insert("b", 2);
1454 assert_eq!(cache.probationary_len(), 2);
1455 assert_eq!(cache.protected_len(), 0);
1456
1457 cache.get(&"a");
1458 assert_eq!(cache.probationary_len(), 1);
1459 assert_eq!(cache.protected_len(), 1);
1460 }
1461 }
1462
1463 mod eviction_behavior {
1468 use super::*;
1469
1470 #[test]
1471 fn eviction_occurs_when_over_capacity() {
1472 let mut cache = SlruCore::new(5, 0.2);
1473
1474 for i in 0..10 {
1475 cache.insert(i, i * 10);
1476 }
1477
1478 assert_eq!(cache.len(), 5);
1479 }
1480
1481 #[test]
1482 fn probationary_evicts_lru_order() {
1483 let mut cache = SlruCore::new(5, 0.4);
1484
1485 cache.insert("first", 1);
1486 cache.insert("second", 2);
1487 cache.insert("third", 3);
1488 cache.insert("fourth", 4);
1489 cache.insert("fifth", 5);
1490 cache.insert("sixth", 6);
1491
1492 assert!(!cache.contains(&"first"));
1493 assert_eq!(cache.len(), 5);
1494 }
1495
1496 #[test]
1497 fn protected_evicts_lru_when_probationary_under_cap() {
1498 let mut cache = SlruCore::new(5, 0.4);
1499
1500 cache.insert("p1", 1);
1501 cache.get(&"p1");
1502 cache.insert("p2", 2);
1503 cache.get(&"p2");
1504 cache.insert("p3", 3);
1505 cache.get(&"p3");
1506
1507 cache.insert("new1", 10);
1508 cache.insert("new2", 20);
1509 cache.insert("new3", 30);
1510
1511 assert!(!cache.contains(&"p1"));
1512 assert_eq!(cache.len(), 5);
1513 }
1514
1515 #[test]
1516 fn scan_items_evicted_before_hot_items() {
1517 let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
1518
1519 cache.insert("hot1".to_string(), 1);
1520 cache.get(&"hot1".to_string());
1521 cache.insert("hot2".to_string(), 2);
1522 cache.get(&"hot2".to_string());
1523
1524 for i in 0..20 {
1525 cache.insert(format!("scan{}", i), i);
1526 }
1527
1528 assert!(cache.contains(&"hot1".to_string()));
1529 assert!(cache.contains(&"hot2".to_string()));
1530 assert_eq!(cache.len(), 10);
1531 }
1532
1533 #[test]
1534 fn eviction_removes_from_index() {
1535 let mut cache = SlruCore::new(3, 0.33);
1536
1537 cache.insert("a", 1);
1538 cache.insert("b", 2);
1539 cache.insert("c", 3);
1540
1541 assert!(cache.contains(&"a"));
1542
1543 cache.insert("d", 4);
1544
1545 assert!(!cache.contains(&"a"));
1546 assert_eq!(cache.get(&"a"), None);
1547 }
1548 }
1549
1550 mod scan_resistance {
1555 use super::*;
1556
1557 #[test]
1558 fn scan_does_not_pollute_protected() {
1559 let mut cache = SlruCore::new(100, 0.25);
1560
1561 for i in 0..50 {
1562 let key = format!("working{}", i);
1563 cache.insert(key.clone(), i);
1564 cache.get(&key);
1565 }
1566
1567 for i in 0..200 {
1568 cache.insert(format!("scan{}", i), i);
1569 }
1570
1571 let mut working_set_hits = 0;
1572 for i in 0..50 {
1573 if cache.contains(&format!("working{}", i)) {
1574 working_set_hits += 1;
1575 }
1576 }
1577
1578 assert!(
1579 working_set_hits >= 40,
1580 "Working set should survive scan, but only {} items remained",
1581 working_set_hits
1582 );
1583 }
1584
1585 #[test]
1586 fn one_time_access_stays_in_probationary() {
1587 let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
1588
1589 cache.insert("once".to_string(), 1);
1590
1591 for i in 0..5 {
1592 cache.insert(format!("other{}", i), i);
1593 }
1594
1595 cache.get(&"once".to_string());
1596
1597 for i in 0..10 {
1598 cache.insert(format!("new{}", i), i);
1599 }
1600
1601 assert!(cache.contains(&"once".to_string()));
1602 }
1603
1604 #[test]
1605 fn repeated_scans_dont_evict_hot_items() {
1606 let mut cache = SlruCore::new(20, 0.25);
1607
1608 for i in 0..10 {
1609 let key = format!("hot{}", i);
1610 cache.insert(key.clone(), i);
1611 cache.get(&key);
1612 cache.get(&key);
1613 cache.get(&key);
1614 }
1615
1616 for scan in 0..3 {
1617 for i in 0..30 {
1618 cache.insert(format!("scan{}_{}", scan, i), i);
1619 }
1620 }
1621
1622 let mut hot_survivors = 0;
1623 for i in 0..10 {
1624 if cache.contains(&format!("hot{}", i)) {
1625 hot_survivors += 1;
1626 }
1627 }
1628
1629 assert!(
1630 hot_survivors >= 8,
1631 "Hot items should survive scans, but only {} survived",
1632 hot_survivors
1633 );
1634 }
1635 }
1636
1637 mod edge_cases {
1642 use super::*;
1643
1644 #[test]
1645 fn single_capacity_cache() {
1646 let mut cache = SlruCore::new(1, 0.5);
1647
1648 cache.insert("a", 1);
1649 assert_eq!(cache.get(&"a"), Some(&1));
1650
1651 cache.insert("b", 2);
1652 assert!(!cache.contains(&"a"));
1653 assert_eq!(cache.get(&"b"), Some(&2));
1654 }
1655
1656 #[test]
1657 fn zero_probationary_fraction() {
1658 let mut cache = SlruCore::new(10, 0.0);
1659
1660 for i in 0..10 {
1661 cache.insert(i, i * 10);
1662 }
1663
1664 assert_eq!(cache.len(), 10);
1665
1666 cache.insert(100, 1000);
1667 assert_eq!(cache.len(), 10);
1668 }
1669
1670 #[test]
1671 fn one_hundred_percent_probationary() {
1672 let mut cache = SlruCore::new(10, 1.0);
1673
1674 for i in 0..10 {
1675 cache.insert(i, i * 10);
1676 }
1677
1678 for i in 0..10 {
1679 cache.get(&i);
1680 }
1681
1682 assert_eq!(cache.len(), 10);
1683 }
1684
1685 #[test]
1686 fn get_after_update() {
1687 let mut cache = SlruCore::new(100, 0.25);
1688
1689 cache.insert("key", "v1");
1690 assert_eq!(cache.get(&"key"), Some(&"v1"));
1691
1692 cache.insert("key", "v2");
1693 assert_eq!(cache.get(&"key"), Some(&"v2"));
1694
1695 cache.insert("key", "v3");
1696 cache.insert("key", "v4");
1697 assert_eq!(cache.get(&"key"), Some(&"v4"));
1698 }
1699
1700 #[test]
1701 fn large_capacity() {
1702 let mut cache = SlruCore::new(10000, 0.25);
1703
1704 for i in 0..10000 {
1705 cache.insert(i, i * 2);
1706 }
1707
1708 assert_eq!(cache.len(), 10000);
1709
1710 assert_eq!(cache.get(&5000), Some(&10000));
1711 assert_eq!(cache.get(&9999), Some(&19998));
1712 }
1713
1714 #[test]
1715 fn empty_cache_operations() {
1716 let mut cache: SlruCore<i32, i32> = SlruCore::new(100, 0.25);
1717
1718 assert!(cache.is_empty());
1719 assert_eq!(cache.get(&1), None);
1720 assert!(!cache.contains(&1));
1721
1722 cache.clear();
1723 assert!(cache.is_empty());
1724 }
1725
1726 #[test]
1727 fn small_fractions() {
1728 let mut cache = SlruCore::new(100, 0.01);
1729
1730 for i in 0..10 {
1731 cache.insert(i, i);
1732 }
1733
1734 assert_eq!(cache.len(), 10);
1735 }
1736
1737 #[test]
1738 fn string_keys_and_values() {
1739 let mut cache = SlruCore::new(100, 0.25);
1740
1741 cache.insert(String::from("hello"), String::from("world"));
1742 cache.insert(String::from("foo"), String::from("bar"));
1743
1744 assert_eq!(
1745 cache.get(&String::from("hello")),
1746 Some(&String::from("world"))
1747 );
1748 assert_eq!(cache.get(&String::from("foo")), Some(&String::from("bar")));
1749 }
1750
1751 #[test]
1752 fn integer_keys() {
1753 let mut cache = SlruCore::new(100, 0.25);
1754
1755 for i in 0..50 {
1756 cache.insert(i, format!("value_{}", i));
1757 }
1758
1759 assert_eq!(cache.get(&25), Some(&String::from("value_25")));
1760 assert_eq!(cache.get(&49), Some(&String::from("value_49")));
1761 }
1762
1763 #[test]
1764 #[should_panic(expected = "probationary_frac must be in 0.0..=1.0")]
1765 fn negative_fraction_panics() {
1766 let _cache: SlruCore<i32, i32> = SlruCore::new(100, -0.5);
1767 }
1768
1769 #[test]
1770 #[should_panic(expected = "probationary_frac must be in 0.0..=1.0")]
1771 fn fraction_above_one_panics() {
1772 let _cache: SlruCore<i32, i32> = SlruCore::new(100, 1.5);
1773 }
1774
1775 #[test]
1776 #[should_panic(expected = "probationary_frac must be in 0.0..=1.0")]
1777 fn nan_fraction_panics() {
1778 let _cache: SlruCore<i32, i32> = SlruCore::new(100, f64::NAN);
1779 }
1780 }
1781
1782 mod boundary_tests {
1787 use super::*;
1788
1789 #[test]
1790 fn exact_capacity_no_eviction() {
1791 let mut cache = SlruCore::new(10, 0.3);
1792
1793 for i in 0..10 {
1794 cache.insert(i, i);
1795 }
1796
1797 assert_eq!(cache.len(), 10);
1798 for i in 0..10 {
1799 assert!(cache.contains(&i));
1800 }
1801 }
1802
1803 #[test]
1804 fn one_over_capacity_triggers_eviction() {
1805 let mut cache = SlruCore::new(10, 0.3);
1806
1807 for i in 0..10 {
1808 cache.insert(i, i);
1809 }
1810
1811 cache.insert(10, 10);
1812
1813 assert_eq!(cache.len(), 10);
1814 assert!(!cache.contains(&0));
1815 assert!(cache.contains(&10));
1816 }
1817
1818 #[test]
1819 fn probationary_cap_boundary() {
1820 let mut cache = SlruCore::new(10, 0.3);
1821
1822 cache.insert("a", 1);
1823 cache.insert("b", 2);
1824 cache.insert("c", 3);
1825
1826 assert_eq!(cache.len(), 3);
1827
1828 cache.insert("d", 4);
1829 assert_eq!(cache.len(), 4);
1830
1831 for key in &["a", "b", "c", "d"] {
1832 assert!(cache.contains(key));
1833 }
1834 }
1835
1836 #[test]
1837 fn promotion_fills_protected() {
1838 let mut cache = SlruCore::new(10, 0.3);
1839
1840 for i in 0..5 {
1841 cache.insert(i, i);
1842 }
1843
1844 for i in 0..5 {
1845 cache.get(&i);
1846 }
1847
1848 for i in 5..10 {
1849 cache.insert(i, i);
1850 }
1851
1852 assert_eq!(cache.len(), 10);
1853
1854 cache.insert(10, 10);
1855 assert_eq!(cache.len(), 10);
1856 }
1857 }
1858
1859 mod regression_tests {
1864 use super::*;
1865
1866 #[test]
1867 fn promotion_actually_moves_to_protected_segment() {
1868 let mut cache: SlruCore<String, i32> = SlruCore::new(5, 0.4);
1869
1870 cache.insert("key".to_string(), 0);
1871 cache.get(&"key".to_string());
1872
1873 cache.insert("p1".to_string(), 1);
1874 cache.insert("p2".to_string(), 2);
1875 cache.insert("p3".to_string(), 3);
1876 cache.insert("p4".to_string(), 4);
1877
1878 assert!(
1879 cache.contains(&"key".to_string()),
1880 "Promoted item should be in protected segment and survive probationary eviction"
1881 );
1882 }
1883
1884 #[test]
1885 fn update_preserves_segment_position() {
1886 let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
1887
1888 cache.insert("key".to_string(), 1);
1889 cache.get(&"key".to_string());
1890
1891 cache.insert("key".to_string(), 2);
1892
1893 assert_eq!(cache.get(&"key".to_string()), Some(&2));
1894
1895 for i in 0..15 {
1896 cache.insert(format!("other{}", i), i);
1897 }
1898
1899 assert!(cache.contains(&"key".to_string()));
1900 }
1901
1902 #[test]
1903 fn eviction_order_consistency() {
1904 for _ in 0..10 {
1905 let mut cache = SlruCore::new(5, 0.4);
1906
1907 cache.insert("a", 1);
1908 cache.insert("b", 2);
1909 cache.insert("c", 3);
1910 cache.insert("d", 4);
1911 cache.insert("e", 5);
1912 cache.insert("f", 6);
1913
1914 assert!(!cache.contains(&"a"), "First item should be evicted");
1915 assert!(cache.contains(&"f"), "New item should exist");
1916 }
1917 }
1918 }
1919
1920 mod workload_simulation {
1925 use super::*;
1926
1927 #[test]
1928 fn database_buffer_pool_workload() {
1929 let mut cache = SlruCore::new(100, 0.25);
1930
1931 for i in 0..10 {
1932 let key = format!("index_page_{}", i);
1933 cache.insert(key.clone(), format!("index_data_{}", i));
1934 cache.get(&key);
1935 cache.get(&key);
1936 }
1937
1938 for i in 0..200 {
1939 cache.insert(format!("table_page_{}", i), format!("row_data_{}", i));
1940 }
1941
1942 let mut index_hits = 0;
1943 for i in 0..10 {
1944 if cache.contains(&format!("index_page_{}", i)) {
1945 index_hits += 1;
1946 }
1947 }
1948
1949 assert!(
1950 index_hits >= 8,
1951 "Index pages should survive table scan, got {} hits",
1952 index_hits
1953 );
1954 }
1955
1956 #[test]
1957 fn web_cache_simulation() {
1958 let mut cache: SlruCore<String, String> = SlruCore::new(50, 0.3);
1959
1960 let popular = vec!["home", "about", "products", "contact"];
1961 for page in &popular {
1962 cache.insert(page.to_string(), format!("{}_content", page));
1963 cache.get(&page.to_string());
1964 cache.get(&page.to_string());
1965 }
1966
1967 for i in 0..100 {
1968 cache.insert(format!("blog_post_{}", i), format!("content_{}", i));
1969 }
1970
1971 for page in &popular {
1972 assert!(
1973 cache.contains(&page.to_string()),
1974 "Popular page '{}' should survive",
1975 page
1976 );
1977 }
1978 }
1979
1980 #[test]
1981 fn mixed_workload() {
1982 let mut cache = SlruCore::new(100, 0.25);
1983
1984 for i in 0..30 {
1985 let key = format!("working_{}", i);
1986 cache.insert(key.clone(), i);
1987 cache.get(&key);
1988 }
1989
1990 for round in 0..5 {
1991 for i in (0..30).step_by(3) {
1992 cache.get(&format!("working_{}", i));
1993 }
1994
1995 for i in 0..20 {
1996 cache.insert(format!("round_{}_{}", round, i), i);
1997 }
1998 }
1999
2000 let mut working_set_hits = 0;
2001 for i in (0..30).step_by(3) {
2002 if cache.contains(&format!("working_{}", i)) {
2003 working_set_hits += 1;
2004 }
2005 }
2006
2007 assert!(
2008 working_set_hits >= 8,
2009 "Frequently accessed working set should survive, got {} hits",
2010 working_set_hits
2011 );
2012 }
2013 }
2014
2015 #[test]
2020 #[cfg(debug_assertions)]
2021 fn validate_invariants_after_operations() {
2022 let mut cache = SlruCore::new(10, 0.3);
2023
2024 for i in 1..=10 {
2025 cache.insert(i, i * 100);
2026 }
2027 cache.validate_invariants();
2028
2029 for _ in 0..3 {
2030 cache.get(&1);
2031 cache.get(&2);
2032 }
2033 cache.validate_invariants();
2034
2035 cache.insert(11, 1100);
2036 cache.validate_invariants();
2037
2038 cache.insert(12, 1200);
2039 cache.validate_invariants();
2040
2041 cache.clear();
2042 cache.validate_invariants();
2043
2044 assert_eq!(cache.len(), 0);
2045 }
2046
2047 #[test]
2048 #[cfg(debug_assertions)]
2049 fn validate_invariants_with_segment_transitions() {
2050 let mut cache = SlruCore::new(5, 0.4);
2051 cache.insert(1, 100);
2052 cache.insert(2, 200);
2053 cache.insert(3, 300);
2054
2055 cache.get(&1);
2056 cache.validate_invariants();
2057
2058 cache.get(&2);
2059 cache.validate_invariants();
2060
2061 cache.insert(4, 400);
2062 cache.insert(5, 500);
2063 cache.validate_invariants();
2064
2065 cache.insert(6, 600);
2066 cache.validate_invariants();
2067
2068 assert_eq!(cache.len(), 5);
2069 }
2070
2071 #[test]
2076 fn zero_capacity_rejects_inserts() {
2077 let mut cache: SlruCore<&str, i32> = SlruCore::new(0, 0.25);
2078 assert_eq!(cache.capacity(), 0);
2079
2080 cache.insert("key", 42);
2081
2082 assert_eq!(
2083 cache.len(),
2084 0,
2085 "SlruCore with capacity=0 should reject inserts"
2086 );
2087 }
2088
2089 #[test]
2090 fn trait_insert_returns_old_value() {
2091 let mut cache: SlruCore<&str, i32> = SlruCore::new(10, 0.25);
2092
2093 let first = Cache::insert(&mut cache, "key", 1);
2094 assert_eq!(first, None);
2095
2096 let second = Cache::insert(&mut cache, "key", 2);
2097 assert_eq!(
2098 second,
2099 Some(1),
2100 "Second insert via trait should return old value"
2101 );
2102 }
2103
2104 #[test]
2105 fn inherent_insert_updates_value() {
2106 let mut cache: SlruCore<&str, i32> = SlruCore::new(10, 0.25);
2107
2108 cache.insert("key", 1);
2109 cache.insert("key", 2);
2110
2111 assert_eq!(cache.get(&"key"), Some(&2));
2112 }
2113
2114 #[test]
2115 fn default_creates_zero_capacity() {
2116 let cache: SlruCore<String, i32> = SlruCore::default();
2117 assert_eq!(cache.capacity(), 0);
2118 assert!(cache.is_empty());
2119 }
2120
2121 #[test]
2122 fn clone_preserves_entries() {
2123 let mut cache = SlruCore::new(100, 0.25);
2124 cache.insert("a", 1);
2125 cache.insert("b", 2);
2126 cache.get(&"a");
2127
2128 let cloned = cache.clone();
2129 assert_eq!(cloned.len(), 2);
2130 assert_eq!(cloned.peek(&"a"), Some(&1));
2131 assert_eq!(cloned.peek(&"b"), Some(&2));
2132 assert_eq!(cloned.capacity(), 100);
2133 }
2134
2135 #[test]
2136 fn clone_is_independent() {
2137 let mut cache = SlruCore::new(100, 0.25);
2138 cache.insert("a", 1);
2139
2140 let mut cloned = cache.clone();
2141 cloned.insert("a", 999);
2142 cloned.insert("b", 2);
2143
2144 assert_eq!(cache.peek(&"a"), Some(&1));
2145 assert!(!cache.contains(&"b"));
2146 }
2147
2148 #[test]
2149 fn from_iterator() {
2150 let data = vec![("a", 1), ("b", 2), ("c", 3)];
2151 let cache: SlruCore<&str, i32> = data.into_iter().collect();
2152
2153 assert_eq!(cache.len(), 3);
2154 assert_eq!(cache.peek(&"a"), Some(&1));
2155 assert_eq!(cache.peek(&"b"), Some(&2));
2156 assert_eq!(cache.peek(&"c"), Some(&3));
2157 }
2158
2159 #[test]
2160 fn into_iterator_owned() {
2161 let mut cache = SlruCore::new(100, 0.25);
2162 cache.insert("a", 1);
2163 cache.insert("b", 2);
2164 cache.insert("c", 3);
2165
2166 let mut items: Vec<_> = cache.into_iter().collect();
2167 items.sort_by_key(|(k, _)| *k);
2168 assert_eq!(items, vec![("a", 1), ("b", 2), ("c", 3)]);
2169 }
2170
2171 #[test]
2172 fn into_iterator_ref() {
2173 let mut cache = SlruCore::new(100, 0.25);
2174 cache.insert("a", 1);
2175 cache.insert("b", 2);
2176
2177 let mut items: Vec<_> = (&cache).into_iter().collect();
2178 items.sort_by_key(|(k, _)| **k);
2179 assert_eq!(items, vec![(&"a", &1), (&"b", &2)]);
2180 }
2181
2182 #[test]
2183 fn iter_exact_size() {
2184 let mut cache = SlruCore::new(100, 0.25);
2185 cache.insert("a", 1);
2186 cache.insert("b", 2);
2187 cache.get(&"a");
2188
2189 let iter = cache.iter();
2190 assert_eq!(iter.len(), 2);
2191 }
2192
2193 #[test]
2194 fn extend_adds_entries() {
2195 let mut cache = SlruCore::new(100, 0.25);
2196 cache.insert("a", 1);
2197
2198 cache.extend(vec![("b", 2), ("c", 3)]);
2199
2200 assert_eq!(cache.len(), 3);
2201 assert_eq!(cache.peek(&"b"), Some(&2));
2202 }
2203
2204 #[test]
2205 fn debug_impl_works() {
2206 let mut cache = SlruCore::new(100, 0.25);
2207 cache.insert("a", 1);
2208 let debug = format!("{:?}", cache);
2209 assert!(debug.contains("SlruCore"));
2210 assert!(debug.contains("capacity"));
2211 }
2212}