github.com/decred/dcrlnd@v0.7.6/neutrinocache/lru/lru_test.go (about) 1 package lru 2 3 import ( 4 "fmt" 5 "sync" 6 "testing" 7 8 cache "github.com/decred/dcrlnd/neutrinocache" 9 ) 10 11 func assertEqual(t *testing.T, a interface{}, b interface{}, message string) { 12 if a == b { 13 return 14 } 15 if len(message) == 0 { 16 message = fmt.Sprintf("%v != %v", a, b) 17 } 18 t.Fatal(message) 19 } 20 21 // sizeable is a simple struct that represents an element of arbitrary size 22 // which holds a simple integer. 23 type sizeable struct { 24 value int 25 size uint64 26 } 27 28 // Size implements the CacheEntry interface on sizeable struct. 29 func (s *sizeable) Size() (uint64, error) { 30 return s.size, nil 31 } 32 33 // getSizeableValue is a helper method used for converting the cache.Value 34 // interface to sizeable struct and extracting the value from it. 35 func getSizeableValue(generic cache.Value, _ error) int { 36 return generic.(*sizeable).value 37 } 38 39 // TestEmptyCacheSizeZero will check that an empty cache has a size of 0. 40 func TestEmptyCacheSizeZero(t *testing.T) { 41 t.Parallel() 42 c := NewCache(10) 43 assertEqual(t, c.Len(), 0, "") 44 } 45 46 // TestCacheNeverExceedsSize inserts many filters into the cache and verifies 47 // at each step that the cache never exceeds it's initial size. 48 func TestCacheNeverExceedsSize(t *testing.T) { 49 t.Parallel() 50 c := NewCache(2) 51 c.Put(1, &sizeable{value: 1, size: 1}) 52 c.Put(2, &sizeable{value: 2, size: 1}) 53 assertEqual(t, c.Len(), 2, "") 54 for i := 0; i < 10; i++ { 55 c.Put(i, &sizeable{value: i, size: 1}) 56 assertEqual(t, c.Len(), 2, "") 57 } 58 } 59 60 // TestCacheAlwaysHasLastAccessedItems will check that the last items that 61 // were put in the cache are always available, it will also check the eviction 62 // behavior when items put in the cache exceeds cache capacity. 63 func TestCacheAlwaysHasLastAccessedItems(t *testing.T) { 64 t.Parallel() 65 c := NewCache(2) 66 c.Put(1, &sizeable{value: 1, size: 1}) 67 c.Put(2, &sizeable{value: 2, size: 1}) 68 two := getSizeableValue(c.Get(2)) 69 one := getSizeableValue(c.Get(1)) 70 assertEqual(t, two, 2, "") 71 assertEqual(t, one, 1, "") 72 73 c = NewCache(2) 74 c.Put(1, &sizeable{value: 1, size: 1}) 75 c.Put(2, &sizeable{value: 2, size: 1}) 76 c.Put(3, &sizeable{value: 3, size: 1}) 77 oneEntry, _ := c.Get(1) 78 two = getSizeableValue(c.Get(2)) 79 three := getSizeableValue(c.Get(3)) 80 assertEqual(t, oneEntry, nil, "") 81 assertEqual(t, two, 2, "") 82 assertEqual(t, three, 3, "") 83 84 c = NewCache(2) 85 c.Put(1, &sizeable{value: 1, size: 1}) 86 c.Put(2, &sizeable{value: 2, size: 1}) 87 c.Get(1) 88 c.Put(3, &sizeable{value: 3, size: 1}) 89 one = getSizeableValue(c.Get(1)) 90 twoEntry, _ := c.Get(2) 91 three = getSizeableValue(c.Get(3)) 92 assertEqual(t, one, 1, "") 93 assertEqual(t, twoEntry, nil, "") 94 assertEqual(t, three, 3, "") 95 } 96 97 // TestElementSizeCapacityEvictsEverything tests that Cache evicts everything 98 // from cache when an element with size=capacity is inserted. 99 func TestElementSizeCapacityEvictsEverything(t *testing.T) { 100 t.Parallel() 101 c := NewCache(3) 102 103 c.Put(1, &sizeable{value: 1, size: 1}) 104 c.Put(2, &sizeable{value: 2, size: 1}) 105 c.Put(3, &sizeable{value: 3, size: 1}) 106 107 // Insert element with size=capacity of cache, should evict everything. 108 c.Put(4, &sizeable{value: 4, size: 3}) 109 assertEqual(t, c.Len(), 1, "") 110 assertEqual(t, len(c.cache), 1, "") 111 four := getSizeableValue(c.Get(4)) 112 assertEqual(t, four, 4, "") 113 114 c = NewCache(6) 115 c.Put(1, &sizeable{value: 1, size: 1}) 116 c.Put(2, &sizeable{value: 2, size: 2}) 117 c.Put(3, &sizeable{value: 3, size: 3}) 118 assertEqual(t, c.size, uint64(6), "") 119 120 // Insert element with size=capacity of cache. 121 c.Put(4, &sizeable{value: 4, size: 6}) 122 assertEqual(t, c.Len(), 1, "") 123 assertEqual(t, len(c.cache), 1, "") 124 four = getSizeableValue(c.Get(4)) 125 assertEqual(t, four, 4, "") 126 } 127 128 // TestCacheFailsInsertionSizeBiggerCapacity tests that the cache fails the 129 // put operation when the element's size is bigger than it's capacity. 130 func TestCacheFailsInsertionSizeBiggerCapacity(t *testing.T) { 131 t.Parallel() 132 c := NewCache(2) 133 134 _, err := c.Put(1, &sizeable{value: 1, size: 3}) 135 if err == nil { 136 t.Fatal("shouldn't be able to put elements larger than cache") 137 } 138 assertEqual(t, c.Len(), 0, "") 139 } 140 141 // TestManySmallElementCanInsertAfterBigEviction tests that when a big element 142 // is evicted from the Cache, multiple smaller ones can be inserted without an 143 // eviction taking place. 144 func TestManySmallElementCanInsertAfterBigEviction(t *testing.T) { 145 t.Parallel() 146 c := NewCache(3) 147 148 _, err := c.Put(1, &sizeable{value: 1, size: 3}) 149 if err != nil { 150 t.Fatal("couldn't insert element") 151 } 152 153 assertEqual(t, c.Len(), 1, "") 154 155 c.Put(2, &sizeable{value: 2, size: 1}) 156 two := getSizeableValue(c.Get(2)) 157 oneEntry, _ := c.Get(1) 158 assertEqual(t, c.Len(), 1, "") 159 assertEqual(t, two, 2, "") 160 assertEqual(t, oneEntry, nil, "") 161 162 c.Put(3, &sizeable{value: 3, size: 1}) 163 assertEqual(t, c.Len(), 2, "") 164 165 c.Put(4, &sizeable{value: 4, size: 1}) 166 assertEqual(t, c.Len(), 3, "") 167 168 two = getSizeableValue(c.Get(2)) 169 three := getSizeableValue(c.Get(3)) 170 four := getSizeableValue(c.Get(4)) 171 assertEqual(t, two, 2, "") 172 assertEqual(t, three, 3, "") 173 assertEqual(t, four, 4, "") 174 } 175 176 // TestReplacingElementValueSmallerSize tests that if an existing element is 177 // replaced with a value of size smaller, that the size shrinks and we can 178 // insert without an eviction taking place. 179 func TestReplacingElementValueSmallerSize(t *testing.T) { 180 t.Parallel() 181 c := NewCache(2) 182 183 c.Put(1, &sizeable{value: 1, size: 2}) 184 185 c.Put(1, &sizeable{value: 1, size: 1}) 186 c.Put(2, &sizeable{value: 2, size: 1}) 187 one := getSizeableValue(c.Get(1)) 188 two := getSizeableValue(c.Get(2)) 189 assertEqual(t, one, 1, "") 190 assertEqual(t, two, 2, "") 191 assertEqual(t, c.Len(), 2, "") 192 } 193 194 // TestReplacingElementValueBiggerSize tests that if an existing element is 195 // replaced with a value of size bigger, that it evicts accordingly. 196 func TestReplacingElementValueBiggerSize(t *testing.T) { 197 t.Parallel() 198 c := NewCache(2) 199 200 c.Put(1, &sizeable{value: 1, size: 1}) 201 c.Put(2, &sizeable{value: 2, size: 1}) 202 203 c.Put(1, &sizeable{value: 3, size: 2}) 204 assertEqual(t, c.Len(), 1, "") 205 one := getSizeableValue(c.Get(1)) 206 assertEqual(t, one, 3, "") 207 } 208 209 // TestConcurrencySimple is a very simple test that checks concurrent access to 210 // the lru cache. When running the test, "-race" option should be passed to 211 // "go test" command. 212 func TestConcurrencySimple(t *testing.T) { 213 t.Parallel() 214 c := NewCache(5) 215 var wg sync.WaitGroup 216 217 for i := 0; i < 5; i++ { 218 wg.Add(1) 219 go func(i int) { 220 defer wg.Done() 221 _, err := c.Put(i, &sizeable{value: i, size: 1}) 222 if err != nil { 223 panic(err) 224 } 225 }(i) 226 } 227 228 for i := 0; i < 5; i++ { 229 wg.Add(1) 230 go func(i int) { 231 defer wg.Done() 232 _, err := c.Get(i) 233 if err != nil && err != cache.ErrElementNotFound { 234 panic(err) 235 } 236 }(i) 237 } 238 239 wg.Wait() 240 } 241 242 // TestConcurrencySmallCache is a test that checks concurrent access to the 243 // lru cache when the cache is smaller than the number of elements we want to 244 // put and retrieve. When running the test, "-race" option should be passed to 245 // "go test" command. 246 func TestConcurrencySmallCache(t *testing.T) { 247 t.Parallel() 248 c := NewCache(5) 249 var wg sync.WaitGroup 250 251 for i := 0; i < 20; i++ { 252 wg.Add(1) 253 go func(i int) { 254 defer wg.Done() 255 _, err := c.Put(i, &sizeable{value: i, size: 1}) 256 if err != nil { 257 panic(err) 258 } 259 }(i) 260 } 261 262 for i := 0; i < 20; i++ { 263 wg.Add(1) 264 go func(i int) { 265 defer wg.Done() 266 _, err := c.Get(i) 267 if err != nil && err != cache.ErrElementNotFound { 268 panic(err) 269 } 270 }(i) 271 } 272 273 wg.Wait() 274 } 275 276 // TestConcurrencyBigCache is a test that checks concurrent access to the 277 // lru cache when the cache is bigger than the number of elements we want to 278 // put and retrieve. When running the test, "-race" option should be passed to 279 // "go test" command. 280 func TestConcurrencyBigCache(t *testing.T) { 281 t.Parallel() 282 c := NewCache(100) 283 var wg sync.WaitGroup 284 285 for i := 0; i < 50; i++ { 286 wg.Add(1) 287 go func(i int) { 288 defer wg.Done() 289 _, err := c.Put(i, &sizeable{value: i, size: 1}) 290 if err != nil { 291 panic(err) 292 } 293 }(i) 294 } 295 296 for i := 0; i < 50; i++ { 297 wg.Add(1) 298 go func(i int) { 299 defer wg.Done() 300 _, err := c.Get(i) 301 if err != nil && err != cache.ErrElementNotFound { 302 panic(err) 303 } 304 }(i) 305 } 306 307 wg.Wait() 308 }