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