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 require.Len(t, cache.bugs.cached, 1)
139 require.Len(t, cache.bugs.excerpts, 2)
140 require.Len(t, cache.identities.cached, 1)
141 require.Len(t, cache.identities.excerpts, 2)
142 require.Equal(t, uint64(2), indexCount(t, identity.Namespace))
143 require.Equal(t, uint64(2), indexCount(t, bug.Namespace))
144
145 // Remove + RemoveAll
146 err = cache.Identities().Remove(iden1.Id().String()[:10])
147 require.NoError(t, err)
148 err = cache.Bugs().Remove(bug1.Id().String()[:10])
149 require.NoError(t, err)
150 require.Len(t, cache.bugs.cached, 0)
151 require.Len(t, cache.bugs.excerpts, 1)
152 require.Len(t, cache.identities.cached, 0)
153 require.Len(t, cache.identities.excerpts, 1)
154 require.Equal(t, uint64(1), indexCount(t, identity.Namespace))
155 require.Equal(t, uint64(1), indexCount(t, bug.Namespace))
156
157 _, err = cache.Identities().New("René Descartes", "rene@descartes.fr")
158 require.NoError(t, err)
159 _, _, err = cache.Bugs().NewRaw(iden2, time.Now().Unix(), "title", "message", nil, nil)
160 require.NoError(t, err)
161
162 err = cache.RemoveAll()
163 require.NoError(t, err)
164 require.Len(t, cache.bugs.cached, 0)
165 require.Len(t, cache.bugs.excerpts, 0)
166 require.Len(t, cache.identities.cached, 0)
167 require.Len(t, cache.identities.excerpts, 0)
168 require.Equal(t, uint64(0), indexCount(t, identity.Namespace))
169 require.Equal(t, uint64(0), indexCount(t, bug.Namespace))
170
171 // Close
172 require.NoError(t, cache.Close())
173 require.Empty(t, cache.bugs.cached)
174 require.Empty(t, cache.bugs.excerpts)
175 require.Empty(t, cache.identities.cached)
176 require.Empty(t, cache.identities.excerpts)
177}
178
179func TestCachePushPull(t *testing.T) {
180 repoA, repoB, _ := repository.SetupGoGitReposAndRemote(t)
181
182 cacheA := createTestRepoCacheNoEvents(t, repoA)
183 cacheB := createTestRepoCacheNoEvents(t, repoB)
184
185 // Create, set and get user identity
186 reneA, err := cacheA.Identities().New("René Descartes", "rene@descartes.fr")
187 require.NoError(t, err)
188 err = cacheA.SetUserIdentity(reneA)
189 require.NoError(t, err)
190 isaacB, err := cacheB.Identities().New("Isaac Newton", "isaac@newton.uk")
191 require.NoError(t, err)
192 err = cacheB.SetUserIdentity(isaacB)
193 require.NoError(t, err)
194
195 // distribute the identity
196 _, err = cacheA.Push("origin")
197 require.NoError(t, err)
198 err = cacheB.Pull("origin")
199 require.NoError(t, err)
200
201 // Create a bug in A
202 _, _, err = cacheA.Bugs().New("bug1", "message")
203 require.NoError(t, err)
204
205 // A --> remote --> B
206 _, err = cacheA.Push("origin")
207 require.NoError(t, err)
208
209 err = cacheB.Pull("origin")
210 require.NoError(t, err)
211
212 require.Len(t, cacheB.Bugs().AllIds(), 1)
213
214 // retrieve and set identity
215 reneB, err := cacheB.Identities().Resolve(reneA.Id())
216 require.NoError(t, err)
217
218 err = cacheB.SetUserIdentity(reneB)
219 require.NoError(t, err)
220
221 // B --> remote --> A
222 _, _, err = cacheB.Bugs().New("bug2", "message")
223 require.NoError(t, err)
224
225 _, err = cacheB.Push("origin")
226 require.NoError(t, err)
227
228 err = cacheA.Pull("origin")
229 require.NoError(t, err)
230
231 require.Len(t, cacheA.Bugs().AllIds(), 2)
232}
233
234func TestRemove(t *testing.T) {
235 repo := repository.CreateGoGitTestRepo(t, false)
236 remoteA := repository.CreateGoGitTestRepo(t, true)
237 remoteB := repository.CreateGoGitTestRepo(t, true)
238
239 err := repo.AddRemote("remoteA", remoteA.GetLocalRemote())
240 require.NoError(t, err)
241
242 err = repo.AddRemote("remoteB", remoteB.GetLocalRemote())
243 require.NoError(t, err)
244
245 repoCache := createTestRepoCacheNoEvents(t, repo)
246
247 rene, err := repoCache.Identities().New("René Descartes", "rene@descartes.fr")
248 require.NoError(t, err)
249
250 err = repoCache.SetUserIdentity(rene)
251 require.NoError(t, err)
252
253 _, _, err = repoCache.Bugs().New("title", "message")
254 require.NoError(t, err)
255
256 // and one more for testing
257 b1, _, err := repoCache.Bugs().New("title", "message")
258 require.NoError(t, err)
259
260 _, err = repoCache.Push("remoteA")
261 require.NoError(t, err)
262
263 _, err = repoCache.Push("remoteB")
264 require.NoError(t, err)
265
266 _, err = repoCache.Fetch("remoteA")
267 require.NoError(t, err)
268
269 _, err = repoCache.Fetch("remoteB")
270 require.NoError(t, err)
271
272 err = repoCache.Bugs().Remove(b1.Id().String())
273 require.NoError(t, err)
274 assert.Len(t, repoCache.bugs.cached, 1)
275 assert.Len(t, repoCache.bugs.excerpts, 1)
276
277 _, err = repoCache.Bugs().Resolve(b1.Id())
278 assert.ErrorAs(t, entity.ErrNotFound{}, err)
279}
280
281func TestCacheEviction(t *testing.T) {
282 repo := repository.CreateGoGitTestRepo(t, false)
283 repoCache := createTestRepoCacheNoEvents(t, repo)
284 repoCache.setCacheSize(2)
285
286 require.Equal(t, 2, repoCache.bugs.maxLoaded)
287 require.Len(t, repoCache.bugs.cached, 0)
288 require.Equal(t, repoCache.bugs.lru.Len(), 0)
289
290 // Generating some bugs
291 rene, err := repoCache.Identities().New("René Descartes", "rene@descartes.fr")
292 require.NoError(t, err)
293 err = repoCache.SetUserIdentity(rene)
294 require.NoError(t, err)
295
296 bug1, _, err := repoCache.Bugs().New("title", "message")
297 require.NoError(t, err)
298
299 checkBugPresence(t, repoCache, bug1, true)
300 require.Len(t, repoCache.bugs.cached, 1)
301 require.Equal(t, 1, repoCache.bugs.lru.Len())
302
303 bug2, _, err := repoCache.Bugs().New("title", "message")
304 require.NoError(t, err)
305
306 checkBugPresence(t, repoCache, bug1, true)
307 checkBugPresence(t, repoCache, bug2, true)
308 require.Len(t, repoCache.bugs.cached, 2)
309 require.Equal(t, 2, repoCache.bugs.lru.Len())
310
311 // Number of bugs should not exceed max size of lruCache, oldest one should be evicted
312 bug3, _, err := repoCache.Bugs().New("title", "message")
313 require.NoError(t, err)
314
315 require.Len(t, repoCache.bugs.cached, 2)
316 require.Equal(t, 2, repoCache.bugs.lru.Len())
317 checkBugPresence(t, repoCache, bug1, false)
318 checkBugPresence(t, repoCache, bug2, true)
319 checkBugPresence(t, repoCache, bug3, true)
320
321 // Accessing bug should update position in lruCache, and therefore it should not be evicted
322 repoCache.bugs.lru.Get(bug2.Id())
323 oldestId, _ := repoCache.bugs.lru.GetOldest()
324 require.Equal(t, bug3.Id(), oldestId)
325
326 checkBugPresence(t, repoCache, bug1, false)
327 checkBugPresence(t, repoCache, bug2, true)
328 checkBugPresence(t, repoCache, bug3, true)
329 require.Len(t, repoCache.bugs.cached, 2)
330 require.Equal(t, 2, repoCache.bugs.lru.Len())
331}
332
333func TestLongDescription(t *testing.T) {
334 // See https://github.com/MichaelMure/git-bug/issues/606
335
336 text := strings.Repeat("x", 65536)
337
338 repo := repository.CreateGoGitTestRepo(t, false)
339
340 backend := createTestRepoCacheNoEvents(t, repo)
341
342 i, err := backend.Identities().New("René Descartes", "rene@descartes.fr")
343 require.NoError(t, err)
344
345 _, _, err = backend.Bugs().NewRaw(i, time.Now().Unix(), text, text, nil, nil)
346 require.NoError(t, err)
347}
348
349func checkBugPresence(t *testing.T, cache *RepoCache, bug *BugCache, presence bool) {
350 t.Helper()
351
352 id := bug.Id()
353 require.Equal(t, presence, cache.bugs.lru.Contains(id))
354 b, ok := cache.bugs.cached[id]
355 require.Equal(t, presence, ok)
356 if ok {
357 require.Equal(t, bug, b)
358 }
359}
360
361func createTestRepoCacheNoEvents(t *testing.T, repo repository.TestedRepo) *RepoCache {
362 t.Helper()
363
364 cache, err := NewRepoCacheNoEvents(repo)
365 require.NoError(t, err)
366
367 t.Cleanup(func() {
368 require.NoError(t, cache.Close())
369 })
370
371 return cache
372}