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