2019-07-31 23:19:07 +02:00
|
|
|
package cache
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
called = 0
|
|
|
|
errSentinel = errors.New("an error")
|
|
|
|
errCached = errors.New("a cached error")
|
|
|
|
)
|
|
|
|
|
|
|
|
func setup(t *testing.T) (*Cache, CreateFunc) {
|
|
|
|
called = 0
|
|
|
|
create := func(path string) (interface{}, bool, error) {
|
|
|
|
assert.Equal(t, 0, called)
|
|
|
|
called++
|
|
|
|
switch path {
|
|
|
|
case "/":
|
|
|
|
return "/", true, nil
|
|
|
|
case "/file.txt":
|
|
|
|
return "/file.txt", true, errCached
|
|
|
|
case "/error":
|
|
|
|
return nil, false, errSentinel
|
2020-11-30 12:46:06 +01:00
|
|
|
case "/err":
|
|
|
|
return nil, false, errSentinel
|
2019-07-31 23:19:07 +02:00
|
|
|
}
|
|
|
|
panic(fmt.Sprintf("Unknown path %q", path))
|
|
|
|
}
|
|
|
|
c := New()
|
|
|
|
return c, create
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGet(t *testing.T) {
|
|
|
|
c, create := setup(t)
|
|
|
|
|
|
|
|
assert.Equal(t, 0, len(c.cache))
|
|
|
|
|
|
|
|
f, err := c.Get("/", create)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, 1, len(c.cache))
|
|
|
|
|
|
|
|
f2, err := c.Get("/", create)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, f, f2)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGetFile(t *testing.T) {
|
|
|
|
c, create := setup(t)
|
|
|
|
|
|
|
|
assert.Equal(t, 0, len(c.cache))
|
|
|
|
|
|
|
|
f, err := c.Get("/file.txt", create)
|
|
|
|
require.Equal(t, errCached, err)
|
|
|
|
|
|
|
|
assert.Equal(t, 1, len(c.cache))
|
|
|
|
|
|
|
|
f2, err := c.Get("/file.txt", create)
|
|
|
|
require.Equal(t, errCached, err)
|
|
|
|
|
|
|
|
assert.Equal(t, f, f2)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGetError(t *testing.T) {
|
|
|
|
c, create := setup(t)
|
|
|
|
|
|
|
|
assert.Equal(t, 0, len(c.cache))
|
|
|
|
|
|
|
|
f, err := c.Get("/error", create)
|
|
|
|
require.Equal(t, errSentinel, err)
|
|
|
|
require.Equal(t, nil, f)
|
|
|
|
|
|
|
|
assert.Equal(t, 0, len(c.cache))
|
|
|
|
}
|
|
|
|
|
2024-01-19 11:33:24 +01:00
|
|
|
func TestPutErr(t *testing.T) {
|
|
|
|
c, create := setup(t)
|
|
|
|
|
|
|
|
assert.Equal(t, 0, len(c.cache))
|
|
|
|
|
|
|
|
c.PutErr("/alien", "slime", errSentinel)
|
|
|
|
|
|
|
|
assert.Equal(t, 1, len(c.cache))
|
|
|
|
|
|
|
|
fNew, err := c.Get("/alien", create)
|
|
|
|
require.Equal(t, errSentinel, err)
|
|
|
|
require.Equal(t, "slime", fNew)
|
|
|
|
|
|
|
|
assert.Equal(t, 1, len(c.cache))
|
|
|
|
}
|
|
|
|
|
2019-07-31 23:19:07 +02:00
|
|
|
func TestPut(t *testing.T) {
|
|
|
|
c, create := setup(t)
|
|
|
|
|
|
|
|
assert.Equal(t, 0, len(c.cache))
|
|
|
|
|
|
|
|
c.Put("/alien", "slime")
|
|
|
|
|
|
|
|
assert.Equal(t, 1, len(c.cache))
|
|
|
|
|
|
|
|
fNew, err := c.Get("/alien", create)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, "slime", fNew)
|
|
|
|
|
|
|
|
assert.Equal(t, 1, len(c.cache))
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCacheExpire(t *testing.T) {
|
|
|
|
c, create := setup(t)
|
|
|
|
|
2021-03-29 18:18:49 +02:00
|
|
|
c.SetExpireInterval(time.Millisecond)
|
2019-07-31 23:19:07 +02:00
|
|
|
assert.Equal(t, false, c.expireRunning)
|
|
|
|
|
|
|
|
_, err := c.Get("/", create)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
c.mu.Lock()
|
|
|
|
entry := c.cache["/"]
|
|
|
|
assert.Equal(t, 1, len(c.cache))
|
|
|
|
c.mu.Unlock()
|
2020-05-01 13:19:19 +02:00
|
|
|
|
2019-07-31 23:19:07 +02:00
|
|
|
c.cacheExpire()
|
2020-05-01 13:19:19 +02:00
|
|
|
|
2019-07-31 23:19:07 +02:00
|
|
|
c.mu.Lock()
|
|
|
|
assert.Equal(t, 1, len(c.cache))
|
|
|
|
entry.lastUsed = time.Now().Add(-c.expireDuration - 60*time.Second)
|
|
|
|
assert.Equal(t, true, c.expireRunning)
|
|
|
|
c.mu.Unlock()
|
2020-05-01 13:19:19 +02:00
|
|
|
|
2019-09-19 17:53:48 +02:00
|
|
|
time.Sleep(250 * time.Millisecond)
|
2020-05-01 13:19:19 +02:00
|
|
|
|
2019-07-31 23:19:07 +02:00
|
|
|
c.mu.Lock()
|
|
|
|
assert.Equal(t, false, c.expireRunning)
|
|
|
|
assert.Equal(t, 0, len(c.cache))
|
|
|
|
c.mu.Unlock()
|
|
|
|
}
|
|
|
|
|
2021-03-29 18:18:49 +02:00
|
|
|
func TestCacheNoExpire(t *testing.T) {
|
|
|
|
c, create := setup(t)
|
|
|
|
|
|
|
|
assert.False(t, c.noCache())
|
|
|
|
|
|
|
|
c.SetExpireDuration(0)
|
|
|
|
assert.Equal(t, false, c.expireRunning)
|
|
|
|
|
|
|
|
assert.True(t, c.noCache())
|
|
|
|
|
|
|
|
f, err := c.Get("/", create)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, f)
|
|
|
|
|
|
|
|
c.mu.Lock()
|
|
|
|
assert.Equal(t, 0, len(c.cache))
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
|
|
c.Put("/alien", "slime")
|
|
|
|
|
|
|
|
c.mu.Lock()
|
|
|
|
assert.Equal(t, 0, len(c.cache))
|
|
|
|
c.mu.Unlock()
|
|
|
|
}
|
|
|
|
|
2020-05-01 13:19:19 +02:00
|
|
|
func TestCachePin(t *testing.T) {
|
|
|
|
c, create := setup(t)
|
|
|
|
|
|
|
|
_, err := c.Get("/", create)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-08-14 04:56:32 +02:00
|
|
|
// Pin a nonexistent item to show nothing happens
|
2020-05-01 13:19:19 +02:00
|
|
|
c.Pin("notfound")
|
|
|
|
|
|
|
|
c.mu.Lock()
|
|
|
|
entry := c.cache["/"]
|
|
|
|
assert.Equal(t, 1, len(c.cache))
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
|
|
c.cacheExpire()
|
|
|
|
|
|
|
|
c.mu.Lock()
|
|
|
|
assert.Equal(t, 1, len(c.cache))
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
|
|
// Pin the entry and check it does not get expired
|
|
|
|
c.Pin("/")
|
|
|
|
|
|
|
|
// Reset last used to make the item expirable
|
|
|
|
c.mu.Lock()
|
|
|
|
entry.lastUsed = time.Now().Add(-c.expireDuration - 60*time.Second)
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
|
|
c.cacheExpire()
|
|
|
|
|
|
|
|
c.mu.Lock()
|
|
|
|
assert.Equal(t, 1, len(c.cache))
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
|
|
// Unpin the entry and check it does get expired now
|
|
|
|
c.Unpin("/")
|
|
|
|
|
|
|
|
// Reset last used
|
|
|
|
c.mu.Lock()
|
|
|
|
entry.lastUsed = time.Now().Add(-c.expireDuration - 60*time.Second)
|
|
|
|
c.mu.Unlock()
|
|
|
|
|
|
|
|
c.cacheExpire()
|
|
|
|
|
|
|
|
c.mu.Lock()
|
|
|
|
assert.Equal(t, 0, len(c.cache))
|
|
|
|
c.mu.Unlock()
|
|
|
|
}
|
|
|
|
|
2019-07-31 23:19:07 +02:00
|
|
|
func TestClear(t *testing.T) {
|
|
|
|
c, create := setup(t)
|
|
|
|
|
|
|
|
assert.Equal(t, 0, len(c.cache))
|
|
|
|
|
|
|
|
_, err := c.Get("/", create)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, 1, len(c.cache))
|
|
|
|
|
|
|
|
c.Clear()
|
|
|
|
|
|
|
|
assert.Equal(t, 0, len(c.cache))
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestEntries(t *testing.T) {
|
|
|
|
c, create := setup(t)
|
|
|
|
|
|
|
|
assert.Equal(t, 0, c.Entries())
|
|
|
|
|
|
|
|
_, err := c.Get("/", create)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, 1, c.Entries())
|
|
|
|
|
|
|
|
c.Clear()
|
|
|
|
|
|
|
|
assert.Equal(t, 0, c.Entries())
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGetMaybe(t *testing.T) {
|
|
|
|
c, create := setup(t)
|
|
|
|
|
|
|
|
value, found := c.GetMaybe("/")
|
|
|
|
assert.Equal(t, false, found)
|
|
|
|
assert.Nil(t, value)
|
|
|
|
|
|
|
|
f, err := c.Get("/", create)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
value, found = c.GetMaybe("/")
|
|
|
|
assert.Equal(t, true, found)
|
|
|
|
assert.Equal(t, f, value)
|
|
|
|
|
|
|
|
c.Clear()
|
|
|
|
|
|
|
|
value, found = c.GetMaybe("/")
|
|
|
|
assert.Equal(t, false, found)
|
|
|
|
assert.Nil(t, value)
|
|
|
|
}
|
2020-05-01 13:19:19 +02:00
|
|
|
|
2020-11-30 12:46:06 +01:00
|
|
|
func TestDelete(t *testing.T) {
|
|
|
|
c, create := setup(t)
|
|
|
|
|
|
|
|
assert.Equal(t, 0, len(c.cache))
|
|
|
|
|
|
|
|
_, err := c.Get("/", create)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, 1, len(c.cache))
|
|
|
|
|
|
|
|
assert.Equal(t, false, c.Delete("notfound"))
|
|
|
|
assert.Equal(t, 1, len(c.cache))
|
|
|
|
|
|
|
|
assert.Equal(t, true, c.Delete("/"))
|
|
|
|
assert.Equal(t, 0, len(c.cache))
|
|
|
|
|
|
|
|
assert.Equal(t, false, c.Delete("/"))
|
|
|
|
assert.Equal(t, 0, len(c.cache))
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestDeletePrefix(t *testing.T) {
|
|
|
|
create := func(path string) (interface{}, bool, error) {
|
|
|
|
return path, true, nil
|
|
|
|
}
|
|
|
|
c := New()
|
|
|
|
|
|
|
|
_, err := c.Get("remote:path", create)
|
|
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Get("remote:path2", create)
|
|
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Get("remote:", create)
|
|
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Get("remote", create)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, 4, len(c.cache))
|
|
|
|
|
|
|
|
assert.Equal(t, 3, c.DeletePrefix("remote:"))
|
|
|
|
assert.Equal(t, 1, len(c.cache))
|
|
|
|
|
|
|
|
assert.Equal(t, 1, c.DeletePrefix(""))
|
|
|
|
assert.Equal(t, 0, len(c.cache))
|
|
|
|
|
|
|
|
assert.Equal(t, 0, c.DeletePrefix(""))
|
|
|
|
assert.Equal(t, 0, len(c.cache))
|
|
|
|
}
|
|
|
|
|
2020-05-01 13:19:19 +02:00
|
|
|
func TestCacheRename(t *testing.T) {
|
|
|
|
c := New()
|
|
|
|
create := func(path string) (interface{}, bool, error) {
|
|
|
|
return path, true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
existing1, err := c.Get("existing1", create)
|
|
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Get("existing2", create)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, 2, c.Entries())
|
|
|
|
|
2022-08-14 04:56:32 +02:00
|
|
|
// rename to nonexistent
|
2020-05-01 13:19:19 +02:00
|
|
|
value, found := c.Rename("existing1", "EXISTING1")
|
|
|
|
assert.Equal(t, true, found)
|
|
|
|
assert.Equal(t, existing1, value)
|
|
|
|
|
|
|
|
assert.Equal(t, 2, c.Entries())
|
|
|
|
|
|
|
|
// rename to existent and check existing value is returned
|
|
|
|
value, found = c.Rename("existing2", "EXISTING1")
|
|
|
|
assert.Equal(t, true, found)
|
|
|
|
assert.Equal(t, existing1, value)
|
|
|
|
|
|
|
|
assert.Equal(t, 1, c.Entries())
|
|
|
|
|
2022-08-14 04:56:32 +02:00
|
|
|
// rename nonexistent
|
2020-05-01 13:19:19 +02:00
|
|
|
value, found = c.Rename("notfound", "NOTFOUND")
|
|
|
|
assert.Equal(t, false, found)
|
|
|
|
assert.Nil(t, value)
|
|
|
|
|
|
|
|
assert.Equal(t, 1, c.Entries())
|
|
|
|
}
|
2022-06-28 13:51:59 +02:00
|
|
|
|
|
|
|
func TestCacheFinalize(t *testing.T) {
|
|
|
|
c := New()
|
|
|
|
numCalled := 0
|
|
|
|
c.SetFinalizer(func(v interface{}) {
|
|
|
|
numCalled++
|
|
|
|
})
|
|
|
|
create := func(path string) (interface{}, bool, error) {
|
|
|
|
return path, true, nil
|
|
|
|
}
|
|
|
|
_, _ = c.Get("ok", create)
|
|
|
|
assert.Equal(t, 0, numCalled)
|
|
|
|
c.Clear()
|
|
|
|
assert.Equal(t, 1, numCalled)
|
|
|
|
|
|
|
|
_, _ = c.Get("ok", create)
|
|
|
|
c.Delete("ok")
|
|
|
|
assert.Equal(t, 2, numCalled)
|
|
|
|
|
|
|
|
_, _ = c.Get("ok", create)
|
|
|
|
c.DeletePrefix("ok")
|
|
|
|
assert.Equal(t, 3, numCalled)
|
|
|
|
|
|
|
|
_, _ = c.Get("old", create)
|
|
|
|
_, _ = c.Get("new", create)
|
|
|
|
c.Rename("old", "new")
|
|
|
|
assert.Equal(t, 4, numCalled)
|
|
|
|
|
|
|
|
c.expireDuration = 1 * time.Millisecond
|
|
|
|
_, _ = c.Get("ok", create)
|
|
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
c.cacheExpire() // "ok" and "new" fall out of cache
|
|
|
|
assert.Equal(t, 6, numCalled)
|
|
|
|
}
|