Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(47)

Unified Diff: common/data/treapstore/store_test.go

Issue 2607663002: Add treapstore, a treap-based in-memory data store (Closed)
Patch Set: Fix test race, better docs, valid zero values. Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « common/data/treapstore/store.go ('k') | logdog/common/storage/bigtable/testing.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: common/data/treapstore/store_test.go
diff --git a/common/data/treapstore/store_test.go b/common/data/treapstore/store_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..91a344058dc5bcf4ea8bfcb9e9c0300d271aa4c9
--- /dev/null
+++ b/common/data/treapstore/store_test.go
@@ -0,0 +1,267 @@
+// Copyright 2016 The LUCI Authors. All rights reserved.
+// Use of this source code is governed under the Apache License, Version 2.0
+// that can be found in the LICENSE file.
+
+package treapstore
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "testing"
+
+ "github.com/luci/gtreap"
+
+ . "github.com/smartystreets/goconvey/convey"
+)
+
+func stringCompare(a, b interface{}) int {
+ return strings.Compare(a.(string), b.(string))
+}
+
+func putMulti(c *Collection, vs ...string) {
+ for _, v := range vs {
+ c.Put(v)
+ }
+}
+
+func iterAll(it *gtreap.Iterator) []string {
+ all := []string{}
+ for {
+ v, ok := it.Next()
+ if !ok {
+ return all
+ }
+ all = append(all, v.(string))
+ }
+}
+
+func TestStore(t *testing.T) {
+ t.Parallel()
+
+ Convey(`Testing a string Store`, t, func() {
+ st := New()
+ coll := st.CreateCollection("test", stringCompare)
+
+ Convey(`When empty`, func() {
+ checkEmpty := func(c *Collection) {
+ So(c.Get("foo"), ShouldBeNil)
+ So(iterAll(c.Iterator("")), ShouldResemble, []string{})
+ }
+
+ // Check the basic Store.
+ checkEmpty(coll)
+
+ // Take a snapshot, then mutate the base Store. Assert that the snapshot
+ // is still empty.
+ snap := st.Snapshot()
+ coll.Put("foo")
+ checkEmpty(snap.GetCollection("test"))
+ })
+
+ Convey(`With keys`, func() {
+ putMulti(coll, "x", "w", "b", "a")
+
+ Convey(`Can iterate`, func() {
+ checkKeys := func(coll *Collection, keys ...string) {
+ for _, k := range keys {
+ So(coll.Get(k), ShouldEqual, k)
+ }
+
+ So(iterAll(coll.Iterator("")), ShouldResemble, keys)
+ for i, k := range keys {
+ So(iterAll(coll.Iterator(k)), ShouldResemble, keys[i:])
+ So(iterAll(coll.Iterator(k+"1")), ShouldResemble, keys[i+1:])
+ }
+ }
+ checkKeys(coll, "a", "b", "w", "x")
+
+ // Take a snapshot, then mutate the base Store. Assert that the snapshot
+ // is still empty.
+ snap := st.Snapshot()
+ snapColl := snap.GetCollection("test")
+ putMulti(coll, "foo")
+ coll.Delete("b")
+ checkKeys(snapColl, "a", "b", "w", "x")
+ checkKeys(coll, "a", "foo", "w", "x")
+ })
+
+ Convey(`Modified after a snapshot`, func() {
+ snap := st.Snapshot()
+ snapColl := snap.GetCollection("test")
+ putMulti(coll, "z")
+
+ Convey(`A snapshot of a snapshot is itself.`, func() {
+ So(snap.Snapshot(), ShouldEqual, snap)
+ })
+
+ Convey(`A snapshot is read-only, and cannot create collections.`, func() {
+ So(func() { snap.CreateCollection("new", stringCompare) }, ShouldPanic)
+ })
+
+ Convey(`Can get its collection name`, func() {
+ So(coll.Name(), ShouldEqual, "test")
+ So(snapColl.Name(), ShouldEqual, "test")
+ })
+
+ Convey(`Can fetch the Min and Max`, func() {
+ So(coll.Min(), ShouldResemble, "a")
+ So(coll.Max(), ShouldResemble, "z")
+
+ So(snapColl.Min(), ShouldResemble, "a")
+ So(snapColl.Max(), ShouldResemble, "x")
+ })
+
+ Convey(`Cannot Put to a read-only snapshot.`, func() {
+ So(func() { snapColl.Put("panic") }, ShouldPanic)
+ })
+ })
+ })
+
+ Convey(`Creating a Collection with a duplicate name will panic.`, func() {
+ So(func() { st.CreateCollection("test", stringCompare) }, ShouldPanic)
+ })
+
+ Convey(`With multiple Collections`, func() {
+ for _, v := range []string{"foo", "bar", "baz"} {
+ st.CreateCollection(v, stringCompare)
+ }
+ So(st.GetCollectionNames(), ShouldResemble, []string{"bar", "baz", "foo", "test"})
+ snap := st.Snapshot()
+ So(snap.GetCollectionNames(), ShouldResemble, []string{"bar", "baz", "foo", "test"})
+
+ Convey(`When new Collections are added, names remain sorted.`, func() {
+ for _, v := range []string{"app", "cat", "bas", "qux"} {
+ st.CreateCollection(v, stringCompare)
+ }
+ So(st.GetCollectionNames(), ShouldResemble,
+ []string{"app", "bar", "bas", "baz", "cat", "foo", "qux", "test"})
+ So(st.Snapshot().GetCollectionNames(), ShouldResemble,
+ []string{"app", "bar", "bas", "baz", "cat", "foo", "qux", "test"})
+ So(snap.GetCollectionNames(), ShouldResemble, []string{"bar", "baz", "foo", "test"})
+ })
+ })
+ })
+}
+
+func TestStoreZeroValue(t *testing.T) {
+ t.Parallel()
+
+ Convey(`A Store's zero value is valid, empty, and read-only.`, t, func() {
+ s := Store{}
+
+ So(s.IsReadOnly(), ShouldBeTrue)
+ So(s.GetCollectionNames(), ShouldBeNil)
+ So(s.GetCollection("foo"), ShouldBeNil)
+ So(s.Snapshot(), ShouldEqual, &s)
+ })
+}
+
+func TestCollectionZeroValue(t *testing.T) {
+ t.Parallel()
+
+ Convey(`A Collection's zero value is valid, empty, and read-only.`, t, func() {
+ c := Collection{}
+
+ So(c.IsReadOnly(), ShouldBeTrue)
+ So(c.Name(), ShouldEqual, "")
+ So(c.Get("foo"), ShouldBeNil)
+ So(c.Min(), ShouldBeNil)
+ So(c.Max(), ShouldBeNil)
+
+ it := c.Iterator(nil)
+ So(it, ShouldNotBeNil)
+ So(iterAll(it), ShouldHaveLength, 0)
+ })
+}
+
+// TestStoreParallel performs several rounds of parallel accesses. Each round
+// takes a snapshot of the "head" Store, then simultaneusly dispatches a round
+// of parallel writes agains the "head" store, reads against the snapshot, and
+// reads against the "head" store.
+//
+// This is meant to be run with "-race" to trigger on race conditions.
+func TestStoreParallel(t *testing.T) {
+ t.Parallel()
+
+ Convey(`Testing a string Store for parallel access.`, t, func() {
+ const (
+ readers = 128
+ writers = 16
+ rounds = 8
+ )
+
+ head := New()
+ head.CreateCollection("", stringCompare)
+ var snaps []*Store
+
+ // Dispatch readers.
+ doReads := func() int {
+ readDoneC := make(chan int, readers)
+ for i := 0; i < readers; i++ {
+ go func() {
+ var (
+ doneC = make(chan int, 1+len(snaps))
+ )
+
+ // "head"
+ go func() {
+ doneC <- len(iterAll(head.GetCollection("").Iterator("")))
+ }()
+
+ // "snap"
+ for _, snap := range snaps {
+ go func(snap *Store) {
+ doneC <- len(iterAll(snap.GetCollection("").Iterator("")))
+ }(snap)
+ }
+
+ total := 0
+ for i := 0; i < 1+len(snaps); i++ {
+ total += <-doneC
+ }
+ readDoneC <- total
+ }()
+ }
+
+ total := 0
+ for i := 0; i < readers; i++ {
+ total += <-readDoneC
+ }
+ return total
+ }
+
+ // Dispatch writers.
+ doWrite := func(base string) {
+ writeDoneC := make(chan struct{}, writers)
+ for i := 0; i < writers; i++ {
+ go func(idx int) {
+ head.GetCollection("").Put(fmt.Sprintf("%s.%d", base, idx))
+ writeDoneC <- struct{}{}
+ }(i)
+ }
+
+ for i := 0; i < writers; i++ {
+ <-writeDoneC
+ }
+ }
+
+ // Main loop.
+ for i := 0; i < rounds; i++ {
+ writeDoneC := make(chan struct{})
+ readDoneC := make(chan int)
+ go func() {
+ doWrite(strconv.Itoa(i))
+ close(writeDoneC)
+ }()
+ // The first round has to actually create the Collection.
+ go func() {
+ readDoneC <- doReads()
+ }()
+
+ <-writeDoneC
+ So(<-readDoneC, ShouldBeGreaterThan, 0)
+ snaps = append(snaps, head.Snapshot())
+ }
+ })
+}
« no previous file with comments | « common/data/treapstore/store.go ('k') | logdog/common/storage/bigtable/testing.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698