OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. |
| 4 |
| 5 package treapstore |
| 6 |
| 7 import ( |
| 8 "fmt" |
| 9 "strconv" |
| 10 "strings" |
| 11 "testing" |
| 12 |
| 13 "github.com/luci/gtreap" |
| 14 |
| 15 . "github.com/smartystreets/goconvey/convey" |
| 16 ) |
| 17 |
| 18 func stringCompare(a, b interface{}) int { |
| 19 return strings.Compare(a.(string), b.(string)) |
| 20 } |
| 21 |
| 22 func putMulti(c *Collection, vs ...string) { |
| 23 for _, v := range vs { |
| 24 c.Put(v) |
| 25 } |
| 26 } |
| 27 |
| 28 func iterAll(it *gtreap.Iterator) []string { |
| 29 all := []string{} |
| 30 for { |
| 31 v, ok := it.Next() |
| 32 if !ok { |
| 33 return all |
| 34 } |
| 35 all = append(all, v.(string)) |
| 36 } |
| 37 } |
| 38 |
| 39 func TestStore(t *testing.T) { |
| 40 t.Parallel() |
| 41 |
| 42 Convey(`Testing a string Store`, t, func() { |
| 43 st := New() |
| 44 coll := st.CreateCollection("test", stringCompare) |
| 45 |
| 46 Convey(`When empty`, func() { |
| 47 checkEmpty := func(c *Collection) { |
| 48 So(c.Get("foo"), ShouldBeNil) |
| 49 So(iterAll(c.Iterator("")), ShouldResemble, []st
ring{}) |
| 50 } |
| 51 |
| 52 // Check the basic Store. |
| 53 checkEmpty(coll) |
| 54 |
| 55 // Take a snapshot, then mutate the base Store. Assert t
hat the snapshot |
| 56 // is still empty. |
| 57 snap := st.Snapshot() |
| 58 coll.Put("foo") |
| 59 checkEmpty(snap.GetCollection("test")) |
| 60 }) |
| 61 |
| 62 Convey(`With keys`, func() { |
| 63 putMulti(coll, "x", "w", "b", "a") |
| 64 |
| 65 Convey(`Can iterate`, func() { |
| 66 checkKeys := func(coll *Collection, keys ...stri
ng) { |
| 67 for _, k := range keys { |
| 68 So(coll.Get(k), ShouldEqual, k) |
| 69 } |
| 70 |
| 71 So(iterAll(coll.Iterator("")), ShouldRes
emble, keys) |
| 72 for i, k := range keys { |
| 73 So(iterAll(coll.Iterator(k)), Sh
ouldResemble, keys[i:]) |
| 74 So(iterAll(coll.Iterator(k+"1"))
, ShouldResemble, keys[i+1:]) |
| 75 } |
| 76 } |
| 77 checkKeys(coll, "a", "b", "w", "x") |
| 78 |
| 79 // Take a snapshot, then mutate the base Store.
Assert that the snapshot |
| 80 // is still empty. |
| 81 snap := st.Snapshot() |
| 82 snapColl := snap.GetCollection("test") |
| 83 putMulti(coll, "foo") |
| 84 coll.Delete("b") |
| 85 checkKeys(snapColl, "a", "b", "w", "x") |
| 86 checkKeys(coll, "a", "foo", "w", "x") |
| 87 }) |
| 88 |
| 89 Convey(`Modified after a snapshot`, func() { |
| 90 snap := st.Snapshot() |
| 91 snapColl := snap.GetCollection("test") |
| 92 putMulti(coll, "z") |
| 93 |
| 94 Convey(`A snapshot of a snapshot is itself.`, fu
nc() { |
| 95 So(snap.Snapshot(), ShouldEqual, snap) |
| 96 }) |
| 97 |
| 98 Convey(`A snapshot is read-only, and cannot crea
te collections.`, func() { |
| 99 So(func() { snap.CreateCollection("new",
stringCompare) }, ShouldPanic) |
| 100 }) |
| 101 |
| 102 Convey(`Can get its collection name`, func() { |
| 103 So(coll.Name(), ShouldEqual, "test") |
| 104 So(snapColl.Name(), ShouldEqual, "test") |
| 105 }) |
| 106 |
| 107 Convey(`Can fetch the Min and Max`, func() { |
| 108 So(coll.Min(), ShouldResemble, "a") |
| 109 So(coll.Max(), ShouldResemble, "z") |
| 110 |
| 111 So(snapColl.Min(), ShouldResemble, "a") |
| 112 So(snapColl.Max(), ShouldResemble, "x") |
| 113 }) |
| 114 |
| 115 Convey(`Cannot Put to a read-only snapshot.`, fu
nc() { |
| 116 So(func() { snapColl.Put("panic") }, Sho
uldPanic) |
| 117 }) |
| 118 }) |
| 119 }) |
| 120 |
| 121 Convey(`Creating a Collection with a duplicate name will panic.`
, func() { |
| 122 So(func() { st.CreateCollection("test", stringCompare) }
, ShouldPanic) |
| 123 }) |
| 124 |
| 125 Convey(`With multiple Collections`, func() { |
| 126 for _, v := range []string{"foo", "bar", "baz"} { |
| 127 st.CreateCollection(v, stringCompare) |
| 128 } |
| 129 So(st.GetCollectionNames(), ShouldResemble, []string{"ba
r", "baz", "foo", "test"}) |
| 130 snap := st.Snapshot() |
| 131 So(snap.GetCollectionNames(), ShouldResemble, []string{"
bar", "baz", "foo", "test"}) |
| 132 |
| 133 Convey(`When new Collections are added, names remain sor
ted.`, func() { |
| 134 for _, v := range []string{"app", "cat", "bas",
"qux"} { |
| 135 st.CreateCollection(v, stringCompare) |
| 136 } |
| 137 So(st.GetCollectionNames(), ShouldResemble, |
| 138 []string{"app", "bar", "bas", "baz", "ca
t", "foo", "qux", "test"}) |
| 139 So(st.Snapshot().GetCollectionNames(), ShouldRes
emble, |
| 140 []string{"app", "bar", "bas", "baz", "ca
t", "foo", "qux", "test"}) |
| 141 So(snap.GetCollectionNames(), ShouldResemble, []
string{"bar", "baz", "foo", "test"}) |
| 142 }) |
| 143 }) |
| 144 }) |
| 145 } |
| 146 |
| 147 func TestStoreZeroValue(t *testing.T) { |
| 148 t.Parallel() |
| 149 |
| 150 Convey(`A Store's zero value is valid, empty, and read-only.`, t, func()
{ |
| 151 s := Store{} |
| 152 |
| 153 So(s.IsReadOnly(), ShouldBeTrue) |
| 154 So(s.GetCollectionNames(), ShouldBeNil) |
| 155 So(s.GetCollection("foo"), ShouldBeNil) |
| 156 So(s.Snapshot(), ShouldEqual, &s) |
| 157 }) |
| 158 } |
| 159 |
| 160 func TestCollectionZeroValue(t *testing.T) { |
| 161 t.Parallel() |
| 162 |
| 163 Convey(`A Collection's zero value is valid, empty, and read-only.`, t, f
unc() { |
| 164 c := Collection{} |
| 165 |
| 166 So(c.IsReadOnly(), ShouldBeTrue) |
| 167 So(c.Name(), ShouldEqual, "") |
| 168 So(c.Get("foo"), ShouldBeNil) |
| 169 So(c.Min(), ShouldBeNil) |
| 170 So(c.Max(), ShouldBeNil) |
| 171 |
| 172 it := c.Iterator(nil) |
| 173 So(it, ShouldNotBeNil) |
| 174 So(iterAll(it), ShouldHaveLength, 0) |
| 175 }) |
| 176 } |
| 177 |
| 178 // TestStoreParallel performs several rounds of parallel accesses. Each round |
| 179 // takes a snapshot of the "head" Store, then simultaneusly dispatches a round |
| 180 // of parallel writes agains the "head" store, reads against the snapshot, and |
| 181 // reads against the "head" store. |
| 182 // |
| 183 // This is meant to be run with "-race" to trigger on race conditions. |
| 184 func TestStoreParallel(t *testing.T) { |
| 185 t.Parallel() |
| 186 |
| 187 Convey(`Testing a string Store for parallel access.`, t, func() { |
| 188 const ( |
| 189 readers = 128 |
| 190 writers = 16 |
| 191 rounds = 8 |
| 192 ) |
| 193 |
| 194 head := New() |
| 195 head.CreateCollection("", stringCompare) |
| 196 var snaps []*Store |
| 197 |
| 198 // Dispatch readers. |
| 199 doReads := func() int { |
| 200 readDoneC := make(chan int, readers) |
| 201 for i := 0; i < readers; i++ { |
| 202 go func() { |
| 203 var ( |
| 204 doneC = make(chan int, 1+len(sna
ps)) |
| 205 ) |
| 206 |
| 207 // "head" |
| 208 go func() { |
| 209 doneC <- len(iterAll(head.GetCol
lection("").Iterator(""))) |
| 210 }() |
| 211 |
| 212 // "snap" |
| 213 for _, snap := range snaps { |
| 214 go func(snap *Store) { |
| 215 doneC <- len(iterAll(sna
p.GetCollection("").Iterator(""))) |
| 216 }(snap) |
| 217 } |
| 218 |
| 219 total := 0 |
| 220 for i := 0; i < 1+len(snaps); i++ { |
| 221 total += <-doneC |
| 222 } |
| 223 readDoneC <- total |
| 224 }() |
| 225 } |
| 226 |
| 227 total := 0 |
| 228 for i := 0; i < readers; i++ { |
| 229 total += <-readDoneC |
| 230 } |
| 231 return total |
| 232 } |
| 233 |
| 234 // Dispatch writers. |
| 235 doWrite := func(base string) { |
| 236 writeDoneC := make(chan struct{}, writers) |
| 237 for i := 0; i < writers; i++ { |
| 238 go func(idx int) { |
| 239 head.GetCollection("").Put(fmt.Sprintf("
%s.%d", base, idx)) |
| 240 writeDoneC <- struct{}{} |
| 241 }(i) |
| 242 } |
| 243 |
| 244 for i := 0; i < writers; i++ { |
| 245 <-writeDoneC |
| 246 } |
| 247 } |
| 248 |
| 249 // Main loop. |
| 250 for i := 0; i < rounds; i++ { |
| 251 writeDoneC := make(chan struct{}) |
| 252 readDoneC := make(chan int) |
| 253 go func() { |
| 254 doWrite(strconv.Itoa(i)) |
| 255 close(writeDoneC) |
| 256 }() |
| 257 // The first round has to actually create the Collection
. |
| 258 go func() { |
| 259 readDoneC <- doReads() |
| 260 }() |
| 261 |
| 262 <-writeDoneC |
| 263 So(<-readDoneC, ShouldBeGreaterThan, 0) |
| 264 snaps = append(snaps, head.Snapshot()) |
| 265 } |
| 266 }) |
| 267 } |
OLD | NEW |