repo_testing.go

  1package repository
  2
  3import (
  4	"math/rand"
  5	"testing"
  6
  7	"github.com/ProtonMail/go-crypto/openpgp"
  8	"github.com/stretchr/testify/require"
  9
 10	"github.com/MichaelMure/git-bug/util/lamport"
 11)
 12
 13// TODO: add tests for RepoStorage
 14
 15type RepoCreator func(t testing.TB, bare bool) TestedRepo
 16
 17// Test suite for a Repo implementation
 18func RepoTest(t *testing.T, creator RepoCreator) {
 19	for bare, name := range map[bool]string{
 20		false: "Plain",
 21		true:  "Bare",
 22	} {
 23		t.Run(name, func(t *testing.T) {
 24			repo := creator(t, bare)
 25
 26			t.Run("Data", func(t *testing.T) {
 27				RepoDataTest(t, repo)
 28				RepoDataSignatureTest(t, repo)
 29			})
 30
 31			t.Run("Config", func(t *testing.T) {
 32				RepoConfigTest(t, repo)
 33			})
 34
 35			t.Run("Index", func(t *testing.T) {
 36				RepoIndexTest(t, repo)
 37			})
 38
 39			t.Run("Clocks", func(t *testing.T) {
 40				RepoClockTest(t, repo)
 41			})
 42		})
 43	}
 44}
 45
 46// helper to test a RepoConfig
 47func RepoConfigTest(t *testing.T, repo RepoConfig) {
 48	testConfig(t, repo.LocalConfig())
 49}
 50
 51func randomHash() Hash {
 52	var letterRunes = "abcdef0123456789"
 53	b := make([]byte, idLengthSHA256)
 54	for i := range b {
 55		b[i] = letterRunes[rand.Intn(len(letterRunes))]
 56	}
 57	return Hash(b)
 58}
 59
 60// helper to test a RepoData
 61func RepoDataTest(t *testing.T, repo RepoData) {
 62	// Blob
 63
 64	data := randomData()
 65
 66	blobHash1, err := repo.StoreData(data)
 67	require.NoError(t, err)
 68	require.True(t, blobHash1.IsValid())
 69
 70	blob1Read, err := repo.ReadData(blobHash1)
 71	require.NoError(t, err)
 72	require.Equal(t, data, blob1Read)
 73
 74	_, err = repo.ReadData(randomHash())
 75	require.ErrorIs(t, err, ErrNotFound)
 76
 77	// Tree
 78
 79	blobHash2, err := repo.StoreData(randomData())
 80	require.NoError(t, err)
 81	blobHash3, err := repo.StoreData(randomData())
 82	require.NoError(t, err)
 83
 84	tree1 := []TreeEntry{
 85		{
 86			ObjectType: Blob,
 87			Hash:       blobHash1,
 88			Name:       "blob1",
 89		},
 90		{
 91			ObjectType: Blob,
 92			Hash:       blobHash2,
 93			Name:       "blob2",
 94		},
 95	}
 96
 97	treeHash1, err := repo.StoreTree(tree1)
 98	require.NoError(t, err)
 99	require.True(t, treeHash1.IsValid())
100
101	tree1Read, err := repo.ReadTree(treeHash1)
102	require.NoError(t, err)
103	require.ElementsMatch(t, tree1, tree1Read)
104
105	tree2 := []TreeEntry{
106		{
107			ObjectType: Tree,
108			Hash:       treeHash1,
109			Name:       "tree1",
110		},
111		{
112			ObjectType: Blob,
113			Hash:       blobHash3,
114			Name:       "blob3",
115		},
116	}
117
118	treeHash2, err := repo.StoreTree(tree2)
119	require.NoError(t, err)
120	require.True(t, treeHash2.IsValid())
121
122	tree2Read, err := repo.ReadTree(treeHash2)
123	require.NoError(t, err)
124	require.ElementsMatch(t, tree2, tree2Read)
125
126	_, err = repo.ReadTree(randomHash())
127	require.ErrorIs(t, err, ErrNotFound)
128
129	// Commit
130
131	commit1, err := repo.StoreCommit(treeHash1)
132	require.NoError(t, err)
133	require.True(t, commit1.IsValid())
134
135	// commit with a parent
136	commit2, err := repo.StoreCommit(treeHash2, commit1)
137	require.NoError(t, err)
138	require.True(t, commit2.IsValid())
139
140	// ReadTree should accept tree and commit hashes
141	tree1read, err := repo.ReadTree(commit1)
142	require.NoError(t, err)
143	require.Equal(t, tree1read, tree1)
144
145	c2, err := repo.ReadCommit(commit2)
146	require.NoError(t, err)
147	c2expected := Commit{Hash: commit2, Parents: []Hash{commit1}, TreeHash: treeHash2}
148	require.Equal(t, c2expected, c2)
149
150	_, err = repo.ReadCommit(randomHash())
151	require.ErrorIs(t, err, ErrNotFound)
152
153	// Ref
154
155	exist1, err := repo.RefExist("refs/bugs/ref1")
156	require.NoError(t, err)
157	require.False(t, exist1)
158
159	err = repo.UpdateRef("refs/bugs/ref1", commit2)
160	require.NoError(t, err)
161
162	exist1, err = repo.RefExist("refs/bugs/ref1")
163	require.NoError(t, err)
164	require.True(t, exist1)
165
166	h, err := repo.ResolveRef("refs/bugs/ref1")
167	require.NoError(t, err)
168	require.Equal(t, commit2, h)
169
170	ls, err := repo.ListRefs("refs/bugs")
171	require.NoError(t, err)
172	require.ElementsMatch(t, []string{"refs/bugs/ref1"}, ls)
173
174	err = repo.CopyRef("refs/bugs/ref1", "refs/bugs/ref2")
175	require.NoError(t, err)
176
177	ls, err = repo.ListRefs("refs/bugs")
178	require.NoError(t, err)
179	require.ElementsMatch(t, []string{"refs/bugs/ref1", "refs/bugs/ref2"}, ls)
180
181	commits, err := repo.ListCommits("refs/bugs/ref2")
182	require.NoError(t, err)
183	require.Equal(t, []Hash{commit1, commit2}, commits)
184
185	_, err = repo.ResolveRef("/refs/bugs/refnotexist")
186	require.ErrorIs(t, err, ErrNotFound)
187
188	err = repo.CopyRef("/refs/bugs/refnotexist", "refs/foo")
189	require.ErrorIs(t, err, ErrNotFound)
190
191	// Cleanup
192
193	err = repo.RemoveRef("refs/bugs/ref1")
194	require.NoError(t, err)
195
196	// RemoveRef is idempotent
197	err = repo.RemoveRef("refs/bugs/ref1")
198	require.NoError(t, err)
199}
200
201func RepoDataSignatureTest(t *testing.T, repo RepoData) {
202	data := randomData()
203
204	blobHash, err := repo.StoreData(data)
205	require.NoError(t, err)
206
207	treeHash, err := repo.StoreTree([]TreeEntry{
208		{
209			ObjectType: Blob,
210			Hash:       blobHash,
211			Name:       "blob",
212		},
213	})
214	require.NoError(t, err)
215
216	pgpEntity1, err := openpgp.NewEntity("", "", "", nil)
217	require.NoError(t, err)
218	keyring1 := openpgp.EntityList{pgpEntity1}
219
220	pgpEntity2, err := openpgp.NewEntity("", "", "", nil)
221	require.NoError(t, err)
222	keyring2 := openpgp.EntityList{pgpEntity2}
223
224	commitHash1, err := repo.StoreSignedCommit(treeHash, pgpEntity1)
225	require.NoError(t, err)
226
227	commit1, err := repo.ReadCommit(commitHash1)
228	require.NoError(t, err)
229
230	_, err = openpgp.CheckDetachedSignature(keyring1, commit1.SignedData, commit1.Signature, nil)
231	require.NoError(t, err)
232
233	_, err = openpgp.CheckDetachedSignature(keyring2, commit1.SignedData, commit1.Signature, nil)
234	require.Error(t, err)
235
236	commitHash2, err := repo.StoreSignedCommit(treeHash, pgpEntity1, commitHash1)
237	require.NoError(t, err)
238
239	commit2, err := repo.ReadCommit(commitHash2)
240	require.NoError(t, err)
241
242	_, err = openpgp.CheckDetachedSignature(keyring1, commit2.SignedData, commit2.Signature, nil)
243	require.NoError(t, err)
244
245	_, err = openpgp.CheckDetachedSignature(keyring2, commit2.SignedData, commit2.Signature, nil)
246	require.Error(t, err)
247}
248
249func RepoIndexTest(t *testing.T, repo RepoIndex) {
250	idx, err := repo.GetIndex("a")
251	require.NoError(t, err)
252
253	// simple indexing
254	err = idx.IndexOne("id1", []string{"foo", "bar", "foobar barfoo"})
255	require.NoError(t, err)
256
257	// batched indexing
258	indexer, closer := idx.IndexBatch()
259	err = indexer("id2", []string{"hello", "foo bar"})
260	require.NoError(t, err)
261	err = indexer("id3", []string{"Hola", "Esta bien"})
262	require.NoError(t, err)
263	err = closer()
264	require.NoError(t, err)
265
266	// search
267	res, err := idx.Search([]string{"foobar"})
268	require.NoError(t, err)
269	require.ElementsMatch(t, []string{"id1"}, res)
270
271	res, err = idx.Search([]string{"foo"})
272	require.NoError(t, err)
273	require.ElementsMatch(t, []string{"id1", "id2"}, res)
274
275	// re-indexing an item replace previous versions
276	err = idx.IndexOne("id2", []string{"hello"})
277	require.NoError(t, err)
278
279	res, err = idx.Search([]string{"foo"})
280	require.NoError(t, err)
281	require.ElementsMatch(t, []string{"id1"}, res)
282
283	err = idx.Clear()
284	require.NoError(t, err)
285
286	res, err = idx.Search([]string{"foo"})
287	require.NoError(t, err)
288	require.Empty(t, res)
289}
290
291// helper to test a RepoClock
292func RepoClockTest(t *testing.T, repo RepoClock) {
293	allClocks, err := repo.AllClocks()
294	require.NoError(t, err)
295	require.Len(t, allClocks, 0)
296
297	clock, err := repo.GetOrCreateClock("foo")
298	require.NoError(t, err)
299	require.Equal(t, lamport.Time(1), clock.Time())
300
301	time, err := clock.Increment()
302	require.NoError(t, err)
303	require.Equal(t, lamport.Time(2), time)
304	require.Equal(t, lamport.Time(2), clock.Time())
305
306	clock2, err := repo.GetOrCreateClock("foo")
307	require.NoError(t, err)
308	require.Equal(t, lamport.Time(2), clock2.Time())
309
310	clock3, err := repo.GetOrCreateClock("bar")
311	require.NoError(t, err)
312	require.Equal(t, lamport.Time(1), clock3.Time())
313
314	allClocks, err = repo.AllClocks()
315	require.NoError(t, err)
316	require.Equal(t, map[string]lamport.Clock{
317		"foo": clock,
318		"bar": clock3,
319	}, allClocks)
320}
321
322func randomData() []byte {
323	var letterRunes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
324	b := make([]byte, 32)
325	for i := range b {
326		b[i] = letterRunes[rand.Intn(len(letterRunes))]
327	}
328	return b
329}