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  }