arnaucube
arnaucube/arboPublic Self-hosted repository
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}