Skip to content

Commit 939e3a2

Browse files
committed
add test check page
Signed-off-by: Mustafa Elbehery <melbeher@redhat.com>
1 parent 248e6f8 commit 939e3a2

File tree

1 file changed

+151
-0
lines changed

1 file changed

+151
-0
lines changed

db_whitebox_test.go

+151
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
package bbolt
22

33
import (
4+
"bytes"
5+
"encoding/binary"
6+
"fmt"
7+
"math/rand"
48
"path/filepath"
59
"testing"
10+
"unsafe"
611

712
"github.com/stretchr/testify/assert"
813
"github.com/stretchr/testify/require"
914

1015
"go.etcd.io/bbolt/errors"
16+
"go.etcd.io/bbolt/internal/common"
17+
"go.etcd.io/bbolt/internal/guts_cli"
1118
)
1219

1320
func TestOpenWithPreLoadFreelist(t *testing.T) {
@@ -112,6 +119,150 @@ func TestMethodPage(t *testing.T) {
112119
}
113120
}
114121

122+
func TestTx_Check_CorruptPage_ViolateBtreeInvariant(t *testing.T) {
123+
bucketKey := "testBucket"
124+
pageSize := 4096
125+
126+
t.Log("Creating db file.")
127+
db, err := Open(filepath.Join(t.TempDir(), "db"), 0600, &Options{PageSize: pageSize})
128+
require.NoError(t, err)
129+
defer func() {
130+
require.NoError(t, db.Close())
131+
}()
132+
133+
uErr := db.Update(func(tx *Tx) error {
134+
t.Logf("Creating bucket '%v'.", bucketKey)
135+
b, bErr := tx.CreateBucketIfNotExists([]byte(bucketKey))
136+
require.NoError(t, bErr)
137+
t.Logf("Generating random data in bucket '%v'.", bucketKey)
138+
generateSampleDataInBucket(t, b, pageSize, 3)
139+
return nil
140+
})
141+
require.NoError(t, uErr)
142+
143+
t.Logf("Corrupting random leaf page in bucket '%v'.", bucketKey)
144+
victimPageId, validPageIds := corruptLeafPage(t, db)
145+
146+
t.Log("Running consistency check.")
147+
vErr := db.View(func(tx *Tx) error {
148+
chkConfig := checkConfig{
149+
kvStringer: HexKVStringer(),
150+
}
151+
152+
t.Log("Check corrupted page.")
153+
ch := make(chan error)
154+
chkConfig.pageId = uint(victimPageId)
155+
go func() {
156+
defer close(ch)
157+
tx.check(chkConfig, ch)
158+
}()
159+
160+
var cErrs []error
161+
for cErr := range ch {
162+
cErrs = append(cErrs, cErr)
163+
}
164+
require.Greater(t, len(cErrs), 0)
165+
166+
t.Log("Check valid pages.")
167+
cErrs = cErrs[:0]
168+
for _, pgId := range validPageIds {
169+
ch = make(chan error)
170+
chkConfig.pageId = uint(pgId)
171+
go func() {
172+
defer close(ch)
173+
tx.check(chkConfig, ch)
174+
}()
175+
176+
for cErr := range ch {
177+
cErrs = append(cErrs, cErr)
178+
}
179+
require.Equal(t, 0, len(cErrs))
180+
}
181+
return nil
182+
})
183+
require.NoError(t, vErr)
184+
}
185+
186+
// corruptLeafPage write an invalid leafPageElement into the victim page.
187+
func corruptLeafPage(t testing.TB, db *DB) (victimPageId common.Pgid, validPageIds []common.Pgid) {
188+
t.Helper()
189+
victimPageId, validPageIds = findVictimPageId(t, db)
190+
victimPage, victimBuf, err := guts_cli.ReadPage(db.Path(), uint64(victimPageId))
191+
require.NoError(t, err)
192+
require.True(t, victimPage.IsLeafPage())
193+
require.True(t, victimPage.Count() > 0)
194+
// Dumping random bytes in victim page for corruption.
195+
copy(victimBuf[32:], generateCorruptionBytes(t))
196+
// Write the corrupt page to db file.
197+
err = guts_cli.WritePage(db.Path(), victimBuf)
198+
require.NoError(t, err)
199+
return victimPageId, validPageIds
200+
}
201+
202+
// findVictimPageId finds all the leaf pages of a bucket and picks a random leaf page as a victim to be corrupted.
203+
func findVictimPageId(t testing.TB, db *DB) (victimPageId common.Pgid, validPageIds []common.Pgid) {
204+
t.Helper()
205+
// Read DB's RootPage.
206+
rootPageId, _, err := guts_cli.GetRootPage(db.Path())
207+
require.NoError(t, err)
208+
rootPage, _, err := guts_cli.ReadPage(db.Path(), uint64(rootPageId))
209+
require.NoError(t, err)
210+
require.True(t, rootPage.IsLeafPage())
211+
require.Equal(t, 1, len(rootPage.LeafPageElements()))
212+
// Find Bucket's RootPage.
213+
lpe := rootPage.LeafPageElement(uint16(0))
214+
require.Equal(t, uint32(common.BranchPageFlag), lpe.Flags())
215+
k := lpe.Key()
216+
require.Equal(t, "testBucket", string(k))
217+
bucketRootPageId := lpe.Bucket().RootPage()
218+
// Read Bucket's RootPage.
219+
bucketRootPage, _, err := guts_cli.ReadPage(db.Path(), uint64(bucketRootPageId))
220+
require.NoError(t, err)
221+
require.Equal(t, uint16(common.BranchPageFlag), bucketRootPage.Flags())
222+
// Retrieve Bucket's PageIds
223+
var bucketPageIds []common.Pgid
224+
for _, bpe := range bucketRootPage.BranchPageElements() {
225+
bucketPageIds = append(bucketPageIds, bpe.Pgid())
226+
}
227+
randomIdx := rand.Intn(len(bucketPageIds))
228+
victimPageId = bucketPageIds[randomIdx]
229+
validPageIds = append(bucketPageIds[:randomIdx], bucketPageIds[randomIdx+1:]...)
230+
return victimPageId, validPageIds
231+
}
232+
233+
// generateSampleDataInBucket fill in sample data into given bucket to create the given
234+
// number of leafPages. To control the number of leafPages, sample data are generated in order.
235+
func generateSampleDataInBucket(t testing.TB, bk *Bucket, pageSize int, lPages int) {
236+
t.Helper()
237+
maxBytesInPage := int(bk.FillPercent * float64(pageSize))
238+
currentKey := 1
239+
currentVal := 100
240+
for i := 0; i < lPages; i++ {
241+
currentSize := common.PageHeaderSize
242+
for {
243+
err := bk.Put([]byte(fmt.Sprintf("key_%d", currentKey)), []byte(fmt.Sprintf("val_%d", currentVal)))
244+
require.NoError(t, err)
245+
currentSize += common.LeafPageElementSize + unsafe.Sizeof(currentKey) + unsafe.Sizeof(currentVal)
246+
if int(currentSize) >= maxBytesInPage {
247+
break
248+
}
249+
currentKey++
250+
currentVal++
251+
}
252+
}
253+
}
254+
255+
// generateCorruptionBytes returns random bytes to corrupt a page.
256+
// It inserts a page element which violates the btree key order if no panic is expected.
257+
func generateCorruptionBytes(t testing.TB) []byte {
258+
t.Helper()
259+
invalidLPE := common.NewLeafPageElement(0, 0, 0, 0)
260+
var buf bytes.Buffer
261+
err := binary.Write(&buf, binary.BigEndian, invalidLPE)
262+
require.NoError(t, err)
263+
return buf.Bytes()
264+
}
265+
115266
func prepareData(t *testing.T) (string, error) {
116267
fileName := filepath.Join(t.TempDir(), "db")
117268
db, err := Open(fileName, 0666, nil)

0 commit comments

Comments
 (0)