Skip to content

Commit 468054c

Browse files
committed
add test check page
Signed-off-by: Mustafa Elbehery <melbeher@redhat.com>
1 parent 8ede234 commit 468054c

File tree

1 file changed

+181
-0
lines changed

1 file changed

+181
-0
lines changed

db_whitebox_test.go

+181
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
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"
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,180 @@ func TestMethodPage(t *testing.T) {
112119
}
113120
}
114121

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

0 commit comments

Comments
 (0)