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 newStringStore() *Store { |
| 19 return New(func(a, b interface{}) int { |
| 20 return strings.Compare(a.(string), b.(string)) |
| 21 }) |
| 22 } |
| 23 |
| 24 func iterAll(it *gtreap.Iterator) []string { |
| 25 all := []string{} |
| 26 for { |
| 27 v, ok := it.Next() |
| 28 if !ok { |
| 29 return all |
| 30 } |
| 31 all = append(all, v.(string)) |
| 32 } |
| 33 } |
| 34 |
| 35 func TestStore(t *testing.T) { |
| 36 t.Parallel() |
| 37 |
| 38 Convey(`Testing a string Store`, t, func() { |
| 39 st := newStringStore() |
| 40 st.GetOrCreateCollection("") |
| 41 |
| 42 Convey(`When empty`, func() { |
| 43 checkEmpty := func(c *Collection) { |
| 44 So(c.Get("foo"), ShouldBeNil) |
| 45 So(iterAll(c.Iterator("")), ShouldResemble, []st
ring{}) |
| 46 } |
| 47 |
| 48 // Check the basic Store. |
| 49 checkEmpty(st.GetCollection("")) |
| 50 |
| 51 // Take a snapshot, then mutate the base Store. Assert t
hat the snapshot |
| 52 // is still empty. |
| 53 snap := st.Snapshot() |
| 54 st.GetCollection("").Put("foo") |
| 55 checkEmpty(snap.GetCollection("")) |
| 56 }) |
| 57 |
| 58 Convey(`With keys`, func() { |
| 59 coll := st.GetCollection("") |
| 60 coll.Put("a") |
| 61 coll.Put("b") |
| 62 coll.Put("z") |
| 63 |
| 64 checkKeys := func(coll *Collection, keys ...string) { |
| 65 for _, k := range keys { |
| 66 So(coll.Get(k), ShouldEqual, k) |
| 67 } |
| 68 |
| 69 So(iterAll(coll.Iterator("")), ShouldResemble, k
eys) |
| 70 for i, k := range keys { |
| 71 So(iterAll(coll.Iterator(k)), ShouldRese
mble, keys[i:]) |
| 72 So(iterAll(coll.Iterator(k+"1")), Should
Resemble, keys[i+1:]) |
| 73 } |
| 74 } |
| 75 checkKeys(coll, "a", "b", "z") |
| 76 |
| 77 // Take a snapshot, then mutate the base Store. Assert t
hat the snapshot |
| 78 // is still empty. |
| 79 snap := st.Snapshot() |
| 80 snapColl := snap.GetCollection("") |
| 81 coll.Put("foo") |
| 82 checkKeys(snapColl, "a", "b", "z") |
| 83 checkKeys(coll, "a", "b", "foo", "z") |
| 84 }) |
| 85 }) |
| 86 } |
| 87 |
| 88 // TestStoreParallel performs several rounds of parallel accesses. Each round |
| 89 // takes a snapshot of the "head" Store, then simultaneusly dispatches a round |
| 90 // of parallel writes agains the "head" store, reads against the snapshot, and |
| 91 // reads against the "head" store. |
| 92 // |
| 93 // This is meant to be run with "-race" to trigger on race conditions. |
| 94 func TestStoreParallel(t *testing.T) { |
| 95 t.Parallel() |
| 96 |
| 97 Convey(`Testing a string Store for parallel access.`, t, func() { |
| 98 const ( |
| 99 readers = 128 |
| 100 writers = 16 |
| 101 rounds = 8 |
| 102 ) |
| 103 |
| 104 head := newStringStore() |
| 105 var snaps []*Store |
| 106 |
| 107 // Dispatch readers. |
| 108 doReads := func() int { |
| 109 readDoneC := make(chan int, readers) |
| 110 for i := 0; i < readers; i++ { |
| 111 go func() { |
| 112 var ( |
| 113 doneC = make(chan int, 1+len(sna
ps)) |
| 114 ) |
| 115 |
| 116 // "head" |
| 117 go func() { |
| 118 doneC <- len(iterAll(head.GetCol
lection("").Iterator(""))) |
| 119 }() |
| 120 |
| 121 // "snap" |
| 122 for _, snap := range snaps { |
| 123 go func(snap *Store) { |
| 124 doneC <- len(iterAll(sna
p.GetCollection("").Iterator(""))) |
| 125 }(snap) |
| 126 } |
| 127 |
| 128 total := 0 |
| 129 for i := 0; i < 1+len(snaps); i++ { |
| 130 total += <-doneC |
| 131 } |
| 132 readDoneC <- total |
| 133 }() |
| 134 } |
| 135 |
| 136 total := 0 |
| 137 for i := 0; i < readers; i++ { |
| 138 total += <-readDoneC |
| 139 } |
| 140 return total |
| 141 } |
| 142 |
| 143 // Dispatch writers. |
| 144 doWrite := func(base string) { |
| 145 writeDoneC := make(chan struct{}, writers) |
| 146 for i := 0; i < writers; i++ { |
| 147 go func(idx int) { |
| 148 head.GetOrCreateCollection("").Put(fmt.S
printf("%s.%d", base, idx)) |
| 149 writeDoneC <- struct{}{} |
| 150 }(i) |
| 151 } |
| 152 |
| 153 for i := 0; i < writers; i++ { |
| 154 <-writeDoneC |
| 155 } |
| 156 } |
| 157 |
| 158 // Main loop. |
| 159 for i := 0; i < rounds; i++ { |
| 160 writeDoneC := make(chan struct{}) |
| 161 readDoneC := make(chan int) |
| 162 go func() { |
| 163 doWrite(strconv.Itoa(i)) |
| 164 close(writeDoneC) |
| 165 }() |
| 166 if i > 0 { |
| 167 // The first round has to actually create the Co
llection. |
| 168 go func() { |
| 169 readDoneC <- doReads() |
| 170 }() |
| 171 } |
| 172 |
| 173 <-writeDoneC |
| 174 if i > 0 { |
| 175 So(<-readDoneC, ShouldBeGreaterThan, 0) |
| 176 } |
| 177 snaps = append(snaps, head.Snapshot()) |
| 178 } |
| 179 }) |
| 180 } |
OLD | NEW |