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