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