repo_testing.go

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