Skip to content

Commit

Permalink
add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
umputun committed Jan 20, 2019
1 parent 764cc9c commit 07a4d66
Show file tree
Hide file tree
Showing 5 changed files with 365 additions and 2 deletions.
19 changes: 19 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
language: go

go:
- "1.11.x"

install: true

before_install:
- export TZ=America/Chicago
- curl -L https://git.io/vp6lP | sh
- go get github.com/mattn/goveralls
- export PATH=$(pwd)/bin:$PATH

script:
- GO111MODULE=on go get ./...
- GO111MODULE=on go mod vendor
- GO111MODULE=on go test -v -mod=vendor -covermode=count -coverprofile=profile.cov ./... || travis_terminate 1;
- ./bin/gometalinter --deadline=120s --exclude=test --exclude=mock --exclude=vendor --exclude=_example --disable-all --enable=errcheck --enable=vet --enable=vetshadow --enable=megacheck --enable=ineffassign --enable=varcheck --enable=unconvert --enable=deadcode --enable=interfacer --enable=gotype ./... || travis_terminate 1;
- $GOPATH/bin/goveralls -coverprofile=profile.cov -service=travis-ci
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Loading Cache Wrapper
# Loading Cache Wrapper [![Build Status](https://travis-ci.org/go-pkgz/lcw.svg?branch=master)](https://travis-ci.org/go-pkgz/lcw) [![Coverage Status](https://coveralls.io/repos/github/go-pkgz/lcw/badge.svg?branch=master)](https://coveralls.io/github/go-pkgz/lcw?branch=master) [![godoc](https://godoc.org/github.com/go-pkgz/lcw?status.svg)](https://godoc.org/github.com/go-pkgz/lcw)

The library adds a thin level on top of [lru cache](https://github.com/hashicorp/golang-lru).

The library adds a thin layer on top of [lru cache](https://github.com/hashicorp/golang-lru).

- LoadingCache (guava style)
- Limit maximum cache size (in bytes)
Expand Down Expand Up @@ -30,3 +31,5 @@ if err != nil {

## Details

- All byte-size limits work for values implementing `lcw.Sizer` interface
- Negative limits (max options) rejected
284 changes: 284 additions & 0 deletions cache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
package lcw

import (
"fmt"
"math/rand"
"strings"
"sync"
"sync/atomic"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestCache_Get(t *testing.T) {
var coldCalls int32
lc, err := NewCache()
require.Nil(t, err)
res, err := lc.Get("key", func() (Value, error) {
atomic.AddInt32(&coldCalls, 1)
return "result", nil
})
assert.Nil(t, err)
assert.Equal(t, "result", res.(string))
assert.Equal(t, int32(1), atomic.LoadInt32(&coldCalls))

res, err = lc.Get("key", func() (Value, error) {
atomic.AddInt32(&coldCalls, 1)
return "result2", nil
})

assert.Nil(t, err)
assert.Equal(t, "result", res.(string))
assert.Equal(t, int32(1), atomic.LoadInt32(&coldCalls), "cache hit")
}

func TestCache_Peek(t *testing.T) {
var coldCalls int32
lc, err := NewCache()
require.Nil(t, err)
res, err := lc.Get("key", func() (Value, error) {
atomic.AddInt32(&coldCalls, 1)
return "result", nil
})
assert.Nil(t, err)
assert.Equal(t, "result", res.(string))
assert.Equal(t, int32(1), atomic.LoadInt32(&coldCalls))

r, ok := lc.Peek("key")
assert.True(t, ok)
assert.Equal(t, "result", r.(string))
}

func TestCache_MaxKeys(t *testing.T) {
var coldCalls int32
lc, err := NewCache(MaxKeys(5), MaxValSize(10))
require.Nil(t, err)

// put 5 keys to cache
for i := 0; i < 5; i++ {
res, e := lc.Get(fmt.Sprintf("key-%d", i), func() (Value, error) {
atomic.AddInt32(&coldCalls, 1)
return fmt.Sprintf("result-%d", i), nil
})
assert.Nil(t, e)
assert.Equal(t, fmt.Sprintf("result-%d", i), res.(string))
assert.Equal(t, int32(i+1), atomic.LoadInt32(&coldCalls))
}

// check if really cached
res, err := lc.Get("key-3", func() (Value, error) {
return "result-blah", nil
})
assert.Nil(t, err)
assert.Equal(t, "result-3", res.(string), "should be cached")

// try to cache after maxKeys reached
res, err = lc.Get("key-X", func() (Value, error) {
return "result-X", nil
})
assert.Nil(t, err)
assert.Equal(t, "result-X", res.(string))
assert.Equal(t, 5, lc.backend.Len())

// put to cache and make sure it cached
res, err = lc.Get("key-Z", func() (Value, error) {
return "result-Z", nil
})
assert.Nil(t, err)
assert.Equal(t, "result-Z", res.(string))

res, err = lc.Get("key-Z", func() (Value, error) {
return "result-Zzzz", nil
})
assert.Nil(t, err)
assert.Equal(t, "result-Z", res.(string), "got cached value")
assert.Equal(t, 5, lc.backend.Len())
}

func TestCache_MaxValueSize(t *testing.T) {
lc, err := NewCache(MaxKeys(5), MaxValSize(10))
require.NoError(t, err)

// put good size value to cache and make sure it cached
res, err := lc.Get("key-Z", func() (Value, error) {
return sizedString("result-Z"), nil
})
assert.NoError(t, err)
assert.Equal(t, sizedString("result-Z"), res.(sizedString))

res, err = lc.Get("key-Z", func() (Value, error) {
return sizedString("result-Zzzz"), nil
})
assert.NoError(t, err)
assert.Equal(t, sizedString("result-Z"), res.(sizedString), "got cached value")

// put too big value to cache and make sure it is not cached
res, err = lc.Get("key-Big", func() (Value, error) {
return sizedString("1234567890"), nil
})
assert.NoError(t, err)
assert.Equal(t, sizedString("1234567890"), res.(sizedString))

res, err = lc.Get("key-Big", func() (Value, error) {
return sizedString("result-big"), nil
})
assert.NoError(t, err)
assert.Equal(t, sizedString("result-big"), res.(sizedString), "got not cached value")

// put too big value to cache but not Sizer
res, err = lc.Get("key-Big2", func() (Value, error) {
return "1234567890", nil
})
assert.NoError(t, err)
assert.Equal(t, "1234567890", res.(string))

res, err = lc.Get("key-Big2", func() (Value, error) {
return "xyz", nil
})
assert.NoError(t, err)
assert.Equal(t, "1234567890", res.(string), "too long, but not Sizer. from cache")
}

func TestCache_MaxCacheSize(t *testing.T) {
lc, err := NewCache(MaxKeys(50), MaxCacheSize(20))
require.Nil(t, err)

// put good size value to cache and make sure it cached
res, err := lc.Get("key-Z", func() (Value, error) {
return sizedString("result-Z"), nil
})
assert.NoError(t, err)
assert.Equal(t, sizedString("result-Z"), res.(sizedString))

res, err = lc.Get("key-Z", func() (Value, error) {
return sizedString("result-Zzzz"), nil
})
assert.NoError(t, err)
assert.Equal(t, sizedString("result-Z"), res.(sizedString), "got cached value")
assert.Equal(t, int64(8), lc.currentSize)

_, err = lc.Get("key-Z2", func() (Value, error) {
return sizedString("result-Y"), nil
})
assert.Nil(t, err)
assert.Equal(t, int64(16), lc.currentSize)

// this will cause removal
_, err = lc.Get("key-Z3", func() (Value, error) {
return sizedString("result-Z"), nil
})
assert.Nil(t, err)
assert.Equal(t, int64(16), lc.currentSize)
assert.Equal(t, 2, lc.backend.Len())
}

func TestCache_MaxCacheSizeParallel(t *testing.T) {
lc, err := NewCache(MaxCacheSize(123), MaxKeys(10000))
require.Nil(t, err)

wg := sync.WaitGroup{}
for i := 0; i < 1000; i++ {
wg.Add(1)
i := i
go func() {
time.Sleep(time.Duration(rand.Intn(100)) * time.Nanosecond)
defer wg.Done()
res, err := lc.Get(fmt.Sprintf("key-%d", i), func() (Value, error) {
return sizedString(fmt.Sprintf("result-%d", i)), nil
})
require.Nil(t, err)
require.Equal(t, sizedString(fmt.Sprintf("result-%d", i)), res.(sizedString))
size := atomic.LoadInt64(&lc.currentSize)
require.True(t, size < 200 && size >= 0, "unexpected size=%d", size) // won't be exactly 123 due parallel
}()
}
wg.Wait()
assert.True(t, lc.currentSize < 123 && lc.currentSize >= 0)
t.Log("size", lc.currentSize)
}

func TestCache_Parallel(t *testing.T) {
var coldCalls int32
lc, err := NewCache()
require.Nil(t, err)

res, err := lc.Get("key", func() (Value, error) {
return "value", nil
})
assert.Nil(t, err)
assert.Equal(t, "value", res.(string))

wg := sync.WaitGroup{}
for i := 0; i < 1000; i++ {
wg.Add(1)
i := i
go func() {
defer wg.Done()
res, err := lc.Get("key", func() (Value, error) {
atomic.AddInt32(&coldCalls, 1)
return fmt.Sprintf("result-%d", i), nil
})
require.Nil(t, err)
require.Equal(t, "value", res.(string))
}()
}
wg.Wait()
assert.Equal(t, int32(0), atomic.LoadInt32(&coldCalls))
}

func TestCache_Invalidate(t *testing.T) {
var coldCalls int32
lc, err := NewCache()
require.Nil(t, err)

// fill cache
for i := 0; i < 1000; i++ {
_, err := lc.Get(fmt.Sprintf("key-%d", i), func() (Value, error) {
atomic.AddInt32(&coldCalls, 1)
return fmt.Sprintf("result-%d", i), nil
})
require.Nil(t, err)
}
assert.Equal(t, int32(1000), atomic.LoadInt32(&coldCalls))
assert.Equal(t, 1000, lc.backend.Len())

lc.Invalidate(func(key string) bool {
return strings.HasSuffix(key, "0")
})

assert.Equal(t, 900, lc.backend.Len(), "100 keys removed")
res, err := lc.Get("key-1", func() (Value, error) {
atomic.AddInt32(&coldCalls, 1)
return "result-xxx", nil
})
require.NoError(t, err)
assert.Equal(t, "result-1", res.(string), "from the cache")

res, err = lc.Get("key-10", func() (Value, error) {
atomic.AddInt32(&coldCalls, 1)
return "result-xxx", nil
})
require.NoError(t, err)
assert.Equal(t, "result-xxx", res.(string), "not from the cache")
}

func TestCache_BadOptions(t *testing.T) {
_, err := NewCache(MaxCacheSize(-1))
assert.EqualError(t, err, "failed to set cache option: negative max cache size")

_, err = NewCache(MaxCacheSize(-1))
assert.EqualError(t, err, "failed to set cache option: negative max cache size")

_, err = NewCache(MaxKeys(-1))
assert.EqualError(t, err, "failed to set cache option: negative max keys")

_, err = NewCache(MaxValSize(-1))
assert.EqualError(t, err, "failed to set cache option: negative max value size")
}

type sizedString string

func (s sizedString) Size() int { return len(s) }
43 changes: 43 additions & 0 deletions interface_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package lcw

import (
"sync/atomic"
"testing"

"github.com/stretchr/testify/assert"
)

func TestNop_Get(t *testing.T) {
var coldCalls int32
c := Nop{}
res, err := c.Get("key1", func() (Value, error) {
atomic.AddInt32(&coldCalls, 1)
return "result", nil
})
assert.Nil(t, err)
assert.Equal(t, "result", res.(string))
assert.Equal(t, int32(1), atomic.LoadInt32(&coldCalls))

res, err = c.Get("key1", func() (Value, error) {
atomic.AddInt32(&coldCalls, 1)
return "result2", nil
})
assert.Nil(t, err)
assert.Equal(t, "result2", res.(string))
assert.Equal(t, int32(2), atomic.LoadInt32(&coldCalls))
}

func TestNop_Peek(t *testing.T) {
var coldCalls int32
c := Nop{}
res, err := c.Get("key1", func() (Value, error) {
atomic.AddInt32(&coldCalls, 1)
return "result", nil
})
assert.Nil(t, err)
assert.Equal(t, "result", res.(string))
assert.Equal(t, int32(1), atomic.LoadInt32(&coldCalls))

_, ok := c.Peek("key1")
assert.False(t, ok)
}
Loading

0 comments on commit 07a4d66

Please sign in to comment.