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