Skip to content

Commit 20e9611

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

File tree

1 file changed

+144
-0
lines changed

1 file changed

+144
-0
lines changed

tx_check_test.go

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package bbolt_test
2+
3+
import (
4+
"bytes"
5+
"encoding/binary"
6+
"fmt"
7+
"math/rand"
8+
"testing"
9+
"unsafe"
10+
11+
"github.com/stretchr/testify/require"
12+
13+
"go.etcd.io/bbolt"
14+
"go.etcd.io/bbolt/internal/btesting"
15+
"go.etcd.io/bbolt/internal/common"
16+
"go.etcd.io/bbolt/internal/guts_cli"
17+
)
18+
19+
func TestTx_Check_CorruptPage(t *testing.T) {
20+
bucketKey := "testBucket"
21+
22+
t.Log("Creating db file.")
23+
db := btesting.MustCreateDBWithOption(t, &bbolt.Options{PageSize: pageSize})
24+
defer func() {
25+
require.NoError(t, db.Close())
26+
}()
27+
28+
uErr := db.Update(func(tx *bbolt.Tx) error {
29+
t.Logf("Creating bucket '%v'.", bucketKey)
30+
b, bErr := tx.CreateBucketIfNotExists([]byte(bucketKey))
31+
require.NoError(t, bErr)
32+
t.Logf("Generating random data in bucket '%v'.", bucketKey)
33+
generateSampleDataInBucket(t, b, pageSize, 3)
34+
return nil
35+
})
36+
require.NoError(t, uErr)
37+
38+
t.Logf("Corrupting random leaf page in bucket '%v'.", bucketKey)
39+
victimPageId, validPageIds := corruptLeafPage(t, db.DB)
40+
41+
t.Log("Running consistency check.")
42+
vErr := db.View(func(tx *bbolt.Tx) error {
43+
var cErrs []error
44+
45+
t.Log("Check corrupted page.")
46+
errChan := tx.Check(bbolt.WithPageId(uint(victimPageId)))
47+
for cErr := range errChan {
48+
cErrs = append(cErrs, cErr)
49+
}
50+
require.Greater(t, len(cErrs), 0)
51+
52+
t.Log("Check valid pages.")
53+
cErrs = cErrs[:0]
54+
for _, pgId := range validPageIds {
55+
errChan = tx.Check(bbolt.WithPageId(uint(pgId)))
56+
for cErr := range errChan {
57+
cErrs = append(cErrs, cErr)
58+
}
59+
require.Equal(t, 0, len(cErrs))
60+
}
61+
return nil
62+
})
63+
require.NoError(t, vErr)
64+
}
65+
66+
// corruptLeafPage write an invalid leafPageElement into the victim page.
67+
func corruptLeafPage(t testing.TB, db *bbolt.DB) (victimPageId common.Pgid, validPageIds []common.Pgid) {
68+
t.Helper()
69+
victimPageId, validPageIds = findVictimPageId(t, db)
70+
victimPage, victimBuf, err := guts_cli.ReadPage(db.Path(), uint64(victimPageId))
71+
require.NoError(t, err)
72+
require.True(t, victimPage.IsLeafPage())
73+
require.True(t, victimPage.Count() > 0)
74+
// Dumping random bytes in victim page for corruption.
75+
copy(victimBuf[32:], generateCorruptionBytes(t))
76+
// Write the corrupt page to db file.
77+
err = guts_cli.WritePage(db.Path(), victimBuf)
78+
require.NoError(t, err)
79+
return victimPageId, validPageIds
80+
}
81+
82+
// findVictimPageId finds all the leaf pages of a bucket and picks a random leaf page as a victim to be corrupted.
83+
func findVictimPageId(t testing.TB, db *bbolt.DB) (victimPageId common.Pgid, validPageIds []common.Pgid) {
84+
t.Helper()
85+
// Read DB's RootPage.
86+
rootPageId, _, err := guts_cli.GetRootPage(db.Path())
87+
require.NoError(t, err)
88+
rootPage, _, err := guts_cli.ReadPage(db.Path(), uint64(rootPageId))
89+
require.NoError(t, err)
90+
require.True(t, rootPage.IsLeafPage())
91+
require.Equal(t, 1, len(rootPage.LeafPageElements()))
92+
// Find Bucket's RootPage.
93+
lpe := rootPage.LeafPageElement(uint16(0))
94+
require.Equal(t, uint32(common.BranchPageFlag), lpe.Flags())
95+
k := lpe.Key()
96+
require.Equal(t, "testBucket", string(k))
97+
bucketRootPageId := lpe.Bucket().RootPage()
98+
// Read Bucket's RootPage.
99+
bucketRootPage, _, err := guts_cli.ReadPage(db.Path(), uint64(bucketRootPageId))
100+
require.NoError(t, err)
101+
require.Equal(t, uint16(common.BranchPageFlag), bucketRootPage.Flags())
102+
// Retrieve Bucket's PageIds
103+
var bucketPageIds []common.Pgid
104+
for _, bpe := range bucketRootPage.BranchPageElements() {
105+
bucketPageIds = append(bucketPageIds, bpe.Pgid())
106+
}
107+
randomIdx := rand.Intn(len(bucketPageIds))
108+
victimPageId = bucketPageIds[randomIdx]
109+
validPageIds = append(bucketPageIds[:randomIdx], bucketPageIds[randomIdx+1:]...)
110+
return victimPageId, validPageIds
111+
}
112+
113+
// generateSampleDataInBucket fill in sample data into given bucket to create the given
114+
// number of leafPages. To control the number of leafPages, sample data are generated in order.
115+
func generateSampleDataInBucket(t testing.TB, bk *bbolt.Bucket, pageSize int, lPages int) {
116+
t.Helper()
117+
maxBytesInPage := int(bk.FillPercent * float64(pageSize))
118+
currentKey := 1
119+
currentVal := 100
120+
for i := 0; i < lPages; i++ {
121+
currentSize := common.PageHeaderSize
122+
for {
123+
err := bk.Put([]byte(fmt.Sprintf("key_%d", currentKey)), []byte(fmt.Sprintf("val_%d", currentVal)))
124+
require.NoError(t, err)
125+
currentSize += common.LeafPageElementSize + unsafe.Sizeof(currentKey) + unsafe.Sizeof(currentVal)
126+
if int(currentSize) >= maxBytesInPage {
127+
break
128+
}
129+
currentKey++
130+
currentVal++
131+
}
132+
}
133+
}
134+
135+
// generateCorruptionBytes returns random bytes to corrupt a page.
136+
// It inserts a page element which violates the btree key order if no panic is expected.
137+
func generateCorruptionBytes(t testing.TB) []byte {
138+
t.Helper()
139+
invalidLPE := common.NewLeafPageElement(0, 0, 0, 0)
140+
var buf bytes.Buffer
141+
err := binary.Write(&buf, binary.BigEndian, invalidLPE)
142+
require.NoError(t, err)
143+
return buf.Bytes()
144+
}

0 commit comments

Comments
 (0)