package querycontext import ( "context" "sync/atomic" "testing" "github.com/featurebasedb/featurebase/v3/roaring" ) type oopsieWrapper struct { tb testing.TB expected int64 } func (o *oopsieWrapper) expect(n int) { atomic.AddInt64(&o.expected, int64(n)) } func (o *oopsieWrapper) oopsie(err error) error { o.tb.Helper() if err == nil { return nil } remaining := atomic.AddInt64(&o.expected, -1) if remaining < 0 { // we use Error here, not Fatal, so we can run in arbitrary goroutines o.tb.Error(err) } return err } // testTxStore is a wrapper which fails a test on unexpected // errors. type testTxStoreWrapper struct { inner TxStore oopsieWrapper } func newTestTxStoreWrapper(tb testing.TB, inner TxStore) *testTxStoreWrapper { return &testTxStoreWrapper{inner: inner, oopsieWrapper: oopsieWrapper{tb: tb}} } func (t *testTxStoreWrapper) Close() error { t.tb.Helper() return t.oopsie(t.inner.Close()) } func (t *testTxStoreWrapper) NewQueryContext(ctx context.Context) (*testQueryContextWrapper, error) { t.tb.Helper() q, err := t.inner.NewQueryContext(ctx) return newTestQueryContextWrapper(t.tb, q), t.oopsie(err) } func (t *testTxStoreWrapper) NewWriteQueryContext(ctx context.Context, scope QueryScope) (*testQueryContextWrapper, error) { t.tb.Helper() q, err := t.inner.NewWriteQueryContext(ctx, scope) return newTestQueryContextWrapper(t.tb, q), t.oopsie(err) } func (t *testTxStoreWrapper) Scope() QueryScope { t.tb.Helper() return t.inner.Scope() } func (t *testTxStoreWrapper) dbPath(dbk dbKey) (string, error) { t.tb.Helper() p, err := t.inner.dbPath(dbk) return p, t.oopsie(err) } func (t *testTxStoreWrapper) keys(index IndexName, field FieldName, view ViewName, shard ShardID) (dbKey, fragKey) { t.tb.Helper() return t.inner.keys(index, field, view, shard) } type testQueryContextWrapper struct { inner QueryContext oopsieWrapper } func newTestQueryContextWrapper(tb testing.TB, inner QueryContext) *testQueryContextWrapper { return &testQueryContextWrapper{inner: inner, oopsieWrapper: oopsieWrapper{tb: tb}} } func (t *testQueryContextWrapper) Release() { t.tb.Helper() t.inner.Release() } func (t *testQueryContextWrapper) Commit() error { t.tb.Helper() return t.oopsie(t.inner.Commit()) } func (t *testQueryContextWrapper) Error(args ...interface{}) { t.tb.Helper() t.inner.Error(args...) } func (t *testQueryContextWrapper) Errorf(msg string, args ...interface{}) { t.tb.Helper() t.inner.Errorf(msg, args...) } func (t *testQueryContextWrapper) NewRead(index IndexName, field FieldName, view ViewName, shard ShardID) (*testQueryReadWrapper, error) { t.tb.Helper() qr, err := t.inner.NewRead(index, field, view, shard) return newTestQueryReadWrapper(t.tb, qr), t.oopsie(err) } func (t *testQueryContextWrapper) NewWrite(index IndexName, field FieldName, view ViewName, shard ShardID) (*testQueryWriteWrapper, error) { t.tb.Helper() qw, err := t.inner.NewWrite(index, field, view, shard) return newTestQueryWriteWrapper(t.tb, qw), t.oopsie(err) } type testQueryReadWrapper struct { inner QueryRead oopsieWrapper } func newTestQueryReadWrapper(tb testing.TB, inner QueryRead) *testQueryReadWrapper { return &testQueryReadWrapper{inner: inner, oopsieWrapper: oopsieWrapper{tb: tb}} } func (t *testQueryReadWrapper) ContainerIterator(ckey uint64) (citer roaring.ContainerIterator, found bool, err error) { t.tb.Helper() citer, found, err = t.inner.ContainerIterator(ckey) return citer, found, t.oopsie(err) } func (t *testQueryReadWrapper) ApplyFilter(ckey uint64, filter roaring.BitmapFilter) (err error) { t.tb.Helper() return t.oopsie(t.inner.ApplyFilter(ckey, filter)) } func (t *testQueryReadWrapper) Container(ckey uint64) (*roaring.Container, error) { t.tb.Helper() c, err := t.inner.Container(ckey) return c, t.oopsie(err) } func (t *testQueryReadWrapper) Contains(v uint64) (exists bool, err error) { t.tb.Helper() exists, err = t.inner.Contains(v) return exists, t.oopsie(err) } func (t *testQueryReadWrapper) Count() (uint64, error) { t.tb.Helper() c, err := t.inner.Count() return c, t.oopsie(err) } func (t *testQueryReadWrapper) Max() (uint64, error) { t.tb.Helper() m, err := t.inner.Max() return m, t.oopsie(err) } func (t *testQueryReadWrapper) Min() (uint64, bool, error) { t.tb.Helper() m, ok, err := t.inner.Min() return m, ok, t.oopsie(err) } func (t *testQueryReadWrapper) CountRange(start, end uint64) (uint64, error) { t.tb.Helper() c, err := t.inner.CountRange(start, end) return c, t.oopsie(err) } func (t *testQueryReadWrapper) OffsetRange(offset, start, end uint64) (*roaring.Bitmap, error) { t.tb.Helper() b, err := t.inner.OffsetRange(offset, start, end) return b, t.oopsie(err) } func (t *testQueryReadWrapper) RoaringBitmap() (*roaring.Bitmap, error) { t.tb.Helper() b, err := t.inner.RoaringBitmap() return b, t.oopsie(err) } type testQueryWriteWrapper struct { inner QueryWrite oopsieWrapper } func newTestQueryWriteWrapper(tb testing.TB, inner QueryWrite) *testQueryWriteWrapper { return &testQueryWriteWrapper{inner: inner, oopsieWrapper: oopsieWrapper{tb: tb}} } func (t *testQueryWriteWrapper) PutContainer(ckey uint64, c *roaring.Container) error { t.tb.Helper() return t.oopsie(t.inner.PutContainer(ckey, c)) } func (t *testQueryWriteWrapper) RemoveContainer(ckey uint64) error { t.tb.Helper() return t.oopsie(t.inner.RemoveContainer(ckey)) } func (t *testQueryWriteWrapper) Add(a ...uint64) (changeCount int, err error) { c, err := t.inner.Add(a...) t.tb.Helper() return c, t.oopsie(err) } func (t *testQueryWriteWrapper) Remove(a ...uint64) (changeCount int, err error) { c, err := t.inner.Remove(a...) t.tb.Helper() return c, t.oopsie(err) } func (t *testQueryWriteWrapper) ApplyRewriter(ckey uint64, filter roaring.BitmapRewriter) (err error) { t.tb.Helper() return t.oopsie(t.inner.ApplyRewriter(ckey, filter)) } func (t *testQueryWriteWrapper) ImportRoaringBits(rit roaring.RoaringIterator, clear bool, rowSize uint64) (changed int, rowSet map[uint64]int, err error) { c, r, err := t.inner.ImportRoaringBits(rit, clear, rowSize) t.tb.Helper() return c, r, t.oopsie(err) } // and we duplicate the QueryRead methods, because it's messy to try to embed a QueryRead wrapper // in the QueryWrite wrapper and keep them sharing a single oopsieWrapper. func (t *testQueryWriteWrapper) ContainerIterator(ckey uint64) (citer roaring.ContainerIterator, found bool, err error) { citer, found, err = t.inner.ContainerIterator(ckey) t.tb.Helper() return citer, found, t.oopsie(err) } func (t *testQueryWriteWrapper) ApplyFilter(ckey uint64, filter roaring.BitmapFilter) (err error) { t.tb.Helper() return t.oopsie(t.inner.ApplyFilter(ckey, filter)) } func (t *testQueryWriteWrapper) Container(ckey uint64) (*roaring.Container, error) { c, err := t.inner.Container(ckey) t.tb.Helper() return c, t.oopsie(err) } func (t *testQueryWriteWrapper) Contains(v uint64) (exists bool, err error) { exists, err = t.inner.Contains(v) t.tb.Helper() return exists, t.oopsie(err) } func (t *testQueryWriteWrapper) Count() (uint64, error) { c, err := t.inner.Count() t.tb.Helper() return c, t.oopsie(err) } func (t *testQueryWriteWrapper) Max() (uint64, error) { m, err := t.inner.Max() t.tb.Helper() return m, t.oopsie(err) } func (t *testQueryWriteWrapper) Min() (uint64, bool, error) { m, ok, err := t.inner.Min() t.tb.Helper() return m, ok, t.oopsie(err) } func (t *testQueryWriteWrapper) CountRange(start, end uint64) (uint64, error) { c, err := t.inner.CountRange(start, end) t.tb.Helper() return c, t.oopsie(err) } func (t *testQueryWriteWrapper) OffsetRange(offset, start, end uint64) (*roaring.Bitmap, error) { b, err := t.inner.OffsetRange(offset, start, end) t.tb.Helper() return b, t.oopsie(err) } func (t *testQueryWriteWrapper) RoaringBitmap() (*roaring.Bitmap, error) { t.tb.Helper() b, err := t.inner.RoaringBitmap() return b, t.oopsie(err) }