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