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}