Skip to content

Commit 2e104a8

Browse files
authored
cache: Add ability to manually advance "now" for mock cache (#601)
Add an `.Advance()` method to MockCache and InstrumentedMockCache to allow the time considered "now" to be moved without needing to actually sleep. This is useful for testing when items are set with a TTL and you would like for them to actually expire as they would in a real cache. Part of grafana/mimir#9386 Signed-off-by: Nick Pillitteri <nick.pillitteri@grafana.com>
1 parent 9bd6dd3 commit 2e104a8

File tree

2 files changed

+24
-7
lines changed

2 files changed

+24
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@
232232
* [ENHANCEMENT] grpcclient: Support custom gRPC compressors. #583
233233
* [ENHANCEMENT] Adapt `metrics.SendSumOfGaugesPerTenant` to use `metrics.MetricOption`. #584
234234
* [ENHANCEMENT] Cache: Add `.Add()` and `.Set()` methods to cache clients. #591
235+
* [ENHANCEMENT] Cache: Add `.Advance()` methods to mock cache clients for easier testing of TTLs. #601
235236
* [CHANGE] Backoff: added `Backoff.ErrCause()` which is like `Backoff.Err()` but returns the context cause if backoff is terminated because the context has been canceled. #538
236237
* [BUGFIX] spanlogger: Support multiple tenant IDs. #59
237238
* [BUGFIX] Memberlist: fixed corrupted packets when sending compound messages with more than 255 messages or messages bigger than 64KB. #85

cache/mock.go

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,26 @@ var (
1919
type MockCache struct {
2020
mu sync.Mutex
2121
cache map[string]Item
22+
now time.Time
2223
}
2324

2425
func NewMockCache() *MockCache {
25-
c := &MockCache{}
26+
c := &MockCache{now: time.Now()}
2627
c.Flush()
2728
return c
2829
}
2930

3031
func (m *MockCache) SetAsync(key string, value []byte, ttl time.Duration) {
3132
m.mu.Lock()
3233
defer m.mu.Unlock()
33-
m.cache[key] = Item{Data: value, ExpiresAt: time.Now().Add(ttl)}
34+
m.cache[key] = Item{Data: value, ExpiresAt: m.now.Add(ttl)}
3435
}
3536

3637
func (m *MockCache) SetMultiAsync(data map[string][]byte, ttl time.Duration) {
3738
m.mu.Lock()
3839
defer m.mu.Unlock()
3940

40-
exp := time.Now().Add(ttl)
41+
exp := m.now.Add(ttl)
4142
for key, val := range data {
4243
m.cache[key] = Item{Data: val, ExpiresAt: exp}
4344
}
@@ -46,19 +47,19 @@ func (m *MockCache) SetMultiAsync(data map[string][]byte, ttl time.Duration) {
4647
func (m *MockCache) Set(_ context.Context, key string, value []byte, ttl time.Duration) error {
4748
m.mu.Lock()
4849
defer m.mu.Unlock()
49-
m.cache[key] = Item{Data: value, ExpiresAt: time.Now().Add(ttl)}
50+
m.cache[key] = Item{Data: value, ExpiresAt: m.now.Add(ttl)}
5051
return nil
5152
}
5253

5354
func (m *MockCache) Add(_ context.Context, key string, value []byte, ttl time.Duration) error {
5455
m.mu.Lock()
5556
defer m.mu.Unlock()
5657

57-
if _, ok := m.cache[key]; ok {
58+
if i, ok := m.cache[key]; ok && i.ExpiresAt.After(m.now) {
5859
return ErrNotStored
5960
}
6061

61-
m.cache[key] = Item{Data: value, ExpiresAt: time.Now().Add(ttl)}
62+
m.cache[key] = Item{Data: value, ExpiresAt: m.now.Add(ttl)}
6263
return nil
6364
}
6465

@@ -68,7 +69,7 @@ func (m *MockCache) GetMulti(_ context.Context, keys []string, _ ...Option) map[
6869

6970
found := make(map[string][]byte, len(keys))
7071

71-
now := time.Now()
72+
now := m.now
7273
for _, k := range keys {
7374
v, ok := m.cache[k]
7475
if ok && now.Before(v.ExpiresAt) {
@@ -107,13 +108,22 @@ func (m *MockCache) Delete(_ context.Context, key string) error {
107108
return nil
108109
}
109110

111+
// Flush removes all entries from the cache
110112
func (m *MockCache) Flush() {
111113
m.mu.Lock()
112114
defer m.mu.Unlock()
113115

114116
m.cache = map[string]Item{}
115117
}
116118

119+
// Advance changes "now" by the given duration
120+
func (m *MockCache) Advance(d time.Duration) {
121+
m.mu.Lock()
122+
defer m.mu.Unlock()
123+
124+
m.now = m.now.Add(d)
125+
}
126+
117127
// InstrumentedMockCache is a mocked cache implementation which also tracks the number
118128
// of times its functions are called.
119129
type InstrumentedMockCache struct {
@@ -172,10 +182,16 @@ func (m *InstrumentedMockCache) GetItems() map[string]Item {
172182
return m.cache.GetItems()
173183
}
174184

185+
// Flush removes all entries from the cache
175186
func (m *InstrumentedMockCache) Flush() {
176187
m.cache.Flush()
177188
}
178189

190+
// Advance changes "now" by the given duration
191+
func (m *InstrumentedMockCache) Advance(d time.Duration) {
192+
m.cache.Advance(d)
193+
}
194+
179195
func (m *InstrumentedMockCache) CountStoreCalls() int {
180196
return int(m.storeCount.Load())
181197
}

0 commit comments

Comments
 (0)