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
 51// helper to test a RepoData
 52func RepoDataTest(t *testing.T, repo RepoData) {
 53	// Blob
 54
 55	data := randomData()
 56
 57	blobHash1, err := repo.StoreData(data)
 58	require.NoError(t, err)
 59	require.True(t, blobHash1.IsValid())
 60
 61	blob1Read, err := repo.ReadData(blobHash1)
 62	require.NoError(t, err)
 63	require.Equal(t, data, blob1Read)
 64
 65	// Tree
 66
 67	blobHash2, err := repo.StoreData(randomData())
 68	require.NoError(t, err)
 69	blobHash3, err := repo.StoreData(randomData())
 70	require.NoError(t, err)
 71
 72	tree1 := []TreeEntry{
 73		{
 74			ObjectType: Blob,
 75			Hash:       blobHash1,
 76			Name:       "blob1",
 77		},
 78		{
 79			ObjectType: Blob,
 80			Hash:       blobHash2,
 81			Name:       "blob2",
 82		},
 83	}
 84
 85	treeHash1, err := repo.StoreTree(tree1)
 86	require.NoError(t, err)
 87	require.True(t, treeHash1.IsValid())
 88
 89	tree1Read, err := repo.ReadTree(treeHash1)
 90	require.NoError(t, err)
 91	require.ElementsMatch(t, tree1, tree1Read)
 92
 93	tree2 := []TreeEntry{
 94		{
 95			ObjectType: Tree,
 96			Hash:       treeHash1,
 97			Name:       "tree1",
 98		},
 99		{
100			ObjectType: Blob,
101			Hash:       blobHash3,
102			Name:       "blob3",
103		},
104	}
105
106	treeHash2, err := repo.StoreTree(tree2)
107	require.NoError(t, err)
108	require.True(t, treeHash2.IsValid())
109
110	tree2Read, err := repo.ReadTree(treeHash2)
111	require.NoError(t, err)
112	require.ElementsMatch(t, tree2, tree2Read)
113
114	// Commit
115
116	commit1, err := repo.StoreCommit(treeHash1)
117	require.NoError(t, err)
118	require.True(t, commit1.IsValid())
119
120	treeHash1Read, err := repo.GetTreeHash(commit1)
121	require.NoError(t, err)
122	require.Equal(t, treeHash1, treeHash1Read)
123
124	// commit with a parent
125	commit2, err := repo.StoreCommit(treeHash2, commit1)
126	require.NoError(t, err)
127	require.True(t, commit2.IsValid())
128
129	treeHash2Read, err := repo.GetTreeHash(commit2)
130	require.NoError(t, err)
131	require.Equal(t, treeHash2, treeHash2Read)
132
133	// ReadTree should accept tree and commit hashes
134	tree1read, err := repo.ReadTree(commit1)
135	require.NoError(t, err)
136	require.Equal(t, tree1read, tree1)
137
138	c2, err := repo.ReadCommit(commit2)
139	require.NoError(t, err)
140	c2expected := Commit{Hash: commit2, Parents: []Hash{commit1}, TreeHash: treeHash2}
141	require.Equal(t, c2expected, c2)
142
143	// Ref
144
145	exist1, err := repo.RefExist("refs/bugs/ref1")
146	require.NoError(t, err)
147	require.False(t, exist1)
148
149	err = repo.UpdateRef("refs/bugs/ref1", commit2)
150	require.NoError(t, err)
151
152	exist1, err = repo.RefExist("refs/bugs/ref1")
153	require.NoError(t, err)
154	require.True(t, exist1)
155
156	h, err := repo.ResolveRef("refs/bugs/ref1")
157	require.NoError(t, err)
158	require.Equal(t, commit2, h)
159
160	ls, err := repo.ListRefs("refs/bugs")
161	require.NoError(t, err)
162	require.ElementsMatch(t, []string{"refs/bugs/ref1"}, ls)
163
164	err = repo.CopyRef("refs/bugs/ref1", "refs/bugs/ref2")
165	require.NoError(t, err)
166
167	ls, err = repo.ListRefs("refs/bugs")
168	require.NoError(t, err)
169	require.ElementsMatch(t, []string{"refs/bugs/ref1", "refs/bugs/ref2"}, ls)
170
171	commits, err := repo.ListCommits("refs/bugs/ref2")
172	require.NoError(t, err)
173	require.Equal(t, []Hash{commit1, commit2}, commits)
174
175	// Graph
176
177	commit3, err := repo.StoreCommit(treeHash1, commit1)
178	require.NoError(t, err)
179
180	ancestorHash, err := repo.FindCommonAncestor(commit2, commit3)
181	require.NoError(t, err)
182	require.Equal(t, commit1, ancestorHash)
183
184	err = repo.RemoveRef("refs/bugs/ref1")
185	require.NoError(t, err)
186
187	// RemoveRef is idempotent
188	err = repo.RemoveRef("refs/bugs/ref1")
189	require.NoError(t, err)
190}
191
192func RepoDataSignatureTest(t *testing.T, repo RepoData) {
193	data := randomData()
194
195	blobHash, err := repo.StoreData(data)
196	require.NoError(t, err)
197
198	treeHash, err := repo.StoreTree([]TreeEntry{
199		{
200			ObjectType: Blob,
201			Hash:       blobHash,
202			Name:       "blob",
203		},
204	})
205	require.NoError(t, err)
206
207	pgpEntity1, err := openpgp.NewEntity("", "", "", nil)
208	require.NoError(t, err)
209	keyring1 := openpgp.EntityList{pgpEntity1}
210
211	pgpEntity2, err := openpgp.NewEntity("", "", "", nil)
212	require.NoError(t, err)
213	keyring2 := openpgp.EntityList{pgpEntity2}
214
215	commitHash1, err := repo.StoreSignedCommit(treeHash, pgpEntity1)
216	require.NoError(t, err)
217
218	commit1, err := repo.ReadCommit(commitHash1)
219	require.NoError(t, err)
220
221	_, err = openpgp.CheckDetachedSignature(keyring1, commit1.SignedData, commit1.Signature, nil)
222	require.NoError(t, err)
223
224	_, err = openpgp.CheckDetachedSignature(keyring2, commit1.SignedData, commit1.Signature, nil)
225	require.Error(t, err)
226
227	commitHash2, err := repo.StoreSignedCommit(treeHash, pgpEntity1, commitHash1)
228	require.NoError(t, err)
229
230	commit2, err := repo.ReadCommit(commitHash2)
231	require.NoError(t, err)
232
233	_, err = openpgp.CheckDetachedSignature(keyring1, commit2.SignedData, commit2.Signature, nil)
234	require.NoError(t, err)
235
236	_, err = openpgp.CheckDetachedSignature(keyring2, commit2.SignedData, commit2.Signature, nil)
237	require.Error(t, err)
238}
239
240func RepoIndexTest(t *testing.T, repo RepoIndex) {
241	idx, err := repo.GetIndex("a")
242	require.NoError(t, err)
243
244	// simple indexing
245	err = idx.IndexOne("id1", []string{"foo", "bar", "foobar barfoo"})
246	require.NoError(t, err)
247
248	// batched indexing
249	indexer, closer := idx.IndexBatch()
250	err = indexer("id2", []string{"hello", "foo bar"})
251	require.NoError(t, err)
252	err = indexer("id3", []string{"Hola", "Esta bien"})
253	require.NoError(t, err)
254	err = closer()
255	require.NoError(t, err)
256
257	// search
258	res, err := idx.Search([]string{"foobar"})
259	require.NoError(t, err)
260	require.ElementsMatch(t, []string{"id1"}, res)
261
262	res, err = idx.Search([]string{"foo"})
263	require.NoError(t, err)
264	require.ElementsMatch(t, []string{"id1", "id2"}, res)
265
266	// re-indexing an item replace previous versions
267	err = idx.IndexOne("id2", []string{"hello"})
268	require.NoError(t, err)
269
270	res, err = idx.Search([]string{"foo"})
271	require.NoError(t, err)
272	require.ElementsMatch(t, []string{"id1"}, res)
273
274	err = idx.Clear()
275	require.NoError(t, err)
276
277	res, err = idx.Search([]string{"foo"})
278	require.NoError(t, err)
279	require.Empty(t, res)
280}
281
282// helper to test a RepoClock
283func RepoClockTest(t *testing.T, repo RepoClock) {
284	allClocks, err := repo.AllClocks()
285	require.NoError(t, err)
286	require.Len(t, allClocks, 0)
287
288	clock, err := repo.GetOrCreateClock("foo")
289	require.NoError(t, err)
290	require.Equal(t, lamport.Time(1), clock.Time())
291
292	time, err := clock.Increment()
293	require.NoError(t, err)
294	require.Equal(t, lamport.Time(2), time)
295	require.Equal(t, lamport.Time(2), clock.Time())
296
297	clock2, err := repo.GetOrCreateClock("foo")
298	require.NoError(t, err)
299	require.Equal(t, lamport.Time(2), clock2.Time())
300
301	clock3, err := repo.GetOrCreateClock("bar")
302	require.NoError(t, err)
303	require.Equal(t, lamport.Time(1), clock3.Time())
304
305	allClocks, err = repo.AllClocks()
306	require.NoError(t, err)
307	require.Equal(t, map[string]lamport.Clock{
308		"foo": clock,
309		"bar": clock3,
310	}, allClocks)
311}
312
313func randomData() []byte {
314	var letterRunes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
315	b := make([]byte, 32)
316	for i := range b {
317		b[i] = letterRunes[rand.Intn(len(letterRunes))]
318	}
319	return b
320}