~whereswaldon/forest-go

f0c1fe5aa09fcced09d04db8ecfb9cc83d1838ba — Chris Waldon 2 years ago b6c61b2
Add better tests for CacheStore

This involved creating a new Store operation, as the CacheStore needs
to ensure a level of consistency between the two layers of store that
it manages. The stores now have a CopyInto() method that causes one
store to replicate its content into another. The reason this approach
was taken over providing a way to iterate over the keys was to allow
stores to use their own internal representation of a given node ID as
a key. Providing a Keys() method would require all implementations to
use the same transformation from *fields.QualifiedHash to key type.
2 files changed, 63 insertions(+), 5 deletions(-)

M store.go
M store_test.go
M store.go => store.go +31 -2
@@ 6,6 6,7 @@ import (

type Store interface {
	Size() (int, error)
	CopyInto(Store) error
	Get(*fields.QualifiedHash) (Node, bool, error)
	Add(Node) error
}


@@ 22,6 23,15 @@ func (m *MemoryStore) Size() (int, error) {
	return len(m.Items), nil
}

func (m *MemoryStore) CopyInto(other Store) error {
	for _, node := range m.Items {
		if err := other.Add(node); err != nil {
			return err
		}
	}
	return nil
}

func (m *MemoryStore) Get(id *fields.QualifiedHash) (Node, bool, error) {
	idString, err := id.MarshalString()
	if err != nil {


@@ 52,12 62,27 @@ func (m *MemoryStore) AddID(id string, node Node) error {
	return nil
}

// CacheStore combines two other stores into one logical store. It is
// useful when store implementations have different performance
// characteristics and one is dramatically faster than the other. Once
// a CacheStore is created, the individual stores within it should not
// be directly modified.
type CacheStore struct {
	Cache, Back Store
}

func NewCacheStore(cache, back Store) *CacheStore {
	return &CacheStore{cache, back}
// NewCacheStore creates a single logical store from the given two stores.
// All items from `cache` are automatically copied into `base` during
// the construction of the CacheStore, and from then on (assuming
// neither store is modified directly outside of CacheStore) all elements
// added are guaranteed to be added to `base`. It is recommended to use
// fast in-memory implementations as the `cache` layer and disk or
// network-based implementations as the `base` layer.
func NewCacheStore(cache, back Store) (*CacheStore, error) {
	if err := cache.CopyInto(back); err != nil {
		return nil, err
	}
	return &CacheStore{cache, back}, nil
}

// Size returns the effective size of this CacheStore, which is the size of the


@@ 86,6 111,10 @@ func (m *CacheStore) Get(id *fields.QualifiedHash) (Node, bool, error) {
	return nil, false, nil
}

func (m *CacheStore) CopyInto(other Store) error {
	return m.Back.CopyInto(other)
}

// Add inserts the given node into both stores of the CacheStore
func (m *CacheStore) Add(node Node) error {
	if err := m.Back.Add(node); err != nil {

M store_test.go => store_test.go +32 -3
@@ 6,7 6,7 @@ import (
	"git.sr.ht/~whereswaldon/forest-go"
)

func TestMemoryStoreAdd(t *testing.T) {
func TestMemoryStore(t *testing.T) {
	s := forest.NewMemoryStore()
	testStandardStoreInterface(t, s, "MemoryStore")
}


@@ 47,9 47,38 @@ func testStandardStoreInterface(t *testing.T, s forest.Store, storeImplName stri
	}
}

func TestCacheStoreAdd(t *testing.T) {
func TestCacheStore(t *testing.T) {
	s1 := forest.NewMemoryStore()
	s2 := forest.NewMemoryStore()
	c := forest.NewCacheStore(s1, s2)
	c, err := forest.NewCacheStore(s1, s2)
	if err != nil {
		t.Errorf("Unexpected error constructing CacheStore: %v", err)
	}
	testStandardStoreInterface(t, c, "CacheStore")
}

func TestCacheStoreDownPropagation(t *testing.T) {
	s1 := forest.NewMemoryStore()
	id, _, com, rep := MakeReplyOrSkip(t)
	nodes := []forest.Node{id, com, rep}
	subrange := nodes[:len(nodes)-1]
	for _, node := range subrange {
		if err := s1.Add(node); err != nil {
			t.Skipf("Failed adding %v to %v", node, s1)
		}
	}
	s2 := forest.NewMemoryStore()
	if _, err := forest.NewCacheStore(s1, s2); err != nil {
		t.Errorf("Unexpected error when constructing CacheStore: %v", err)
	}

	for _, node := range subrange {
		if n2, has, err := s2.Get(node.ID()); err != nil {
			t.Errorf("Unexpected error getting node from cache base layer: %s", err)
		} else if !has {
			t.Errorf("Expected cache base layer to contain %v", node.ID())
		} else if !n2.Equals(node) {
			t.Errorf("Expected cache base layer to contain the same value for ID %v", node.ID())
		}
	}
}