featurebase/rbf/util_test.go
2022-01-21 10:57:05 -07:00

175 lines
4 KiB
Go

// Copyright 2021 Molecula Corp. All rights reserved.
package rbf
import (
"fmt"
"io"
"os"
"testing"
rbfcfg "github.com/molecula/featurebase/v3/rbf/cfg"
"github.com/molecula/featurebase/v3/roaring"
"github.com/molecula/featurebase/v3/testhook"
)
// util_test adds reusable utilities for testing.
// Here we catch BitN and ElemN mis-settings by
// scanning all data under an rbf-root (logically equivalent
// to a single roaring.Bitmap with multiple rows).
var _ = keysFromParents // linter happy
// verify that BitN and ElemN are correct.
func (c_orig *Cursor) DebugSlowCheckAllPages() {
// work with a totally new Cursor, so we don't impact our current cursor
// so any test using the cursor isn't disturbed.
c2 := Cursor{tx: c_orig.tx}
c2.stack.elems[0] = c_orig.stack.elems[0]
err := c2.First()
if err != nil {
if err == io.EOF {
// ok, can be empty
return
} else {
panic(err)
}
}
checkElemNBitN(c2.tx, 0)
}
// checkElemNBitN recursively writes the tree representation starting from a given page to STDERR.
func checkElemNBitN(tx *Tx, pgno uint32) {
page, _, err := tx.readPage(pgno)
if err != nil {
panic(err)
}
if IsMetaPage(page) {
visitor := func(pgno uint32, records []*RootRecord) {
for _, record := range records {
checkElemNBitN(tx, record.Pgno)
}
}
Walk(tx, readMetaRootRecordPageNo(page), visitor)
return
}
// Handle each type of page
switch typ := readFlags(page); typ {
case PageTypeBranch:
for i, n := 0, readCellN(page); i < n; i++ {
cell := readBranchCell(page, i)
if cell.Flags&uint32(ContainerTypeBitmap) == 0 { // leaf/branch child page
checkElemNBitN(tx, cell.ChildPgno)
}
// else is a bitmap
}
case PageTypeLeaf:
cellCheckElemNBitN(tx, page)
}
}
func cellCheckElemNBitN(tx *Tx, b []byte) {
pgno := readPageNo(b)
if pgno == Magic32() {
// skip meta page
return
}
flags := readFlags(b)
cellN := readCellN(b)
switch {
case flags&PageTypeLeaf != 0:
for i := 0; i < cellN; i++ {
cell := readLeafCell(b, i)
verifyElemNBitN(tx, cell)
}
}
}
func verifyElemNBitN(tx *Tx, lc leafCell) {
obsElemN := int32(-1)
obsBitN := int32(-1)
typ := ""
var c *roaring.Container
switch lc.Type {
case ContainerTypeArray:
typ = "array"
a := toArray16(lc.Data)
c = roaring.NewContainerArray(a)
obsBitN = c.N()
obsElemN = int32(len(a))
case ContainerTypeRLE:
typ = "rle"
a := toInterval16(lc.Data)
c = roaring.NewContainerRun(a)
obsBitN = c.N()
obsElemN = int32(len(a))
case ContainerTypeBitmap:
panic("should never get an actual bitmap!")
case ContainerTypeBitmapPtr:
typ = "bitmap_ptr"
_, bm, _ := tx.leafCellBitmap(toPgno(lc.Data))
c = roaring.NewContainerBitmap(-1, bm)
obsBitN = int32(c.N())
obsElemN = 0 // by definition, should always see 0 for raw bitmaps's ElemN
}
if lc.BitN != int(obsBitN) {
panic(fmt.Sprintf("lc.BitN(%v) != obsBitN(%v); typ='%v'", lc.BitN, obsBitN, typ))
}
if lc.ElemN != int(obsElemN) {
panic(fmt.Sprintf("lc.ElemN(%v) != obsElemN(%v); typ='%v'", lc.ElemN, obsElemN, typ))
}
}
func testHelperMustOpenNewDB(tb testing.TB, cfg ...*rbfcfg.Config) *DB {
tb.Helper()
path, err := testhook.TempDir(tb, "rbfdb")
if err != nil {
panic(err)
}
var cfg0 *rbfcfg.Config
if len(cfg) > 0 {
cfg0 = cfg[0]
}
db := NewDB(path, cfg0)
if err := db.Open(); err != nil {
tb.Fatal(err)
}
return db
}
// MustCloseDB closes db. On error, fail test.
// This function also also performs an integrity check on the DB.
func MustCloseDB(tb testing.TB, db *DB) {
tb.Helper()
if err := db.Check(); err != nil && err != ErrClosed {
tb.Fatal(err)
} else if n := db.TxN(); n != 0 {
tb.Fatalf("db still has %d active transactions; must closed before closing db", n)
} else if err := db.Close(); err != nil && err != ErrClosed {
tb.Fatal(err)
} else if err := os.RemoveAll(db.Path); err != nil {
tb.Fatal(err)
}
}
// MustBegin returns a new transaction or fails.
func MustBegin(tb testing.TB, db *DB, writable bool) *Tx {
tb.Helper()
tx, err := db.Begin(writable)
if err != nil {
tb.Fatal(err)
}
return tx
}