#89 Generalise the three Storage interfaces and structs into one generic storage manager.
This commit is contained in:
@ -1,42 +0,0 @@
|
||||
package execution
|
||||
|
||||
import (
|
||||
"github.com/openHPI/poseidon/pkg/dto"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// localStorage stores execution objects in the local application memory.
|
||||
// ToDo: Create implementation that use some persistent storage like a database.
|
||||
type localStorage struct {
|
||||
sync.RWMutex
|
||||
executions map[ID]*dto.ExecutionRequest
|
||||
}
|
||||
|
||||
// NewLocalStorage responds with an Storer implementation.
|
||||
// This implementation stores the data thread-safe in the local application memory.
|
||||
func NewLocalStorage() *localStorage {
|
||||
return &localStorage{
|
||||
executions: make(map[ID]*dto.ExecutionRequest),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *localStorage) Add(id ID, executionRequest *dto.ExecutionRequest) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
s.executions[id] = executionRequest
|
||||
}
|
||||
|
||||
func (s *localStorage) Exists(id ID) bool {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
_, ok := s.executions[id]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *localStorage) Pop(id ID) (*dto.ExecutionRequest, bool) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
request, ok := s.executions[id]
|
||||
delete(s.executions, id)
|
||||
return request, ok
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
package execution
|
||||
|
||||
import (
|
||||
"github.com/openHPI/poseidon/pkg/dto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
testExecutionRequest = dto.ExecutionRequest{
|
||||
Command: "echo 'Hello Poseidon'",
|
||||
TimeLimit: 42,
|
||||
Environment: map[string]string{
|
||||
"PATH": "/bin",
|
||||
},
|
||||
}
|
||||
anotherTestExecutionRequest = dto.ExecutionRequest{
|
||||
Command: "echo 'Bye Poseidon'",
|
||||
TimeLimit: 1337,
|
||||
Environment: nil,
|
||||
}
|
||||
testID = ID("test")
|
||||
)
|
||||
|
||||
func TestNewLocalExecutionStorage(t *testing.T) {
|
||||
storage := NewLocalStorage()
|
||||
assert.Zero(t, len(storage.executions))
|
||||
}
|
||||
|
||||
func TestLocalStorage(t *testing.T) {
|
||||
storage := NewLocalStorage()
|
||||
|
||||
t.Run("cannot pop when id does not exist", func(t *testing.T) {
|
||||
request, ok := storage.Pop(testID)
|
||||
assert.False(t, ok)
|
||||
assert.Nil(t, request)
|
||||
})
|
||||
|
||||
t.Run("adds execution request", func(t *testing.T) {
|
||||
storage.Add(testID, &testExecutionRequest)
|
||||
|
||||
request, ok := storage.executions[testID]
|
||||
assert.Equal(t, len(storage.executions), 1)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, testExecutionRequest, *request)
|
||||
})
|
||||
|
||||
t.Run("overwrites execution request", func(t *testing.T) {
|
||||
storage.Add(testID, &anotherTestExecutionRequest)
|
||||
|
||||
request, ok := storage.executions[testID]
|
||||
assert.Equal(t, len(storage.executions), 1)
|
||||
assert.True(t, ok)
|
||||
require.NotNil(t, request)
|
||||
assert.Equal(t, anotherTestExecutionRequest, *request)
|
||||
})
|
||||
|
||||
t.Run("removes execution request", func(t *testing.T) {
|
||||
request, ok := storage.Pop(testID)
|
||||
|
||||
assert.True(t, ok)
|
||||
require.NotNil(t, request)
|
||||
assert.Equal(t, anotherTestExecutionRequest, *request)
|
||||
|
||||
request, ok = storage.executions[testID]
|
||||
assert.Nil(t, request)
|
||||
assert.False(t, ok)
|
||||
})
|
||||
}
|
107
pkg/storage/storage.go
Normal file
107
pkg/storage/storage.go
Normal file
@ -0,0 +1,107 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Storage is an interface for storing objects.
|
||||
type Storage[T any] interface {
|
||||
// List returns all objects from the storage.
|
||||
List() []T
|
||||
|
||||
// Add adds an object to the storage.
|
||||
// It overwrites the old object if one with the same id was already stored.
|
||||
Add(id string, o T)
|
||||
|
||||
// Get returns an object from the storage.
|
||||
// Iff the object does not exist in the storage, ok will be false.
|
||||
Get(id string) (o T, ok bool)
|
||||
|
||||
// Delete deletes the object with the passed id from the storage.
|
||||
// It does nothing if no object with the id is present in the store.
|
||||
Delete(id string)
|
||||
|
||||
// Pop deletes the object with the given id from the storage and returns it.
|
||||
// Iff no such execution exists, ok is false and true otherwise.
|
||||
Pop(id string) (o T, ok bool)
|
||||
|
||||
// Purge removes all objects from the storage.
|
||||
Purge()
|
||||
|
||||
// Length returns the number of currently stored objects in the storage.
|
||||
Length() uint
|
||||
|
||||
// Sample returns and removes an arbitrary object from the storage.
|
||||
// ok is true iff an object was returned.
|
||||
Sample() (o T, ok bool)
|
||||
}
|
||||
|
||||
// localStorage stores objects in the local application memory.
|
||||
type localStorage[T any] struct {
|
||||
sync.RWMutex
|
||||
objects map[string]T
|
||||
}
|
||||
|
||||
// NewLocalStorage responds with a Storage implementation.
|
||||
// This implementation stores the data thread-safe in the local application memory.
|
||||
func NewLocalStorage[T any]() *localStorage[T] {
|
||||
return &localStorage[T]{
|
||||
objects: make(map[string]T),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *localStorage[T]) List() (o []T) {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
for _, value := range s.objects {
|
||||
o = append(o, value)
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func (s *localStorage[T]) Add(id string, o T) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
s.objects[id] = o
|
||||
}
|
||||
|
||||
func (s *localStorage[T]) Get(id string) (o T, ok bool) {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
o, ok = s.objects[id]
|
||||
return
|
||||
}
|
||||
|
||||
func (s *localStorage[T]) Delete(id string) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
delete(s.objects, id)
|
||||
}
|
||||
|
||||
func (s *localStorage[T]) Pop(id string) (T, bool) {
|
||||
o, ok := s.Get(id)
|
||||
s.Delete(id)
|
||||
return o, ok
|
||||
}
|
||||
|
||||
func (s *localStorage[T]) Purge() {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
s.objects = make(map[string]T)
|
||||
}
|
||||
|
||||
func (s *localStorage[T]) Sample() (o T, ok bool) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
for key, object := range s.objects {
|
||||
delete(s.objects, key)
|
||||
return object, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *localStorage[T]) Length() uint {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
return uint(len(s.objects))
|
||||
}
|
100
pkg/storage/storage_test.go
Normal file
100
pkg/storage/storage_test.go
Normal file
@ -0,0 +1,100 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/suite"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRunnerPoolTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(ObjectPoolTestSuite))
|
||||
}
|
||||
|
||||
type ObjectPoolTestSuite struct {
|
||||
suite.Suite
|
||||
objectStorage *localStorage[any]
|
||||
object int
|
||||
}
|
||||
|
||||
func (s *ObjectPoolTestSuite) SetupTest() {
|
||||
s.objectStorage = NewLocalStorage[any]()
|
||||
s.object = 42
|
||||
}
|
||||
|
||||
func (s *ObjectPoolTestSuite) TestAddedObjectCanBeRetrieved() {
|
||||
s.objectStorage.Add("my_id", s.object)
|
||||
retrievedRunner, ok := s.objectStorage.Get("my_id")
|
||||
s.True(ok, "A saved object should be retrievable")
|
||||
s.Equal(s.object, retrievedRunner)
|
||||
}
|
||||
|
||||
func (s *ObjectPoolTestSuite) TestObjectWithSameIdOverwritesOldOne() {
|
||||
otherObject := 21
|
||||
// assure object is actually different
|
||||
s.NotEqual(s.object, otherObject)
|
||||
|
||||
s.objectStorage.Add("my_id", s.object)
|
||||
s.objectStorage.Add("my_id", otherObject)
|
||||
retrievedObject, _ := s.objectStorage.Get("my_id")
|
||||
s.NotEqual(s.object, retrievedObject)
|
||||
s.Equal(otherObject, retrievedObject)
|
||||
}
|
||||
|
||||
func (s *ObjectPoolTestSuite) TestDeletedObjectsAreNotAccessible() {
|
||||
s.objectStorage.Add("my_id", s.object)
|
||||
s.objectStorage.Delete("my_id")
|
||||
retrievedObject, ok := s.objectStorage.Get("my_id")
|
||||
s.Nil(retrievedObject)
|
||||
s.False(ok, "A deleted object should not be accessible")
|
||||
}
|
||||
|
||||
func (s *ObjectPoolTestSuite) TestSampleReturnsObjectWhenOneIsAvailable() {
|
||||
s.objectStorage.Add("my_id", s.object)
|
||||
sampledObject, ok := s.objectStorage.Sample()
|
||||
s.NotNil(sampledObject)
|
||||
s.True(ok)
|
||||
}
|
||||
|
||||
func (s *ObjectPoolTestSuite) TestSampleReturnsFalseWhenNoneIsAvailable() {
|
||||
sampledObject, ok := s.objectStorage.Sample()
|
||||
s.Nil(sampledObject)
|
||||
s.False(ok)
|
||||
}
|
||||
|
||||
func (s *ObjectPoolTestSuite) TestSampleRemovesObjectFromPool() {
|
||||
s.objectStorage.Add("my_id", s.object)
|
||||
_, _ = s.objectStorage.Sample()
|
||||
_, ok := s.objectStorage.Get("my_id")
|
||||
s.False(ok)
|
||||
}
|
||||
|
||||
func (s *ObjectPoolTestSuite) TestLenOfEmptyPoolIsZero() {
|
||||
s.Equal(uint(0), s.objectStorage.Length())
|
||||
}
|
||||
|
||||
func (s *ObjectPoolTestSuite) TestLenChangesOnStoreContentChange() {
|
||||
s.Run("len increases when object is added", func() {
|
||||
s.objectStorage.Add("my_id_1", s.object)
|
||||
s.Equal(uint(1), s.objectStorage.Length())
|
||||
})
|
||||
|
||||
s.Run("len does not increase when object with same id is added", func() {
|
||||
s.objectStorage.Add("my_id_1", s.object)
|
||||
s.Equal(uint(1), s.objectStorage.Length())
|
||||
})
|
||||
|
||||
s.Run("len increases again when different object is added", func() {
|
||||
anotherObject := 21
|
||||
s.objectStorage.Add("my_id_2", anotherObject)
|
||||
s.Equal(uint(2), s.objectStorage.Length())
|
||||
})
|
||||
|
||||
s.Run("len decreases when object is deleted", func() {
|
||||
s.objectStorage.Delete("my_id_1")
|
||||
s.Equal(uint(1), s.objectStorage.Length())
|
||||
})
|
||||
|
||||
s.Run("len decreases when object is sampled", func() {
|
||||
_, _ = s.objectStorage.Sample()
|
||||
s.Equal(uint(0), s.objectStorage.Length())
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user