| OLD | NEW |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | 1 // Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package gaeconfig | 5 package gaeconfig |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "math/rand" |
| 8 "testing" | 9 "testing" |
| 9 "time" | 10 "time" |
| 10 | 11 |
| 11 "golang.org/x/net/context" | 12 "golang.org/x/net/context" |
| 12 | 13 |
| 13 "github.com/luci/gae/filter/featureBreaker" | 14 "github.com/luci/gae/filter/featureBreaker" |
| 14 "github.com/luci/gae/impl/memory" | |
| 15 mc "github.com/luci/gae/service/memcache" | 15 mc "github.com/luci/gae/service/memcache" |
| 16 "github.com/luci/luci-go/common/clock/testclock" | 16 "github.com/luci/luci-go/common/clock/testclock" |
| 17 » "github.com/luci/luci-go/common/data/caching/proccache" | 17 » memconfig "github.com/luci/luci-go/common/config/impl/memory" |
| 18 » "github.com/luci/luci-go/common/errors" |
| 19 » "github.com/luci/luci-go/server/config" |
| 20 » "github.com/luci/luci-go/server/config/caching" |
| 21 » "github.com/luci/luci-go/server/config/testconfig" |
| 18 | 22 |
| 19 » . "github.com/luci/luci-go/common/testing/assertions" | 23 » "github.com/luci/gae/impl/memory" |
| 24 |
| 20 . "github.com/smartystreets/goconvey/convey" | 25 . "github.com/smartystreets/goconvey/convey" |
| 21 ) | 26 ) |
| 22 | 27 |
| 23 func TestCache(t *testing.T) { | 28 func TestMemcache(t *testing.T) { |
| 24 t.Parallel() | 29 t.Parallel() |
| 25 | 30 |
| 26 Convey("Test cache", t, func() { | 31 Convey("Test cache", t, func() { |
| 27 c := context.Background() | 32 c := context.Background() |
| 28 c = memory.Use(c) | 33 c = memory.Use(c) |
| 29 | 34 |
| 30 pc := &proccache.Cache{} | |
| 31 c = proccache.Use(c, pc) | |
| 32 c, clk := testclock.UseTime(c, testclock.TestTimeUTC) | 35 c, clk := testclock.UseTime(c, testclock.TestTimeUTC) |
| 33 _ = clk | 36 _ = clk |
| 34 | 37 |
| 35 c, mcFB := featureBreaker.FilterMC(c, nil) | 38 c, mcFB := featureBreaker.FilterMC(c, nil) |
| 36 | 39 |
| 37 » » cache := &cache{} | 40 » » getMCStats := func(c context.Context) *mc.Statistics { |
| 41 » » » st, err := mc.Stats(c) |
| 42 » » » if err != nil { |
| 43 » » » » panic(err) |
| 44 » » » } |
| 45 » » » return st |
| 46 » » } |
| 38 | 47 |
| 39 » » Convey("Should be able to store stuff", func() { | 48 » » configDB := map[string]memconfig.ConfigSet{ |
| 40 » » » Convey("memcache+proccache", func() { | 49 » » » "projects/foo": { |
| 41 » » » » cache.Store(c, "item", time.Second, []byte("foob
ar")) | 50 » » » » "test.cfg": "foo", |
| 42 » » » » itm, err := mc.GetKey(c, string(cacheKey("item")
)) | 51 » » » }, |
| 43 » » » » So(err, ShouldBeNil) | 52 » » } |
| 44 » » » » So(itm.Value(), ShouldResemble, []byte("\xff\xf4
\xea\x9c\xf7\xd8\xde\xdc\x01foobar")) | |
| 45 » » » » val, ok := proccache.Get(c, cacheKey("item")) | |
| 46 » » » » So(ok, ShouldBeTrue) | |
| 47 » » » » So(val, ShouldResemble, []byte("foobar")) | |
| 48 | 53 |
| 49 » » » » Convey("and get it back", func() { | 54 » » var backend config.Backend |
| 50 » » » » » So(cache.Retrieve(c, "item"), ShouldRese
mble, []byte("foobar")) | 55 » » backend = &config.ClientBackend{ |
| 56 » » » Provider: &testconfig.LocalClientProvider{ |
| 57 » » » » Base: memconfig.New(configDB), |
| 58 » » » }, |
| 59 » » } |
| 60 » » backend = memcacheBackend(backend, time.Minute) |
| 61 » » c = config.WithBackend(c, backend) |
| 51 | 62 |
| 52 » » » » » Convey("unless it's expired", func() { | 63 » » Convey(`Pulling items from memcache`, func() { |
| 53 » » » » » » clk.Add(time.Second * 2) | 64 » » » var content string |
| 54 » » » » » » So(cache.Retrieve(c, "item"), Sh
ouldBeNil) | |
| 55 » » » » » }) | |
| 56 | 65 |
| 57 » » » » » Convey("when missing from proccache", fu
nc() { | 66 » » » Convey(`Should be able to fetch a config as service`, fu
nc() { |
| 58 » » » » » » proccache.GetCache(c).Mutate(cac
heKey("item"), func(*proccache.Entry) *proccache.Entry { | 67 » » » » So(config.Get(c, config.AsService, "projects/foo
", "test.cfg", config.String(&content), nil), ShouldBeNil) |
| 59 » » » » » » » return nil | 68 » » » » So(content, ShouldEqual, "foo") |
| 60 » » » » » » }) | 69 » » » » So(getMCStats(c).Misses, ShouldEqual, 1) |
| 61 » » » » » » So(cache.Retrieve(c, "item"), Sh
ouldResemble, []byte("foobar")) | 70 » » » » So(getMCStats(c).Hits, ShouldEqual, 0) |
| 62 » » » » » }) | 71 » » » » So(getMCStats(c).Items, ShouldEqual, 1) |
| 63 | 72 |
| 64 » » » » » Convey("unless memcache is corrupt", fun
c() { | 73 » » » » // A second fetch hits memcache. |
| 65 » » » » » » proccache.GetCache(c).Mutate(cac
heKey("item"), func(*proccache.Entry) *proccache.Entry { | 74 » » » » So(config.Get(c, config.AsService, "projects/foo
", "test.cfg", config.String(&content), nil), ShouldBeNil) |
| 66 » » » » » » » return nil | 75 » » » » So(content, ShouldEqual, "foo") |
| 67 » » » » » » }) | 76 » » » » So(getMCStats(c).Misses, ShouldEqual, 1) |
| 68 » » » » » » err := mc.Set(c, mc.NewItem(c, s
tring(cacheKey("item"))).SetValue([]byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\x
ff\xff"))) | 77 » » » » So(getMCStats(c).Hits, ShouldEqual, 1) |
| 69 » » » » » » So(err, ShouldBeNil) | 78 » » » » So(getMCStats(c).Items, ShouldEqual, 1) |
| 70 | 79 |
| 71 » » » » » » So(cache.Retrieve(c, "item"), Sh
ouldBeNil) | 80 » » » » // Memcache expires, full reload. |
| 72 » » » » » }) | 81 » » » » clk.Add(time.Minute) |
| 73 | 82 |
| 74 » » » » }) | 83 » » » » So(config.Get(c, config.AsService, "projects/foo
", "test.cfg", config.String(&content), nil), ShouldBeNil) |
| 84 » » » » So(content, ShouldEqual, "foo") |
| 85 » » » » So(getMCStats(c).Misses, ShouldEqual, 2) |
| 86 » » » » So(getMCStats(c).Hits, ShouldEqual, 1) |
| 87 » » » » So(getMCStats(c).Items, ShouldEqual, 1) |
| 75 }) | 88 }) |
| 76 | 89 |
| 77 » » » Convey("if memcache is down", func() { | 90 » » » Convey(`When memcache is broken`, func() { |
| 78 » » » » mcFB.BreakFeatures(nil, "SetMulti") | 91 » » » » testErr := errors.New("test error") |
| 92 » » » » mcFB.BreakFeatures(testErr, "GetMulti") |
| 79 | 93 |
| 80 » » » » cache.Store(c, "item", time.Second, []byte("foob
ar")) | 94 » » » » So(config.Get(c, config.AsService, "projects/foo
", "test.cfg", config.String(&content), nil), ShouldBeNil) |
| 81 » » » » _, err := mc.GetKey(c, string(cacheKey("item"))) | 95 » » » » So(content, ShouldEqual, "foo") |
| 82 » » » » So(err, ShouldErrLike, mc.ErrCacheMiss) | 96 » » » » So(getMCStats(c).Misses, ShouldEqual, 0) |
| 97 » » » » So(getMCStats(c).Hits, ShouldEqual, 0) |
| 98 » » » » So(getMCStats(c).Items, ShouldEqual, 0) |
| 99 » » » }) |
| 83 | 100 |
| 84 » » » » val, ok := proccache.Get(c, cacheKey("item")) | 101 » » » Convey(`When a cached entry is corrupted`, func() { |
| 85 » » » » So(ok, ShouldBeTrue) | 102 » » » » cacheKey := caching.Key{ |
| 86 » » » » So(val, ShouldResemble, []byte("foobar")) | 103 » » » » » Schema: caching.Schema, |
| 104 » » » » » Op: caching.OpGet, |
| 105 » » » » » ConfigSet: "projects/foo", |
| 106 » » » » » Path: "test.cfg", |
| 107 » » » » » Content: true, |
| 108 » » » » » Authority: config.AsService, |
| 109 » » » » } |
| 110 » » » » So(mc.Set(c, mc.NewItem(c, memcacheKey(&cacheKey
)).SetValue([]byte("!!! trash !!!"))), ShouldBeNil) |
| 111 » » » » So(config.Get(c, config.AsService, "projects/foo
", "test.cfg", config.String(&content), nil), ShouldBeNil) |
| 112 » » » » So(content, ShouldEqual, "foo") |
| 113 » » » » So(getMCStats(c).Misses, ShouldEqual, 0) |
| 114 » » » » So(getMCStats(c).Hits, ShouldEqual, 1) |
| 115 » » » » So(getMCStats(c).Items, ShouldEqual, 1) |
| 116 » » » }) |
| 87 | 117 |
| 88 » » » » Convey("and still get it back", func() { | 118 » » » Convey(`Will skip the cache if an item is too large.`, f
unc() { |
| 89 » » » » » So(cache.Retrieve(c, "item"), ShouldRese
mble, []byte("foobar")) | 119 » » » » // We need to use a pseudo-random string b/c we
compress it. |
| 120 » » » » buf := make([]byte, maxMemCacheSize*2) |
| 121 » » » » rng := rand.New(rand.NewSource(0)) |
| 122 » » » » _, _ = rng.Read(buf) |
| 90 | 123 |
| 91 » » » » » Convey("unless it's expired", func() { | 124 » » » » orig := string(buf) |
| 92 » » » » » » clk.Add(time.Second * 2) | 125 » » » » configDB["projects/foo"]["test.cfg"] = orig |
| 93 » » » » » » So(cache.Retrieve(c, "item"), Sh
ouldBeNil) | 126 |
| 94 » » » » » }) | 127 » » » » So(config.Get(c, config.AsService, "projects/foo
", "test.cfg", config.String(&content), nil), ShouldBeNil) |
| 95 » » » » }) | 128 » » » » So(content, ShouldEqual, orig) |
| 129 » » » » So(getMCStats(c).Misses, ShouldEqual, 1) |
| 130 » » » » So(getMCStats(c).Hits, ShouldEqual, 0) |
| 131 » » » » So(getMCStats(c).Items, ShouldEqual, 0) |
| 96 }) | 132 }) |
| 97 }) | 133 }) |
| 98 }) | 134 }) |
| 99 } | 135 } |
| OLD | NEW |