mirror of https://github.com/etcd-io/bbolt.git
167 lines
4.7 KiB
Go
167 lines
4.7 KiB
Go
package bbolt_test
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"go.etcd.io/bbolt"
|
|
"go.etcd.io/bbolt/internal/btesting"
|
|
"go.etcd.io/bbolt/internal/common"
|
|
"go.etcd.io/bbolt/internal/guts_cli"
|
|
)
|
|
|
|
func TestTx_Check_CorruptPage(t *testing.T) {
|
|
bucketName := []byte("data")
|
|
|
|
t.Log("Creating db file.")
|
|
db := btesting.MustCreateDBWithOption(t, &bbolt.Options{PageSize: 4096})
|
|
|
|
// Each page can hold roughly 20 key/values pair, so 100 such
|
|
// key/value pairs will consume about 5 leaf pages.
|
|
err := db.Fill(bucketName, 1, 100,
|
|
func(tx int, k int) []byte { return []byte(fmt.Sprintf("%04d", k)) },
|
|
func(tx int, k int) []byte { return make([]byte, 100) },
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
t.Log("Corrupting a random leaf page.")
|
|
victimPageId, validPageIds := corruptRandomLeafPageInBucket(t, db.DB, bucketName)
|
|
|
|
t.Log("Running consistency check.")
|
|
vErr := db.View(func(tx *bbolt.Tx) error {
|
|
var cErrs []error
|
|
|
|
t.Log("Check corrupted page.")
|
|
errChan := tx.Check(bbolt.WithPageId(uint64(victimPageId)))
|
|
for cErr := range errChan {
|
|
cErrs = append(cErrs, cErr)
|
|
}
|
|
require.Greater(t, len(cErrs), 0)
|
|
|
|
t.Log("Check valid pages.")
|
|
cErrs = cErrs[:0]
|
|
for _, pgId := range validPageIds {
|
|
errChan = tx.Check(bbolt.WithPageId(uint64(pgId)))
|
|
for cErr := range errChan {
|
|
cErrs = append(cErrs, cErr)
|
|
}
|
|
require.Equal(t, 0, len(cErrs))
|
|
}
|
|
return nil
|
|
})
|
|
require.NoError(t, vErr)
|
|
t.Log("All check passed")
|
|
|
|
// Manually close the db, otherwise the PostTestCleanup will
|
|
// check the db again and accordingly fail the test.
|
|
db.MustClose()
|
|
}
|
|
|
|
func TestTx_Check_WithNestBucket(t *testing.T) {
|
|
parentBucketName := []byte("parentBucket")
|
|
|
|
t.Log("Creating db file.")
|
|
db := btesting.MustCreateDBWithOption(t, &bbolt.Options{PageSize: 4096})
|
|
|
|
err := db.Update(func(tx *bbolt.Tx) error {
|
|
pb, bErr := tx.CreateBucket(parentBucketName)
|
|
if bErr != nil {
|
|
return bErr
|
|
}
|
|
|
|
t.Log("put some key/values under the parent bucket directly")
|
|
for i := 0; i < 10; i++ {
|
|
k, v := fmt.Sprintf("%04d", i), fmt.Sprintf("value_%4d", i)
|
|
if pErr := pb.Put([]byte(k), []byte(v)); pErr != nil {
|
|
return pErr
|
|
}
|
|
}
|
|
|
|
t.Log("create a nested bucket and put some key/values under the nested bucket")
|
|
cb, bErr := pb.CreateBucket([]byte("nestedBucket"))
|
|
if bErr != nil {
|
|
return bErr
|
|
}
|
|
|
|
for i := 0; i < 2000; i++ {
|
|
k, v := fmt.Sprintf("%04d", i), fmt.Sprintf("value_%4d", i)
|
|
if pErr := cb.Put([]byte(k), []byte(v)); pErr != nil {
|
|
return pErr
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Get the bucket's root page.
|
|
bucketRootPageId := mustGetBucketRootPage(t, db.DB, parentBucketName)
|
|
|
|
t.Logf("Running consistency check starting from pageId: %d", bucketRootPageId)
|
|
vErr := db.View(func(tx *bbolt.Tx) error {
|
|
var cErrs []error
|
|
|
|
errChan := tx.Check(bbolt.WithPageId(uint64(bucketRootPageId)))
|
|
for cErr := range errChan {
|
|
cErrs = append(cErrs, cErr)
|
|
}
|
|
require.Equal(t, 0, len(cErrs))
|
|
|
|
return nil
|
|
})
|
|
require.NoError(t, vErr)
|
|
t.Log("All check passed")
|
|
|
|
// Manually close the db, otherwise the PostTestCleanup will
|
|
// check the db again and accordingly fail the test.
|
|
db.MustClose()
|
|
}
|
|
|
|
// corruptRandomLeafPage corrupts one random leaf page.
|
|
func corruptRandomLeafPageInBucket(t testing.TB, db *bbolt.DB, bucketName []byte) (victimPageId common.Pgid, validPageIds []common.Pgid) {
|
|
bucketRootPageId := mustGetBucketRootPage(t, db, bucketName)
|
|
bucketRootPage, _, err := guts_cli.ReadPage(db.Path(), uint64(bucketRootPageId))
|
|
require.NoError(t, err)
|
|
require.True(t, bucketRootPage.IsBranchPage())
|
|
|
|
// Retrieve all the leaf pages included in the branch page, and pick up random one from them.
|
|
var bucketPageIds []common.Pgid
|
|
for _, bpe := range bucketRootPage.BranchPageElements() {
|
|
bucketPageIds = append(bucketPageIds, bpe.Pgid())
|
|
}
|
|
randomIdx := rand.Intn(len(bucketPageIds))
|
|
victimPageId = bucketPageIds[randomIdx]
|
|
validPageIds = append(bucketPageIds[:randomIdx], bucketPageIds[randomIdx+1:]...)
|
|
|
|
victimPage, victimBuf, err := guts_cli.ReadPage(db.Path(), uint64(victimPageId))
|
|
require.NoError(t, err)
|
|
require.True(t, victimPage.IsLeafPage())
|
|
require.True(t, victimPage.Count() > 1)
|
|
|
|
// intentionally make the second key < the first key.
|
|
element := victimPage.LeafPageElement(1)
|
|
key := element.Key()
|
|
key[0] = 0
|
|
|
|
// Write the corrupt page to db file.
|
|
err = guts_cli.WritePage(db.Path(), victimBuf)
|
|
require.NoError(t, err)
|
|
return victimPageId, validPageIds
|
|
}
|
|
|
|
// mustGetBucketRootPage returns the root page for the provided bucket.
|
|
func mustGetBucketRootPage(t testing.TB, db *bbolt.DB, bucketName []byte) common.Pgid {
|
|
var rootPageId common.Pgid
|
|
_ = db.View(func(tx *bbolt.Tx) error {
|
|
b := tx.Bucket(bucketName)
|
|
require.NotNil(t, b)
|
|
rootPageId = b.RootPage()
|
|
return nil
|
|
})
|
|
|
|
return rootPageId
|
|
}
|