Skip to content

Commit 46ec63b

Browse files
shazowfjl
authored andcommitted
ethdb/dbtest: addd test suite for ethdb backends (#19960)
- Move the existing tests from memorydb into a generalized testsuite that can be run by any ethdb backend implementation. - Add several more test cases to clarify some non-obvious nuances when implementing a custom ethdb backend, such as the behaviour of NewIteratorWithPrefix vs NewIteratorWithStart. - Add leveldb to the testsuite using in-memory storage for fast execution.
1 parent 1cd5bf0 commit 46ec63b

File tree

3 files changed

+364
-77
lines changed

3 files changed

+364
-77
lines changed

ethdb/dbtest/testsuite.go

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
// Copyright 2019 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package dbtest
18+
19+
import (
20+
"bytes"
21+
"reflect"
22+
"sort"
23+
"testing"
24+
25+
"github.com/ethereum/go-ethereum/ethdb"
26+
)
27+
28+
// TestDatabaseSuite runs a suite of tests against a KeyValueStore database
29+
// implementation.
30+
func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) {
31+
t.Run("Iterator", func(t *testing.T) {
32+
tests := []struct {
33+
content map[string]string
34+
prefix string
35+
order []string
36+
}{
37+
// Empty databases should be iterable
38+
{map[string]string{}, "", nil},
39+
{map[string]string{}, "non-existent-prefix", nil},
40+
41+
// Single-item databases should be iterable
42+
{map[string]string{"key": "val"}, "", []string{"key"}},
43+
{map[string]string{"key": "val"}, "k", []string{"key"}},
44+
{map[string]string{"key": "val"}, "l", nil},
45+
46+
// Multi-item databases should be fully iterable
47+
{
48+
map[string]string{"k1": "v1", "k5": "v5", "k2": "v2", "k4": "v4", "k3": "v3"},
49+
"",
50+
[]string{"k1", "k2", "k3", "k4", "k5"},
51+
},
52+
{
53+
map[string]string{"k1": "v1", "k5": "v5", "k2": "v2", "k4": "v4", "k3": "v3"},
54+
"k",
55+
[]string{"k1", "k2", "k3", "k4", "k5"},
56+
},
57+
{
58+
map[string]string{"k1": "v1", "k5": "v5", "k2": "v2", "k4": "v4", "k3": "v3"},
59+
"l",
60+
nil,
61+
},
62+
// Multi-item databases should be prefix-iterable
63+
{
64+
map[string]string{
65+
"ka1": "va1", "ka5": "va5", "ka2": "va2", "ka4": "va4", "ka3": "va3",
66+
"kb1": "vb1", "kb5": "vb5", "kb2": "vb2", "kb4": "vb4", "kb3": "vb3",
67+
},
68+
"ka",
69+
[]string{"ka1", "ka2", "ka3", "ka4", "ka5"},
70+
},
71+
{
72+
map[string]string{
73+
"ka1": "va1", "ka5": "va5", "ka2": "va2", "ka4": "va4", "ka3": "va3",
74+
"kb1": "vb1", "kb5": "vb5", "kb2": "vb2", "kb4": "vb4", "kb3": "vb3",
75+
},
76+
"kc",
77+
nil,
78+
},
79+
}
80+
for i, tt := range tests {
81+
// Create the key-value data store
82+
db := New()
83+
for key, val := range tt.content {
84+
if err := db.Put([]byte(key), []byte(val)); err != nil {
85+
t.Fatalf("test %d: failed to insert item %s:%s into database: %v", i, key, val, err)
86+
}
87+
}
88+
// Iterate over the database with the given configs and verify the results
89+
it, idx := db.NewIteratorWithPrefix([]byte(tt.prefix)), 0
90+
for it.Next() {
91+
if len(tt.order) <= idx {
92+
t.Errorf("test %d: prefix=%q more items than expected: checking idx=%d (key %q), expecting len=%d", i, tt.prefix, idx, it.Key(), len(tt.order))
93+
break
94+
}
95+
if !bytes.Equal(it.Key(), []byte(tt.order[idx])) {
96+
t.Errorf("test %d: item %d: key mismatch: have %s, want %s", i, idx, string(it.Key()), tt.order[idx])
97+
}
98+
if !bytes.Equal(it.Value(), []byte(tt.content[tt.order[idx]])) {
99+
t.Errorf("test %d: item %d: value mismatch: have %s, want %s", i, idx, string(it.Value()), tt.content[tt.order[idx]])
100+
}
101+
idx++
102+
}
103+
if err := it.Error(); err != nil {
104+
t.Errorf("test %d: iteration failed: %v", i, err)
105+
}
106+
if idx != len(tt.order) {
107+
t.Errorf("test %d: iteration terminated prematurely: have %d, want %d", i, idx, len(tt.order))
108+
}
109+
db.Close()
110+
}
111+
})
112+
113+
t.Run("IteratorWith", func(t *testing.T) {
114+
db := New()
115+
defer db.Close()
116+
117+
keys := []string{"1", "2", "3", "4", "6", "10", "11", "12", "20", "21", "22"}
118+
sort.Strings(keys) // 1, 10, 11, etc
119+
120+
for _, k := range keys {
121+
if err := db.Put([]byte(k), nil); err != nil {
122+
t.Fatal(err)
123+
}
124+
}
125+
126+
{
127+
it := db.NewIterator()
128+
got, want := iterateKeys(it), keys
129+
if err := it.Error(); err != nil {
130+
t.Fatal(err)
131+
}
132+
it.Release()
133+
if !reflect.DeepEqual(got, want) {
134+
t.Errorf("Iterator: got: %s; want: %s", got, want)
135+
}
136+
}
137+
138+
{
139+
it := db.NewIteratorWithPrefix([]byte("1"))
140+
got, want := iterateKeys(it), []string{"1", "10", "11", "12"}
141+
if err := it.Error(); err != nil {
142+
t.Fatal(err)
143+
}
144+
it.Release()
145+
if !reflect.DeepEqual(got, want) {
146+
t.Errorf("IteratorWithPrefix(1): got: %s; want: %s", got, want)
147+
}
148+
}
149+
150+
{
151+
it := db.NewIteratorWithPrefix([]byte("5"))
152+
got, want := iterateKeys(it), []string{}
153+
if err := it.Error(); err != nil {
154+
t.Fatal(err)
155+
}
156+
it.Release()
157+
if !reflect.DeepEqual(got, want) {
158+
t.Errorf("IteratorWithPrefix(1): got: %s; want: %s", got, want)
159+
}
160+
}
161+
162+
{
163+
it := db.NewIteratorWithStart([]byte("2"))
164+
got, want := iterateKeys(it), []string{"2", "20", "21", "22", "3", "4", "6"}
165+
if err := it.Error(); err != nil {
166+
t.Fatal(err)
167+
}
168+
it.Release()
169+
if !reflect.DeepEqual(got, want) {
170+
t.Errorf("IteratorWithStart(2): got: %s; want: %s", got, want)
171+
}
172+
}
173+
174+
{
175+
it := db.NewIteratorWithStart([]byte("5"))
176+
got, want := iterateKeys(it), []string{"6"}
177+
if err := it.Error(); err != nil {
178+
t.Fatal(err)
179+
}
180+
it.Release()
181+
if !reflect.DeepEqual(got, want) {
182+
t.Errorf("IteratorWithStart(2): got: %s; want: %s", got, want)
183+
}
184+
}
185+
})
186+
187+
t.Run("KeyValueOperations", func(t *testing.T) {
188+
db := New()
189+
defer db.Close()
190+
191+
key := []byte("foo")
192+
193+
if got, err := db.Has(key); err != nil {
194+
t.Error(err)
195+
} else if got {
196+
t.Errorf("wrong value: %t", got)
197+
}
198+
199+
value := []byte("hello world")
200+
if err := db.Put(key, value); err != nil {
201+
t.Error(err)
202+
}
203+
204+
if got, err := db.Has(key); err != nil {
205+
t.Error(err)
206+
} else if !got {
207+
t.Errorf("wrong value: %t", got)
208+
}
209+
210+
if got, err := db.Get(key); err != nil {
211+
t.Error(err)
212+
} else if !bytes.Equal(got, value) {
213+
t.Errorf("wrong value: %q", got)
214+
}
215+
216+
if err := db.Delete(key); err != nil {
217+
t.Error(err)
218+
}
219+
220+
if got, err := db.Has(key); err != nil {
221+
t.Error(err)
222+
} else if got {
223+
t.Errorf("wrong value: %t", got)
224+
}
225+
})
226+
227+
t.Run("Batch", func(t *testing.T) {
228+
db := New()
229+
defer db.Close()
230+
231+
b := db.NewBatch()
232+
for _, k := range []string{"1", "2", "3", "4"} {
233+
if err := b.Put([]byte(k), nil); err != nil {
234+
t.Fatal(err)
235+
}
236+
}
237+
238+
if has, err := db.Has([]byte("1")); err != nil {
239+
t.Fatal(err)
240+
} else if has {
241+
t.Error("db contains element before batch write")
242+
}
243+
244+
if err := b.Write(); err != nil {
245+
t.Fatal(err)
246+
}
247+
248+
{
249+
it := db.NewIterator()
250+
if got, want := iterateKeys(it), []string{"1", "2", "3", "4"}; !reflect.DeepEqual(got, want) {
251+
t.Errorf("got: %s; want: %s", got, want)
252+
}
253+
it.Release()
254+
}
255+
256+
b.Reset()
257+
258+
// Mix writes and deletes in batch
259+
b.Put([]byte("5"), nil)
260+
b.Delete([]byte("1"))
261+
b.Put([]byte("6"), nil)
262+
b.Delete([]byte("3"))
263+
b.Put([]byte("3"), nil)
264+
265+
if err := b.Write(); err != nil {
266+
t.Fatal(err)
267+
}
268+
269+
{
270+
it := db.NewIterator()
271+
if got, want := iterateKeys(it), []string{"2", "3", "4", "5", "6"}; !reflect.DeepEqual(got, want) {
272+
t.Errorf("got: %s; want: %s", got, want)
273+
}
274+
it.Release()
275+
}
276+
})
277+
278+
t.Run("BatchReplay", func(t *testing.T) {
279+
db := New()
280+
defer db.Close()
281+
282+
want := []string{"1", "2", "3", "4"}
283+
b := db.NewBatch()
284+
for _, k := range want {
285+
if err := b.Put([]byte(k), nil); err != nil {
286+
t.Fatal(err)
287+
}
288+
}
289+
290+
b2 := db.NewBatch()
291+
if err := b.Replay(b2); err != nil {
292+
t.Fatal(err)
293+
}
294+
295+
if err := b2.Replay(db); err != nil {
296+
t.Fatal(err)
297+
}
298+
299+
it := db.NewIterator()
300+
if got := iterateKeys(it); !reflect.DeepEqual(got, want) {
301+
t.Errorf("got: %s; want: %s", got, want)
302+
}
303+
it.Release()
304+
})
305+
306+
}
307+
308+
func iterateKeys(it ethdb.Iterator) []string {
309+
keys := []string{}
310+
for it.Next() {
311+
keys = append(keys, string(it.Key()))
312+
}
313+
sort.Strings(keys)
314+
return keys
315+
}

ethdb/leveldb/leveldb_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2019 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package leveldb
18+
19+
import (
20+
"testing"
21+
22+
"github.com/ethereum/go-ethereum/ethdb"
23+
"github.com/ethereum/go-ethereum/ethdb/dbtest"
24+
"github.com/syndtr/goleveldb/leveldb"
25+
"github.com/syndtr/goleveldb/leveldb/storage"
26+
)
27+
28+
func TestLevelDB(t *testing.T) {
29+
t.Run("DatabaseSuite", func(t *testing.T) {
30+
dbtest.TestDatabaseSuite(t, func() ethdb.KeyValueStore {
31+
db, err := leveldb.Open(storage.NewMemStorage(), nil)
32+
if err != nil {
33+
t.Fatal(err)
34+
}
35+
return &Database{
36+
db: db,
37+
}
38+
})
39+
})
40+
}

0 commit comments

Comments
 (0)