Files
public/mycache/cache2go/cache_test.go

518 lines
12 KiB
Go

/*
* Simple caching library with expiration capabilities
* Copyright (c) 2013-2017, Christian Muehlhaeuser <muesli@gmail.com>
*
* For license see LICENSE.txt
*/
package cache2go
import (
"bytes"
"log"
"strconv"
"sync"
"sync/atomic"
"testing"
"time"
)
var (
k = "testkey"
v = "testvalue"
)
func TestCache(t *testing.T) {
// add an expiring item after a non-expiring one to
// trigger expirationCheck iterating over non-expiring items
table := Cache("testCache")
table.Add(k+"_1", 0*time.Second, v)
table.Add(k+"_2", 1*time.Second, v)
// check if both items are still there
p, err := table.Value(k + "_1")
if err != nil || p == nil || p.Data().(string) != v {
t.Error("Error retrieving non expiring data from cache", err)
}
p, err = table.Value(k + "_2")
if err != nil || p == nil || p.Data().(string) != v {
t.Error("Error retrieving data from cache", err)
}
// sanity checks
if p.AccessCount() != 1 {
t.Error("Error getting correct access count")
}
if p.LifeSpan() != 1*time.Second {
t.Error("Error getting correct life-span")
}
if p.AccessedOn().Unix() == 0 {
t.Error("Error getting access time")
}
if p.CreatedOn().Unix() == 0 {
t.Error("Error getting creation time")
}
}
func TestCacheExpire(t *testing.T) {
table := Cache("testCache")
table.Add(k+"_1", 250*time.Millisecond, v+"_1")
table.Add(k+"_2", 200*time.Millisecond, v+"_2")
time.Sleep(100 * time.Millisecond)
// check key `1` is still alive
_, err := table.Value(k + "_1")
if err != nil {
t.Error("Error retrieving value from cache:", err)
}
time.Sleep(150 * time.Millisecond)
// check key `1` again, it should still be alive since we just accessed it
_, err = table.Value(k + "_1")
if err != nil {
t.Error("Error retrieving value from cache:", err)
}
// check key `2`, it should have been removed by now
_, err = table.Value(k + "_2")
if err == nil {
t.Error("Found key which should have been expired by now")
}
}
func TestExists(t *testing.T) {
// add an expiring item
table := Cache("testExists")
table.Add(k, 0, v)
// check if it exists
if !table.Exists(k) {
t.Error("Error verifying existing data in cache")
}
}
func TestNotFoundAdd(t *testing.T) {
table := Cache("testNotFoundAdd")
if !table.NotFoundAdd(k, 0, v) {
t.Error("Error verifying NotFoundAdd, data not in cache")
}
if table.NotFoundAdd(k, 0, v) {
t.Error("Error verifying NotFoundAdd data in cache")
}
}
func TestNotFoundAddConcurrency(t *testing.T) {
table := Cache("testNotFoundAdd")
var finish sync.WaitGroup
var added int32
var idle int32
fn := func(id int) {
for i := 0; i < 100; i++ {
if table.NotFoundAdd(i, 0, i+id) {
atomic.AddInt32(&added, 1)
} else {
atomic.AddInt32(&idle, 1)
}
time.Sleep(0)
}
finish.Done()
}
finish.Add(10)
go fn(0x0000)
go fn(0x1100)
go fn(0x2200)
go fn(0x3300)
go fn(0x4400)
go fn(0x5500)
go fn(0x6600)
go fn(0x7700)
go fn(0x8800)
go fn(0x9900)
finish.Wait()
t.Log(added, idle)
table.Foreach(func(key interface{}, item *CacheItem) {
v, _ := item.Data().(int)
k, _ := key.(int)
t.Logf("%02x %04x\n", k, v)
})
}
func TestCacheKeepAlive(t *testing.T) {
// add an expiring item
table := Cache("testKeepAlive")
p := table.Add(k, 250*time.Millisecond, v)
// keep it alive before it expires
time.Sleep(100 * time.Millisecond)
p.KeepAlive()
// check it's still alive after it was initially supposed to expire
time.Sleep(150 * time.Millisecond)
if !table.Exists(k) {
t.Error("Error keeping item alive")
}
// check it expires eventually
time.Sleep(300 * time.Millisecond)
if table.Exists(k) {
t.Error("Error expiring item after keeping it alive")
}
}
func TestPeek(t *testing.T) {
// add an expiring item
table := Cache("TestPeek")
_ = table.Add(k, 250*time.Millisecond, v)
_ = table.Add(k+"_", 250*time.Millisecond, v)
// test peek item
time.Sleep(150 * time.Millisecond)
p, _ := table.Peek(k)
if p.Data() != v {
t.Error("Error peek item")
}
// test k is expired
time.Sleep(150 * time.Millisecond)
if table.Exists(k) {
t.Error("Error peek but expired time updated")
}
// test peek nil
_, err := table.Peek(k)
if err != ErrKeyNotFound {
t.Error("Error peek nil value but not return ErrKeyNotFound")
}
// test DataLoader
table.SetDataLoader(func(key interface{}, args ...interface{}) *CacheItem {
if key == k {
return NewCacheItem(key, 150*time.Millisecond, "new value")
}
return nil
})
p, _ = table.Peek(k)
if p.Data() != "new value" {
t.Error("Error peek DataLoader callback did not take effect")
}
_, err = table.Peek(k + "_")
if err != ErrKeyNotFoundOrLoadable {
t.Error("Error peek DataLoader err")
}
}
func TestDelete(t *testing.T) {
// add an item to the cache
table := Cache("testDelete")
table.Add(k, 0, v)
// check it's really cached
p, err := table.Value(k)
if err != nil || p == nil || p.Data().(string) != v {
t.Error("Error retrieving data from cache", err)
}
// try to delete it
table.Delete(k)
// verify it has been deleted
p, err = table.Value(k)
if err == nil || p != nil {
t.Error("Error deleting data")
}
// test error handling
_, err = table.Delete(k)
if err == nil {
t.Error("Expected error deleting item")
}
}
func TestFlush(t *testing.T) {
// add an item to the cache
table := Cache("testFlush")
table.Add(k, 10*time.Second, v)
// flush the entire table
table.Flush()
// try to retrieve the item
p, err := table.Value(k)
if err == nil || p != nil {
t.Error("Error flushing table")
}
// make sure there's really nothing else left in the cache
if table.Count() != 0 {
t.Error("Error verifying count of flushed table")
}
}
func TestCount(t *testing.T) {
// add a huge amount of items to the cache
table := Cache("testCount")
count := 100000
for i := 0; i < count; i++ {
key := k + strconv.Itoa(i)
table.Add(key, 10*time.Second, v)
}
// confirm every single item has been cached
for i := 0; i < count; i++ {
key := k + strconv.Itoa(i)
p, err := table.Value(key)
if err != nil || p == nil || p.Data().(string) != v {
t.Error("Error retrieving data")
}
}
// make sure the item count matches (no dupes etc.)
if table.Count() != count {
t.Error("Data count mismatch")
}
}
func TestDataLoader(t *testing.T) {
// setup a cache with a configured data-loader
table := Cache("testDataLoader")
table.SetDataLoader(func(key interface{}, args ...interface{}) *CacheItem {
var item *CacheItem
if key.(string) != "nil" {
val := k + key.(string)
i := NewCacheItem(key, 500*time.Millisecond, val)
item = i
}
return item
})
// make sure data-loader works as expected and handles unloadable keys
_, err := table.Value("nil")
if err == nil || table.Exists("nil") {
t.Error("Error validating data loader for nil values")
}
// retrieve a bunch of items via the data-loader
for i := 0; i < 10; i++ {
key := k + strconv.Itoa(i)
vp := k + key
p, err := table.Value(key)
if err != nil || p == nil || p.Data().(string) != vp {
t.Error("Error validating data loader")
}
}
}
func TestAccessCount(t *testing.T) {
// add 100 items to the cache
count := 100
table := Cache("testAccessCount")
for i := 0; i < count; i++ {
table.Add(i, 10*time.Second, v)
}
// never access the first item, access the second item once, the third
// twice and so on...
for i := 0; i < count; i++ {
for j := 0; j < i; j++ {
table.Value(i)
}
}
// check MostAccessed returns the items in correct order
ma := table.MostAccessed(int64(count))
for i, item := range ma {
if item.Key() != count-1-i {
t.Error("Most accessed items seem to be sorted incorrectly")
}
}
// check MostAccessed returns the correct amount of items
ma = table.MostAccessed(int64(count - 1))
if len(ma) != count-1 {
t.Error("MostAccessed returns incorrect amount of items")
}
}
func TestCallbacks(t *testing.T) {
var m sync.Mutex
addedKey := ""
removedKey := ""
calledAddedItem := false
calledRemoveItem := false
expired := false
calledExpired := false
// setup a cache with AddedItem & SetAboutToDelete handlers configured
table := Cache("testCallbacks")
table.SetAddedItemCallback(func(item *CacheItem) {
m.Lock()
addedKey = item.Key().(string)
m.Unlock()
})
table.SetAddedItemCallback(func(item *CacheItem) {
m.Lock()
calledAddedItem = true
m.Unlock()
})
table.SetAboutToDeleteItemCallback(func(item *CacheItem) {
m.Lock()
removedKey = item.Key().(string)
m.Unlock()
})
table.SetAboutToDeleteItemCallback(func(item *CacheItem) {
m.Lock()
calledRemoveItem = true
m.Unlock()
})
// add an item to the cache and setup its AboutToExpire handler
i := table.Add(k, 500*time.Millisecond, v)
i.SetAboutToExpireCallback(func(key interface{}) {
m.Lock()
expired = true
m.Unlock()
})
i.SetAboutToExpireCallback(func(key interface{}) {
m.Lock()
calledExpired = true
m.Unlock()
})
// verify the AddedItem handler works
time.Sleep(250 * time.Millisecond)
m.Lock()
if addedKey == k && !calledAddedItem {
t.Error("AddedItem callback not working")
}
m.Unlock()
// verify the AboutToDelete handler works
time.Sleep(500 * time.Millisecond)
m.Lock()
if removedKey == k && !calledRemoveItem {
t.Error("AboutToDeleteItem callback not working:" + k + "_" + removedKey)
}
// verify the AboutToExpire handler works
if expired && !calledExpired {
t.Error("AboutToExpire callback not working")
}
m.Unlock()
}
func TestCallbackQueue(t *testing.T) {
var m sync.Mutex
addedKey := ""
addedkeyCallback2 := ""
secondCallbackResult := "second"
removedKey := ""
removedKeyCallback := ""
expired := false
calledExpired := false
// setup a cache with AddedItem & SetAboutToDelete handlers configured
table := Cache("testCallbacks")
// test callback queue
table.AddAddedItemCallback(func(item *CacheItem) {
m.Lock()
addedKey = item.Key().(string)
m.Unlock()
})
table.AddAddedItemCallback(func(item *CacheItem) {
m.Lock()
addedkeyCallback2 = secondCallbackResult
m.Unlock()
})
table.AddAboutToDeleteItemCallback(func(item *CacheItem) {
m.Lock()
removedKey = item.Key().(string)
m.Unlock()
})
table.AddAboutToDeleteItemCallback(func(item *CacheItem) {
m.Lock()
removedKeyCallback = secondCallbackResult
m.Unlock()
})
i := table.Add(k, 500*time.Millisecond, v)
i.AddAboutToExpireCallback(func(key interface{}) {
m.Lock()
expired = true
m.Unlock()
})
i.AddAboutToExpireCallback(func(key interface{}) {
m.Lock()
calledExpired = true
m.Unlock()
})
time.Sleep(250 * time.Millisecond)
m.Lock()
if addedKey != k && addedkeyCallback2 != secondCallbackResult {
t.Error("AddedItem callback queue not working")
}
m.Unlock()
time.Sleep(500 * time.Millisecond)
m.Lock()
if removedKey != k && removedKeyCallback != secondCallbackResult {
t.Error("Item removed callback queue not working")
}
m.Unlock()
// test removing of the callbacks
table.RemoveAddedItemCallbacks()
table.RemoveAboutToDeleteItemCallback()
secondItemKey := "itemKey02"
expired = false
i = table.Add(secondItemKey, 500*time.Millisecond, v)
i.SetAboutToExpireCallback(func(key interface{}) {
m.Lock()
expired = true
m.Unlock()
})
i.RemoveAboutToExpireCallback()
// verify if the callbacks were removed
time.Sleep(250 * time.Millisecond)
m.Lock()
if addedKey == secondItemKey {
t.Error("AddedItemCallbacks were not removed")
}
m.Unlock()
// verify the AboutToDelete handler works
time.Sleep(500 * time.Millisecond)
m.Lock()
if removedKey == secondItemKey {
t.Error("AboutToDeleteItem not removed")
}
// verify the AboutToExpire handler works
if !expired && !calledExpired {
t.Error("AboutToExpire callback not working")
}
m.Unlock()
}
func TestLogger(t *testing.T) {
// setup a logger
out := new(bytes.Buffer)
l := log.New(out, "cache2go ", log.Ldate|log.Ltime)
// setup a cache with this logger
table := Cache("testLogger")
table.SetLogger(l)
table.Add(k, 0, v)
time.Sleep(100 * time.Millisecond)
// verify the logger has been used
if out.Len() == 0 {
t.Error("Logger is empty")
}
}