github.com/blend/go-sdk@v1.20220411.3/cache/local_cache_test.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package cache 9 10 import ( 11 "context" 12 "fmt" 13 "strconv" 14 "testing" 15 "time" 16 17 "github.com/blend/go-sdk/assert" 18 "github.com/blend/go-sdk/graceful" 19 ) 20 21 var ( 22 _ graceful.Graceful = (*LocalCache)(nil) 23 ) 24 25 type itemKey struct{} 26 type altItemKey struct{} 27 28 func TestLocalCache(t *testing.T) { 29 assert := assert.New(t) 30 31 c := New() 32 33 t1 := time.Date(2019, 06, 14, 12, 10, 9, 8, time.UTC) 34 t2 := time.Date(2019, 06, 14, 00, 01, 02, 03, time.UTC) 35 t3 := time.Date(2019, 06, 14, 12, 01, 02, 03, time.UTC) 36 37 c.Set(itemKey{}, "foo", OptValueExpires(t1)) 38 assert.True(c.Has(itemKey{})) 39 assert.False(c.Has(altItemKey{})) 40 41 found, ok := c.Get(itemKey{}) 42 assert.True(ok) 43 assert.Equal("foo", found) 44 45 c.Set(altItemKey{}, "alt-bar") 46 assert.True(c.Has(itemKey{})) 47 assert.True(c.Has(altItemKey{})) 48 49 found, ok = c.Get(itemKey{}) 50 assert.True(ok) 51 assert.Equal("foo", found) 52 53 found, ok = c.Get(altItemKey{}) 54 assert.True(ok) 55 assert.Equal("alt-bar", found) 56 57 c.Set(itemKey{}, "foo-2", OptValueExpires(t2)) 58 assert.Equal(t2, c.Data[itemKey{}].Expires) 59 c.Set(altItemKey{}, "alt-bar-2", OptValueExpires(t3)) 60 assert.Equal(t3, c.Data[altItemKey{}].Expires) 61 62 found, ok = c.Get(itemKey{}) 63 assert.True(ok) 64 assert.Equal("foo-2", found) 65 66 c.Remove(itemKey{}) 67 assert.False(c.Has(itemKey{})) 68 assert.True(c.Has(altItemKey{})) 69 70 found, ok = c.Get(itemKey{}) 71 assert.False(ok) 72 assert.Nil(found) 73 74 c.Set(itemKey{}, "bar", OptValueExpires(time.Now().UTC().Add(-time.Hour))) 75 assert.True(c.Has(itemKey{})) 76 assert.True(c.Has(altItemKey{})) 77 } 78 79 func try(action func()) (err error) { 80 defer func() { 81 if r := recover(); r != nil { 82 err = fmt.Errorf("%v", r) 83 } 84 }() 85 86 action() 87 return 88 } 89 90 func TestLocalCacheKeyPanic(t *testing.T) { 91 assert := assert.New(t) 92 93 c := New() 94 95 assert.NotNil(try(func() { 96 c.Set(nil, "bar") 97 })) 98 assert.NotNil(try(func() { 99 c.Set([]int{}, "bar") 100 })) 101 } 102 103 func TestLocalCacheGetOrSet(t *testing.T) { 104 assert := assert.New(t) 105 106 valueProvider := func() (interface{}, error) { return "foo", nil } 107 108 lc := New() 109 found, ok, err := lc.GetOrSet(itemKey{}, valueProvider) 110 assert.Nil(err) 111 assert.False(ok) 112 assert.Equal("foo", found) 113 assert.True(lc.Has(itemKey{})) 114 assert.Equal(itemKey{}, lc.LRU.Peek().Key) 115 116 found, ok, err = lc.GetOrSet(itemKey{}, valueProvider) 117 assert.Nil(err) 118 assert.True(ok) 119 assert.Equal("foo", found) 120 121 lc.Set(itemKey{}, "bar") 122 123 found, ok, err = lc.GetOrSet(itemKey{}, valueProvider) 124 assert.Nil(err) 125 assert.True(ok) 126 assert.Equal("bar", found) 127 128 lc.Remove(itemKey{}) 129 assert.False(lc.Has(itemKey{})) 130 131 found, ok, err = lc.GetOrSet(itemKey{}, valueProvider) 132 assert.Nil(err) 133 assert.False(ok) 134 assert.Equal("foo", found) 135 } 136 137 func TestLocalCacheGetOrSetError(t *testing.T) { 138 assert := assert.New(t) 139 140 valueProvider := func() (interface{}, error) { 141 return nil, fmt.Errorf("test") 142 } 143 144 lc := New() 145 146 found, ok, err := lc.GetOrSet("test", valueProvider) 147 assert.NotNil(err) 148 assert.False(ok) 149 assert.Nil(found) 150 151 assert.False(lc.Has("test")) 152 } 153 154 func TestLocalCacheGetOrSetDoubleCheckRace(t *testing.T) { 155 assert := assert.New(t) 156 157 didSet := make(chan struct{}) 158 valueProvider := func() (interface{}, error) { 159 <-didSet 160 return "foo", nil 161 } 162 163 lc := New() 164 165 go func() { 166 lc.Set("test", "bar2") 167 close(didSet) 168 }() 169 170 found, ok, err := lc.GetOrSet("test", valueProvider) 171 assert.Nil(err) 172 assert.True(ok) 173 assert.Equal("bar2", found) 174 } 175 176 func TestLocalCacheSetUpdatesLRU(t *testing.T) { 177 assert := assert.New(t) 178 179 c := New() 180 c.Set("k1", "v1", OptValueTTL(0)) 181 c.Set("k2", "v2", OptValueTTL(0)) 182 assert.Equal("k1", c.LRU.Peek().Key) 183 184 time.Sleep(time.Millisecond) 185 // Should trigger sorting of underlying LRU so k2 can be 186 // deleted in next sweep 187 c.Set("k1", "v3", OptValueTTL(time.Second)) 188 assert.Equal("k2", c.LRU.Peek().Key) 189 190 assert.Nil(c.Sweep(context.Background())) 191 assert.True(c.Has("k1")) 192 assert.False(c.Has("k2")) 193 } 194 195 func TestLocalCacheSweep(t *testing.T) { 196 assert := assert.New(t) 197 198 c := New() 199 200 var didSweep, didRemove bool 201 c.Set(itemKey{}, "foo", 202 OptValueTimestamp(time.Now().UTC().Add(-2*time.Minute)), 203 OptValueTTL(time.Minute), 204 OptValueOnRemove(func(_ interface{}, reason RemovalReason) { 205 if reason == Expired { 206 didSweep = true 207 } 208 }), 209 ) 210 found, ok := c.Get(itemKey{}) 211 assert.True(ok) 212 assert.Equal("foo", found) 213 214 c.Set(altItemKey{}, "bar", 215 OptValueTTL(time.Minute), 216 ) 217 218 found, ok = c.Get(altItemKey{}) 219 assert.True(ok) 220 assert.Equal("bar", found) 221 222 assert.Nil(c.Sweep(context.Background())) 223 224 found, ok = c.Get(itemKey{}) 225 assert.False(ok) 226 assert.Nil(found) 227 assert.True(didSweep) 228 assert.False(didRemove) 229 230 found, ok = c.Get(altItemKey{}) 231 assert.True(ok) 232 assert.Equal("bar", found) 233 } 234 235 func TestLocalCacheStartSweeping(t *testing.T) { 236 assert := assert.New(t) 237 238 c := New(OptSweepInterval(time.Millisecond)) 239 240 didSweep := make(chan struct{}) 241 c.Set(itemKey{}, "a value", 242 OptValueTTL(time.Microsecond), 243 OptValueOnRemove(func(_ interface{}, reason RemovalReason) { 244 if reason == Expired { 245 close(didSweep) 246 } 247 }), 248 ) 249 250 found, ok := c.Get(itemKey{}) 251 assert.True(ok) 252 assert.Equal("a value", found) 253 254 c.Set(altItemKey{}, "bar", 255 OptValueTTL(time.Minute), 256 ) 257 258 found, ok = c.Get(altItemKey{}) 259 assert.True(ok) 260 assert.Equal("bar", found) 261 262 go func() { _ = c.Start() }() 263 <-c.NotifyStarted() 264 defer func() { _ = c.Stop() }() 265 <-didSweep 266 267 found, ok = c.Get(itemKey{}) 268 assert.False(ok) 269 assert.Nil(found) 270 271 found, ok = c.Get(altItemKey{}) 272 assert.True(ok) 273 assert.Equal("bar", found) 274 } 275 276 func TestLocalCacheStats(t *testing.T) { 277 assert := assert.New(t) 278 279 t1 := time.Date(2019, 06, 14, 12, 10, 9, 8, time.UTC) 280 t2 := time.Date(2019, 06, 14, 00, 01, 02, 03, time.UTC) 281 t3 := time.Date(2019, 06, 14, 12, 01, 02, 03, time.UTC) 282 283 lc := New() 284 285 lc.Set("foo", "bar", OptValueTimestamp(t1)) 286 lc.Set("foo2", "bar2", OptValueTimestamp(t2)) 287 lc.Set("foo3", "bar3", OptValueTimestamp(t3)) 288 289 stats := lc.Stats() 290 assert.Equal(3, stats.Count) 291 assert.Equal(24, stats.SizeBytes) 292 assert.NotZero(stats.MaxAge) 293 } 294 295 func TestLocalCacheResetDefault(t *testing.T) { 296 assert := assert.New(t) 297 298 var keyWasSet, didCallRemoveHandler, removalReasonWasRemoved bool 299 lc := New() 300 lc.Set("foo", "foo-value") 301 lc.Set("bar", "bar-value") 302 lc.Set("remove-handler", "remove-handler-value", OptValueOnRemove(func(key interface{}, reason RemovalReason) { 303 didCallRemoveHandler = true 304 keyWasSet = key.(string) == "remove-handler" 305 removalReasonWasRemoved = reason == Removed 306 })) 307 308 assert.Equal(3, len(lc.Data)) 309 lc.Reset() 310 assert.Zero(lc.LRU.Len()) 311 assert.True(didCallRemoveHandler, "should have called remove handler for `remove-handler`") 312 assert.True(keyWasSet, "key should have been `remove-handler`") 313 assert.True(removalReasonWasRemoved, "removal reason should have been `Removed`") 314 315 lc.Set("foo", "foo-value") 316 lc.Set("bar", "bar-value") 317 assert.Equal(2, len(lc.Data)) 318 } 319 320 func TestLocalCacheResetHeap(t *testing.T) { 321 assert := assert.New(t) 322 323 var keyWasSet, didCallRemoveHandler, removalReasonWasRemoved bool 324 lc := New(OptLRU(NewLRUHeap())) 325 lc.Set("foo", "foo-value") 326 lc.Set("bar", "bar-value") 327 lc.Set("remove-handler", "remove-handler-value", OptValueOnRemove(func(key interface{}, reason RemovalReason) { 328 didCallRemoveHandler = true 329 keyWasSet = key.(string) == "remove-handler" 330 removalReasonWasRemoved = reason == Removed 331 })) 332 333 assert.Equal(3, len(lc.Data)) 334 lc.Reset() 335 assert.Zero(lc.LRU.Len()) 336 assert.True(didCallRemoveHandler, "should have called remove handler for `remove-handler`") 337 assert.True(keyWasSet, "key should have been `remove-handler`") 338 assert.True(removalReasonWasRemoved, "removal reason should have been `Removed`") 339 340 lc.Set("foo", "foo-value") 341 lc.Set("bar", "bar-value") 342 assert.Equal(2, len(lc.Data)) 343 } 344 345 func BenchmarkLocalCache(b *testing.B) { 346 for x := 0; x < b.N; x++ { 347 benchLocalCache(1024) 348 } 349 } 350 351 func benchLocalCache(items int) { 352 lc := New() 353 for x := 0; x < items; x++ { 354 lc.Set(x, strconv.Itoa(x), OptValueTTL(time.Millisecond)) 355 } 356 for x := 0; x < items; x++ { 357 lc.Set(x, strconv.Itoa(x), OptValueTTL(time.Second)) 358 } 359 var value interface{} 360 var ok bool 361 for x := 0; x < items; x++ { 362 value, ok = lc.Get(x) 363 if !ok { 364 panic("value not found") 365 } 366 if value.(string) != strconv.Itoa(x) { 367 panic("wrong value") 368 } 369 } 370 _ = lc.Sweep(context.Background()) 371 }