Skip to content

Commit c0d651b

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

File tree

1 file changed

+216
-0
lines changed

1 file changed

+216
-0
lines changed

db_whitebox_test.go

+216
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
package bbolt
22

33
import (
4+
"bytes"
5+
crand "crypto/rand"
6+
"encoding/binary"
7+
"fmt"
8+
"math/rand"
49
"path/filepath"
510
"testing"
11+
"unsafe"
612

713
"github.com/stretchr/testify/assert"
814
"github.com/stretchr/testify/require"
915

1016
"go.etcd.io/bbolt/errors"
17+
"go.etcd.io/bbolt/internal/common"
18+
"go.etcd.io/bbolt/internal/guts_cli"
1119
)
1220

1321
func TestOpenWithPreLoadFreelist(t *testing.T) {
@@ -112,6 +120,214 @@ func TestMethodPage(t *testing.T) {
112120
}
113121
}
114122

123+
func TestTx_Check_CorruptPage_ViolateBtreeInvariant(t *testing.T) {
124+
bucketKey := "testBucket"
125+
pageSize := 4096
126+
127+
t.Log("Creating db file.")
128+
db, err := Open(filepath.Join(t.TempDir(), "db"), 0600, &Options{PageSize: pageSize})
129+
require.NoError(t, err)
130+
defer func() {
131+
require.NoError(t, db.Close())
132+
}()
133+
134+
uErr := db.Update(func(tx *Tx) error {
135+
t.Logf("Creating bucket '%v'.", bucketKey)
136+
b, bErr := tx.CreateBucketIfNotExists([]byte(bucketKey))
137+
require.NoError(t, bErr)
138+
t.Logf("Generating random data in bucket '%v'.", bucketKey)
139+
generateSampleDataInBucket(t, b, pageSize, 3)
140+
return nil
141+
})
142+
require.NoError(t, uErr)
143+
144+
t.Logf("Corrupting random leaf page in bucket '%v'.", bucketKey)
145+
victimPageId, validPageIds := corruptLeafPage(t, db, pageSize, false)
146+
147+
t.Log("Running consistency check.")
148+
vErr := db.View(func(tx *Tx) error {
149+
chkConfig := checkConfig{
150+
kvStringer: HexKVStringer(),
151+
}
152+
153+
t.Log("Check corrupted page.")
154+
ch := make(chan error)
155+
chkConfig.pageId = uint(victimPageId)
156+
go func() {
157+
defer close(ch)
158+
tx.check(chkConfig, ch)
159+
}()
160+
161+
var cErrs []error
162+
for cErr := range ch {
163+
cErrs = append(cErrs, cErr)
164+
}
165+
require.Greater(t, len(cErrs), 0)
166+
167+
t.Log("Check valid pages.")
168+
cErrs = cErrs[:0]
169+
for _, pgId := range validPageIds {
170+
ch = make(chan error)
171+
chkConfig.pageId = uint(pgId)
172+
go func() {
173+
defer close(ch)
174+
tx.check(chkConfig, ch)
175+
}()
176+
177+
for cErr := range ch {
178+
cErrs = append(cErrs, cErr)
179+
}
180+
require.Equal(t, 0, len(cErrs))
181+
}
182+
return nil
183+
})
184+
require.NoError(t, vErr)
185+
}
186+
187+
func TestTx_Check_CorruptPage_DumpRandomBytes(t *testing.T) {
188+
bucketKey := "testBucket"
189+
pageSize := 4096
190+
191+
t.Log("Creating db file.")
192+
db, err := Open(filepath.Join(t.TempDir(), "db"), 0600, &Options{PageSize: pageSize})
193+
require.NoError(t, err)
194+
defer func() {
195+
require.NoError(t, db.Close())
196+
}()
197+
198+
uErr := db.Update(func(tx *Tx) error {
199+
t.Logf("Creating bucket '%v'.", bucketKey)
200+
b, bErr := tx.CreateBucketIfNotExists([]byte(bucketKey))
201+
require.NoError(t, bErr)
202+
t.Logf("Generating random data in bucket '%v'.", bucketKey)
203+
generateSampleDataInBucket(t, b, pageSize, 3)
204+
return nil
205+
})
206+
require.NoError(t, uErr)
207+
208+
t.Logf("Corrupting random leaf page in bucket '%v'.", bucketKey)
209+
victimPageId, _ := corruptLeafPage(t, db, pageSize, true)
210+
211+
t.Log("Running consistency check.")
212+
vErr := db.View(func(tx *Tx) error {
213+
chkConfig := checkConfig{
214+
kvStringer: HexKVStringer(),
215+
pageId: uint(victimPageId),
216+
}
217+
218+
ch := make(chan error)
219+
defer close(ch)
220+
221+
defer func() {
222+
r := recover()
223+
require.NotNil(t, r)
224+
}()
225+
226+
tx.check(chkConfig, ch)
227+
228+
return nil
229+
})
230+
require.NoError(t, vErr)
231+
}
232+
233+
// corruptLeafPage write an invalid leafPageElement into the victim page.
234+
func corruptLeafPage(t testing.TB, db *DB, pageSize int, expectPanic bool) (victimPageId common.Pgid, validPageIds []common.Pgid) {
235+
t.Helper()
236+
237+
victimPageId, validPageIds = findVictimPageId(t, db)
238+
239+
victimPage, victimBuf, err := guts_cli.ReadPage(db.Path(), uint64(victimPageId))
240+
require.NoError(t, err)
241+
require.True(t, victimPage.IsLeafPage())
242+
require.True(t, victimPage.Count() > 0)
243+
244+
// Dumping random bytes in victim page for corruption.
245+
copy(victimBuf[32:], generateCorruptionBytes(t, pageSize, expectPanic))
246+
// Write the corrupt page to db file.
247+
err = guts_cli.WritePage(db.Path(), victimBuf)
248+
require.NoError(t, err)
249+
250+
return victimPageId, validPageIds
251+
}
252+
253+
// findVictimPageId finds all the leaf pages of a bucket and picks a random leaf page as a victim to be corrupted.
254+
func findVictimPageId(t testing.TB, db *DB) (victimPageId common.Pgid, validPageIds []common.Pgid) {
255+
t.Helper()
256+
// Read DB's RootPage.
257+
rootPageId, _, err := guts_cli.GetRootPage(db.Path())
258+
require.NoError(t, err)
259+
rootPage, _, err := guts_cli.ReadPage(db.Path(), uint64(rootPageId))
260+
require.NoError(t, err)
261+
require.True(t, rootPage.IsLeafPage())
262+
require.Equal(t, 1, len(rootPage.LeafPageElements()))
263+
// Find Bucket's RootPage.
264+
lpe := rootPage.LeafPageElement(uint16(0))
265+
require.Equal(t, uint32(common.BranchPageFlag), lpe.Flags())
266+
k := lpe.Key()
267+
require.Equal(t, "testBucket", string(k))
268+
bucketRootPageId := lpe.Bucket().RootPage()
269+
// Read Bucket's RootPage.
270+
bucketRootPage, _, err := guts_cli.ReadPage(db.Path(), uint64(bucketRootPageId))
271+
require.NoError(t, err)
272+
require.Equal(t, uint16(common.BranchPageFlag), bucketRootPage.Flags())
273+
// Retrieve Bucket's PageIds
274+
var bucketPageIds []common.Pgid
275+
for _, bpe := range bucketRootPage.BranchPageElements() {
276+
bucketPageIds = append(bucketPageIds, bpe.Pgid())
277+
}
278+
279+
randomIdx := rand.Intn(len(bucketPageIds))
280+
victimPageId = bucketPageIds[randomIdx]
281+
validPageIds = append(bucketPageIds[:randomIdx], bucketPageIds[randomIdx+1:]...)
282+
return victimPageId, validPageIds
283+
}
284+
285+
// generateSampleDataInBucket fill in sample data into given bucket to create the given
286+
// number of leafPages. To control the number of leafPages, sample data are generated in order.
287+
func generateSampleDataInBucket(t testing.TB, bk *Bucket, pageSize int, lPages int) {
288+
t.Helper()
289+
290+
maxBytesInPage := int(DefaultFillPercent * float32(pageSize))
291+
292+
currentKey := 1
293+
currentVal := 100
294+
for i := 0; i < lPages; i++ {
295+
currentSize := common.PageHeaderSize
296+
for {
297+
err := bk.Put([]byte(fmt.Sprintf("key_%d", currentKey)), []byte(fmt.Sprintf("val_%d", currentVal)))
298+
require.NoError(t, err)
299+
currentSize += common.LeafPageElementSize + unsafe.Sizeof(currentKey) + unsafe.Sizeof(currentVal)
300+
if int(currentSize) >= maxBytesInPage {
301+
break
302+
}
303+
currentKey++
304+
currentVal++
305+
}
306+
}
307+
}
308+
309+
// generateCorruptionBytes returns random bytes to corrupt a page.
310+
// It inserts a page element which violates the btree key order if no panic is expected.
311+
// Otherwise, it dumps random bytes into a page which causes a panic.
312+
func generateCorruptionBytes(t testing.TB, pageSize int, expectPanic bool) []byte {
313+
if expectPanic {
314+
// Generated data size is between pageHeader and pageSize.
315+
maxLen := pageSize - int(common.PageHeaderSize)
316+
minLen := 16
317+
corruptDataLength := rand.Intn(maxLen-minLen) + minLen
318+
corruptData := make([]byte, corruptDataLength)
319+
_, err := crand.Read(corruptData)
320+
require.NoError(t, err)
321+
return corruptData
322+
}
323+
// Insert LeafPageElement which violates the BTree range.
324+
invalidLPE := common.NewLeafPageElement(0, 0, 0, 0)
325+
var buf bytes.Buffer
326+
err := binary.Write(&buf, binary.BigEndian, invalidLPE)
327+
require.NoError(t, err)
328+
return buf.Bytes()
329+
}
330+
115331
func prepareData(t *testing.T) (string, error) {
116332
fileName := filepath.Join(t.TempDir(), "db")
117333
db, err := Open(fileName, 0666, nil)

0 commit comments

Comments
 (0)