1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
|
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package cache
import (
"errors"
"testing"
"time"
)
var startTime = time.Date(2023, time.March, 1, 0, 0, 0, 0, time.UTC)
func TestSingleCache(t *testing.T) {
testTime := startTime
timeNow := func() time.Time { return testTime }
c := &Single[string, int]{
timeNow: timeNow,
}
t.Run("NoServeExpired", func(t *testing.T) {
testCacheImpl(t, c, &testTime, false)
})
t.Run("ServeExpired", func(t *testing.T) {
c.Empty()
c.ServeExpired = true
testTime = startTime
testCacheImpl(t, c, &testTime, true)
})
}
func TestLocking(t *testing.T) {
testTime := startTime
timeNow := func() time.Time { return testTime }
c := NewLocking(&Single[string, int]{
timeNow: timeNow,
})
// Just verify that the inner cache's behaviour hasn't changed.
testCacheImpl(t, c, &testTime, false)
}
func testCacheImpl(t *testing.T, c Cache[string, int], testTime *time.Time, serveExpired bool) {
var fillTime time.Time
t.Run("InitialFill", func(t *testing.T) {
fillTime = testTime.Add(time.Hour)
val, err := c.Get("key", func() (int, time.Time, error) {
return 123, fillTime, nil
})
if err != nil {
t.Fatal(err)
}
if val != 123 {
t.Fatalf("got val=%d; want 123", val)
}
})
// Fetching again won't call our fill function
t.Run("SecondFetch", func(t *testing.T) {
*testTime = fillTime.Add(-1 * time.Second)
called := false
val, err := c.Get("key", func() (int, time.Time, error) {
called = true
return -1, fillTime, nil
})
if called {
t.Fatal("wanted no call to fill function")
}
if err != nil {
t.Fatal(err)
}
if val != 123 {
t.Fatalf("got val=%d; want 123", val)
}
})
// Fetching after the expiry time will re-fill
t.Run("ReFill", func(t *testing.T) {
*testTime = fillTime.Add(1)
fillTime = fillTime.Add(time.Hour)
val, err := c.Get("key", func() (int, time.Time, error) {
return 999, fillTime, nil
})
if err != nil {
t.Fatal(err)
}
if val != 999 {
t.Fatalf("got val=%d; want 999", val)
}
})
// An error on fetch will serve the expired value.
t.Run("FetchError", func(t *testing.T) {
if !serveExpired {
t.Skipf("not testing ServeExpired")
}
*testTime = fillTime.Add(time.Hour + 1)
val, err := c.Get("key", func() (int, time.Time, error) {
return 0, time.Time{}, errors.New("some error")
})
if err != nil {
t.Fatal(err)
}
if val != 999 {
t.Fatalf("got val=%d; want 999", val)
}
})
// Fetching a different key re-fills
t.Run("DifferentKey", func(t *testing.T) {
*testTime = fillTime.Add(time.Hour + 1)
var calls int
val, err := c.Get("key1", func() (int, time.Time, error) {
calls++
return 123, fillTime, nil
})
if err != nil {
t.Fatal(err)
}
if val != 123 {
t.Fatalf("got val=%d; want 123", val)
}
if calls != 1 {
t.Errorf("got %d, want 1 call", calls)
}
val, err = c.Get("key2", func() (int, time.Time, error) {
calls++
return 456, fillTime, nil
})
if err != nil {
t.Fatal(err)
}
if val != 456 {
t.Fatalf("got val=%d; want 456", val)
}
if calls != 2 {
t.Errorf("got %d, want 2 call", calls)
}
})
// Calling Forget with the wrong key does nothing, and with the correct
// key will drop the cache.
t.Run("Forget", func(t *testing.T) {
// Add some time so that previously-cached values don't matter.
fillTime = testTime.Add(2 * time.Hour)
*testTime = fillTime.Add(-1 * time.Second)
const key = "key"
var calls int
val, err := c.Get(key, func() (int, time.Time, error) {
calls++
return 123, fillTime, nil
})
if err != nil {
t.Fatal(err)
}
if val != 123 {
t.Fatalf("got val=%d; want 123", val)
}
if calls != 1 {
t.Errorf("got %d, want 1 call", calls)
}
// Forgetting the wrong key does nothing
c.Forget("other")
val, err = c.Get(key, func() (int, time.Time, error) {
t.Fatal("should not be called")
panic("unreachable")
})
if err != nil {
t.Fatal(err)
}
if val != 123 {
t.Fatalf("got val=%d; want 123", val)
}
// Forgetting the correct key re-fills
c.Forget(key)
val, err = c.Get("key2", func() (int, time.Time, error) {
calls++
return 456, fillTime, nil
})
if err != nil {
t.Fatal(err)
}
if val != 456 {
t.Fatalf("got val=%d; want 456", val)
}
if calls != 2 {
t.Errorf("got %d, want 2 call", calls)
}
})
}
|