diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 6bb54e93e..000000000 --- a/.gitignore +++ /dev/null @@ -1,13 +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 diff --git a/06_puppy/undrewb/MapStore.go b/06_puppy/undrewb/MapStore.go new file mode 100644 index 000000000..d349f2684 --- /dev/null +++ b/06_puppy/undrewb/MapStore.go @@ -0,0 +1,64 @@ +package main + +import ( + "errors" + "fmt" +) + +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 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.store[p.ID] = p.Clone() + return nil +} + +func (msp *MapStore) ReadPuppy(id uint32) (*Puppy, error) { + p, err := msp.store[id] + if !err { + return nil, errors.New("no such puppy") + } + return p, nil +} + +func (msp *MapStore) DeletePuppy(id uint32) (bool, error) { + p, exist := msp.store[id] + if !exist { + return false, errors.New("no such puppy") + } + delete(msp.store, 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.store[id] + if !exist { + return errors.New("no such puppy") + } + if p.ID != puppy.ID { + return errors.New("bad puppy") + } + msp.store[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..610b22290 --- /dev/null +++ b/06_puppy/undrewb/SyncStore.go @@ -0,0 +1,59 @@ +package main + +import ( + "fmt" + "sync" +) + +type SyncStore struct { + sync.Map + sync.RWMutex + nextID uint32 +} + +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", puppy.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..9c3fdaba1 --- /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[] 0}\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..123d70764 --- /dev/null +++ b/06_puppy/undrewb/store_test.go @@ -0,0 +1,189 @@ +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 InitMapStore() }, + }) + suite.Run(t, &storerSuite{ + storerType: func() Storer { return InitSyncStore() }, + }) +} + +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(tt.want, got) { + 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 tt.want != got { + 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) +}