From bbe6d2a3c95ca6a646e924974dad20247fd38496 Mon Sep 17 00:00:00 2001 From: pcman312 Date: Fri, 9 Jun 2017 19:55:59 -0600 Subject: [PATCH] Add clock interface, real and fake clocks (#33) --- arc.go | 10 ++++++---- arc_test.go | 20 +++++++++---------- cache.go | 13 ++++++++++-- cache_test.go | 49 +++++++++++++++++++++------------------------ clock.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ helpers_test.go | 32 ++++++++++++++--------------- lfu.go | 10 ++++++---- lfu_test.go | 16 +++++++-------- lru.go | 10 ++++++---- lru_test.go | 16 +++++++-------- simple.go | 12 ++++++----- simple_test.go | 16 +++++++-------- 12 files changed, 157 insertions(+), 100 deletions(-) create mode 100644 clock.go diff --git a/arc.go b/arc.go index 33530d7..a7af464 100644 --- a/arc.go +++ b/arc.go @@ -70,7 +70,7 @@ func (c *ARC) SetWithExpire(key, value interface{}, expiration time.Duration) er return err } - t := time.Now().Add(expiration) + t := c.clock.Now().Add(expiration) item.(*arcItem).expiration = &t return nil } @@ -89,6 +89,7 @@ func (c *ARC) set(key, value interface{}) (interface{}, error) { item.value = value } else { item = &arcItem{ + clock: c.clock, key: key, value: value, } @@ -96,7 +97,7 @@ func (c *ARC) set(key, value interface{}) (interface{}, error) { } if c.expiration != nil { - t := time.Now().Add(*c.expiration) + t := c.clock.Now().Add(*c.expiration) item.expiration = &t } @@ -243,7 +244,7 @@ func (c *ARC) getWithLoader(key interface{}, isWait bool) (interface{}, error) { return nil, err } if expiration != nil { - t := time.Now().Add(*expiration) + t := c.clock.Now().Add(*expiration) item.(*arcItem).expiration = &t } return v, nil @@ -343,7 +344,7 @@ func (it *arcItem) IsExpired(now *time.Time) bool { return false } if now == nil { - t := time.Now() + t := it.clock.Now() now = &t } return it.expiration.Before(*now) @@ -355,6 +356,7 @@ type arcList struct { } type arcItem struct { + clock Clock key interface{} value interface{} expiration *time.Time diff --git a/arc_test.go b/arc_test.go index 831f76d..020492b 100644 --- a/arc_test.go +++ b/arc_test.go @@ -1,30 +1,28 @@ -package gcache_test +package gcache import ( "fmt" "testing" "time" - - "github.com/bluele/gcache" ) -func buildARCache(size int) gcache.Cache { - return gcache.New(size). +func buildARCache(size int) Cache { + return New(size). ARC(). EvictedFunc(evictedFuncForARC). Build() } -func buildLoadingARCache(size int) gcache.Cache { - return gcache.New(size). +func buildLoadingARCache(size int) Cache { + return New(size). ARC(). LoaderFunc(loader). EvictedFunc(evictedFuncForARC). Build() } -func buildLoadingARCacheWithExpiration(size int, ep time.Duration) gcache.Cache { - return gcache.New(size). +func buildLoadingARCacheWithExpiration(size int, ep time.Duration) Cache { + return New(size). ARC(). Expiration(ep). LoaderFunc(loader). @@ -82,9 +80,9 @@ func TestARCEvictItem(t *testing.T) { } func TestARCGetIFPresent(t *testing.T) { - testGetIFPresent(t, gcache.TYPE_ARC) + testGetIFPresent(t, TYPE_ARC) } func TestARCGetALL(t *testing.T) { - testGetALL(t, gcache.TYPE_ARC) + testGetALL(t, TYPE_ARC) } diff --git a/cache.go b/cache.go index 6aa3ac9..4249649 100644 --- a/cache.go +++ b/cache.go @@ -31,6 +31,7 @@ type Cache interface { } type baseCache struct { + clock Clock size int loaderExpireFunc LoaderExpireFunc evictedFunc EvictedFunc @@ -53,6 +54,7 @@ type ( ) type CacheBuilder struct { + clock Clock tp string size int loaderExpireFunc LoaderExpireFunc @@ -68,11 +70,17 @@ func New(size int) *CacheBuilder { panic("gcache: size <= 0") } return &CacheBuilder{ - tp: TYPE_SIMPLE, - size: size, + clock: NewRealClock(), + tp: TYPE_SIMPLE, + size: size, } } +func (cb *CacheBuilder) Clock(clock Clock) *CacheBuilder { + cb.clock = clock + return cb +} + // Set a loader function. // loaderFunc: create a new value with this function if cached value is expired. func (cb *CacheBuilder) LoaderFunc(loaderFunc LoaderFunc) *CacheBuilder { @@ -157,6 +165,7 @@ func (cb *CacheBuilder) build() Cache { } func buildCache(c *baseCache, cb *CacheBuilder) { + c.clock = cb.clock c.size = cb.size c.loaderExpireFunc = cb.loaderExpireFunc c.expiration = cb.expiration diff --git a/cache_test.go b/cache_test.go index 36dd603..012664c 100644 --- a/cache_test.go +++ b/cache_test.go @@ -1,24 +1,21 @@ -package gcache_test +package gcache import ( "bytes" "encoding/gob" - "testing" - "time" - "sync" "sync/atomic" - - "github.com/bluele/gcache" + "testing" + "time" ) func TestLoaderFunc(t *testing.T) { size := 2 - var testCaches = []*gcache.CacheBuilder{ - gcache.New(size).Simple(), - gcache.New(size).LRU(), - gcache.New(size).LFU(), - gcache.New(size).ARC(), + var testCaches = []*CacheBuilder{ + New(size).Simple(), + New(size).LRU(), + New(size).LFU(), + New(size).ARC(), } for _, builder := range testCaches { var testCounter int64 @@ -53,11 +50,11 @@ func TestLoaderFunc(t *testing.T) { func TestLoaderExpireFuncWithoutExpire(t *testing.T) { size := 2 - var testCaches = []*gcache.CacheBuilder{ - gcache.New(size).Simple(), - gcache.New(size).LRU(), - gcache.New(size).LFU(), - gcache.New(size).ARC(), + var testCaches = []*CacheBuilder{ + New(size).Simple(), + New(size).LRU(), + New(size).LFU(), + New(size).ARC(), } for _, builder := range testCaches { var testCounter int64 @@ -92,11 +89,11 @@ func TestLoaderExpireFuncWithoutExpire(t *testing.T) { func TestLoaderExpireFuncWithExpire(t *testing.T) { size := 2 - var testCaches = []*gcache.CacheBuilder{ - gcache.New(size).Simple(), - gcache.New(size).LRU(), - gcache.New(size).LFU(), - gcache.New(size).ARC(), + var testCaches = []*CacheBuilder{ + New(size).Simple(), + New(size).LRU(), + New(size).LFU(), + New(size).ARC(), } for _, builder := range testCaches { var testCounter int64 @@ -143,16 +140,16 @@ func TestDeserializeFunc(t *testing.T) { var cases = []struct { tp string }{ - {gcache.TYPE_SIMPLE}, - {gcache.TYPE_LRU}, - {gcache.TYPE_LFU}, - {gcache.TYPE_ARC}, + {TYPE_SIMPLE}, + {TYPE_LRU}, + {TYPE_LFU}, + {TYPE_ARC}, } for _, cs := range cases { key1, value1 := "key1", "value1" key2, value2 := "key2", "value2" - cc := gcache.New(32). + cc := New(32). EvictType(cs.tp). LoaderFunc(func(k interface{}) (interface{}, error) { return value1, nil diff --git a/clock.go b/clock.go new file mode 100644 index 0000000..3acc3f0 --- /dev/null +++ b/clock.go @@ -0,0 +1,53 @@ +package gcache + +import ( + "sync" + "time" +) + +type Clock interface { + Now() time.Time +} + +type RealClock struct{} + +func NewRealClock() Clock { + return RealClock{} +} + +func (rc RealClock) Now() time.Time { + t := time.Now() + return t +} + +type FakeClock interface { + Clock + + Advance(d time.Duration) +} + +func NewFakeClock() FakeClock { + return &fakeclock{ + // Taken from github.com/jonboulle/clockwork: use a fixture that does not fulfill Time.IsZero() + now: time.Date(1984, time.April, 4, 0, 0, 0, 0, time.UTC), + } +} + +type fakeclock struct { + now time.Time + + mutex sync.RWMutex +} + +func (fc *fakeclock) Now() time.Time { + fc.mutex.RLock() + defer fc.mutex.RUnlock() + t := fc.now + return t +} + +func (fc *fakeclock) Advance(d time.Duration) { + fc.mutex.Lock() + defer fc.mutex.Unlock() + fc.now = fc.now.Add(d) +} diff --git a/helpers_test.go b/helpers_test.go index f4ecc15..3761853 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -1,18 +1,16 @@ -package gcache_test +package gcache import ( "fmt" "testing" "time" - - "github.com/bluele/gcache" ) func loader(key interface{}) (interface{}, error) { return fmt.Sprintf("valueFor%s", key), nil } -func testSetCache(t *testing.T, gc gcache.Cache, numbers int) { +func testSetCache(t *testing.T, gc Cache, numbers int) { for i := 0; i < numbers; i++ { key := fmt.Sprintf("Key-%d", i) value, err := loader(key) @@ -24,7 +22,7 @@ func testSetCache(t *testing.T, gc gcache.Cache, numbers int) { } } -func testGetCache(t *testing.T, gc gcache.Cache, numbers int) { +func testGetCache(t *testing.T, gc Cache, numbers int) { for i := 0; i < numbers; i++ { key := fmt.Sprintf("Key-%d", i) v, err := gc.Get(key) @@ -39,17 +37,17 @@ func testGetCache(t *testing.T, gc gcache.Cache, numbers int) { } func testGetIFPresent(t *testing.T, evT string) { - cache := gcache. + cache := New(8). - EvictType(evT). - LoaderFunc( - func(key interface{}) (interface{}, error) { - return "value", nil - }). - Build() + EvictType(evT). + LoaderFunc( + func(key interface{}) (interface{}, error) { + return "value", nil + }). + Build() v, err := cache.GetIFPresent("key") - if err != gcache.KeyNotFoundError { + if err != KeyNotFoundError { t.Errorf("err should not be %v", err) } @@ -66,11 +64,11 @@ func testGetIFPresent(t *testing.T, evT string) { func testGetALL(t *testing.T, evT string) { size := 8 - cache := gcache. + cache := New(size). - Expiration(time.Millisecond). - EvictType(evT). - Build() + Expiration(time.Millisecond). + EvictType(evT). + Build() for i := 0; i < size; i++ { cache.Set(i, i*i) } diff --git a/lfu.go b/lfu.go index e5e11df..cddd2b1 100644 --- a/lfu.go +++ b/lfu.go @@ -47,7 +47,7 @@ func (c *LFUCache) SetWithExpire(key, value interface{}, expiration time.Duratio return err } - t := time.Now().Add(expiration) + t := c.clock.Now().Add(expiration) item.(*lfuItem).expiration = &t return nil } @@ -71,6 +71,7 @@ func (c *LFUCache) set(key, value interface{}) (interface{}, error) { c.evict(1) } item = &lfuItem{ + clock: c.clock, key: key, value: value, freqElement: nil, @@ -84,7 +85,7 @@ func (c *LFUCache) set(key, value interface{}) (interface{}, error) { } if c.expiration != nil { - t := time.Now().Add(*c.expiration) + t := c.clock.Now().Add(*c.expiration) item.expiration = &t } @@ -165,7 +166,7 @@ func (c *LFUCache) getWithLoader(key interface{}, isWait bool) (interface{}, err return nil, err } if expiration != nil { - t := time.Now().Add(*expiration) + t := c.clock.Now().Add(*expiration) item.(*lfuItem).expiration = &t } return v, nil @@ -292,6 +293,7 @@ type freqEntry struct { } type lfuItem struct { + clock Clock key interface{} value interface{} freqElement *list.Element @@ -304,7 +306,7 @@ func (it *lfuItem) IsExpired(now *time.Time) bool { return false } if now == nil { - t := time.Now() + t := it.clock.Now() now = &t } return it.expiration.Before(*now) diff --git a/lfu_test.go b/lfu_test.go index 2e3eea2..f9c65f8 100644 --- a/lfu_test.go +++ b/lfu_test.go @@ -1,27 +1,25 @@ -package gcache_test +package gcache import ( "fmt" "testing" "time" - - "github.com/bluele/gcache" ) func evictedFuncForLFU(key, value interface{}) { fmt.Printf("[LFU] Key:%v Value:%v will evicted.\n", key, value) } -func buildLFUCache(size int) gcache.Cache { - return gcache.New(size). +func buildLFUCache(size int) Cache { + return New(size). LFU(). EvictedFunc(evictedFuncForLFU). Expiration(time.Second). Build() } -func buildLoadingLFUCache(size int, loader gcache.LoaderFunc) gcache.Cache { - return gcache.New(size). +func buildLoadingLFUCache(size int, loader LoaderFunc) Cache { + return New(size). LFU(). LoaderFunc(loader). EvictedFunc(evictedFuncForLFU). @@ -71,9 +69,9 @@ func TestLFUEvictItem(t *testing.T) { } func TestLFUGetIFPresent(t *testing.T) { - testGetIFPresent(t, gcache.TYPE_LFU) + testGetIFPresent(t, TYPE_LFU) } func TestLFUGetALL(t *testing.T) { - testGetALL(t, gcache.TYPE_LFU) + testGetALL(t, TYPE_LFU) } diff --git a/lru.go b/lru.go index 7237a6b..6a0b24d 100644 --- a/lru.go +++ b/lru.go @@ -47,6 +47,7 @@ func (c *LRUCache) set(key, value interface{}) (interface{}, error) { c.evict(1) } item = &lruItem{ + clock: c.clock, key: key, value: value, } @@ -54,7 +55,7 @@ func (c *LRUCache) set(key, value interface{}) (interface{}, error) { } if c.expiration != nil { - t := time.Now().Add(*c.expiration) + t := c.clock.Now().Add(*c.expiration) item.expiration = &t } @@ -82,7 +83,7 @@ func (c *LRUCache) SetWithExpire(key, value interface{}, expiration time.Duratio return err } - t := time.Now().Add(expiration) + t := c.clock.Now().Add(expiration) item.(*lruItem).expiration = &t return nil } @@ -158,7 +159,7 @@ func (c *LRUCache) getWithLoader(key interface{}, isWait bool) (interface{}, err return nil, err } if expiration != nil { - t := time.Now().Add(*expiration) + t := c.clock.Now().Add(*expiration) item.(*lruItem).expiration = &t } return v, nil @@ -257,6 +258,7 @@ func (c *LRUCache) Purge() { } type lruItem struct { + clock Clock key interface{} value interface{} expiration *time.Time @@ -268,7 +270,7 @@ func (it *lruItem) IsExpired(now *time.Time) bool { return false } if now == nil { - t := time.Now() + t := it.clock.Now() now = &t } return it.expiration.Before(*now) diff --git a/lru_test.go b/lru_test.go index 67b7b0f..7dffbf4 100644 --- a/lru_test.go +++ b/lru_test.go @@ -1,27 +1,25 @@ -package gcache_test +package gcache import ( "fmt" "testing" "time" - - "github.com/bluele/gcache" ) func evictedFuncForLRU(key, value interface{}) { fmt.Printf("[LRU] Key:%v Value:%v will evicted.\n", key, value) } -func buildLRUCache(size int) gcache.Cache { - return gcache.New(size). +func buildLRUCache(size int) Cache { + return New(size). LRU(). EvictedFunc(evictedFuncForLRU). Expiration(time.Second). Build() } -func buildLoadingLRUCache(size int, loader gcache.LoaderFunc) gcache.Cache { - return gcache.New(size). +func buildLoadingLRUCache(size int, loader LoaderFunc) Cache { + return New(size). LRU(). LoaderFunc(loader). EvictedFunc(evictedFuncForLRU). @@ -67,9 +65,9 @@ func TestLRUEvictItem(t *testing.T) { } func TestLRUGetIFPresent(t *testing.T) { - testGetIFPresent(t, gcache.TYPE_LRU) + testGetIFPresent(t, TYPE_LRU) } func TestLRUGetALL(t *testing.T) { - testGetALL(t, gcache.TYPE_LRU) + testGetALL(t, TYPE_LRU) } diff --git a/simple.go b/simple.go index c415b21..b5ef4ae 100644 --- a/simple.go +++ b/simple.go @@ -38,7 +38,7 @@ func (c *SimpleCache) SetWithExpire(key, value interface{}, expiration time.Dura return err } - t := time.Now().Add(expiration) + t := c.clock.Now().Add(expiration) item.(*simpleItem).expiration = &t return nil } @@ -62,13 +62,14 @@ func (c *SimpleCache) set(key, value interface{}) (interface{}, error) { c.evict(1) } item = &simpleItem{ + clock: c.clock, value: value, } c.items[key] = item } if c.expiration != nil { - t := time.Now().Add(*c.expiration) + t := c.clock.Now().Add(*c.expiration) item.expiration = &t } @@ -148,7 +149,7 @@ func (c *SimpleCache) getWithLoader(key interface{}, isWait bool) (interface{}, return nil, err } if expiration != nil { - t := time.Now().Add(*expiration) + t := c.clock.Now().Add(*expiration) item.(*simpleItem).expiration = &t } return v, nil @@ -160,7 +161,7 @@ func (c *SimpleCache) getWithLoader(key interface{}, isWait bool) (interface{}, } func (c *SimpleCache) evict(count int) { - now := time.Now() + now := c.clock.Now() current := 0 for key, item := range c.items { if current >= count { @@ -244,6 +245,7 @@ func (c *SimpleCache) Purge() { } type simpleItem struct { + clock Clock value interface{} expiration *time.Time } @@ -254,7 +256,7 @@ func (si *simpleItem) IsExpired(now *time.Time) bool { return false } if now == nil { - t := time.Now() + t := si.clock.Now() now = &t } return si.expiration.Before(*now) diff --git a/simple_test.go b/simple_test.go index e7f3b2f..f68ced2 100644 --- a/simple_test.go +++ b/simple_test.go @@ -1,21 +1,19 @@ -package gcache_test +package gcache import ( "fmt" "testing" - - gcache "github.com/bluele/gcache" ) -func buildSimpleCache(size int) gcache.Cache { - return gcache.New(size). +func buildSimpleCache(size int) Cache { + return New(size). Simple(). EvictedFunc(evictedFuncForSimple). Build() } -func buildLoadingSimpleCache(size int, loader gcache.LoaderFunc) gcache.Cache { - return gcache.New(size). +func buildLoadingSimpleCache(size int, loader LoaderFunc) Cache { + return New(size). LoaderFunc(loader). Simple(). EvictedFunc(evictedFuncForSimple). @@ -64,9 +62,9 @@ func TestSimpleEvictItem(t *testing.T) { } func TestSimpleGetIFPresent(t *testing.T) { - testGetIFPresent(t, gcache.TYPE_SIMPLE) + testGetIFPresent(t, TYPE_SIMPLE) } func TestSimpleGetALL(t *testing.T) { - testGetALL(t, gcache.TYPE_SIMPLE) + testGetALL(t, TYPE_SIMPLE) }