github.com/letsencrypt/trillian@v1.1.2-0.20180615153820-ae375a99d36a/quota/cacheqm/cache_test.go (about) 1 // Copyright 2017 Google Inc. All Rights Reserved. 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 cacheqm 16 17 import ( 18 "context" 19 "errors" 20 "testing" 21 "time" 22 23 "github.com/golang/mock/gomock" 24 "github.com/google/trillian/quota" 25 "github.com/google/trillian/testonly/matchers" 26 "github.com/kylelemons/godebug/pretty" 27 ) 28 29 const ( 30 minBatchSize = 20 31 maxEntries = 10 32 ) 33 34 var ( 35 specs = []quota.Spec{ 36 {Group: quota.Global, Kind: quota.Read}, 37 {Group: quota.Global, Kind: quota.Write}, 38 } 39 ) 40 41 func TestNewCachedManagerErrors(t *testing.T) { 42 tests := []struct { 43 minBatchSize, maxEntries int 44 }{ 45 {minBatchSize: 0, maxEntries: 10}, 46 {minBatchSize: -1, maxEntries: 10}, 47 {minBatchSize: 10, maxEntries: 0}, 48 {minBatchSize: 10, maxEntries: -1}, 49 } 50 qm := quota.Noop() 51 for _, test := range tests { 52 if _, err := NewCachedManager(qm, test.minBatchSize, test.maxEntries); err == nil { 53 t.Errorf("NewCachedManager(_, %v, %v) returned err = nil, want non-nil", test.minBatchSize, test.maxEntries) 54 } 55 } 56 } 57 58 func TestCachedManager_GetUser(t *testing.T) { 59 ctrl := gomock.NewController(t) 60 defer ctrl.Finish() 61 62 ctx := context.Background() 63 want := "llama" 64 mock := quota.NewMockManager(ctrl) 65 mock.EXPECT().GetUser(ctx, nil).Return(want) 66 67 qm, err := NewCachedManager(mock, minBatchSize, maxEntries) 68 if err != nil { 69 t.Fatalf("NewCachedManager() returned err = %v", err) 70 } 71 72 if got := qm.GetUser(ctx, nil /* req */); got != want { 73 t.Errorf("GetUser() = %v, want = %v", got, want) 74 } 75 } 76 77 func TestCachedManager_PeekTokens(t *testing.T) { 78 ctrl := gomock.NewController(t) 79 defer ctrl.Finish() 80 81 tests := []struct { 82 desc string 83 wantTokens map[quota.Spec]int 84 wantErr error 85 }{ 86 { 87 desc: "success", 88 wantTokens: map[quota.Spec]int{ 89 {Group: quota.Global, Kind: quota.Read}: 10, 90 {Group: quota.Global, Kind: quota.Read}: 11, 91 }, 92 }, 93 { 94 desc: "error", 95 wantErr: errors.New("llama ate all tokens"), 96 }, 97 } 98 99 ctx := context.Background() 100 for _, test := range tests { 101 mock := quota.NewMockManager(ctrl) 102 mock.EXPECT().PeekTokens(ctx, specs).Return(test.wantTokens, test.wantErr) 103 104 qm, err := NewCachedManager(mock, minBatchSize, maxEntries) 105 if err != nil { 106 t.Fatalf("NewCachedManager() returned err = %v", err) 107 } 108 109 tokens, err := qm.PeekTokens(ctx, specs) 110 if diff := pretty.Compare(tokens, test.wantTokens); diff != "" { 111 t.Errorf("%v: post-PeekTokens() diff (-got +want):\n%v", test.desc, diff) 112 } 113 if err != test.wantErr { 114 t.Errorf("%v: PeekTokens() returned err = %#v, want = %#v", test.desc, err, test.wantErr) 115 } 116 } 117 } 118 119 // TestCachedManager_DelegatedMethods tests all delegated methods that have a single error return. 120 func TestCachedManager_DelegatedMethods(t *testing.T) { 121 ctrl := gomock.NewController(t) 122 defer ctrl.Finish() 123 124 ctx := context.Background() 125 tokens := 5 126 for _, want := range []error{nil, errors.New("llama ate all tokens")} { 127 mock := quota.NewMockManager(ctrl) 128 qm, err := NewCachedManager(mock, minBatchSize, maxEntries) 129 if err != nil { 130 t.Fatalf("NewCachedManager() returned err = %v", err) 131 } 132 133 mock.EXPECT().PutTokens(ctx, tokens, specs).Return(want) 134 if err := qm.PutTokens(ctx, tokens, specs); err != want { 135 t.Errorf("PutTokens() returned err = %#v, want = %#v", err, want) 136 } 137 138 mock.EXPECT().ResetQuota(ctx, specs).Return(want) 139 if err := qm.ResetQuota(ctx, specs); err != want { 140 t.Errorf("ResetQuota() returned err = %#v, want = %#v", err, want) 141 } 142 } 143 } 144 145 func TestCachedManager_GetTokens_CachesTokens(t *testing.T) { 146 ctrl := gomock.NewController(t) 147 defer ctrl.Finish() 148 149 ctx := context.Background() 150 tokens := 3 151 mock := quota.NewMockManager(ctrl) 152 mock.EXPECT().GetTokens(ctx, matchers.AtLeast(minBatchSize), specs).Times(2).Return(nil) 153 154 qm, err := NewCachedManager(mock, minBatchSize, maxEntries) 155 if err != nil { 156 t.Fatalf("NewCachedManager() returned err = %v", err) 157 } 158 159 // Quota requests happen in tokens+minBatchSize steps, so that minBatchSize tokens get cached 160 // after the the request is satisfied. 161 // Therefore, the call pattern below is satisfied by just 2 underlying GetTokens() calls. 162 calls := []int{tokens, minBatchSize, tokens, minBatchSize / 2, minBatchSize / 2} 163 for i, call := range calls { 164 if err := qm.GetTokens(ctx, call, specs); err != nil { 165 t.Fatalf("GetTokens() returned err = %v (call #%v)", err, i+1) 166 } 167 } 168 } 169 170 func TestCachedManager_GetTokens_EvictsCache(t *testing.T) { 171 ctrl := gomock.NewController(t) 172 defer ctrl.Finish() 173 174 mock := quota.NewMockManager(ctrl) 175 mock.EXPECT().GetTokens(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(nil) 176 177 ctx := context.Background() 178 maxEntries := 100 179 qm, err := NewCachedManager(mock, minBatchSize, maxEntries) 180 if err != nil { 181 t.Fatalf("NewCachedManager() returned err = %v", err) 182 } 183 184 originalNow := now 185 defer func() { now = originalNow }() 186 currentTime := time.Time{} 187 now = func() time.Time { 188 currentTime = currentTime.Add(1 * time.Second) 189 return currentTime 190 } 191 192 // Ensure Global quotas are the oldest, we don't want those to get evicted regardless of age. 193 tokens := 5 194 if err := qm.GetTokens(ctx, tokens, []quota.Spec{ 195 {Group: quota.Global, Kind: quota.Read}, 196 {Group: quota.Global, Kind: quota.Write}, 197 }); err != nil { 198 t.Fatalf("GetTokens() returned err = %v", err) 199 } 200 201 // Fill the cache up to maxEntries 202 firstTree := int64(10) 203 tree := firstTree 204 for i := 0; i < maxEntries-2; i++ { 205 if err := qm.GetTokens(ctx, tokens, treeSpecs(tree)); err != nil { 206 t.Fatalf("GetTokens() returned err = %v (i = %v)", err, i) 207 } 208 tree++ 209 } 210 211 // All entries added from now on must cause eviction of the oldest entries. 212 // Evict trees in pairs to exercise the inner evict loop. 213 evicts := 20 214 for i := 0; i < evicts; i += 2 { 215 mock.EXPECT().PutTokens(ctx, minBatchSize, treeSpecs(firstTree+int64(i))).Return(nil) 216 mock.EXPECT().PutTokens(ctx, minBatchSize, treeSpecs(firstTree+int64(i+1))).Return(nil) 217 218 specs := []quota.Spec{treeSpec(tree), treeSpec(tree + 1)} 219 tree += 2 220 if err := qm.GetTokens(ctx, tokens, specs); err != nil { 221 t.Fatalf("GetTokens() returned err = %v (i = %v)", err, i) 222 } 223 } 224 225 waitChan := make(chan bool, 1) 226 go func() { 227 qm.(*manager).wait() 228 waitChan <- true 229 }() 230 231 select { 232 case <-waitChan: 233 // OK, test exited cleanly 234 case <-time.After(5 * time.Second): 235 t.Errorf("Timed out waiting for qm.wait(), failing test") 236 } 237 } 238 239 func TestManager_GetTokensErrors(t *testing.T) { 240 ctrl := gomock.NewController(t) 241 defer ctrl.Finish() 242 243 ctx := context.Background() 244 want := errors.New("llama ate all tokens") 245 mock := quota.NewMockManager(ctrl) 246 mock.EXPECT().GetTokens(ctx, gomock.Any(), specs).Return(want) 247 248 qm, err := NewCachedManager(mock, minBatchSize, maxEntries) 249 if err != nil { 250 t.Fatalf("NewCachedManager() returned err = %v", err) 251 } 252 253 if err := qm.GetTokens(ctx, 5 /* numTokens */, specs); err != want { 254 t.Errorf("GetTokens() returned err = %#v, want = %#v", err, want) 255 } 256 } 257 258 func treeSpecs(treeID int64) []quota.Spec { 259 return []quota.Spec{treeSpec(treeID)} 260 } 261 262 func treeSpec(treeID int64) quota.Spec { 263 return quota.Spec{Group: quota.Tree, Kind: quota.Write, TreeID: treeID} 264 }