1package cache
2
3import (
4 "strings"
5 "testing"
6 "time"
7
8 "github.com/stretchr/testify/assert"
9 "github.com/stretchr/testify/require"
10
11 "github.com/MichaelMure/git-bug/entities/bug"
12 "github.com/MichaelMure/git-bug/entities/identity"
13 "github.com/MichaelMure/git-bug/entity"
14 "github.com/MichaelMure/git-bug/query"
15 "github.com/MichaelMure/git-bug/repository"
16)
17
18func TestCache(t *testing.T) {
19 repo := repository.CreateGoGitTestRepo(t, false)
20
21 indexCount := func(t *testing.T, name string) uint64 {
22 t.Helper()
23
24 idx, err := repo.GetIndex(name)
25 require.NoError(t, err)
26 count, err := idx.DocCount()
27 require.NoError(t, err)
28
29 return count
30 }
31
32 cache, err := NewRepoCacheNoEvents(repo)
33 require.NoError(t, err)
34
35 // Create, set and get user identity
36 iden1, err := cache.Identities().New("René Descartes", "rene@descartes.fr")
37 require.NoError(t, err)
38 err = cache.SetUserIdentity(iden1)
39 require.NoError(t, err)
40 userIden, err := cache.GetUserIdentity()
41 require.NoError(t, err)
42 require.Equal(t, iden1.Id(), userIden.Id())
43
44 // it's possible to create two identical identities
45 iden2, err := cache.Identities().New("René Descartes", "rene@descartes.fr")
46 require.NoError(t, err)
47
48 // Two identical identities yield a different id
49 require.NotEqual(t, iden1.Id(), iden2.Id())
50
51 // There is now two identities in the cache
52 require.Len(t, cache.Identities().AllIds(), 2)
53 require.Len(t, cache.identities.excerpts, 2)
54 require.Len(t, cache.identities.cached, 2)
55 require.Equal(t, uint64(2), indexCount(t, identity.Namespace))
56 require.Equal(t, uint64(0), indexCount(t, bug.Namespace))
57
58 // Create a bug
59 bug1, _, err := cache.Bugs().New("title", "message")
60 require.NoError(t, err)
61
62 // It's possible to create two identical bugs
63 bug2, _, err := cache.Bugs().New("title", "marker")
64 require.NoError(t, err)
65
66 // two identical bugs yield a different id
67 require.NotEqual(t, bug1.Id(), bug2.Id())
68
69 // There is now two bugs in the cache
70 require.Len(t, cache.Bugs().AllIds(), 2)
71 require.Len(t, cache.bugs.excerpts, 2)
72 require.Len(t, cache.bugs.cached, 2)
73 require.Equal(t, uint64(2), indexCount(t, identity.Namespace))
74 require.Equal(t, uint64(2), indexCount(t, bug.Namespace))
75
76 // Resolving
77 _, err = cache.Identities().Resolve(iden1.Id())
78 require.NoError(t, err)
79 _, err = cache.Identities().ResolveExcerpt(iden1.Id())
80 require.NoError(t, err)
81 _, err = cache.Identities().ResolvePrefix(iden1.Id().String()[:10])
82 require.NoError(t, err)
83
84 _, err = cache.Bugs().Resolve(bug1.Id())
85 require.NoError(t, err)
86 _, err = cache.Bugs().ResolveExcerpt(bug1.Id())
87 require.NoError(t, err)
88 _, err = cache.Bugs().ResolvePrefix(bug1.Id().String()[:10])
89 require.NoError(t, err)
90
91 // Querying
92 q, err := query.Parse("status:open author:descartes sort:edit-asc")
93 require.NoError(t, err)
94 res, err := cache.Bugs().Query(q)
95 require.NoError(t, err)
96 require.Len(t, res, 2)
97
98 q, err = query.Parse("status:open marker") // full-text search
99 require.NoError(t, err)
100 res, err = cache.Bugs().Query(q)
101 require.NoError(t, err)
102 require.Len(t, res, 1)
103
104 // Close
105 require.NoError(t, cache.Close())
106 require.Empty(t, cache.bugs.cached)
107 require.Empty(t, cache.bugs.excerpts)
108 require.Empty(t, cache.identities.cached)
109 require.Empty(t, cache.identities.excerpts)
110
111 // Reload, only excerpt are loaded, but as we need to load the identities used in the bugs
112 // to check the signatures, we also load the identity used above
113 cache, err = NewRepoCacheNoEvents(repo)
114 require.NoError(t, err)
115
116 require.Len(t, cache.bugs.cached, 0)
117 require.Len(t, cache.bugs.excerpts, 2)
118 require.Len(t, cache.identities.cached, 0)
119 require.Len(t, cache.identities.excerpts, 2)
120 require.Equal(t, uint64(2), indexCount(t, identity.Namespace))
121 require.Equal(t, uint64(2), indexCount(t, bug.Namespace))
122
123 // Resolving load from the disk
124 _, err = cache.Identities().Resolve(iden1.Id())
125 require.NoError(t, err)
126 _, err = cache.Identities().ResolveExcerpt(iden1.Id())
127 require.NoError(t, err)
128 _, err = cache.Identities().ResolvePrefix(iden1.Id().String()[:10])
129 require.NoError(t, err)
130
131 _, err = cache.Bugs().Resolve(bug1.Id())
132 require.NoError(t, err)
133 _, err = cache.Bugs().ResolveExcerpt(bug1.Id())
134 require.NoError(t, err)
135 _, err = cache.Bugs().ResolvePrefix(bug1.Id().String()[:10])
136 require.NoError(t, err)
137
138 // Close
139 require.NoError(t, cache.Close())
140 require.Empty(t, cache.bugs.cached)
141 require.Empty(t, cache.bugs.excerpts)
142 require.Empty(t, cache.identities.cached)
143 require.Empty(t, cache.identities.excerpts)
144}
145
146func TestCachePushPull(t *testing.T) {
147 repoA, repoB, _ := repository.SetupGoGitReposAndRemote(t)
148
149 cacheA := createTestRepoCacheNoEvents(t, repoA)
150 cacheB := createTestRepoCacheNoEvents(t, repoB)
151
152 // Create, set and get user identity
153 reneA, err := cacheA.Identities().New("René Descartes", "rene@descartes.fr")
154 require.NoError(t, err)
155 err = cacheA.SetUserIdentity(reneA)
156 require.NoError(t, err)
157 isaacB, err := cacheB.Identities().New("Isaac Newton", "isaac@newton.uk")
158 require.NoError(t, err)
159 err = cacheB.SetUserIdentity(isaacB)
160 require.NoError(t, err)
161
162 // distribute the identity
163 _, err = cacheA.Push("origin")
164 require.NoError(t, err)
165 err = cacheB.Pull("origin")
166 require.NoError(t, err)
167
168 // Create a bug in A
169 _, _, err = cacheA.Bugs().New("bug1", "message")
170 require.NoError(t, err)
171
172 // A --> remote --> B
173 _, err = cacheA.Push("origin")
174 require.NoError(t, err)
175
176 err = cacheB.Pull("origin")
177 require.NoError(t, err)
178
179 require.Len(t, cacheB.Bugs().AllIds(), 1)
180
181 // retrieve and set identity
182 reneB, err := cacheB.Identities().Resolve(reneA.Id())
183 require.NoError(t, err)
184
185 err = cacheB.SetUserIdentity(reneB)
186 require.NoError(t, err)
187
188 // B --> remote --> A
189 _, _, err = cacheB.Bugs().New("bug2", "message")
190 require.NoError(t, err)
191
192 _, err = cacheB.Push("origin")
193 require.NoError(t, err)
194
195 err = cacheA.Pull("origin")
196 require.NoError(t, err)
197
198 require.Len(t, cacheA.Bugs().AllIds(), 2)
199}
200
201func TestRemove(t *testing.T) {
202 repo := repository.CreateGoGitTestRepo(t, false)
203 remoteA := repository.CreateGoGitTestRepo(t, true)
204 remoteB := repository.CreateGoGitTestRepo(t, true)
205
206 err := repo.AddRemote("remoteA", remoteA.GetLocalRemote())
207 require.NoError(t, err)
208
209 err = repo.AddRemote("remoteB", remoteB.GetLocalRemote())
210 require.NoError(t, err)
211
212 repoCache := createTestRepoCacheNoEvents(t, repo)
213
214 rene, err := repoCache.Identities().New("René Descartes", "rene@descartes.fr")
215 require.NoError(t, err)
216
217 err = repoCache.SetUserIdentity(rene)
218 require.NoError(t, err)
219
220 _, _, err = repoCache.Bugs().New("title", "message")
221 require.NoError(t, err)
222
223 // and one more for testing
224 b1, _, err := repoCache.Bugs().New("title", "message")
225 require.NoError(t, err)
226
227 _, err = repoCache.Push("remoteA")
228 require.NoError(t, err)
229
230 _, err = repoCache.Push("remoteB")
231 require.NoError(t, err)
232
233 _, err = repoCache.Fetch("remoteA")
234 require.NoError(t, err)
235
236 _, err = repoCache.Fetch("remoteB")
237 require.NoError(t, err)
238
239 err = repoCache.Bugs().Remove(b1.Id().String())
240 require.NoError(t, err)
241 assert.Len(t, repoCache.bugs.cached, 1)
242 assert.Len(t, repoCache.bugs.excerpts, 1)
243
244 _, err = repoCache.Bugs().Resolve(b1.Id())
245 assert.ErrorAs(t, entity.ErrNotFound{}, err)
246}
247
248func TestCacheEviction(t *testing.T) {
249 repo := repository.CreateGoGitTestRepo(t, false)
250 repoCache := createTestRepoCacheNoEvents(t, repo)
251 repoCache.setCacheSize(2)
252
253 require.Equal(t, 2, repoCache.bugs.maxLoaded)
254 require.Len(t, repoCache.bugs.cached, 0)
255 require.Equal(t, repoCache.bugs.lru.Len(), 0)
256
257 // Generating some bugs
258 rene, err := repoCache.Identities().New("René Descartes", "rene@descartes.fr")
259 require.NoError(t, err)
260 err = repoCache.SetUserIdentity(rene)
261 require.NoError(t, err)
262
263 bug1, _, err := repoCache.Bugs().New("title", "message")
264 require.NoError(t, err)
265
266 checkBugPresence(t, repoCache, bug1, true)
267 require.Len(t, repoCache.bugs.cached, 1)
268 require.Equal(t, 1, repoCache.bugs.lru.Len())
269
270 bug2, _, err := repoCache.Bugs().New("title", "message")
271 require.NoError(t, err)
272
273 checkBugPresence(t, repoCache, bug1, true)
274 checkBugPresence(t, repoCache, bug2, true)
275 require.Len(t, repoCache.bugs.cached, 2)
276 require.Equal(t, 2, repoCache.bugs.lru.Len())
277
278 // Number of bugs should not exceed max size of lruCache, oldest one should be evicted
279 bug3, _, err := repoCache.Bugs().New("title", "message")
280 require.NoError(t, err)
281
282 require.Len(t, repoCache.bugs.cached, 2)
283 require.Equal(t, 2, repoCache.bugs.lru.Len())
284 checkBugPresence(t, repoCache, bug1, false)
285 checkBugPresence(t, repoCache, bug2, true)
286 checkBugPresence(t, repoCache, bug3, true)
287
288 // Accessing bug should update position in lruCache and therefore it should not be evicted
289 repoCache.bugs.lru.Get(bug2.Id())
290 oldestId, _ := repoCache.bugs.lru.GetOldest()
291 require.Equal(t, bug3.Id(), oldestId)
292
293 checkBugPresence(t, repoCache, bug1, false)
294 checkBugPresence(t, repoCache, bug2, true)
295 checkBugPresence(t, repoCache, bug3, true)
296 require.Len(t, repoCache.bugs.cached, 2)
297 require.Equal(t, 2, repoCache.bugs.lru.Len())
298}
299
300func TestLongDescription(t *testing.T) {
301 // See https://github.com/MichaelMure/git-bug/issues/606
302
303 text := strings.Repeat("x", 65536)
304
305 repo := repository.CreateGoGitTestRepo(t, false)
306
307 backend := createTestRepoCacheNoEvents(t, repo)
308
309 i, err := backend.Identities().New("René Descartes", "rene@descartes.fr")
310 require.NoError(t, err)
311
312 _, _, err = backend.Bugs().NewRaw(i, time.Now().Unix(), text, text, nil, nil)
313 require.NoError(t, err)
314}
315
316func checkBugPresence(t *testing.T, cache *RepoCache, bug *BugCache, presence bool) {
317 t.Helper()
318
319 id := bug.Id()
320 require.Equal(t, presence, cache.bugs.lru.Contains(id))
321 b, ok := cache.bugs.cached[id]
322 require.Equal(t, presence, ok)
323 if ok {
324 require.Equal(t, bug, b)
325 }
326}
327
328func createTestRepoCacheNoEvents(t *testing.T, repo repository.TestedRepo) *RepoCache {
329 t.Helper()
330
331 cache, err := NewRepoCacheNoEvents(repo)
332 require.NoError(t, err)
333
334 t.Cleanup(func() {
335 require.NoError(t, cache.Close())
336 })
337
338 return cache
339}