From e3ec2ff16c41bd928013a390f91cc1d9f4fd7eb4 Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Mon, 11 Jul 2016 14:11:38 +0100 Subject: [PATCH] Add very basic in-memory storage adapter --- storage/inmemory/store.go | 69 ++++++++++++++ storage/inmemory/store_test.go | 164 +++++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 storage/inmemory/store.go create mode 100644 storage/inmemory/store_test.go diff --git a/storage/inmemory/store.go b/storage/inmemory/store.go new file mode 100644 index 0000000..a350456 --- /dev/null +++ b/storage/inmemory/store.go @@ -0,0 +1,69 @@ +package inmemory + +import ( + "errors" + "sync" +) + +// ErrNotFound is returned when Get() tries to fetch a non-existent key. +var ErrNotFound = errors.New("not found") + +// New creates a new Store using given path to persist data. +func New() (*Store, error) { + store := &Store{ + Data: map[string][]byte{}, + Sequence: 0, + } + return store, nil +} + +// Store allows storing data into a in-memory map. +type Store struct { + sync.RWMutex + Data map[string][]byte + Sequence int + Closed bool +} + +// Close database. +func (s *Store) Close() error { + s.Data = make(map[string][]byte) + s.Sequence = 0 + return nil +} + +// Get a given key's value. +func (s *Store) Get(key []byte) ([]byte, error) { + s.RLock() + value := s.Data[string(key)] + s.RUnlock() + if value == nil { + return nil, ErrNotFound + } + return value, nil +} + +// Set a given key's to the specified value. +func (s *Store) Set(key []byte, value []byte) error { + s.Lock() + s.Data[string(key)] = value + s.Unlock() + return nil +} + +// Delete a given key. +func (s *Store) Delete(key []byte) error { + s.Lock() + delete(s.Data, string(key)) + s.Unlock() + return nil +} + +// NextSequence returns a auto-incrementing int. +func (s *Store) NextSequence() (int, error) { + s.Lock() + s.Sequence++ + seq := s.Sequence + s.Unlock() + return seq, nil +} diff --git a/storage/inmemory/store_test.go b/storage/inmemory/store_test.go new file mode 100644 index 0000000..a20b17b --- /dev/null +++ b/storage/inmemory/store_test.go @@ -0,0 +1,164 @@ +package inmemory + +import ( + "testing" + + "github.com/jimeh/ozu.io/storage" + "github.com/stretchr/testify/suite" +) + +// Setup Suite + +var examples = []struct { + key []byte + value []byte +}{ + {key: []byte("hello"), value: []byte("world")}, + {key: []byte("foo"), value: []byte("bar")}, + {key: []byte("wtf"), value: []byte("dude")}, +} + +type StoreSuite struct { + suite.Suite + store *Store +} + +func (s *StoreSuite) SetupTest() { + store, err := New() + s.Require().NoError(err) + s.store = store +} + +func (s *StoreSuite) TearDownTest() { + _ = s.store.Close() +} + +func (s *StoreSuite) Seed() { + for _, e := range examples { + s.store.Data[string(e.key)] = e.value + } +} + +// Tests + +func (s *StoreSuite) TestStoreInterface() { + s.Implements(new(storage.Store), new(Store)) +} + +func (s *StoreSuite) TestSet() { + for _, e := range examples { + err := s.store.Set(e.key, e.value) + s.NoError(err) + } + + for _, e := range examples { + result, _ := s.store.Data[string(e.key)] + s.Equal(e.value, result) + } +} + +func (s *StoreSuite) TestGetExisting() { + s.Seed() + + for _, e := range examples { + result, err := s.store.Get(e.key) + s.NoError(err) + s.Equal(e.value, result) + } +} + +func (s *StoreSuite) TestGetNonExistant() { + result, err := s.store.Get([]byte("does-not-exist")) + s.Nil(result) + s.EqualError(err, "not found") +} + +func (s *StoreSuite) TestDeleteExisting() { + s.Seed() + + for _, e := range examples { + value := s.store.Data[string(e.key)] + s.Require().Equal(e.value, value) + + value, err := s.store.Get(e.key) + s.Require().NoError(err) + s.Require().Equal(value, e.value) + + err = s.store.Delete(e.key) + s.NoError(err) + + value, err = s.store.Get(e.key) + s.Nil(value) + s.EqualError(err, "not found") + + _, has := s.store.Data[string(e.key)] + s.Equal(false, has) + } +} + +func (s *StoreSuite) TestDeleteNonExistant() { + err := s.store.Delete([]byte("does-not-exist")) + s.NoError(err) +} + +func (s *StoreSuite) TestNextSequenceExisting() { + s.store.Sequence = 5 + + result, err := s.store.NextSequence() + s.NoError(err) + s.Equal(6, result) +} + +func (s *StoreSuite) TestNextSequenceNonExistant() { + for i := 1; i < 10; i++ { + result, err := s.store.NextSequence() + + s.NoError(err) + s.Equal(i, result) + } +} + +// Run Suite + +func TestStoreSuite(t *testing.T) { + suite.Run(t, new(StoreSuite)) +} + +// Benchmarks + +func BenchmarkGet(b *testing.B) { + store, _ := New() + + key := []byte("hello") + value := []byte("world") + _ = store.Set(key, value) + + for n := 0; n < b.N; n++ { + _, _ = store.Get(key) + } + + _ = store.Close() +} + +func BenchmarkSet(b *testing.B) { + store, _ := New() + + key := []byte("hello") + value := []byte("world") + + for n := 0; n < b.N; n++ { + _ = store.Set(append(key, string(n)...), value) + } + + _ = store.Close() +} + +func BenchmarkNextSequence(b *testing.B) { + store, _ := New() + + for n := 0; n < b.N; n++ { + _, _ = store.NextSequence() + } + + _ = store.Close() +}