11// Copyright (c) Zefchain Labs, Inc.
22// SPDX-License-Identifier: Apache-2.0
33
4- use std:: { borrow:: Cow , collections :: BTreeSet } ;
4+ use std:: borrow:: Cow ;
55
66use linera_base:: {
77 crypto:: CryptoHash ,
@@ -23,7 +23,7 @@ fn test_retrieve_missing_value() {
2323 let hash = CryptoHash :: test_hash ( "Missing value" ) ;
2424
2525 assert ! ( cache. get( & hash) . is_none( ) ) ;
26- assert ! ( cache. keys :: < Vec <_>> ( ) . is_empty ( ) ) ;
26+ assert_eq ! ( cache. len ( ) , 0 ) ;
2727}
2828
2929/// Tests inserting a certificate value in the cache.
@@ -36,7 +36,7 @@ fn test_insert_single_certificate_value() {
3636 assert ! ( cache. insert( Cow :: Borrowed ( & value) ) ) ;
3737 assert ! ( cache. contains( & hash) ) ;
3838 assert_eq ! ( cache. get( & hash) , Some ( value) ) ;
39- assert_eq ! ( cache. keys :: < BTreeSet <_>> ( ) , BTreeSet :: from ( [ hash ] ) ) ;
39+ assert_eq ! ( cache. len ( ) , 1 ) ;
4040}
4141
4242/// Tests inserting many certificate values in the cache, one-by-one.
@@ -54,10 +54,7 @@ fn test_insert_many_certificate_values_individually() {
5454 assert_eq ! ( cache. get( & value. hash( ) ) . as_ref( ) , Some ( value) ) ;
5555 }
5656
57- assert_eq ! (
58- cache. keys:: <BTreeSet <_>>( ) ,
59- BTreeSet :: from_iter( values. iter( ) . map( Hashed :: hash) )
60- ) ;
57+ assert_eq ! ( cache. len( ) , TEST_CACHE_SIZE ) ;
6158}
6259
6360/// Tests inserting many values in the cache, all-at-once.
@@ -73,10 +70,7 @@ fn test_insert_many_values_together() {
7370 assert_eq ! ( cache. get( & value. hash( ) ) . as_ref( ) , Some ( value) ) ;
7471 }
7572
76- assert_eq ! (
77- cache. keys:: <BTreeSet <_>>( ) ,
78- BTreeSet :: from_iter( values. iter( ) . map( |el| el. hash( ) ) )
79- ) ;
73+ assert_eq ! ( cache. len( ) , TEST_CACHE_SIZE ) ;
8074}
8175
8276/// Tests re-inserting many values in the cache, all-at-once.
@@ -96,97 +90,126 @@ fn test_reinsertion_of_values() {
9690 assert_eq ! ( cache. get( & value. hash( ) ) . as_ref( ) , Some ( value) ) ;
9791 }
9892
99- assert_eq ! (
100- cache. keys:: <BTreeSet <_>>( ) ,
101- BTreeSet :: from_iter( values. iter( ) . map( Hashed :: hash) )
93+ // Re-insertion should not increase the count
94+ assert_eq ! ( cache. len( ) , TEST_CACHE_SIZE ) ;
95+ }
96+
97+ /// Tests eviction when cache is full.
98+ /// Note: quick_cache uses S3-FIFO eviction, so we verify that the cache
99+ /// doesn't grow unboundedly and eviction occurs.
100+ #[ test]
101+ fn test_eviction_occurs ( ) {
102+ let cache = ValueCache :: < CryptoHash , Hashed < Timeout > > :: new ( TEST_CACHE_SIZE ) ;
103+ // Insert more than capacity
104+ let values =
105+ create_dummy_certificate_values ( 0 ..( ( TEST_CACHE_SIZE as u64 ) * 2 ) ) . collect :: < Vec < _ > > ( ) ;
106+
107+ for value in & values {
108+ cache. insert ( Cow :: Borrowed ( value) ) ;
109+ }
110+
111+ // Cache size should be bounded by capacity
112+ assert ! (
113+ cache. len( ) <= TEST_CACHE_SIZE ,
114+ "Cache size {} exceeds capacity {}" ,
115+ cache. len( ) ,
116+ TEST_CACHE_SIZE
117+ ) ;
118+
119+ // Count how many values are still in cache
120+ let present_count = values. iter ( ) . filter ( |v| cache. contains ( & v. hash ( ) ) ) . count ( ) ;
121+
122+ // At least some values should have been evicted
123+ assert ! (
124+ present_count < values. len( ) ,
125+ "Cache should have evicted at least some entries"
102126 ) ;
103127}
104128
105- /// Tests eviction of one entry.
129+ /// Tests eviction when inserting one more than capacity.
130+ /// With S3-FIFO, the first inserted item may or may not be evicted (depends on access patterns).
106131#[ test]
107- fn test_one_eviction ( ) {
132+ fn test_one_over_capacity ( ) {
108133 let cache = ValueCache :: < CryptoHash , Hashed < Timeout > > :: new ( TEST_CACHE_SIZE ) ;
109134 let values = create_dummy_certificate_values ( 0 ..=( TEST_CACHE_SIZE as u64 ) ) . collect :: < Vec < _ > > ( ) ;
110135
111136 cache. insert_all ( values. iter ( ) . map ( Cow :: Borrowed ) ) ;
112137
113- assert ! ( !cache . contains ( & values [ 0 ] . hash ( ) ) ) ;
114- assert ! ( cache . get ( & values [ 0 ] . hash ( ) ) . is_none ( ) ) ;
115-
116- for value in values . iter ( ) . skip ( 1 ) {
117- assert ! ( cache. contains ( & value . hash ( ) ) ) ;
118- assert_eq ! ( cache . get ( & value . hash ( ) ) . as_ref ( ) , Some ( value ) ) ;
119- }
138+ // Cache should not exceed capacity
139+ assert ! (
140+ cache . len ( ) <= TEST_CACHE_SIZE ,
141+ "Cache size {} exceeds capacity {}" ,
142+ cache. len ( ) ,
143+ TEST_CACHE_SIZE
144+ ) ;
120145
146+ // Exactly one value should have been evicted
147+ let present_count = values. iter ( ) . filter ( |v| cache. contains ( & v. hash ( ) ) ) . count ( ) ;
121148 assert_eq ! (
122- cache. keys:: <BTreeSet <_>>( ) ,
123- BTreeSet :: from_iter( values. iter( ) . skip( 1 ) . map( Hashed :: hash) )
149+ present_count, TEST_CACHE_SIZE ,
150+ "Expected {} items in cache after inserting {} items with capacity {}" ,
151+ TEST_CACHE_SIZE ,
152+ values. len( ) ,
153+ TEST_CACHE_SIZE
124154 ) ;
125155}
126156
127- /// Tests eviction of the second entry.
157+ /// Tests that accessing a value affects eviction (values accessed recently are more likely to stay).
158+ /// S3-FIFO uses frequency-based eviction, so frequently accessed items should survive.
128159#[ test]
129- fn test_eviction_of_second_entry ( ) {
160+ fn test_access_affects_eviction ( ) {
130161 let cache = ValueCache :: < CryptoHash , Hashed < Timeout > > :: new ( TEST_CACHE_SIZE ) ;
131- let values = create_dummy_certificate_values ( 0 ..= ( TEST_CACHE_SIZE as u64 ) ) . collect :: < Vec < _ > > ( ) ;
162+ let values = create_dummy_certificate_values ( 0 ..( TEST_CACHE_SIZE as u64 ) ) . collect :: < Vec < _ > > ( ) ;
132163
133- cache. insert_all ( values. iter ( ) . take ( TEST_CACHE_SIZE ) . map ( Cow :: Borrowed ) ) ;
134- cache. get ( & values[ 0 ] . hash ( ) ) ;
135- assert ! ( cache. insert( Cow :: Borrowed ( & values[ TEST_CACHE_SIZE ] ) ) ) ;
164+ // Fill the cache
165+ cache. insert_all ( values. iter ( ) . map ( Cow :: Borrowed ) ) ;
136166
137- assert ! ( cache. contains( & values[ 0 ] . hash( ) ) ) ;
138- assert_eq ! ( cache. get( & values[ 0 ] . hash( ) ) . as_ref( ) , Some ( & values[ 0 ] ) ) ;
167+ // Access the first value multiple times to make it "hot"
168+ for _ in 0 ..5 {
169+ cache. get ( & values[ 0 ] . hash ( ) ) ;
170+ }
139171
140- assert ! ( !cache. contains( & values[ 1 ] . hash( ) ) ) ;
141- assert ! ( cache. get( & values[ 1 ] . hash( ) ) . is_none( ) ) ;
172+ // Insert additional values to trigger eviction
173+ let extra_values =
174+ create_dummy_certificate_values ( ( TEST_CACHE_SIZE as u64 ) ..( ( TEST_CACHE_SIZE as u64 ) + 5 ) )
175+ . collect :: < Vec < _ > > ( ) ;
142176
143- for value in values. iter ( ) . skip ( 2 ) {
144- assert ! ( cache. contains( & value. hash( ) ) ) ;
145- assert_eq ! ( cache. get( & value. hash( ) ) . as_ref( ) , Some ( value) ) ;
177+ for value in & extra_values {
178+ cache. insert ( Cow :: Borrowed ( value) ) ;
146179 }
147180
148- assert_eq ! (
149- cache. keys:: <BTreeSet <_>>( ) ,
150- BTreeSet :: from_iter(
151- values
152- . iter( )
153- . skip( 2 )
154- . map( Hashed :: hash)
155- . chain( Some ( values[ 0 ] . hash( ) ) )
156- )
181+ // The frequently accessed first value should still be present
182+ assert ! (
183+ cache. contains( & values[ 0 ] . hash( ) ) ,
184+ "Frequently accessed value should survive eviction"
157185 ) ;
158186}
159187
160- /// Tests if reinsertion of the first entry promotes it so that it's not evicted so soon .
188+ /// Tests that re-inserting a value promotes it in the eviction order .
161189#[ test]
162190fn test_promotion_of_reinsertion ( ) {
163191 let cache = ValueCache :: < CryptoHash , Hashed < Timeout > > :: new ( TEST_CACHE_SIZE ) ;
164- let values = create_dummy_certificate_values ( 0 ..= ( TEST_CACHE_SIZE as u64 ) ) . collect :: < Vec < _ > > ( ) ;
192+ let values = create_dummy_certificate_values ( 0 ..( TEST_CACHE_SIZE as u64 ) ) . collect :: < Vec < _ > > ( ) ;
165193
166- cache. insert_all ( values. iter ( ) . take ( TEST_CACHE_SIZE ) . map ( Cow :: Borrowed ) ) ;
167- assert ! ( !cache. insert( Cow :: Borrowed ( & values[ 0 ] ) ) ) ;
168- assert ! ( cache. insert( Cow :: Borrowed ( & values[ TEST_CACHE_SIZE ] ) ) ) ;
194+ // Fill the cache
195+ cache. insert_all ( values. iter ( ) . map ( Cow :: Borrowed ) ) ;
169196
170- assert ! ( cache . contains ( & values [ 0 ] . hash ( ) ) ) ;
171- assert_eq ! ( cache. get ( & values [ 0 ] . hash ( ) ) . as_ref ( ) , Some ( & values[ 0 ] ) ) ;
197+ // Re-insert the first value (this should "promote" it)
198+ assert ! ( ! cache. insert ( Cow :: Borrowed ( & values[ 0 ] ) ) ) ;
172199
173- assert ! ( !cache. contains( & values[ 1 ] . hash( ) ) ) ;
174- assert ! ( cache. get( & values[ 1 ] . hash( ) ) . is_none( ) ) ;
200+ // Insert additional values to trigger eviction
201+ let extra_values =
202+ create_dummy_certificate_values ( ( TEST_CACHE_SIZE as u64 ) ..( ( TEST_CACHE_SIZE as u64 ) + 3 ) )
203+ . collect :: < Vec < _ > > ( ) ;
175204
176- for value in values. iter ( ) . skip ( 2 ) {
177- assert ! ( cache. contains( & value. hash( ) ) ) ;
178- assert_eq ! ( cache. get( & value. hash( ) ) . as_ref( ) , Some ( value) ) ;
205+ for value in & extra_values {
206+ cache. insert ( Cow :: Borrowed ( value) ) ;
179207 }
180208
181- assert_eq ! (
182- cache. keys:: <BTreeSet <_>>( ) ,
183- BTreeSet :: from_iter(
184- values
185- . iter( )
186- . skip( 2 )
187- . map( Hashed :: hash)
188- . chain( Some ( values[ 0 ] . hash( ) ) )
189- )
209+ // The re-inserted first value should still be present
210+ assert ! (
211+ cache. contains( & values[ 0 ] . hash( ) ) ,
212+ "Re-inserted value should survive eviction"
190213 ) ;
191214}
192215
0 commit comments