1
// Copyright 2017 Istio Authors
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
7
// http://www.apache.org/licenses/LICENSE-2.0
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.
34
func testCacheBasic(c Cache, t *testing.T) {
42
// try to get when the entry isn't present
43
{Get, "X", "", false, Stats{Misses: 1}},
45
// add an entry and make sure we can get it
46
{Set, "X", "12", false, Stats{Misses: 1, Writes: 1}},
47
{Get, "X", "12", true, Stats{Misses: 1, Writes: 1, Hits: 1}},
48
{Get, "X", "12", true, Stats{Misses: 1, Writes: 1, Hits: 2}},
50
// check interference between get/set
51
{Get, "Y", "", false, Stats{Misses: 2, Writes: 1, Hits: 2}},
52
{Set, "X", "23", false, Stats{Misses: 2, Writes: 2, Hits: 2}},
53
{Get, "X", "23", true, Stats{Misses: 2, Writes: 2, Hits: 3}},
54
{Set, "Y", "34", false, Stats{Misses: 2, Writes: 3, Hits: 3}},
55
{Get, "X", "23", true, Stats{Misses: 2, Writes: 3, Hits: 4}},
56
{Get, "Y", "34", true, Stats{Misses: 2, Writes: 3, Hits: 5}},
58
// ensure removing X works and doesn't affect Y
59
{Remove, "X", "", false, Stats{Misses: 2, Writes: 3, Hits: 5}},
60
{Get, "X", "", false, Stats{Misses: 3, Writes: 3, Hits: 5}},
61
{Get, "Y", "34", true, Stats{Misses: 3, Writes: 3, Hits: 6}},
63
// make sure everything recovers from remove and then get/set
64
{Remove, "X", "", false, Stats{Misses: 3, Writes: 3, Hits: 6}},
65
{Remove, "Y", "", false, Stats{Misses: 3, Writes: 3, Hits: 6}},
66
{Get, "Y", "", false, Stats{Misses: 4, Writes: 3, Hits: 6}},
67
{Set, "X", "45", false, Stats{Misses: 4, Writes: 4, Hits: 6}},
68
{Get, "X", "45", true, Stats{Misses: 4, Writes: 4, Hits: 7}},
69
{Get, "Y", "", false, Stats{Misses: 5, Writes: 4, Hits: 7}},
71
// remove a missing entry, should be a nop
72
{Remove, "Z", "", false, Stats{Misses: 5, Writes: 4, Hits: 7}},
75
{Set, "A", "45", false, Stats{Misses: 5, Writes: 5, Hits: 7}},
76
{Set, "B", "45", false, Stats{Misses: 5, Writes: 6, Hits: 7}},
77
{RemoveAll, "", "", false, Stats{Misses: 5, Writes: 6, Hits: 7}},
78
{Get, "A", "45", false, Stats{Misses: 6, Writes: 6, Hits: 7}},
79
{Get, "B", "45", false, Stats{Misses: 7, Writes: 6, Hits: 7}},
82
for i, tc := range cases {
83
t.Run(strconv.Itoa(i), func(t *testing.T) {
86
value, result := c.Get(tc.key)
88
if result != tc.result {
89
t.Errorf("Got result %v, expected %v", result, tc.result)
96
t.Errorf("Got value %v, expected %v", str, tc.value)
98
} else if value != nil {
99
t.Errorf("Got value %v, expected nil", value)
103
c.Set(tc.key, tc.value)
114
// removals are inconsistently tracked between implementations, so we ignore these here
118
t.Errorf("Got stats of %v, expected %v", s, tc.stats)
124
func testCacheConcurrent(c Cache, t *testing.T) {
125
wg := new(sync.WaitGroup)
126
workers := runtime.NumCPU()
129
const numIters = 10000
130
for i := 0; i < workers; i++ {
133
for j := 0; j < numIters; j++ {
135
key := "X" + strconv.Itoa(workerNum) + "." + strconv.Itoa(workerNum)
139
t.Errorf("Got false for key %s, expecting true", key)
140
} else if v.(int) != j {
141
t.Errorf("Got %d for key %s, expecting %d", v, key, j)
151
if stats.Misses != 0 {
152
t.Errorf("Got %d misses, expecting %d", stats.Misses, 0)
155
if stats.Hits != uint64(workers*numIters) {
156
t.Errorf("Got %d hits, expecting %d", stats.Hits, workers*numIters)
159
if stats.Writes != uint64(workers*numIters) {
160
t.Errorf("Got %d writes, expecting %d", stats.Writes, workers*numIters*2)
164
// WARNING: This test expects the cache to have been created with no automatic eviction.
165
func testCacheExpiration(c ExpiringCache, evictExpired func(time.Time), t *testing.T) {
168
c.SetWithExpiration("EARLY", "123", 10*time.Millisecond)
169
c.SetWithExpiration("LATER", "123", 20*time.Millisecond+123*time.Nanosecond)
174
_, ok := c.Get("EARLY")
176
t.Errorf("Got no value, expected EARLY to be present")
179
_, ok = c.Get("LATER")
181
t.Errorf("Got no value, expected LATER to be present")
184
if s.Evictions != 0 {
185
t.Errorf("Got %d evictions, expecting 0", s.Evictions)
188
evictExpired(now.Add(15 * time.Millisecond))
191
_, ok = c.Get("EARLY")
193
t.Errorf("Got value, expected EARLY to have been evicted")
196
_, ok = c.Get("LATER")
198
t.Errorf("Got no value, expected LATER to still be present")
201
if s.Evictions != 1 {
202
t.Errorf("Got %d evictions, expecting 1", s.Evictions)
205
evictExpired(now.Add(25 * time.Millisecond))
208
_, ok = c.Get("EARLY")
210
t.Errorf("Got value, expected EARLY to have been evicted")
213
_, ok = c.Get("LATER")
215
t.Errorf("Got value, expected LATER to have been evicted")
218
if s.Evictions != 2 {
219
t.Errorf("Got %d evictions, expecting 2", s.Evictions)
223
func testCacheEvictExpired(c ExpiringCache, t *testing.T) {
224
c.SetWithExpiration("A", "A", 1*time.Millisecond)
228
t.Error("Got no entry, expecting it to be there")
231
time.Sleep(10 * time.Millisecond)
236
t.Error("Got an entry, expecting it to have been evicted")
240
func testCacheEvicter(c ExpiringCache) {
241
c.SetWithExpiration("A", "A", 1*time.Millisecond)
243
// loop until eviction happens. If eviction doesn't happen, this loop will get stuck forever which is fine
245
time.Sleep(10 * time.Millisecond)
249
// item disappeared, we're done
255
func testCacheFinalizer(gate *sync.WaitGroup) {
256
runtime.GC() //nolint: revive
260
func benchmarkCacheGet(c Cache, b *testing.B) {
264
for i := 0; i < b.N; i++ {
269
func benchmarkCacheGetConcurrent(c Cache, b *testing.B) {
278
wg := new(sync.WaitGroup)
279
workers := runtime.NumCPU()
280
each := b.N / workers
284
for i := 0; i < workers; i++ {
286
for j := 0; j < each; j++ {
293
c.Get("foo8") // doesn't exist
301
func benchmarkCacheSet(c Cache, b *testing.B) {
303
for i := 0; i < b.N; i++ {
308
func benchmarkCacheSetConcurrent(c Cache, b *testing.B) {
309
wg := new(sync.WaitGroup)
310
workers := runtime.NumCPU()
311
each := b.N / workers
315
for i := 0; i < workers; i++ {
317
for j := 0; j < each; j++ {
332
func benchmarkCacheGetSetConcurrent(c Cache, b *testing.B) {
341
wg := new(sync.WaitGroup)
342
workers := runtime.NumCPU()
343
each := b.N / workers
347
for i := 0; i < workers; i++ {
349
for j := 0; j < each; j++ {
356
c.Get("foo8") // doesn't exist
366
func benchmarkCacheSetRemove(c Cache, b *testing.B) {
368
for i := 0; i < b.N; i++ {
369
name := "foo" + strconv.Itoa(i)