arbo/db/internal/dbtest/dbtest.go
db/internal/dbtest/dbtest.goRaw
1// Package dbtest provides shared behavioral tests for database implementations.
2package dbtest
3
4import (
5 "bytes"
6 "strconv"
7 "sync"
8 "testing"
9
10 "github.com/arnaucube/arbo/db"
11 qt "github.com/frankban/quicktest"
12)
13
14// TestWriteTx checks transaction reads and committed writes.
15func TestWriteTx(t *testing.T, database db.Database) {
16 wTx := database.WriteTx()
17
18 if _, err := wTx.Get([]byte("a")); err != db.ErrKeyNotFound {
19 t.Fatal(err)
20 }
21
22 err := wTx.Set([]byte("a"), []byte("b"))
23 qt.Assert(t, err, qt.IsNil)
24
25 v, err := wTx.Get([]byte("a"))
26 qt.Assert(t, err, qt.IsNil)
27
28 if !bytes.Equal(v, []byte("b")) {
29 t.Errorf("expected v (%v) to be equal to %v", v, []byte("b"))
30 }
31 err = wTx.Commit()
32 qt.Assert(t, err, qt.IsNil)
33
34 // Discard should not give any problem
35 wTx.Discard()
36
37 qt.Assert(t, err, qt.IsNil)
38
39 // get value from a new db after the previous commit
40 wTx = database.WriteTx()
41 v, err = wTx.Get([]byte("a"))
42 qt.Assert(t, err, qt.IsNil)
43 qt.Assert(t, v, qt.DeepEquals, []byte("b"))
44
45 err = wTx.Commit()
46 qt.Assert(t, err, qt.IsNil)
47}
48
49// TestIterate checks iteration with and without key prefixes.
50func TestIterate(t *testing.T, d db.Database) {
51 prefix0 := []byte("a")
52 prefix0NumKeys := 20
53 prefix1 := []byte("b")
54 prefix1NumKeys := 30
55
56 wTx := d.WriteTx()
57 defer wTx.Discard()
58 for i := 0; i < prefix0NumKeys; i++ {
59 err := wTx.Set(append(prefix0, []byte(strconv.Itoa(i))...), []byte(strconv.Itoa(i)))
60 qt.Assert(t, err, qt.IsNil)
61 }
62 for i := 0; i < prefix1NumKeys; i++ {
63 err := wTx.Set(append(prefix1, []byte(strconv.Itoa(i))...), []byte(strconv.Itoa(i)))
64 qt.Assert(t, err, qt.IsNil)
65 }
66 err := wTx.Commit()
67 qt.Assert(t, err, qt.IsNil)
68
69 noPrefixKeysFound := 0
70 err = d.Iterate(nil, func(_, _ []byte) bool {
71 noPrefixKeysFound++
72 return true
73 })
74 qt.Assert(t, err, qt.IsNil)
75 qt.Assert(t, noPrefixKeysFound, qt.Equals, prefix0NumKeys+prefix1NumKeys)
76
77 prefix0KeysFound := 0
78 err = d.Iterate(prefix0, func(_, _ []byte) bool {
79 prefix0KeysFound++
80 return true
81 })
82 qt.Assert(t, err, qt.IsNil)
83 qt.Assert(t, prefix0KeysFound, qt.Equals, prefix0NumKeys)
84
85 prefix1KeysFound := 0
86 err = d.Iterate(prefix1, func(_, _ []byte) bool {
87 prefix1KeysFound++
88 return true
89 })
90 qt.Assert(t, err, qt.IsNil)
91 qt.Assert(t, prefix1KeysFound, qt.Equals, prefix1NumKeys)
92}
93
94// TestConcurrentWriteTx validates the behaviour of pebbledb when multiple
95// write transactions modify the same key.
96func TestConcurrentWriteTx(t *testing.T, database db.Database) {
97 key := []byte{1}
98 wTx := database.WriteTx()
99 qt.Assert(t, wTx.Set(key, []byte{0}), qt.IsNil)
100 qt.Assert(t, wTx.Commit(), qt.IsNil)
101
102 var wgSync sync.WaitGroup
103 const concurrentWriters = 2
104 wgSync.Add(concurrentWriters)
105 inc := func(t *testing.T, m *sync.Mutex, database db.Database) error {
106 key := []byte{1}
107 wTx := database.WriteTx()
108 // Sync here so that both goroutines have created a WriteTx
109 // before operating with it.
110 wgSync.Done()
111 wgSync.Wait()
112
113 m.Lock()
114 val, err := wTx.Get(key)
115 qt.Assert(t, err, qt.IsNil)
116 qt.Assert(t, wTx.Set(key, []byte{val[0] + 1}), qt.IsNil)
117 m.Unlock()
118
119 return wTx.Commit()
120 }
121
122 var wgInc sync.WaitGroup
123 var m sync.Mutex
124 // A
125 var errA error
126 wgInc.Add(1)
127 go func() {
128 errA = inc(t, &m, database)
129 wgInc.Done()
130 }()
131
132 // B
133 var errB error
134 wgInc.Add(1)
135 go func() {
136 errB = inc(t, &m, database)
137 wgInc.Done()
138 }()
139
140 wgInc.Wait()
141 val, err := database.Get(key)
142 qt.Assert(t, err, qt.IsNil)
143 qt.Assert(t, val, qt.DeepEquals, []byte{1})
144
145 if errA == nil {
146 qt.Assert(t, errA, qt.IsNil)
147 qt.Assert(t, errB, qt.Equals, db.ErrConflict)
148 } else {
149 qt.Assert(t, errA, qt.Equals, db.ErrConflict)
150 qt.Assert(t, errB, qt.IsNil)
151 }
152}
153
154// TestWriteTxApply checks that applying a transaction copies its entries.
155func TestWriteTxApply(t *testing.T, d db.Database) {
156 wTx0 := d.WriteTx()
157 defer wTx0.Discard()
158
159 for i := 0; i < 10; i++ {
160 err := wTx0.Set([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i)))
161 qt.Assert(t, err, qt.IsNil)
162 }
163
164 wTx1 := d.WriteTx()
165 defer wTx1.Discard()
166
167 err := wTx1.Apply(wTx0)
168 qt.Assert(t, err, qt.IsNil)
169
170 const copiedKey = 3
171 gV, err := wTx1.Get([]byte(strconv.Itoa(copiedKey)))
172 qt.Assert(t, err, qt.IsNil)
173 qt.Assert(t, gV, qt.DeepEquals, []byte(strconv.Itoa(copiedKey)))
174}
175
176// TestWriteTxApplyPrefixed checks applying transactions across prefixes.
177func TestWriteTxApplyPrefixed(t *testing.T, d, dbWithPrefix db.Database) {
178 wTxWithPrefix := dbWithPrefix.WriteTx()
179 defer wTxWithPrefix.Discard()
180 wTx := d.WriteTx()
181 defer wTx.Discard()
182
183 qt.Assert(t, wTxWithPrefix.Set([]byte("a"), []byte("a")), qt.IsNil)
184 qt.Assert(t, wTx.Set([]byte("b"), []byte("b")), qt.IsNil)
185
186 // try to put the contents from the normal wTx into the wTxWithPrefix
187 err := wTxWithPrefix.Apply(wTx)
188 qt.Assert(t, err, qt.IsNil)
189
190 // try to put the contents from wTxWithPrefix into the normal wTx
191 err = wTx.Apply(wTxWithPrefix)
192 qt.Assert(t, err, qt.IsNil)
193}