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

Side by Side Diff: impl/cloud/memcache_test.go

Issue 2513253002: impl/cloud: Add support for "memcached" memcache. (Closed)
Patch Set: Add test for key hasing. Created 4 years, 1 month 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 unified diff | Download patch
« no previous file with comments | « impl/cloud/memcache.go ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 cloud
6
7 import (
8 "flag"
9 "fmt"
10 "math"
11 "strings"
12 "testing"
13 "time"
14
15 "github.com/luci/luci-go/common/errors"
16
17 "github.com/luci/gae/service/info"
18 mc "github.com/luci/gae/service/memcache"
19
20 "github.com/bradfitz/gomemcache/memcache"
21 "golang.org/x/net/context"
22
23 . "github.com/smartystreets/goconvey/convey"
24 )
25
26 var memcacheServer = flag.String("test.memcache-server", "",
27 "[<addr>]:<port> of memcached service to test against. THIS WILL FLUSH T HE CACHE.")
28
29 // TestMemcache tests the memcache implementation against a live "memcached"
30 // instance. The test assumes ownership of the instance, and will flush all of
31 // its keys in between test suites, so DO NOT connect this to a production
32 // memcached cluster!
33 //
34 // The memcache host is passed to this test suite via the "-test.memcache-host"
35 // flag. If the flag is not provided, this test suite will be skipped.
36 //
37 // Starting a local memcached server (on default port 11211) can be done with:
38 // $ memcached -l localhost -vvv
39 //
40 // Testing against this service can be done using the flag:
41 // "-test.memcache-server localhost:11211
42 func TestMemcache(t *testing.T) {
43 t.Parallel()
44
45 // See if a memcache server is configured. If no server is configured, w e will
46 // skip this test suite.
47 if *memcacheServer == "" {
48 t.Logf("No memcache server detected (-test.memcache-server). Ski pping test suite.")
49 return
50 }
51
52 Convey(fmt.Sprintf(`A memcache instance bound to %q`, *memcacheServer), t, func() {
53 client := memcache.New(*memcacheServer)
54 if err := client.DeleteAll(); err != nil {
55 t.Fatalf("failed to flush memcache before running test s uite: %s", err)
56 }
57
58 cfg := Config{MC: client}
59 c := cfg.Use(context.Background())
60
61 get := func(c context.Context, keys ...string) []string {
62 bmc := bindMemcacheClient(nil, info.GetNamespace(c))
63
64 v := make([]string, len(keys))
65 for i, k := range keys {
66 itm, err := client.Get(bmc.makeKey(k))
67 switch err {
68 case nil:
69 v[i] = string(itm.Value)
70
71 case memcache.ErrCacheMiss:
72 break
73
74 default:
75 t.Fatalf("item %q could not be loaded: % s", itm.Key, err)
76 }
77 }
78 return v
79 }
80
81 Convey(`AddMulti`, func() {
82 items := []mc.Item{
83 mc.NewItem(c, "foo").SetValue([]byte("FOO")),
84 mc.NewItem(c, "bar").SetValue([]byte("BAR")),
85 mc.NewItem(c, "baz").SetValue([]byte("BAZ")),
86 }
87 So(mc.Add(c, items...), ShouldBeNil)
88 So(get(c, "foo", "bar", "baz"), ShouldResemble, []string {"FOO", "BAR", "BAZ"})
89
90 // Namespaced.
91 oc := info.MustNamespace(c, "other")
92 oitems := make([]mc.Item, len(items))
93 for i, itm := range items {
94 oitems[i] = mc.NewItem(oc, itm.Key())
95 oitems[i].SetValue([]byte("OTHER_" + string(itm. Value())))
96 }
97 So(mc.Add(oc, oitems...), ShouldBeNil)
98 So(get(oc, "foo", "bar", "baz"), ShouldResemble, []strin g{"OTHER_FOO", "OTHER_BAR", "OTHER_BAZ"})
99
100 Convey(`Returns ErrNotStored if the item is already adde d.`, func() {
101 So(mc.Add(c, items...), ShouldResemble, errors.M ultiError{
102 mc.ErrNotStored, mc.ErrNotStored, mc.Err NotStored})
103 })
104
105 Convey(`GetMulti`, func() {
106 getItems := []mc.Item{
107 mc.NewItem(c, "foo"),
108 mc.NewItem(c, "bar"),
109 mc.NewItem(c, "baz"),
110 }
111 So(mc.Get(c, getItems...), ShouldBeNil)
112 So(getItems[0].Value(), ShouldResemble, []byte(" FOO"))
113 So(getItems[1].Value(), ShouldResemble, []byte(" BAR"))
114 So(getItems[2].Value(), ShouldResemble, []byte(" BAZ"))
115
116 // Namespaced.
117 So(mc.Get(oc, getItems...), ShouldBeNil)
118 So(oitems[0].Value(), ShouldResemble, []byte("OT HER_FOO"))
119 So(oitems[1].Value(), ShouldResemble, []byte("OT HER_BAR"))
120 So(oitems[2].Value(), ShouldResemble, []byte("OT HER_BAZ"))
121 })
122
123 Convey(`SetMulti`, func() {
124 items[2].SetValue([]byte("NEWBAZ"))
125 items = append(items, mc.NewItem(c, "qux").SetVa lue([]byte("QUX")))
126
127 oitems[2].SetValue([]byte("OTHER_NEWBAZ"))
128 oitems = append(oitems, mc.NewItem(c, "qux").Set Value([]byte("OTHER_QUX")))
129
130 So(mc.Set(c, items...), ShouldBeNil)
131 So(mc.Set(oc, oitems...), ShouldBeNil)
132
133 So(get(c, "foo", "bar", "baz", "qux"), ShouldRes emble, []string{"FOO", "BAR", "NEWBAZ", "QUX"})
134 So(get(oc, "foo", "bar", "baz", "qux"), ShouldRe semble,
135 []string{"OTHER_FOO", "OTHER_BAR", "OTHE R_NEWBAZ", "OTHER_QUX"})
136 })
137
138 Convey(`DeleteMulti`, func() {
139 So(mc.Delete(c, "foo", "bar"), ShouldBeNil)
140 So(get(c, "foo", "bar", "baz"), ShouldResemble, []string{"", "", "BAZ"})
141
142 So(mc.Delete(oc, "foo", "bar"), ShouldBeNil)
143 So(get(oc, "foo", "bar", "baz"), ShouldResemble, []string{"", "", "OTHER_BAZ"})
144 })
145
146 Convey(`CompareAndSwapMulti`, func() {
147 // Get all of the values.
148 So(mc.Get(c, items...), ShouldBeNil)
149
150 // Change "foo"'s value.
151 newFoo := mc.NewItem(c, "foo")
152 newFoo.SetAll(items[0])
153 newFoo.SetValue([]byte("NEWFOO"))
154 So(mc.Set(c, newFoo), ShouldBeNil)
155
156 Convey(`Works`, func() {
157 // Do CAS on foo, bar. "foo" should fail because it's changed.
158 items[0].SetValue([]byte("CASFOO"))
159 items[1].SetValue([]byte("CASBAR"))
160 So(mc.CompareAndSwap(c, items...), Shoul dResemble, errors.MultiError{mc.ErrCASConflict, nil, nil})
161
162 So(get(c, "foo", "bar", "baz"), ShouldRe semble, []string{"NEWFOO", "CASBAR", "BAZ"})
163 })
164
165 Convey(`SetAll transfers CAS ID`, func() {
166 // Do CAS on foo, bar. "foo" should fail because it's changed.
167 foo := mc.NewItem(c, "foo")
168 foo.SetAll(items[0])
169 foo.SetValue([]byte("CASFOO"))
170
171 bar := mc.NewItem(c, "bar")
172 bar.SetAll(items[1])
173 bar.SetValue([]byte("CASBAR"))
174
175 So(mc.CompareAndSwap(c, foo, bar), Shoul dResemble, errors.MultiError{mc.ErrCASConflict, nil})
176
177 So(get(c, "foo", "bar"), ShouldResemble, []string{"NEWFOO", "CASBAR"})
178 })
179 })
180
181 Convey(`Flush`, func() {
182 So(mc.Flush(c), ShouldBeNil)
183 So(get(c, "foo", "bar", "baz"), ShouldResemble, []string{"", "", ""})
184 })
185 })
186
187 Convey(`Increment`, func() {
188
189 Convey(`Missing`, func() {
190 // IncrementExisting on non-existent item will r eturn ErrCacheMiss.
191 _, err := mc.IncrementExisting(c, "foo", 1)
192 So(err, ShouldEqual, mc.ErrCacheMiss)
193
194 // IncrementExisting with delta of 0 on non-exis tent item will return
195 // ErrCacheMiss.
196 _, err = mc.IncrementExisting(c, "foo", 1)
197 So(err, ShouldEqual, mc.ErrCacheMiss)
198 })
199
200 Convey(`Initial value (positive)`, func() {
201 // Can set the initial value.
202 nv, err := mc.Increment(c, "foo", 1, 1336)
203 So(err, ShouldBeNil)
204 So(nv, ShouldEqual, 1337)
205 So(get(c, "foo"), ShouldResemble, []string{"1337 "})
206
207 // Followup increment (initial value is ignored) .
208 nv, err = mc.Increment(c, "foo", 1, 20000)
209 So(err, ShouldBeNil)
210 So(nv, ShouldEqual, 1338)
211 })
212
213 Convey(`Initial value (positive, overflows)`, func() {
214 // Can set the initial value.
215 nv, err := mc.Increment(c, "foo", 10, math.MaxUi nt64)
216 So(err, ShouldBeNil)
217 So(nv, ShouldEqual, 9)
218 So(get(c, "foo"), ShouldResemble, []string{"9"})
219 })
220
221 Convey(`Initial value (negative)`, func() {
222 // Can set the initial value.
223 nv, err := mc.Increment(c, "foo", -1, 1338)
224 So(err, ShouldBeNil)
225 So(nv, ShouldEqual, 1337)
226 So(get(c, "foo"), ShouldResemble, []string{"1337 "})
227 })
228
229 Convey(`Initial value (negative, underflows to zero)`, f unc() {
230 // Can set the initial value.
231 nv, err := mc.Increment(c, "foo", -1337, 2)
232 So(err, ShouldBeNil)
233 So(nv, ShouldEqual, 0)
234 So(get(c, "foo"), ShouldResemble, []string{"0"})
235 })
236
237 Convey(`With large initial value (zero delta)`, func() {
238 const iv = uint64(math.MaxUint64 - 1)
239
240 // Can set the initial value.
241 nv, err := mc.Increment(c, "foo", 0, iv)
242 So(err, ShouldBeNil)
243 So(nv, ShouldEqual, uint64(math.MaxUint64-1))
244
245 Convey(`Can increment`, func() {
246 nv, err := mc.IncrementExisting(c, "foo" , 1)
247 So(err, ShouldBeNil)
248 So(nv, ShouldEqual, iv+1)
249 })
250
251 Convey(`Can increment (overflow, wraps)`, func() {
252 nv, err := mc.IncrementExisting(c, "foo" , 10)
253 So(err, ShouldBeNil)
254 So(nv, ShouldEqual, 8)
255 })
256
257 Convey(`Can decrement`, func() {
258 nv, err := mc.IncrementExisting(c, "foo" , -123)
259 So(err, ShouldBeNil)
260 So(nv, ShouldEqual, iv-123)
261 })
262 })
263
264 Convey(`With small initial value (zero delta)`, func() {
265 // Can set the initial value.
266 nv, err := mc.Increment(c, "foo", 0, 1337)
267 So(err, ShouldBeNil)
268 So(nv, ShouldEqual, 1337)
269
270 Convey(`Can decrement (underflow to zero)`, func () {
271 nv, err := mc.IncrementExisting(c, "foo" , -2000)
272 So(err, ShouldBeNil)
273 So(nv, ShouldEqual, 0)
274 })
275 })
276
277 Convey(`Namespaced`, func() {
278 oc := info.MustNamespace(c, "other")
279
280 // Initial (non-namespaced).
281 nv, err := mc.Increment(c, "foo", 1, 100)
282 So(err, ShouldBeNil)
283 So(nv, ShouldEqual, 101)
284
285 // Initial (namespaced).
286 _, err = mc.IncrementExisting(oc, "foo", -1)
287 So(err, ShouldEqual, mc.ErrCacheMiss)
288
289 nv, err = mc.Increment(oc, "foo", -1, 20000)
290 So(err, ShouldBeNil)
291 So(nv, ShouldEqual, 19999)
292
293 // Increment (namespaced).
294 nv, err = mc.IncrementExisting(oc, "foo", 1)
295 So(err, ShouldBeNil)
296 So(nv, ShouldEqual, 20000)
297
298 // Increment (non-namespaced).
299 nv, err = mc.IncrementExisting(c, "foo", 1)
300 So(err, ShouldBeNil)
301 So(nv, ShouldEqual, 102)
302 })
303 })
304
305 Convey(`Stats returns ErrNoStats`, func() {
306 _, err := mc.Stats(c)
307 So(err, ShouldEqual, mc.ErrNoStats)
308 })
309
310 Convey(`A really long key gets hashed.`, func() {
311 key := strings.Repeat("X", keyHashSizeThreshold+1)
312 item := mc.NewItem(c, key)
313 item.SetValue([]byte("ohaithere"))
314 So(mc.Add(c, item), ShouldBeNil)
315
316 // Retrieve the item. The memcache layer doesn't change the key, so we
317 // will assert this by building the hash key ourselves a nd asserting that
318 // it also retrieves the item.
319 hashedKey := hashBytes([]byte(key))
320 So(get(c, hashedKey), ShouldResemble, []string{"ohaither e"})
321 })
322
323 Convey(`Items will expire (sleeps 1 second).`, func() {
324 item := mc.NewItem(c, "foo").SetValue([]byte("FOO")).Set Expiration(1 * time.Second)
325 So(mc.Add(c, item), ShouldBeNil)
326
327 // Immediate Get.
328 So(mc.Get(c, item), ShouldBeNil)
329 So(item.Value(), ShouldResemble, []byte("FOO"))
330
331 // Expire.
332 time.Sleep(1 * time.Second)
333 So(mc.Get(c, item), ShouldEqual, mc.ErrCacheMiss)
334 })
335 })
336 }
OLDNEW
« no previous file with comments | « impl/cloud/memcache.go ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698