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 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 } |
OLD | NEW |