278 lines
5.7 KiB
Go
278 lines
5.7 KiB
Go
package badgerbs
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
blocks "github.com/ipfs/go-block-format"
|
|
cid "github.com/ipfs/go-cid"
|
|
|
|
"github.com/filecoin-project/lotus/blockstore"
|
|
)
|
|
|
|
func TestBadgerBlockstore(t *testing.T) {
|
|
(&Suite{
|
|
NewBlockstore: newBlockstore(DefaultOptions),
|
|
OpenBlockstore: openBlockstore(DefaultOptions),
|
|
}).RunTests(t, "non_prefixed")
|
|
|
|
prefixed := func(path string) Options {
|
|
opts := DefaultOptions(path)
|
|
opts.Prefix = "/prefixed/"
|
|
return opts
|
|
}
|
|
|
|
(&Suite{
|
|
NewBlockstore: newBlockstore(prefixed),
|
|
OpenBlockstore: openBlockstore(prefixed),
|
|
}).RunTests(t, "prefixed")
|
|
}
|
|
|
|
func TestStorageKey(t *testing.T) {
|
|
bs, _ := newBlockstore(DefaultOptions)(t)
|
|
bbs := bs.(*Blockstore)
|
|
defer bbs.Close() //nolint:errcheck
|
|
|
|
cid1 := blocks.NewBlock([]byte("some data")).Cid()
|
|
cid2 := blocks.NewBlock([]byte("more data")).Cid()
|
|
cid3 := blocks.NewBlock([]byte("a little more data")).Cid()
|
|
require.NotEqual(t, cid1, cid2) // sanity check
|
|
require.NotEqual(t, cid2, cid3) // sanity check
|
|
|
|
// nil slice; let StorageKey allocate for us.
|
|
k1 := bbs.StorageKey(nil, cid1)
|
|
require.Len(t, k1, 55)
|
|
require.True(t, cap(k1) == len(k1))
|
|
|
|
// k1's backing array is reused.
|
|
k2 := bbs.StorageKey(k1, cid2)
|
|
require.Len(t, k2, 55)
|
|
require.True(t, cap(k2) == len(k1))
|
|
|
|
// bring k2 to len=0, and verify that its backing array gets reused
|
|
// (i.e. k1 and k2 are overwritten)
|
|
k3 := bbs.StorageKey(k2[:0], cid3)
|
|
require.Len(t, k3, 55)
|
|
require.True(t, cap(k3) == len(k3))
|
|
|
|
// backing array of k1 and k2 has been modified, i.e. memory is shared.
|
|
require.Equal(t, k3, k1)
|
|
require.Equal(t, k3, k2)
|
|
}
|
|
|
|
func newBlockstore(optsSupplier func(path string) Options) func(tb testing.TB) (bs blockstore.BasicBlockstore, path string) {
|
|
return func(tb testing.TB) (bs blockstore.BasicBlockstore, path string) {
|
|
tb.Helper()
|
|
|
|
path, err := ioutil.TempDir("", "")
|
|
if err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
|
|
db, err := Open(optsSupplier(path))
|
|
if err != nil {
|
|
tb.Fatal(err)
|
|
}
|
|
|
|
tb.Cleanup(func() {
|
|
_ = os.RemoveAll(path)
|
|
})
|
|
|
|
return db, path
|
|
}
|
|
}
|
|
|
|
func openBlockstore(optsSupplier func(path string) Options) func(tb testing.TB, path string) (bs blockstore.BasicBlockstore, err error) {
|
|
return func(tb testing.TB, path string) (bs blockstore.BasicBlockstore, err error) {
|
|
tb.Helper()
|
|
return Open(optsSupplier(path))
|
|
}
|
|
}
|
|
|
|
func testMove(t *testing.T, optsF func(string) Options) {
|
|
ctx := context.Background()
|
|
basePath, err := ioutil.TempDir("", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
dbPath := filepath.Join(basePath, "db")
|
|
|
|
t.Cleanup(func() {
|
|
_ = os.RemoveAll(basePath)
|
|
})
|
|
|
|
db, err := Open(optsF(dbPath))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
defer db.Close() //nolint
|
|
|
|
var have []blocks.Block
|
|
var deleted []cid.Cid
|
|
|
|
// add some blocks
|
|
for i := 0; i < 10; i++ {
|
|
blk := blocks.NewBlock([]byte(fmt.Sprintf("some data %d", i)))
|
|
err := db.Put(ctx, blk)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
have = append(have, blk)
|
|
}
|
|
|
|
// delete some of them
|
|
for i := 5; i < 10; i++ {
|
|
c := have[i].Cid()
|
|
err := db.DeleteBlock(ctx, c)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
deleted = append(deleted, c)
|
|
}
|
|
have = have[:5]
|
|
|
|
// start a move concurrent with some more puts
|
|
g := new(errgroup.Group)
|
|
g.Go(func() error {
|
|
for i := 10; i < 1000; i++ {
|
|
blk := blocks.NewBlock([]byte(fmt.Sprintf("some data %d", i)))
|
|
err := db.Put(ctx, blk)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
have = append(have, blk)
|
|
}
|
|
return nil
|
|
})
|
|
g.Go(func() error {
|
|
return db.CollectGarbage(blockstore.WithFullGC(true))
|
|
})
|
|
|
|
err = g.Wait()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// now check that we have all the blocks in have and none in the deleted lists
|
|
checkBlocks := func() {
|
|
for _, blk := range have {
|
|
has, err := db.Has(ctx, blk.Cid())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !has {
|
|
t.Fatal("missing block")
|
|
}
|
|
|
|
blk2, err := db.Get(ctx, blk.Cid())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !bytes.Equal(blk.RawData(), blk2.RawData()) {
|
|
t.Fatal("data mismatch")
|
|
}
|
|
}
|
|
|
|
for _, c := range deleted {
|
|
has, err := db.Has(ctx, c)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if has {
|
|
t.Fatal("resurrected block")
|
|
}
|
|
}
|
|
}
|
|
|
|
checkBlocks()
|
|
|
|
// check the basePath -- it should contain a directory with name db.{timestamp}, soft-linked
|
|
// to db and nothing else
|
|
checkPath := func() {
|
|
entries, err := os.ReadDir(basePath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(entries) != 2 {
|
|
t.Fatalf("too many entries; expected %d but got %d", 2, len(entries))
|
|
}
|
|
|
|
var haveDB, haveDBLink bool
|
|
for _, e := range entries {
|
|
if e.Name() == "db" {
|
|
if (e.Type() & os.ModeSymlink) == 0 {
|
|
t.Fatal("found db, but it's not a symlink")
|
|
}
|
|
haveDBLink = true
|
|
continue
|
|
}
|
|
if strings.HasPrefix(e.Name(), "db.") {
|
|
if !e.Type().IsDir() {
|
|
t.Fatal("found db prefix, but it's not a directory")
|
|
}
|
|
haveDB = true
|
|
continue
|
|
}
|
|
}
|
|
|
|
if !haveDB {
|
|
t.Fatal("db directory is missing")
|
|
}
|
|
if !haveDBLink {
|
|
t.Fatal("db link is missing")
|
|
}
|
|
}
|
|
|
|
checkPath()
|
|
|
|
// now do another FullGC to test the double move and following of symlinks
|
|
if err := db.CollectGarbage(blockstore.WithFullGC(true)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
checkBlocks()
|
|
checkPath()
|
|
|
|
// reopen the db to make sure our relative link works:
|
|
err = db.Close()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
db, err = Open(optsF(dbPath))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// db.Close() is already deferred
|
|
|
|
checkBlocks()
|
|
}
|
|
|
|
func TestMoveNoPrefix(t *testing.T) {
|
|
testMove(t, DefaultOptions)
|
|
}
|
|
|
|
func TestMoveWithPrefix(t *testing.T) {
|
|
testMove(t, func(path string) Options {
|
|
opts := DefaultOptions(path)
|
|
opts.Prefix = "/prefixed/"
|
|
return opts
|
|
})
|
|
}
|