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}