blob: 421a9e5eb3712ed204e007b03c5580359ef4f1a5 [file] [log] [blame]
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package store
import (
"bytes"
"reflect"
"testing"
"android.googlesource.com/platform/tools/gpu/binary"
"android.googlesource.com/platform/tools/gpu/binary/cyclic"
"android.googlesource.com/platform/tools/gpu/binary/vle"
"android.googlesource.com/platform/tools/gpu/log"
)
type storeCallback func(id binary.ID, r binary.Object, d []byte) error
type loadCallback func(id binary.ID, out binary.Object) (size int, err error)
type containsCallback func(id binary.ID) bool
type closeCallback func()
type mockStore struct {
onStore storeCallback
onLoad loadCallback
onContains containsCallback
onClose closeCallback
}
func (s mockStore) Store(id binary.ID, r binary.Object, d []byte, _ log.Logger) error {
return s.onStore(id, r, d)
}
func (s mockStore) Load(id binary.ID, _ log.Logger, out binary.Object) (size int, err error) {
return s.onLoad(id, out)
}
func (s mockStore) Contains(id binary.ID) bool {
return s.onContains(id)
}
func (s mockStore) Close() {
s.onClose()
}
func createMockStore() *mockStore {
return &mockStore{
onStore: func(binary.ID, binary.Object, []byte) error { return nil },
onLoad: func(binary.ID, binary.Object) (size int, err error) { return -1, nil },
onContains: func(binary.ID) bool { return false },
onClose: func() {},
}
}
func validateLoad(t *testing.T, cache *cache, id binary.ID, expectedSize int, expectedData *Blob, expectedErr error) {
data := &Blob{Data: []byte{}}
size, err := cache.Load(id, nil, data)
if expectedSize != size {
t.Errorf("invalid size, expected %v got %v", expectedSize, size)
}
if !reflect.DeepEqual(expectedData, data) {
t.Errorf("invalid data, expected %v got %v", expectedData, data)
}
if expectedErr != err {
t.Errorf("invalid error, expected %v got %v", expectedErr, err)
}
}
func encode(r binary.Object) []byte {
buf := &bytes.Buffer{}
enc := cyclic.Encoder(vle.Writer(buf))
if err := enc.Value(r); err != nil {
panic(err)
}
return buf.Bytes()
}
func calcSize(r binary.Object) int {
return len(encode(r))
}
func TestCacheMiss(t *testing.T) {
id := binary.NewID([]byte("123"))
data := &Blob{Data: []byte{1, 2, 3}}
size := calcSize(data)
loadCallCount := 0
inner := createMockStore()
inner.onLoad = func(actualId binary.ID, out binary.Object) (int, error) {
if id != actualId {
t.Errorf("invalid id, expected %v got %v", id, actualId)
}
loadCallCount++
CopyResource(out, data)
return size, nil
}
cache := CreateCache(64, inner).(*cache)
// Nothing should have called inner.Load() yet
if 0 != loadCallCount {
t.Errorf("invalid load call count, expected %v got %v", 0, loadCallCount)
}
validateLoad(t, cache, id, size, data, nil)
// The cache should have missed, calling inner.Load()
if 1 != loadCallCount {
t.Errorf("invalid load call count, expected %v got %v", 1, loadCallCount)
}
}
func TestCacheHit(t *testing.T) {
id := binary.NewID([]byte("123"))
data := &Blob{Data: []byte{1, 2, 3}}
size := calcSize(data)
loadCallCount := 0
inner := createMockStore()
inner.onLoad = func(actualId binary.ID, out binary.Object) (int, error) {
if id != actualId {
t.Errorf("invalid id, expected %v got %v", id, actualId)
}
loadCallCount++
CopyResource(out, data)
return size, nil
}
cache := CreateCache(64, inner).(*cache)
// Warm the cache
validateLoad(t, cache, id, size, data, nil)
// The cache should have hit, skipping the call into inner.Load()
validateLoad(t, cache, id, size, data, nil)
if 1 != loadCallCount {
t.Errorf("invalid load call count, expected %v got %v", 1, loadCallCount)
}
}
func TestCacheMultipleLoads(t *testing.T) {
id := binary.NewID([]byte("123"))
data := &Blob{Data: []byte{1, 2, 3}}
size := calcSize(data)
loadCallCount := 0
loadSync := make(chan bool)
loadUnblock := make(chan bool)
loadComplete := make(chan bool)
inner := createMockStore()
inner.onLoad = func(actualId binary.ID, out binary.Object) (int, error) {
if id != actualId {
t.Errorf("invalid id, expected %v got %v", id, actualId)
}
loadCallCount++
loadSync <- true
<-loadUnblock
CopyResource(out, data)
return size, nil
}
cache := CreateCache(64, inner).(*cache)
checkLoad := func() {
validateLoad(t, cache, id, size, data, nil)
loadComplete <- true
}
// Begin a number of loads, but don't let the inner load finish just yet.
go checkLoad()
go checkLoad()
go checkLoad()
// Sync with the inner load so we can query the load call count
<-loadSync
// There should only have been one call to the inner load
if 1 != loadCallCount {
t.Errorf("invalid load call count, expected %v got %v", 1, loadCallCount)
}
// Unblock the inner load
loadUnblock <- true
// The three load requests should now finish
<-loadComplete
<-loadComplete
<-loadComplete
// There still should only have been one call to the inner load
if 1 != loadCallCount {
t.Errorf("invalid load call count, expected %v got %v", 1, loadCallCount)
}
}
func TestCacheOverflow(t *testing.T) {
idA, idB, idC := binary.NewID([]byte("123")), binary.NewID([]byte("456")), binary.NewID([]byte("789"))
dataA := &Blob{Data: []byte{1, 2, 3, 4}}
sizeA := calcSize(dataA)
dataB := &Blob{Data: []byte{4, 5, 6, 7}}
sizeB := calcSize(dataB)
dataC := &Blob{Data: []byte{8, 9, 10, 11}}
sizeC := calcSize(dataC)
loadCallCount := 0
inner := createMockStore()
inner.onLoad = func(id binary.ID, out binary.Object) (int, error) {
loadCallCount++
switch id {
case idA:
CopyResource(out, dataA)
return sizeA, nil
case idB:
CopyResource(out, dataB)
return sizeB, nil
case idC:
CopyResource(out, dataC)
return sizeC, nil
default:
panic("Unexpected id!")
}
}
// Create a cache that only has room for dataA and dataB
cache := CreateCache(sizeA+sizeB, inner).(*cache)
// Warm the cache with A and B
validateLoad(t, cache, idA, sizeA, dataA, nil)
validateLoad(t, cache, idB, sizeB, dataB, nil)
// These should have both cache missed, calling into inner.Load()
if 2 != loadCallCount {
t.Errorf("invalid load call count, expected %v got %v", 2, loadCallCount)
}
// Request load of a third resource, that will overflow the cache
validateLoad(t, cache, idC, sizeC, dataC, nil)
if 3 != loadCallCount {
t.Errorf("invalid load call count, expected %v got %v", 3, loadCallCount)
}
// The cache should now be holding B and C, with A evicted.
// Requesting B should not call inner.Load() as it should be cached
validateLoad(t, cache, idB, sizeB, dataB, nil)
if 3 != loadCallCount {
t.Errorf("invalid load call count, expected %v got %v", 3, loadCallCount)
}
// Requesting C should not call inner.Load() as it should be cached
validateLoad(t, cache, idC, sizeC, dataC, nil)
if 3 != loadCallCount {
t.Errorf("invalid load call count, expected %v got %v", 3, loadCallCount)
}
// Requesting A should call inner.Load() as it should not be cached
validateLoad(t, cache, idA, sizeA, dataA, nil)
if 4 != loadCallCount {
t.Errorf("invalid load call count, expected %v got %v", 4, loadCallCount)
}
}
func TestCacheReplace(t *testing.T) {
id := binary.NewID([]byte("123"))
dataA := &Blob{Data: []byte{1, 2, 3}}
dataB := &Blob{Data: []byte{4, 5, 6, 7, 8, 9}}
sizeB := calcSize(dataB)
inner := createMockStore()
cache := CreateCache(64, inner).(*cache)
// Fill the cache with dataA
cache.Store(id, dataA, encode(dataA), nil)
// Replace dataA with dataB
cache.Store(id, dataB, encode(dataB), nil)
// We expect dataB to be loaded
validateLoad(t, cache, id, sizeB, dataB, nil)
}