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}