-
Notifications
You must be signed in to change notification settings - Fork 164
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
355 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
|
||
"github.com/google/uuid" | ||
) | ||
|
||
// MapStore stores puppies. | ||
type MapStore map[uint32]*Puppy | ||
|
||
// length used for testing. | ||
func (s MapStore) length() int { | ||
return len(s) | ||
} | ||
|
||
// ErrNotConstructed returned if the interface was called without | ||
// first constructing the underlaying structure. | ||
var ErrNotConstructed = errors.New("store not created") | ||
|
||
// CreatePuppy add a puppy to storage | ||
// but will modify the member ID. | ||
func (s MapStore) CreatePuppy(p *Puppy) (uint32, error) { | ||
if s == nil { | ||
return 0, ErrNotConstructed | ||
} | ||
p.ID = uuid.New().ID() | ||
sp := *p | ||
s[p.ID] = &sp | ||
return p.ID, nil | ||
} | ||
|
||
// ReadPuppy retrieve your puppy. | ||
func (s MapStore) ReadPuppy(id uint32) (*Puppy, error) { | ||
if s == nil { | ||
return nil, ErrNotConstructed | ||
} | ||
val, found := s[id] | ||
if !found { | ||
return nil, fmt.Errorf("no puppy with ID %v found", id) | ||
} | ||
retVal := *val | ||
return &retVal, nil | ||
} | ||
|
||
// UpdatePuppy update your puppy store. | ||
func (s MapStore) UpdatePuppy(id uint32, puppy *Puppy) error { | ||
if s == nil { | ||
return ErrNotConstructed | ||
} | ||
if _, ok := s[id]; !ok { | ||
return fmt.Errorf("no puppy with ID %v found", id) | ||
} | ||
puppy.ID = id | ||
sp := *puppy | ||
s[id] = &sp | ||
return nil | ||
} | ||
|
||
// DeletePuppy remove the puppy from store. | ||
func (s MapStore) DeletePuppy(id uint32) error { | ||
if s == nil { | ||
return ErrNotConstructed | ||
} | ||
delete(s, id) | ||
return nil | ||
} | ||
|
||
// NewMapStore constructor creates the map. | ||
func NewMapStore() MapStore { | ||
return MapStore{} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package main | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
// TestMapStoreWithoutContructor may not be required as it may not be | ||
// possible to create an instance of that type without using the constructor | ||
func TestMapStoreWithoutContructor(t *testing.T) { | ||
var puppyStore MapStore | ||
pup := Puppy{1, "kelpie", "brown", "indispensable"} | ||
|
||
_, err := puppyStore.CreatePuppy(&pup) | ||
assert.Equal(t, ErrNotConstructed, err) | ||
err = puppyStore.UpdatePuppy(1, &pup) | ||
assert.Equal(t, ErrNotConstructed, err) | ||
_, err = puppyStore.ReadPuppy(1) | ||
assert.Equal(t, ErrNotConstructed, err) | ||
err = puppyStore.DeletePuppy(1) | ||
assert.Equal(t, ErrNotConstructed, err) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"os" | ||
) | ||
|
||
var out io.Writer = os.Stdout | ||
|
||
func main() { | ||
var puppyStore Storer = NewMapStore() | ||
pup := Puppy{Breed: "kelpie", Colour: "brown", Value: "indispensable"} | ||
id, _ := puppyStore.CreatePuppy(&pup) | ||
if pup, err := puppyStore.ReadPuppy(id); err == nil { | ||
fmt.Fprintf(out, "retrieved: %v %v %v\n", pup.Breed, pup.Colour, pup.Value) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"testing" | ||
) | ||
|
||
func TestMain(t *testing.T) { | ||
expected := "retrieved: kelpie brown indispensable\n" | ||
var buf bytes.Buffer | ||
out = &buf | ||
main() | ||
actual := buf.String() | ||
if actual != expected { | ||
t.Errorf("expected %v, actual %v", expected, actual) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package main | ||
|
||
// Storer defines standard CRUD operations for Puppy | ||
type Storer interface { | ||
CreatePuppy(p *Puppy) (uint32, error) | ||
ReadPuppy(ID uint32) (*Puppy, error) | ||
UpdatePuppy(ID uint32, Puppy *Puppy) error | ||
DeletePuppy(ID uint32) error | ||
} | ||
|
||
// Puppy stores puppy details. | ||
type Puppy struct { | ||
ID uint32 | ||
Breed string | ||
Colour string | ||
Value string | ||
} | ||
|
||
// mapTest used during testing to verify underlaying map changes | ||
type mapTest interface { | ||
length() int | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/suite" | ||
) | ||
|
||
type storesSuite struct { | ||
suite.Suite | ||
store Storer | ||
mapper mapTest | ||
} | ||
|
||
const brown = "brown" | ||
const black = "black" | ||
const grey = "grey" | ||
|
||
func TestSuite(t *testing.T) { | ||
suite.Run(t, &storesSuite{store: NewMapStore()}) | ||
suite.Run(t, &storesSuite{store: &SyncMapStore{}}) | ||
} | ||
|
||
//SetupTest creates the correct empty map for each test | ||
func (s *storesSuite) SetupTest() { | ||
switch s.store.(type) { | ||
case MapStore: | ||
s.store = NewMapStore() | ||
case *SyncMapStore: | ||
s.store = &SyncMapStore{} | ||
default: | ||
s.Fail("Unknown Storer implementation") | ||
} | ||
s.mapper = s.store.(mapTest) | ||
} | ||
|
||
func (s *storesSuite) TestReadSuccess() { | ||
pup := create(s) | ||
// now check by reading the value back and compare | ||
pup2, err2 := s.store.ReadPuppy(pup.ID) | ||
s.Require().NoError(err2) | ||
s.Equal(brown, pup2.Colour) | ||
// modify the retured value to make sure the | ||
// value in the store does not change | ||
pup2.Colour = grey | ||
pup3, err2 := s.store.ReadPuppy(pup.ID) | ||
s.Require().NoError(err2) | ||
s.Equal(brown, pup3.Colour) | ||
s.NotEqual(pup2, pup3) | ||
} | ||
|
||
// TestCreateSuccess add to the store and verify | ||
// by reading that it is in the store | ||
func (s *storesSuite) TestCreateSuccess() { | ||
pup := create(s) | ||
// Now modify the original and make sure the | ||
// value in the store will not change | ||
pup.Colour = black | ||
// now check by reading the value back and compare | ||
pup2, err2 := s.store.ReadPuppy(pup.ID) | ||
s.Require().NoError(err2) | ||
s.Equal("kelpie", pup2.Breed) | ||
s.Equal(brown, pup2.Colour) | ||
s.Equal("indispensable", pup2.Value) | ||
s.True(pup2.Colour == brown) | ||
s.True(pup.Colour == black) | ||
s.NotEqual(pup, pup2) | ||
} | ||
|
||
func create(s *storesSuite) *Puppy { | ||
pup := Puppy{Breed: "kelpie", Colour: brown, Value: "indispensable"} | ||
id, err := s.store.CreatePuppy(&pup) | ||
s.Require().NoError(err) | ||
s.Require().NotEqual(pup.ID, uint32(1)) | ||
s.Require().Equal(id, pup.ID, "Pup id must be set to actual id") | ||
return &pup | ||
} | ||
|
||
func (s *storesSuite) TestUpdateSuccess() { | ||
pup := create(s) | ||
pup2 := Puppy{Breed: "kelpie", Colour: black, Value: "indispensable"} | ||
err := s.store.UpdatePuppy(pup.ID, &pup2) | ||
s.Require().NoError(err) | ||
pup2.Colour = brown | ||
// now check by reading the updated value back and compare | ||
pup3, err2 := s.store.ReadPuppy(pup.ID) | ||
if s.Nil(err2, "Reading back updated value should work") { | ||
s.True(pup2.Colour == brown) | ||
s.True(pup3.Colour == black) | ||
s.NotEqual(pup2, *pup3) | ||
} | ||
} | ||
|
||
//TestUpdateFailure checks the error returned when updating with an invalid id | ||
func (s *storesSuite) TestUpdateFailure() { | ||
create(s) | ||
pup2 := Puppy{Breed: "kelpie", Colour: black, Value: "indispensable"} | ||
err := s.store.UpdatePuppy(1, &pup2) | ||
success := s.NotNil(err, "Update on id 1 should have failed") | ||
if !success { | ||
return | ||
} | ||
st := fmt.Sprintf("no puppy with ID %v found", 1) | ||
s.Equal(st, err.Error()) | ||
} | ||
|
||
func (s *storesSuite) TestDeleteSuccess() { | ||
pup := create(s) | ||
err := s.store.DeletePuppy(pup.ID) | ||
s.Require().NoError(err) | ||
_, err = s.store.ReadPuppy(pup.ID) | ||
s.NotNil(err) | ||
} | ||
|
||
func (s *storesSuite) TestReadFailure() { | ||
pup2, err := s.store.ReadPuppy(1) | ||
s.Require().Nil(pup2) | ||
s.Require().Error(err) | ||
st := fmt.Sprintf("no puppy with ID %v found", 1) | ||
s.Equal(st, err.Error()) | ||
} | ||
|
||
func (s *storesSuite) TestMapChanges() { | ||
s.Equal(0, s.mapper.length()) | ||
pup := Puppy{Breed: "kelpie", Colour: brown, Value: "high"} | ||
id, err := s.store.CreatePuppy(&pup) | ||
s.Require().Nil(err, "Create puppy failed") | ||
s.Equal(1, s.mapper.length()) | ||
pup2 := Puppy{Breed: "kelpie", Colour: black, Value: "low"} | ||
err = s.store.UpdatePuppy(id, &pup2) | ||
s.Require().Nil(err, "Update puppy failed") | ||
s.Equal(1, s.mapper.length()) | ||
err = s.store.DeletePuppy(id) | ||
s.Require().Nil(err, "Delete puppy failed") | ||
s.Equal(0, s.mapper.length()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"sync" | ||
|
||
"github.com/google/uuid" | ||
) | ||
|
||
// SyncMapStore stores puppies threadsafe. | ||
type SyncMapStore struct { | ||
sync.Map | ||
} | ||
|
||
// length is not concorrency safe. As the go doc says: | ||
// Range does not necessarily correspond to any consistent snapshot of the | ||
// Map's contents: no key will be visited more than once, but if the value | ||
// for any key is stored or deleted concurrently, Range may reflect any | ||
// mapping for that key from any point during the Range call. | ||
// | ||
func (s *SyncMapStore) length() int { | ||
var length int | ||
s.Range(func(key interface{}, value interface{}) bool { | ||
length++ | ||
return true | ||
}) | ||
return length | ||
} | ||
|
||
// CreatePuppy threadsafe adding a puppy to storage | ||
// but will modify the member ID. | ||
func (s *SyncMapStore) CreatePuppy(p *Puppy) (uint32, error) { | ||
p.ID = uuid.New().ID() | ||
sp := *p | ||
s.Store(p.ID, &sp) | ||
return p.ID, nil | ||
} | ||
|
||
// ReadPuppy threadsafe retrieval of your puppy. | ||
func (s *SyncMapStore) ReadPuppy(id uint32) (*Puppy, error) { | ||
val, found := s.Load(id) | ||
if !found { | ||
return nil, fmt.Errorf("no puppy with ID %v found", id) | ||
} | ||
retPup := *val.(*Puppy) | ||
return &retPup, nil | ||
} | ||
|
||
// UpdatePuppy threadsafe update your puppy store. | ||
func (s *SyncMapStore) UpdatePuppy(id uint32, puppy *Puppy) error { | ||
_, found := s.Load(id) | ||
if !found { | ||
return fmt.Errorf("no puppy with ID %v found", id) | ||
} | ||
puppy.ID = id | ||
sp := *puppy | ||
s.Store(id, &sp) | ||
return nil | ||
} | ||
|
||
// DeletePuppy threadsafe removal of the puppy from store. | ||
func (s *SyncMapStore) DeletePuppy(id uint32) error { | ||
s.Delete(id) | ||
return nil | ||
} |