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