From 8674c3ad2ecab6a0cbcc1ae7e9732a123703ec51 Mon Sep 17 00:00:00 2001 From: "Bucknell, Andrew" Date: Tue, 2 Jul 2019 17:46:19 +1000 Subject: [PATCH 1/3] Implement CreatePuppy in storer Create puupy struct Implement Init to initialize a puppy and assign a random id Implement String method to print a puppy struct At this point there is no check for collision ids Implement a map backed storer only at this time Implement ReadStore Error checking hasnt been implemented. At this stage nil will be returned if the item cant be found. This will be addressed in a later lab Implement DeletePuppy Fix boolean logic in ReadPuppy Implement UpdatePuppy Implement clone method on puppy Resolve problem with usage of pointers and references Separate MapStore into its own file Create a basic main_test Integrate linter feedback Rename ID to id where used as parameter Change mapstore test to use a test suite using the Storer interface Begin implementing a syncstore version of the Storer interface Implement all methids for syncstore Implement all cases for store_tests Ensure 100% test coverqge Fix linter errors Capitals and newlines in error messages Not assign table tests when ranging across them Misalignment of structs Verified lint and code cov passed Separate types into their own file Resolve linting issue in types.go --- .gitignore | 1 + 06_puppy/undrewb/MapStore.go | 48 +++++++++ 06_puppy/undrewb/SyncStore.go | 47 +++++++++ 06_puppy/undrewb/main.go | 16 +++ 06_puppy/undrewb/main_test.go | 21 ++++ 06_puppy/undrewb/store_test.go | 177 +++++++++++++++++++++++++++++++++ 06_puppy/undrewb/types.go | 24 +++++ 7 files changed, 334 insertions(+) create mode 100644 06_puppy/undrewb/MapStore.go create mode 100644 06_puppy/undrewb/SyncStore.go create mode 100644 06_puppy/undrewb/main.go create mode 100644 06_puppy/undrewb/main_test.go create mode 100644 06_puppy/undrewb/store_test.go create mode 100644 06_puppy/undrewb/types.go diff --git a/.gitignore b/.gitignore index 6bb54e93e..f11e119f6 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out coverage.txt +06_puppy/undrewb/.vscode/launch.json diff --git a/06_puppy/undrewb/MapStore.go b/06_puppy/undrewb/MapStore.go new file mode 100644 index 000000000..d3e997a9c --- /dev/null +++ b/06_puppy/undrewb/MapStore.go @@ -0,0 +1,48 @@ +package main + +import ( + "errors" + "fmt" +) + +type MapStore map[uint32]*Puppy + +func (msp *MapStore) CreatePuppy(p *Puppy) error { + if _, exist := (*msp)[p.ID]; exist { + return fmt.Errorf("puppy %d already exists", p.ID) + } + (*msp)[p.ID] = p.Clone() + return nil +} + +func (msp *MapStore) ReadPuppy(id uint32) (*Puppy, error) { + p, err := (*msp)[id] + if !err { + return nil, errors.New("no such puppy") + } + return p, nil +} + +func (msp *MapStore) DeletePuppy(id uint32) (bool, error) { + p, exist := (*msp)[id] + if !exist { + return false, errors.New("no such puppy") + } + delete(*msp, p.ID) + return true, nil +} + +func (msp *MapStore) UpdatePuppy(id uint32, puppy *Puppy) error { + if puppy == nil { + return fmt.Errorf("cant update to a nil puppy") + } + p, exist := (*msp)[id] + if !exist { + return errors.New("no such puppy") + } + if p.ID != puppy.ID { + return errors.New("bad puppy") + } + (*msp)[id] = puppy.Clone() + return nil +} diff --git a/06_puppy/undrewb/SyncStore.go b/06_puppy/undrewb/SyncStore.go new file mode 100644 index 000000000..265e36370 --- /dev/null +++ b/06_puppy/undrewb/SyncStore.go @@ -0,0 +1,47 @@ +package main + +import ( + "fmt" + "sync" +) + +type SyncStore struct { + sync.Map +} + +func (store *SyncStore) CreatePuppy(p *Puppy) error { + if _, ok := store.Load(p.ID); !ok { + store.Store(p.ID, p) + return nil + } + return fmt.Errorf("store already has a puppy with id : %d", p.ID) +} + +func (store *SyncStore) ReadPuppy(id uint32) (*Puppy, error) { + if p, ok := store.Load(id); ok { + return p.(*Puppy), nil + } + return nil, fmt.Errorf("could not load puppy %d", id) +} + +func (store *SyncStore) UpdatePuppy(id uint32, puppy *Puppy) error { + if puppy == nil { + return fmt.Errorf("cant update to a nil puppy") + } + if p, ok := store.Load(id); ok { + if p.(*Puppy).ID == puppy.ID { + store.Store(id, puppy.Clone()) + return nil + } + return fmt.Errorf("bad puppy : %d != %d", p.(*Puppy).ID, id) + } + return fmt.Errorf("could not find puppy id = %d", id) +} + +func (store *SyncStore) DeletePuppy(id uint32) (bool, error) { + if _, ok := store.Load(id); ok { + store.Delete(id) + return true, nil + } + return false, fmt.Errorf("could not find puppy id = %d", id) +} diff --git a/06_puppy/undrewb/main.go b/06_puppy/undrewb/main.go new file mode 100644 index 000000000..dd5bc8fdd --- /dev/null +++ b/06_puppy/undrewb/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "fmt" + "io" + "os" +) + +var out io.Writer = os.Stdout + +func main() { + + store := new(MapStore) + fmt.Fprintln(out, store) + +} diff --git a/06_puppy/undrewb/main_test.go b/06_puppy/undrewb/main_test.go new file mode 100644 index 000000000..e99f63c72 --- /dev/null +++ b/06_puppy/undrewb/main_test.go @@ -0,0 +1,21 @@ +package main + +import ( + "bytes" + "strconv" + "testing" +) + +func TestMainOutput(t *testing.T) { + var buf bytes.Buffer + out = &buf + + main() + + expected := strconv.Quote("&map[]\n") + actual := strconv.Quote(buf.String()) + + if expected != actual { + t.Errorf("unexpected result in main() : expected = %s, actual = %s\n", expected, actual) + } +} diff --git a/06_puppy/undrewb/store_test.go b/06_puppy/undrewb/store_test.go new file mode 100644 index 000000000..9e6868762 --- /dev/null +++ b/06_puppy/undrewb/store_test.go @@ -0,0 +1,177 @@ +package main + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/suite" +) + +type storerSuite struct { + suite.Suite + store Storer + storerType func() Storer +} + +func TestStorers(t *testing.T) { + suite.Run(t, &storerSuite{ + storerType: func() Storer { return &MapStore{} }, + }) + suite.Run(t, &storerSuite{ + storerType: func() Storer { return &SyncStore{} }, + }) +} + +func (s *storerSuite) SetupTest() { + s.store = s.storerType() + + corgi := &Puppy{1000, "corgi", "orange and white", "$$$$$$$"} + _ = s.store.CreatePuppy(corgi) + spaniel := &Puppy{2000, "spaniel", "brown", "$$$$$$"} + _ = s.store.CreatePuppy(spaniel) + cujo := &Puppy{2500, "cujo", "tan", "$"} + _ = s.store.CreatePuppy(cujo) + terrier := &Puppy{3000, "terrier", "black", "$$$"} + _ = s.store.CreatePuppy(terrier) + bulldog := &Puppy{4000, "bulldog", "white", "$$"} + _ = s.store.CreatePuppy(bulldog) +} + +func (s *storerSuite) TestMapStore_CreatePuppy() { + + corgi := &Puppy{10000, "new corgi", "even brighter orange and white", "$$$$$$$"} + + tests := []struct { + name string + puppy *Puppy + wantErr bool + }{ + {name: "New corgi", + puppy: corgi, + wantErr: false}, + {name: "Existing corgi", + puppy: corgi, + wantErr: true}, + } + for _, tt := range tests { + tt := tt + s.T().Run(tt.name, func(t *testing.T) { + if err := s.store.CreatePuppy(tt.puppy); (err != nil) != tt.wantErr { + t.Errorf("CreatePuppy() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func (s *storerSuite) TestMapStore_ReadPuppy() { + + corgi := &Puppy{1000, "corgi", "orange and white", "$$$$$$$"} + _ = s.store.CreatePuppy(corgi) + + tests := []struct { + name string + id uint32 + wantErr bool + want *Puppy + }{ + {name: "lookup existing puppy", + id: 1000, + want: corgi, + wantErr: false, + }, + {name: "lookup non-existing puppy", + id: 1001, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + tt := tt + s.T().Run(tt.name, func(t *testing.T) { + got, err := s.store.ReadPuppy(tt.id) + if (err != nil) != tt.wantErr { + t.Errorf("ReadPuppy() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ReadPuppy() = %v, want %v", got, tt.want) + } + }) + } +} + +func (s *storerSuite) TestMapStore_DeletePuppy() { + tests := []struct { + name string + id uint32 + want bool + wantErr bool + }{ + {name: "delete existing corgi", + id: 1000, + want: true, + wantErr: false, + }, + {name: "delete non-existent corgi", + id: 1000, + want: false, + wantErr: true, + }, + } + for _, tt := range tests { + tt := tt + s.T().Run(tt.name, func(t *testing.T) { + got, err := s.store.DeletePuppy(tt.id) + if (err != nil) != tt.wantErr { + t.Errorf("DeletePuppy() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("DeletePuppy() = %v, want %v", got, tt.want) + } + }) + } +} + +func (s *storerSuite) TestMapStore_UpdatePuppy() { + albinoCorgi := &Puppy{1000, "corgi", "white", "$$$$$$$"} + corruptCorgi := &Puppy{1009, "corgi", "white", "$$$$$$$"} + // _ = s.store.CreatePuppy(albinoCorgi) + + tests := []struct { + name string + id uint32 + wantErr bool + puppy *Puppy + }{ + {name: "update existing puppy", + id: 1000, + puppy: albinoCorgi, + wantErr: false, + }, + {name: "update non-existing puppy", + id: 1001, + puppy: albinoCorgi, + wantErr: true, + }, + {name: "update with an empty puppy", + id: 1000, + puppy: nil, + wantErr: true, + }, + {name: "update with a corrupt puppy", + id: 1000, + puppy: corruptCorgi, + wantErr: true, + }, + } + + for _, tt := range tests { + tt := tt + s.T().Run(tt.name, func(t *testing.T) { + if err := s.store.UpdatePuppy(tt.id, tt.puppy); (err != nil) != tt.wantErr { + t.Errorf("UpdatePuppy() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/06_puppy/undrewb/types.go b/06_puppy/undrewb/types.go new file mode 100644 index 000000000..0a03e7ca9 --- /dev/null +++ b/06_puppy/undrewb/types.go @@ -0,0 +1,24 @@ +package main + +type Puppy struct { + ID uint32 `json:"ID"` + Breed string `json:"breed"` + Colour string `json:"colour"` + Value string `json:"value"` +} + +func (p *Puppy) Clone() *Puppy { + puppy := new(Puppy) + puppy.ID = p.ID + puppy.Breed = p.Breed + puppy.Colour = p.Colour + puppy.Value = p.Value + return puppy +} + +type Storer interface { + CreatePuppy(*Puppy) error + ReadPuppy(id uint32) (*Puppy, error) + UpdatePuppy(id uint32, puppy *Puppy) error + DeletePuppy(id uint32) (bool, error) +} From ccd97c797d39a79cff1028613430c80ca2f26123 Mon Sep 17 00:00:00 2001 From: "Bucknell, Andrew" Date: Thu, 22 Aug 2019 23:19:39 +1000 Subject: [PATCH 2/3] Integrate code review feedback Change format of test case data Change parameter ordering of test cases Implement ID assignment in storers --- 06_puppy/undrewb/MapStore.go | 32 ++++++++++++++++++------- 06_puppy/undrewb/SyncStore.go | 20 ++++++++++++---- 06_puppy/undrewb/main_test.go | 2 +- 06_puppy/undrewb/store_test.go | 44 +++++++++++++++++++++------------- 4 files changed, 69 insertions(+), 29 deletions(-) diff --git a/06_puppy/undrewb/MapStore.go b/06_puppy/undrewb/MapStore.go index d3e997a9c..d349f2684 100644 --- a/06_puppy/undrewb/MapStore.go +++ b/06_puppy/undrewb/MapStore.go @@ -5,18 +5,34 @@ import ( "fmt" ) -type MapStore map[uint32]*Puppy +type MapStore struct { + store map[uint32]*Puppy + nextID uint32 +} + +func InitMapStore() *MapStore { + return &MapStore{ + store: map[uint32]*Puppy{}, + nextID: 1, + } +} func (msp *MapStore) CreatePuppy(p *Puppy) error { - if _, exist := (*msp)[p.ID]; exist { + if p.ID == 0 { + p.ID = msp.nextID + msp.nextID++ + } + + if _, exist := msp.store[p.ID]; exist { return fmt.Errorf("puppy %d already exists", p.ID) } - (*msp)[p.ID] = p.Clone() + + msp.store[p.ID] = p.Clone() return nil } func (msp *MapStore) ReadPuppy(id uint32) (*Puppy, error) { - p, err := (*msp)[id] + p, err := msp.store[id] if !err { return nil, errors.New("no such puppy") } @@ -24,11 +40,11 @@ func (msp *MapStore) ReadPuppy(id uint32) (*Puppy, error) { } func (msp *MapStore) DeletePuppy(id uint32) (bool, error) { - p, exist := (*msp)[id] + p, exist := msp.store[id] if !exist { return false, errors.New("no such puppy") } - delete(*msp, p.ID) + delete(msp.store, p.ID) return true, nil } @@ -36,13 +52,13 @@ func (msp *MapStore) UpdatePuppy(id uint32, puppy *Puppy) error { if puppy == nil { return fmt.Errorf("cant update to a nil puppy") } - p, exist := (*msp)[id] + p, exist := msp.store[id] if !exist { return errors.New("no such puppy") } if p.ID != puppy.ID { return errors.New("bad puppy") } - (*msp)[id] = puppy.Clone() + msp.store[id] = puppy.Clone() return nil } diff --git a/06_puppy/undrewb/SyncStore.go b/06_puppy/undrewb/SyncStore.go index 265e36370..610b22290 100644 --- a/06_puppy/undrewb/SyncStore.go +++ b/06_puppy/undrewb/SyncStore.go @@ -7,14 +7,26 @@ import ( type SyncStore struct { sync.Map + sync.RWMutex + nextID uint32 } -func (store *SyncStore) CreatePuppy(p *Puppy) error { - if _, ok := store.Load(p.ID); !ok { - store.Store(p.ID, p) +func InitSyncStore() *SyncStore { + return &SyncStore{ + nextID: 1, + } +} + +func (store *SyncStore) CreatePuppy(puppy *Puppy) error { + if puppy.ID == 0 { + puppy.ID = store.nextID + store.nextID++ + } + if _, ok := store.Load(puppy.ID); !ok { + store.Store(puppy.ID, puppy) return nil } - return fmt.Errorf("store already has a puppy with id : %d", p.ID) + return fmt.Errorf("store already has a puppy with id : %d", puppy.ID) } func (store *SyncStore) ReadPuppy(id uint32) (*Puppy, error) { diff --git a/06_puppy/undrewb/main_test.go b/06_puppy/undrewb/main_test.go index e99f63c72..9c3fdaba1 100644 --- a/06_puppy/undrewb/main_test.go +++ b/06_puppy/undrewb/main_test.go @@ -12,7 +12,7 @@ func TestMainOutput(t *testing.T) { main() - expected := strconv.Quote("&map[]\n") + expected := strconv.Quote("&{map[] 0}\n") actual := strconv.Quote(buf.String()) if expected != actual { diff --git a/06_puppy/undrewb/store_test.go b/06_puppy/undrewb/store_test.go index 9e6868762..123d70764 100644 --- a/06_puppy/undrewb/store_test.go +++ b/06_puppy/undrewb/store_test.go @@ -15,10 +15,10 @@ type storerSuite struct { func TestStorers(t *testing.T) { suite.Run(t, &storerSuite{ - storerType: func() Storer { return &MapStore{} }, + storerType: func() Storer { return InitMapStore() }, }) suite.Run(t, &storerSuite{ - storerType: func() Storer { return &SyncStore{} }, + storerType: func() Storer { return InitSyncStore() }, }) } @@ -46,12 +46,16 @@ func (s *storerSuite) TestMapStore_CreatePuppy() { puppy *Puppy wantErr bool }{ - {name: "New corgi", + { + name: "New corgi", puppy: corgi, - wantErr: false}, - {name: "Existing corgi", + wantErr: false, + }, + { + name: "Existing corgi", puppy: corgi, - wantErr: true}, + wantErr: true, + }, } for _, tt := range tests { tt := tt @@ -74,12 +78,14 @@ func (s *storerSuite) TestMapStore_ReadPuppy() { wantErr bool want *Puppy }{ - {name: "lookup existing puppy", + { + name: "lookup existing puppy", id: 1000, want: corgi, wantErr: false, }, - {name: "lookup non-existing puppy", + { + name: "lookup non-existing puppy", id: 1001, want: nil, wantErr: true, @@ -93,7 +99,7 @@ func (s *storerSuite) TestMapStore_ReadPuppy() { t.Errorf("ReadPuppy() error = %v, wantErr %v", err, tt.wantErr) return } - if !reflect.DeepEqual(got, tt.want) { + if !reflect.DeepEqual(tt.want, got) { t.Errorf("ReadPuppy() = %v, want %v", got, tt.want) } }) @@ -107,12 +113,14 @@ func (s *storerSuite) TestMapStore_DeletePuppy() { want bool wantErr bool }{ - {name: "delete existing corgi", + { + name: "delete existing corgi", id: 1000, want: true, wantErr: false, }, - {name: "delete non-existent corgi", + { + name: "delete non-existent corgi", id: 1000, want: false, wantErr: true, @@ -126,7 +134,7 @@ func (s *storerSuite) TestMapStore_DeletePuppy() { t.Errorf("DeletePuppy() error = %v, wantErr %v", err, tt.wantErr) return } - if got != tt.want { + if tt.want != got { t.Errorf("DeletePuppy() = %v, want %v", got, tt.want) } }) @@ -144,22 +152,26 @@ func (s *storerSuite) TestMapStore_UpdatePuppy() { wantErr bool puppy *Puppy }{ - {name: "update existing puppy", + { + name: "update existing puppy", id: 1000, puppy: albinoCorgi, wantErr: false, }, - {name: "update non-existing puppy", + { + name: "update non-existing puppy", id: 1001, puppy: albinoCorgi, wantErr: true, }, - {name: "update with an empty puppy", + { + name: "update with an empty puppy", id: 1000, puppy: nil, wantErr: true, }, - {name: "update with a corrupt puppy", + { + name: "update with a corrupt puppy", id: 1000, puppy: corruptCorgi, wantErr: true, From 6024201b9374b188fcf17174e678058f4ed8ca24 Mon Sep 17 00:00:00 2001 From: "Bucknell, Andrew" Date: Thu, 22 Aug 2019 23:24:34 +1000 Subject: [PATCH 3/3] Stop tracking gitignore file --- .gitignore | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index f11e119f6..000000000 --- a/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, build with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out -coverage.txt -06_puppy/undrewb/.vscode/launch.json