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