From 62ec810e6daa1a0347357687c5ef7742598b41ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Abdelkader=20Mart=C3=ADnez=20P=C3=A9rez?= Date: Sat, 5 Oct 2019 12:22:38 +0200 Subject: [PATCH] Implemented user server state aka route list --- internal/server/user/state.go | 37 ++++++++ internal/server/user/state_test.go | 136 +++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 internal/server/user/state.go create mode 100644 internal/server/user/state_test.go diff --git a/internal/server/user/state.go b/internal/server/user/state.go new file mode 100644 index 0000000..726bb46 --- /dev/null +++ b/internal/server/user/state.go @@ -0,0 +1,37 @@ +package user + +import ( + "sync" + + "github.com/BBVA/kapow/internal/server/model" +) + +type safeRouteList struct { + rs []model.Route + m sync.RWMutex +} + +var Routes = New() + +func New() safeRouteList { + return safeRouteList{} +} + +func (srl *safeRouteList) Append(r model.Route) { + srl.m.Lock() + srl.rs = append(srl.rs, r) + srl.m.Unlock() +} + +func (srl *safeRouteList) Snapshot() []model.Route { + srl.m.RLock() + defer srl.m.RUnlock() + + if srl.rs == nil { + return nil + } else { + rs := make([]model.Route, len(srl.rs)) + copy(rs, srl.rs) + return rs + } +} diff --git a/internal/server/user/state_test.go b/internal/server/user/state_test.go new file mode 100644 index 0000000..2b67f3c --- /dev/null +++ b/internal/server/user/state_test.go @@ -0,0 +1,136 @@ +package user + +import ( + "github.com/BBVA/kapow/internal/server/model" + "reflect" + "testing" + "time" +) + +func TestNewReturnAnEmptyStruct(t *testing.T) { + srl := New() + + if len(srl.rs) != 0 { + t.Error("Unexpected member in slice") + } +} + +func TestPackageHaveASingletonEmptyRouteList(t *testing.T) { + if !reflect.DeepEqual(Routes, New()) { + t.Error("Routes is not an empty safeRouteList") + } +} + +func TestAppendAppendsANewRouteToTheList(t *testing.T) { + srl := New() + + srl.Append(model.Route{}) + + if len(srl.rs) == 0 { + t.Error("Route not added to the list") + } +} + +func TestAppendAdquiresMutexBeforeAdding(t *testing.T) { + srl := New() + + srl.m.Lock() + defer srl.m.Unlock() + go srl.Append(model.Route{}) + + time.Sleep(10 * time.Millisecond) + + if len(srl.rs) != 0 { + t.Error("Route added while mutex was adquired") + } +} + +func TestAppendAddsRouteAfterMutexIsReleased(t *testing.T) { + srl := New() + + srl.m.Lock() + go srl.Append(model.Route{}) + srl.m.Unlock() + + time.Sleep(10 * time.Millisecond) + + if len(srl.rs) != 1 { + t.Error("Route not added after mutex release") + } +} + +func TestSnapshotReturnTheCurrentListOfRoutes(t *testing.T) { + srl := New() + srl.Append(model.Route{Id: "FOO"}) + + rs := srl.Snapshot() + + if !reflect.DeepEqual(srl.rs, rs) { + t.Error("Route list returned is not the current one") + } +} + +func TestSnapshotReturnADeepCopyOfTheListWhenEmpty(t *testing.T) { + srl := New() + + rs := srl.Snapshot() + + if rs != nil { + t.Fatal("Route list copy is not empty") + } +} + +func TestSnapshotReturnADeepCopyOfTheListWhenNonEmpty(t *testing.T) { + srl := New() + srl.Append(model.Route{Id: "FOO"}) + + rs := srl.Snapshot() + + if &rs == &srl.rs { + t.Fatal("Route list is not a copy") + } + + for i := 0; i < len(rs); i++ { + if &rs[i] == &srl.rs[i] { + t.Errorf("Route %q is not a copy", i) + } + } +} + +func TestSnapshotWaitsForTheWriterToFinish(t *testing.T) { + srl := New() + srl.Append(model.Route{Id: "FOO"}) + + srl.m.Lock() + defer srl.m.Unlock() + + c := make(chan []model.Route) + go func() { c <- srl.Snapshot() }() + + time.Sleep(10 * time.Millisecond) + + select { + case <-c: + t.Error("Route list readed while mutex was adquired") + default: // This default prevents the select from being blocking + } +} + +func TestSnapshotNonBlockingReadWithOtherReaders(t *testing.T) { + srl := New() + srl.Append(model.Route{Id: "FOO"}) + + srl.m.RLock() + defer srl.m.RUnlock() + + c := make(chan []model.Route) + go func() { c <- srl.Snapshot() }() + + time.Sleep(10 * time.Millisecond) + + select { + case <-c: + default: // This default prevents the select from being blocking + t.Error("Route list couldn't be readed while mutex was adquired for read") + } +}