270 lines
5.6 KiB
Go
270 lines
5.6 KiB
Go
|
package dataext
|
||
|
|
||
|
import (
|
||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||
|
"math/rand"
|
||
|
"strconv"
|
||
|
"testing"
|
||
|
)
|
||
|
|
||
|
func init() {
|
||
|
rand.Seed(0)
|
||
|
}
|
||
|
|
||
|
func TestResultCache1(t *testing.T) {
|
||
|
cache := NewLRUMap(8)
|
||
|
verifyLRUList(cache, t)
|
||
|
|
||
|
key := randomKey()
|
||
|
val := randomVal()
|
||
|
|
||
|
if cache.Size() != 0 {
|
||
|
t.Errorf("cache size expected == 0, actual == %v", cache.Size())
|
||
|
}
|
||
|
|
||
|
if _, ok := cache.TryGet(key); ok {
|
||
|
t.Errorf("empty cache TryGet returned value")
|
||
|
}
|
||
|
verifyLRUList(cache, t)
|
||
|
|
||
|
cache.Put(key, val)
|
||
|
verifyLRUList(cache, t)
|
||
|
|
||
|
if cache.Size() != 1 {
|
||
|
t.Errorf("cache size expected == 1, actual == %v", cache.Size())
|
||
|
}
|
||
|
|
||
|
cacheval, ok := cache.TryGet(key)
|
||
|
verifyLRUList(cache, t)
|
||
|
if !ok {
|
||
|
t.Errorf("cache TryGet returned no value")
|
||
|
}
|
||
|
if !eq(cacheval, val) {
|
||
|
t.Errorf("cache TryGet returned different value (%+v <> %+v)", cacheval, val)
|
||
|
}
|
||
|
|
||
|
if _, ok := cache.TryGet(randomKey()); ok {
|
||
|
t.Errorf("cache TryGet returned a value for non-existant key")
|
||
|
}
|
||
|
verifyLRUList(cache, t)
|
||
|
}
|
||
|
|
||
|
func TestResultCache2(t *testing.T) {
|
||
|
cache := NewLRUMap(8)
|
||
|
verifyLRUList(cache, t)
|
||
|
|
||
|
key1 := "key1"
|
||
|
val1 := randomVal()
|
||
|
cache.Put(key1, val1)
|
||
|
verifyLRUList(cache, t)
|
||
|
|
||
|
key2 := "key2"
|
||
|
val2 := randomVal()
|
||
|
cache.Put(key2, val2)
|
||
|
verifyLRUList(cache, t)
|
||
|
|
||
|
key3 := "key3"
|
||
|
val3 := randomVal()
|
||
|
cache.Put(key3, val3)
|
||
|
verifyLRUList(cache, t)
|
||
|
|
||
|
key4 := "key4"
|
||
|
val4 := randomVal()
|
||
|
cache.Put(key4, val4)
|
||
|
verifyLRUList(cache, t)
|
||
|
|
||
|
if _, ok := cache.TryGet(key1); !ok {
|
||
|
t.Errorf("cache TryGet returned no value")
|
||
|
}
|
||
|
verifyLRUList(cache, t)
|
||
|
if _, ok := cache.TryGet(key2); !ok {
|
||
|
t.Errorf("cache TryGet returned no value")
|
||
|
}
|
||
|
verifyLRUList(cache, t)
|
||
|
if _, ok := cache.TryGet(key3); !ok {
|
||
|
t.Errorf("cache TryGet returned no value")
|
||
|
}
|
||
|
verifyLRUList(cache, t)
|
||
|
if _, ok := cache.TryGet(key4); !ok {
|
||
|
t.Errorf("cache TryGet returned no value")
|
||
|
}
|
||
|
verifyLRUList(cache, t)
|
||
|
if _, ok := cache.TryGet(randomKey()); ok {
|
||
|
t.Errorf("cache TryGet returned a value for non-existant key")
|
||
|
}
|
||
|
verifyLRUList(cache, t)
|
||
|
if cache.Size() != 4 {
|
||
|
t.Errorf("cache size expected == 4, actual == %v", cache.Size())
|
||
|
}
|
||
|
verifyLRUList(cache, t)
|
||
|
|
||
|
cache.Put(key4, val4) // same key again
|
||
|
verifyLRUList(cache, t)
|
||
|
|
||
|
if cache.Size() != 4 {
|
||
|
t.Errorf("cache size expected == 4, actual == %v", cache.Size())
|
||
|
}
|
||
|
|
||
|
cache.Put(randomKey(), randomVal())
|
||
|
verifyLRUList(cache, t)
|
||
|
cache.Put(randomKey(), randomVal())
|
||
|
verifyLRUList(cache, t)
|
||
|
cache.Put(randomKey(), randomVal())
|
||
|
verifyLRUList(cache, t)
|
||
|
cache.Put(randomKey(), randomVal())
|
||
|
verifyLRUList(cache, t)
|
||
|
|
||
|
if cache.Size() != 8 {
|
||
|
t.Errorf("cache size expected == 8, actual == %v", cache.Size())
|
||
|
}
|
||
|
|
||
|
cache.Put(randomKey(), randomVal()) // drops key1
|
||
|
verifyLRUList(cache, t)
|
||
|
|
||
|
if cache.Size() != 8 {
|
||
|
t.Errorf("cache size expected == 8, actual == %v", cache.Size())
|
||
|
}
|
||
|
|
||
|
if _, ok := cache.TryGet(key1); ok {
|
||
|
t.Errorf("[key1] should be dropped from cache")
|
||
|
}
|
||
|
verifyLRUList(cache, t)
|
||
|
if _, ok := cache.TryGet(key2); !ok { // moves key2 to most-recently used
|
||
|
t.Errorf("[key2] should still be in cache")
|
||
|
}
|
||
|
verifyLRUList(cache, t)
|
||
|
|
||
|
cache.Put(randomKey(), randomVal()) // drops key3
|
||
|
verifyLRUList(cache, t)
|
||
|
|
||
|
if cache.Size() != 8 {
|
||
|
t.Errorf("cache size expected == 8, actual == %v", cache.Size())
|
||
|
}
|
||
|
|
||
|
if _, ok := cache.TryGet(key3); ok {
|
||
|
t.Errorf("[key3] should be dropped from cache")
|
||
|
}
|
||
|
if _, ok := cache.TryGet(key2); !ok {
|
||
|
t.Errorf("[key2] should still be in cache")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestResultCache3(t *testing.T) {
|
||
|
cache := NewLRUMap(8)
|
||
|
verifyLRUList(cache, t)
|
||
|
|
||
|
key1 := "key1"
|
||
|
val1 := randomVal()
|
||
|
val2 := randomVal()
|
||
|
|
||
|
cache.Put(key1, val1)
|
||
|
verifyLRUList(cache, t)
|
||
|
|
||
|
if val, ok := cache.TryGet(key1); !ok || !eq(val, val1) {
|
||
|
t.Errorf("Value in cache should be [val1]")
|
||
|
}
|
||
|
|
||
|
cache.Put(key1, val2)
|
||
|
verifyLRUList(cache, t)
|
||
|
|
||
|
if val, ok := cache.TryGet(key1); !ok || !eq(val, val2) {
|
||
|
t.Errorf("Value in cache should be [val2]")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// does a basic consistency check over the internal cache representation
|
||
|
func verifyLRUList(cache *LRUMap, t *testing.T) {
|
||
|
size := 0
|
||
|
|
||
|
tailFound := false
|
||
|
headFound := false
|
||
|
curr := cache.lfuHead
|
||
|
for curr != nil {
|
||
|
size++
|
||
|
|
||
|
if curr.parent == nil {
|
||
|
headFound = true
|
||
|
if curr != cache.lfuHead {
|
||
|
t.Errorf("head != lfuHead")
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if curr.child == nil {
|
||
|
tailFound = true
|
||
|
if curr != cache.lfuTail {
|
||
|
t.Errorf("tail != lfuTail")
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if curr.child != nil {
|
||
|
if curr.child.parent != curr {
|
||
|
t.Errorf("error in child <-> parent link")
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if curr.parent != nil {
|
||
|
if curr.parent.child != curr {
|
||
|
t.Errorf("error in parent <-> child link")
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
curr = curr.child
|
||
|
}
|
||
|
|
||
|
if cache.Size() > 0 && cache.lfuHead == nil {
|
||
|
t.Errorf("no head in cache")
|
||
|
}
|
||
|
if cache.Size() > 0 && cache.lfuTail == nil {
|
||
|
t.Errorf("no tail in cache")
|
||
|
}
|
||
|
|
||
|
if cache.Size() == 0 && cache.lfuHead != nil {
|
||
|
t.Errorf("dangling head in cache")
|
||
|
}
|
||
|
if cache.Size() == 0 && cache.lfuTail != nil {
|
||
|
t.Errorf("dangling tail in cache")
|
||
|
}
|
||
|
|
||
|
if cache.Size() > 0 && !headFound {
|
||
|
t.Errorf("head not found")
|
||
|
}
|
||
|
|
||
|
if cache.Size() > 0 && !tailFound {
|
||
|
t.Errorf("tail not found")
|
||
|
}
|
||
|
|
||
|
if size != cache.Size() {
|
||
|
t.Errorf("error size mismatch (%v <> %v)", size, cache.Size())
|
||
|
}
|
||
|
|
||
|
if cache.Size() > cache.maxsize {
|
||
|
t.Errorf("too many items: %v", cache.Size())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func randomKey() string {
|
||
|
return strconv.FormatInt(rand.Int63(), 16)
|
||
|
}
|
||
|
|
||
|
func randomVal() LRUData {
|
||
|
v := primitive.NewObjectID()
|
||
|
return &v
|
||
|
}
|
||
|
|
||
|
func eq(a LRUData, b LRUData) bool {
|
||
|
v1, ok1 := a.(*primitive.ObjectID)
|
||
|
v2, ok2 := b.(*primitive.ObjectID)
|
||
|
if ok1 && ok2 {
|
||
|
if v1 == nil || v2 == nil {
|
||
|
return false
|
||
|
}
|
||
|
return v1.Hex() == v2.Hex()
|
||
|
}
|
||
|
|
||
|
return false
|
||
|
}
|