Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ea86554

Browse files
committedJan 19, 2024
add test check page
Signed-off-by: Mustafa Elbehery <melbeher@redhat.com>
1 parent 6383011 commit ea86554

File tree

1 file changed

+180
-0
lines changed

1 file changed

+180
-0
lines changed
 

‎db_whitebox_test.go

+180
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,179 @@ 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+
r := recover()
168+
require.NotNil(t, r)
169+
}()
170+
}
171+
172+
t.Log("Running consistency check.")
173+
var cErrs []error
174+
vErr := db.View(func(tx *Tx) error {
175+
chkConfig := checkConfig{
176+
kvStringer: HexKVStringer(),
177+
}
178+
ch := make(chan error)
179+
defer close(ch)
180+
181+
go func() {
182+
for cErr := range ch {
183+
cErrs = append(cErrs, cErr)
184+
}
185+
}()
186+
187+
tx.check(chkConfig, ch)
188+
return nil
189+
})
190+
if !tc.isPanic {
191+
require.NoError(t, vErr)
192+
require.Equal(t, 1, len(cErrs))
193+
require.ErrorContains(t, cErrs[0], fmt.Sprintf("leaf page(%d)", victimPageId))
194+
}
195+
})
196+
}
197+
}
198+
199+
// corruptLeafPage write an invalid leafPageElement into the victim page.
200+
func corruptLeafPage(t testing.TB, db *DB, isPanic bool) common.Pgid {
201+
t.Helper()
202+
203+
victimPageId := findVictimPageId(t, db)
204+
205+
victimPage, victimBuf, err := guts_cli.ReadPage(db.Path(), uint64(victimPageId))
206+
require.NoError(t, err)
207+
require.NotNil(t, victimPage)
208+
require.NotNil(t, victimBuf)
209+
require.True(t, victimPage.IsLeafPage())
210+
require.True(t, victimPage.Count() > 0)
211+
212+
// Dumping random bytes in victim page for corruption.
213+
copy(victimBuf[16:], generateCorruptionBytes(t, isPanic))
214+
// Write the corrupt page to db file.
215+
err = guts_cli.WritePage(db.Path(), victimBuf)
216+
require.NoError(t, err)
217+
218+
return victimPageId
219+
}
220+
221+
// findVictimPageId finds all the leaf pages of a bucket and picks a leaf page to be corrupted.
222+
func findVictimPageId(t testing.TB, db *DB) common.Pgid {
223+
t.Helper()
224+
// Read DB's RootPage.
225+
rootPageId, _, err := guts_cli.GetRootPage(db.Path())
226+
require.NoError(t, err)
227+
rootPage, _, err := guts_cli.ReadPage(db.Path(), uint64(rootPageId))
228+
require.NoError(t, err)
229+
require.True(t, rootPage.IsLeafPage())
230+
require.Equal(t, 1, len(rootPage.LeafPageElements()))
231+
// Find Bucket's RootPage.
232+
lpe := rootPage.LeafPageElement(uint16(0))
233+
require.Equal(t, uint32(common.BranchPageFlag), lpe.Flags())
234+
k := lpe.Key()
235+
require.Equal(t, "testBucket", string(k))
236+
bucketRootPageId := lpe.Bucket().RootPage()
237+
// Read Bucket's RootPage.
238+
bucketRootPage, _, err := guts_cli.ReadPage(db.Path(), uint64(bucketRootPageId))
239+
require.NoError(t, err)
240+
require.Equal(t, uint16(common.BranchPageFlag), bucketRootPage.Flags())
241+
// Retrieve Bucket's PageIds
242+
var bucketPageIds []common.Pgid
243+
for _, bpe := range bucketRootPage.BranchPageElements() {
244+
bucketPageIds = append(bucketPageIds, bpe.Pgid())
245+
}
246+
247+
victimPageId := bucketPageIds[rand.Intn(len(bucketPageIds))]
248+
return victimPageId
249+
}
250+
251+
// generateSampleDataInBucket fill in sample data into given bucket to create the given
252+
// number of leafPages. To control the number of leafPages, sample data are generated in order.
253+
func generateSampleDataInBucket(t testing.TB, bk *Bucket, lPages int) {
254+
t.Helper()
255+
currentKey := 1
256+
currentVal := 100
257+
for i := 0; i < lPages; i++ {
258+
currentSize := common.PageHeaderSize
259+
for {
260+
err := bk.Put(convertIntIntoBytes(t, currentKey), convertIntIntoBytes(t, currentVal))
261+
require.NoError(t, err)
262+
currentSize += 16 + 4 + 4
263+
if currentSize >= 4096 {
264+
break
265+
}
266+
currentKey++
267+
currentVal++
268+
}
269+
}
270+
}
271+
272+
func convertIntIntoBytes(t testing.TB, i int) []byte {
273+
t.Helper()
274+
buf := make([]byte, 4)
275+
binary.BigEndian.PutUint32(buf, uint32(i))
276+
return buf
277+
}
278+
279+
func generateCorruptionBytes(t testing.TB, isPanic bool) []byte {
280+
if isPanic {
281+
// Generated data size is between pageHeader and pageSize.
282+
corruptDataLength := rand.Intn(4096-16) + 16
283+
corruptData := make([]byte, corruptDataLength)
284+
_, err := crand.Read(corruptData)
285+
require.NoError(t, err)
286+
return corruptData
287+
}
288+
// Insert LeafPageElement which violates the BTree range.
289+
invalidLPE := common.NewLeafPageElement(0, 0, 0, 0)
290+
var buf bytes.Buffer
291+
binary.Write(&buf, binary.BigEndian, invalidLPE)
292+
return buf.Bytes()
293+
}
294+
115295
func prepareData(t *testing.T) (string, error) {
116296
fileName := filepath.Join(t.TempDir(), "db")
117297
db, err := Open(fileName, 0666, nil)

0 commit comments

Comments
 (0)
Please sign in to comment.