Skip to content

Commit 8083e8f

Browse files
committed
mock implementation of TxnDatastore for testing KeyTransform Wrapper
1 parent 5bb1488 commit 8083e8f

File tree

1 file changed

+170
-0
lines changed

1 file changed

+170
-0
lines changed

test/test_util.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"testing"
1010

1111
dstore "github.com/ipfs/go-datastore"
12+
dsq "github.com/ipfs/go-datastore/query"
1213
)
1314

1415
var ErrTest = errors.New("test error")
@@ -187,3 +188,172 @@ func (d *testDatastore) CollectGarbage(_ context.Context) error {
187188
}
188189
return nil
189190
}
191+
192+
var _ dstore.TxnDatastore = (*testTxnDatastore)(nil)
193+
194+
type testTxnDatastore struct {
195+
testErrors bool
196+
197+
*dstore.MapDatastore
198+
}
199+
200+
func NewTestTxnDatastore(testErrors bool) *testTxnDatastore {
201+
return &testTxnDatastore{
202+
testErrors: testErrors,
203+
MapDatastore: dstore.NewMapDatastore(),
204+
}
205+
}
206+
207+
func (t *testTxnDatastore) NewTransaction(ctx context.Context, readOnly bool) (dstore.Txn, error) {
208+
if t.testErrors {
209+
return nil, ErrTest
210+
}
211+
return newTestTx(t.testErrors, t.MapDatastore), nil
212+
}
213+
214+
var _ dstore.Txn = (*testTxn)(nil)
215+
216+
type testTxn struct {
217+
testTxErrors bool
218+
219+
dirty map[dstore.Key][]byte
220+
committed *dstore.MapDatastore
221+
}
222+
223+
func newTestTx(testTxErrors bool, committed *dstore.MapDatastore) *testTxn {
224+
return &testTxn{
225+
testTxErrors: testTxErrors,
226+
dirty: make(map[dstore.Key][]byte),
227+
committed: committed,
228+
}
229+
}
230+
231+
// It is unclear from the dstore.Txn interface definition whether reads should happen from the dirty or committed or both
232+
// It says that operations will not be applied until Commit() is called, but this doesn't really make sense for the Read
233+
// operations as their interface is not designed for returning results asynchronously (except Query).
234+
// For this test datastore, we simply Read from both dirty and committed entries with dirty values overshadowing committed values.
235+
236+
// NOTE: looking at go-ds-badger2, it looks like Get, Has, and GetSize only read from the dirty (uncommitted badger txn),
237+
// whereas Query considers both the dirty transaction and the underlying committed datastore.
238+
239+
func (t *testTxn) Get(ctx context.Context, key dstore.Key) ([]byte, error) {
240+
if t.testTxErrors {
241+
return nil, ErrTest
242+
}
243+
if val, ok := t.dirty[key]; ok {
244+
return val, nil
245+
}
246+
return t.committed.Get(ctx, key)
247+
}
248+
249+
func (t *testTxn) Has(ctx context.Context, key dstore.Key) (bool, error) {
250+
if t.testTxErrors {
251+
return false, ErrTest
252+
}
253+
if _, ok := t.dirty[key]; ok {
254+
return true, nil
255+
}
256+
257+
return t.committed.Has(ctx, key)
258+
}
259+
260+
func (t *testTxn) GetSize(ctx context.Context, key dstore.Key) (int, error) {
261+
if t.testTxErrors {
262+
return 0, ErrTest
263+
}
264+
if val, ok := t.dirty[key]; ok {
265+
return len(val), nil
266+
}
267+
268+
return t.committed.GetSize(ctx, key)
269+
}
270+
271+
func (t *testTxn) Query(ctx context.Context, q dsq.Query) (dsq.Results, error) {
272+
if t.testTxErrors {
273+
return nil, ErrTest
274+
}
275+
276+
// not entirely sure if Query is *supposed* to access both uncommitted and committed data, but if so I think this
277+
// is the simplest way of handling it and the overhead should be fine for testing purposes
278+
transientStore := dstore.NewMapDatastore()
279+
transientBatch, err := transientStore.Batch(ctx)
280+
if err != nil {
281+
return nil, err
282+
}
283+
284+
// move committed results into the transientStore
285+
committedResults, err := t.committed.Query(ctx, q)
286+
if err != nil {
287+
return nil, err
288+
}
289+
defer func() {
290+
committedResults.Close()
291+
}()
292+
293+
for {
294+
res, ok := committedResults.NextSync()
295+
if !ok {
296+
break
297+
}
298+
if res.Error != nil {
299+
return nil, res.Error
300+
}
301+
key := dstore.RawKey(res.Key)
302+
if err := transientBatch.Put(ctx, key, res.Value); err != nil {
303+
return nil, err
304+
}
305+
}
306+
// overwrite transientStore with the dirty results so we can query the union of them
307+
for k, v := range t.dirty {
308+
if err := transientBatch.Put(ctx, k, v); err != nil {
309+
return nil, err
310+
}
311+
}
312+
313+
// commit the transientStore batch
314+
if err := transientBatch.Commit(ctx); err != nil {
315+
return nil, err
316+
}
317+
318+
// apply the query to the transient store, return its results
319+
return transientStore.Query(ctx, q)
320+
}
321+
322+
func (t *testTxn) Put(ctx context.Context, key dstore.Key, value []byte) error {
323+
if t.testTxErrors {
324+
return ErrTest
325+
}
326+
t.dirty[key] = value
327+
return nil
328+
}
329+
330+
func (t *testTxn) Delete(ctx context.Context, key dstore.Key) error {
331+
if t.testTxErrors {
332+
return ErrTest
333+
}
334+
if _, ok := t.dirty[key]; ok {
335+
delete(t.dirty, key)
336+
}
337+
return t.committed.Delete(ctx, key)
338+
}
339+
340+
func (t *testTxn) Commit(ctx context.Context) error {
341+
if t.testTxErrors {
342+
return ErrTest
343+
}
344+
345+
batch, err := t.committed.Batch(ctx)
346+
if err != nil {
347+
return err
348+
}
349+
for k, v := range t.dirty {
350+
if err := batch.Put(ctx, k, v); err != nil {
351+
return err
352+
}
353+
}
354+
return batch.Commit(ctx)
355+
}
356+
357+
func (t *testTxn) Discard(ctx context.Context) {
358+
t.dirty = make(map[dstore.Key][]byte)
359+
}

0 commit comments

Comments
 (0)