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