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