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