repo_cache_test.go

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