mirror of
https://github.com/jimeh/ozu.io.git
synced 2026-02-19 08:06:39 +00:00
Add very basic in-memory storage adapter
This commit is contained in:
69
storage/inmemory/store.go
Normal file
69
storage/inmemory/store.go
Normal file
@@ -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
|
||||
}
|
||||
164
storage/inmemory/store_test.go
Normal file
164
storage/inmemory/store_test.go
Normal file
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user