go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/filter/count/count_test.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package count 16 17 import ( 18 "context" 19 "fmt" 20 "testing" 21 22 "go.chromium.org/luci/gae/filter/featureBreaker" 23 "go.chromium.org/luci/gae/impl/memory" 24 ds "go.chromium.org/luci/gae/service/datastore" 25 "go.chromium.org/luci/gae/service/info" 26 "go.chromium.org/luci/gae/service/mail" 27 "go.chromium.org/luci/gae/service/memcache" 28 "go.chromium.org/luci/gae/service/taskqueue" 29 "go.chromium.org/luci/gae/service/user" 30 31 . "github.com/smartystreets/goconvey/convey" 32 . "go.chromium.org/luci/common/testing/assertions" 33 ) 34 35 func shouldHaveSuccessesAndErrors(actual any, expected ...any) string { 36 a := actual.(Entry) 37 if len(expected) != 2 { 38 panic("Invalid number of expected, should be 2 (successes, errors).") 39 } 40 s, e := expected[0].(int), expected[1].(int) 41 42 if val := a.Successes(); val != s { 43 return fmt.Sprintf("Actual successes (%d) don't match expected (%d)", val, s) 44 } 45 if val := a.Errors(); val != e { 46 return fmt.Sprintf("Actual errors (%d) don't match expected (%d)", val, e) 47 } 48 return "" 49 } 50 51 func die(err error) { 52 if err != nil { 53 panic(err) 54 } 55 } 56 57 func TestCount(t *testing.T) { 58 t.Parallel() 59 60 Convey("Test Count filter", t, func() { 61 c, fb := featureBreaker.FilterRDS(memory.Use(context.Background()), nil) 62 c, ctr := FilterRDS(c) 63 64 So(c, ShouldNotBeNil) 65 So(ctr, ShouldNotBeNil) 66 67 vals := []ds.PropertyMap{{ 68 "Val": ds.MkProperty(100), 69 "$key": ds.MkPropertyNI(ds.NewKey(c, "Kind", "", 1, nil)), 70 }} 71 72 Convey("Calling a ds function should reflect in counter", func() { 73 So(ds.Put(c, vals), ShouldBeNil) 74 So(ctr.PutMulti.Successes(), ShouldEqual, 1) 75 76 Convey("effects are cumulative", func() { 77 So(ds.Put(c, vals), ShouldBeNil) 78 So(ctr.PutMulti.Successes(), ShouldEqual, 2) 79 80 Convey("even within transactions", func() { 81 die(ds.RunInTransaction(c, func(c context.Context) error { 82 So(ds.Put(c, append(vals, vals[0])), ShouldBeNil) 83 return nil 84 }, nil)) 85 }) 86 }) 87 }) 88 89 Convey("errors count against errors", func() { 90 fb.BreakFeatures(nil, "GetMulti") 91 92 So(ds.Get(c, vals), ShouldErrLike, `"GetMulti" is broken`) 93 So(ctr.GetMulti.Errors(), ShouldEqual, 1) 94 95 fb.UnbreakFeatures("GetMulti") 96 97 So(ds.Put(c, vals), ShouldBeNil) 98 99 die(ds.Get(c, vals)) 100 So(ctr.GetMulti.Errors(), ShouldEqual, 1) 101 So(ctr.GetMulti.Successes(), ShouldEqual, 1) 102 So(ctr.GetMulti.Total(), ShouldEqual, 2) 103 }) 104 105 Convey(`datastore.Stop does not count as an error for queries`, func() { 106 fb.BreakFeatures(ds.Stop, "Run") 107 108 So(ds.Run(c, ds.NewQuery("foof"), func(_ ds.PropertyMap) error { 109 return nil 110 }), ShouldBeNil) 111 So(ctr.Run.Successes(), ShouldEqual, 1) 112 So(ctr.Run.Errors(), ShouldEqual, 0) 113 So(ctr.Run.Total(), ShouldEqual, 1) 114 }) 115 }) 116 117 Convey("works for memcache", t, func() { 118 c, ctr := FilterMC(memory.Use(context.Background())) 119 So(c, ShouldNotBeNil) 120 So(ctr, ShouldNotBeNil) 121 122 die(memcache.Set(c, memcache.NewItem(c, "hello").SetValue([]byte("sup")))) 123 124 _, err := memcache.GetKey(c, "Wat") 125 So(err, ShouldNotBeNil) 126 127 _, err = memcache.GetKey(c, "hello") 128 die(err) 129 130 So(ctr.SetMulti, shouldHaveSuccessesAndErrors, 1, 0) 131 So(ctr.GetMulti, shouldHaveSuccessesAndErrors, 2, 0) 132 So(ctr.NewItem, shouldHaveSuccessesAndErrors, 3, 0) 133 }) 134 135 Convey("works for taskqueue", t, func() { 136 c, ctr := FilterTQ(memory.Use(context.Background())) 137 So(c, ShouldNotBeNil) 138 So(ctr, ShouldNotBeNil) 139 140 die(taskqueue.Add(c, "", &taskqueue.Task{Name: "wat"})) 141 So(taskqueue.Add(c, "DNE_QUEUE", &taskqueue.Task{Name: "wat"}), 142 ShouldErrLike, "UNKNOWN_QUEUE") 143 144 So(ctr.AddMulti, shouldHaveSuccessesAndErrors, 1, 1) 145 }) 146 147 Convey("works for global info", t, func() { 148 c, fb := featureBreaker.FilterGI(memory.Use(context.Background()), nil) 149 c, ctr := FilterGI(c) 150 So(c, ShouldNotBeNil) 151 So(ctr, ShouldNotBeNil) 152 153 _, err := info.Namespace(c, "foo") 154 die(err) 155 fb.BreakFeatures(nil, "Namespace") 156 _, err = info.Namespace(c, "boom") 157 So(err, ShouldErrLike, `"Namespace" is broken`) 158 159 So(ctr.Namespace, shouldHaveSuccessesAndErrors, 1, 1) 160 }) 161 162 Convey("works for user", t, func() { 163 c, fb := featureBreaker.FilterUser(memory.Use(context.Background()), nil) 164 c, ctr := FilterUser(c) 165 So(c, ShouldNotBeNil) 166 So(ctr, ShouldNotBeNil) 167 168 _, err := user.CurrentOAuth(c, "foo") 169 die(err) 170 fb.BreakFeatures(nil, "CurrentOAuth") 171 _, err = user.CurrentOAuth(c, "foo") 172 So(err, ShouldErrLike, `"CurrentOAuth" is broken`) 173 174 So(ctr.CurrentOAuth, shouldHaveSuccessesAndErrors, 1, 1) 175 }) 176 177 Convey("works for mail", t, func() { 178 c, fb := featureBreaker.FilterMail(memory.Use(context.Background()), nil) 179 c, ctr := FilterMail(c) 180 So(c, ShouldNotBeNil) 181 So(ctr, ShouldNotBeNil) 182 183 err := mail.Send(c, &mail.Message{ 184 Sender: "admin@example.com", 185 To: []string{"coolDood@example.com"}, 186 Body: "hi", 187 }) 188 die(err) 189 190 fb.BreakFeatures(nil, "Send") 191 err = mail.Send(c, &mail.Message{ 192 Sender: "admin@example.com", 193 To: []string{"coolDood@example.com"}, 194 Body: "hi", 195 }) 196 So(err, ShouldErrLike, `"Send" is broken`) 197 198 So(ctr.Send, shouldHaveSuccessesAndErrors, 1, 1) 199 }) 200 } 201 202 func ExampleFilterRDS() { 203 // Set up your context using a base service implementation (memory or prod) 204 c := memory.Use(context.Background()) 205 206 // Apply the counter.FilterRDS 207 c, counter := FilterRDS(c) 208 209 // functions use ds from the context like normal... they don't need to know 210 // that there are any filters at all. 211 someCalledFunc := func(c context.Context) { 212 vals := []ds.PropertyMap{{ 213 "FieldName": ds.MkProperty(100), 214 "$key": ds.MkProperty(ds.NewKey(c, "Kind", "", 1, nil))}, 215 } 216 if err := ds.Put(c, vals); err != nil { 217 panic(err) 218 } 219 } 220 221 // Using the other function. 222 someCalledFunc(c) 223 someCalledFunc(c) 224 225 // Then we can see what happened! 226 fmt.Printf("%d\n", counter.PutMulti.Successes()) 227 // Output: 228 // 2 229 }