mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
676 lines
18 KiB
Go
676 lines
18 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package merkledb
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"io"
|
|
"math"
|
|
"math/rand"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/luxfi/container/maybe"
|
|
"github.com/luxfi/ids"
|
|
)
|
|
|
|
var (
|
|
encodeDBNodeTests = []struct {
|
|
name string
|
|
n *dbNode
|
|
expectedBytes []byte
|
|
}{
|
|
{
|
|
name: "empty node",
|
|
n: &dbNode{
|
|
children: make(map[byte]*child),
|
|
},
|
|
expectedBytes: []byte{
|
|
0x00, // value.HasValue()
|
|
0x00, // len(children)
|
|
},
|
|
},
|
|
{
|
|
name: "has value",
|
|
n: &dbNode{
|
|
value: maybe.Some([]byte("value")),
|
|
children: make(map[byte]*child),
|
|
},
|
|
expectedBytes: []byte{
|
|
0x01, // value.HasValue()
|
|
0x05, // len(value.Value())
|
|
'v', 'a', 'l', 'u', 'e', // value.Value()
|
|
0x00, // len(children)
|
|
},
|
|
},
|
|
{
|
|
name: "1 child",
|
|
n: &dbNode{
|
|
value: maybe.Some([]byte("value")),
|
|
children: map[byte]*child{
|
|
0: {
|
|
compressedKey: ToKey([]byte{0}),
|
|
id: ids.ID{
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
|
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
|
},
|
|
hasValue: true,
|
|
},
|
|
},
|
|
},
|
|
expectedBytes: []byte{
|
|
0x01, // value.HasValue()
|
|
0x05, // len(value.Value())
|
|
'v', 'a', 'l', 'u', 'e', // value.Value()
|
|
0x01, // len(children)
|
|
0x00, // children[0].index
|
|
0x08, // len(children[0].compressedKey)
|
|
0x00, // children[0].compressedKey
|
|
// children[0].id
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
|
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
|
0x01, // children[0].hasValue
|
|
},
|
|
},
|
|
{
|
|
name: "2 children",
|
|
n: &dbNode{
|
|
value: maybe.Some([]byte("value")),
|
|
children: map[byte]*child{
|
|
0: {
|
|
compressedKey: ToKey([]byte{0}),
|
|
id: ids.ID{
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
|
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
|
},
|
|
hasValue: true,
|
|
},
|
|
1: {
|
|
compressedKey: ToKey([]byte{1, 2, 3}),
|
|
id: ids.ID{
|
|
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
|
|
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
|
|
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
|
|
0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
|
|
},
|
|
hasValue: false,
|
|
},
|
|
},
|
|
},
|
|
expectedBytes: []byte{
|
|
0x01, // value.HasValue()
|
|
0x05, // len(value.Value())
|
|
'v', 'a', 'l', 'u', 'e', // value.Value()
|
|
0x02, // len(children)
|
|
0x00, // children[0].index
|
|
0x08, // len(children[0].compressedKey)
|
|
0x00, // children[0].compressedKey
|
|
// children[0].id
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
|
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
|
0x01, // children[0].hasValue
|
|
0x01, // children[1].index
|
|
0x18, // len(children[1].compressedKey)
|
|
0x01, 0x02, 0x03, // children[1].compressedKey
|
|
// children[1].id
|
|
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
|
|
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
|
|
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
|
|
0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
|
|
0x00, // children[1].hasValue
|
|
},
|
|
},
|
|
{
|
|
name: "16 children",
|
|
n: func() *dbNode {
|
|
n := &dbNode{
|
|
value: maybe.Some([]byte("value")),
|
|
children: make(map[byte]*child),
|
|
}
|
|
for i := byte(0); i < 16; i++ {
|
|
n.children[i] = &child{
|
|
compressedKey: ToKey([]byte{i}),
|
|
id: ids.ID{
|
|
0x00 + i, 0x01 + i, 0x02 + i, 0x03 + i,
|
|
0x04 + i, 0x05 + i, 0x06 + i, 0x07 + i,
|
|
0x08 + i, 0x09 + i, 0x0a + i, 0x0b + i,
|
|
0x0c + i, 0x0d + i, 0x0e + i, 0x0f + i,
|
|
0x10 + i, 0x11 + i, 0x12 + i, 0x13 + i,
|
|
0x14 + i, 0x15 + i, 0x16 + i, 0x17 + i,
|
|
0x18 + i, 0x19 + i, 0x1a + i, 0x1b + i,
|
|
0x1c + i, 0x1d + i, 0x1e + i, 0x1f + i,
|
|
},
|
|
hasValue: i%2 == 0,
|
|
}
|
|
}
|
|
return n
|
|
}(),
|
|
expectedBytes: []byte{
|
|
0x01, // value.HasValue()
|
|
0x05, // len(value.Value())
|
|
'v', 'a', 'l', 'u', 'e', // value.Value()
|
|
0x10, // len(children)
|
|
0x00, // children[0].index
|
|
0x08, // len(children[0].compressedKey)
|
|
0x00, // children[0].compressedKey
|
|
// children[0].id
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
|
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
|
0x01, // children[0].hasValue
|
|
0x01, // children[1].index
|
|
0x08, // len(children[1].compressedKey)
|
|
0x01, // children[1].compressedKey
|
|
// children[1].id
|
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
|
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
|
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
|
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
|
0x00, // children[1].hasValue
|
|
0x02, // children[2].index
|
|
0x08, // len(children[2].compressedKey)
|
|
0x02, // children[2].compressedKey
|
|
// children[2].id
|
|
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
|
|
0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
|
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
|
|
0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
|
0x01, // children[2].hasValue
|
|
0x03, // children[3].index
|
|
0x08, // len(children[3].compressedKey)
|
|
0x03, // children[3].compressedKey
|
|
// children[3].id
|
|
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
|
|
0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12,
|
|
0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
|
|
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22,
|
|
0x00, // children[3].hasValue
|
|
0x04, // children[4].index
|
|
0x08, // len(children[4].compressedKey)
|
|
0x04, // children[4].compressedKey
|
|
// children[4].id
|
|
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
|
|
0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
|
|
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
|
0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
|
|
0x01, // children[4].hasValue
|
|
0x05, // children[5].index
|
|
0x08, // len(children[5].compressedKey)
|
|
0x05, // children[5].compressedKey
|
|
// children[5].id
|
|
0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
|
|
0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
|
|
0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
|
|
0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24,
|
|
0x00, // children[5].hasValue
|
|
0x06, // children[6].index
|
|
0x08, // len(children[6].compressedKey)
|
|
0x06, // children[6].compressedKey
|
|
// children[6].id
|
|
0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
|
|
0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
|
|
0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
|
|
0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
|
|
0x01, // children[6].hasValue
|
|
0x07, // children[7].index
|
|
0x08, // len(children[7].compressedKey)
|
|
0x07, // children[7].compressedKey
|
|
// children[7].id
|
|
0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
|
|
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
|
|
0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e,
|
|
0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
|
|
0x00, // children[7].hasValue
|
|
0x08, // children[8].index
|
|
0x08, // len(children[8].compressedKey)
|
|
0x08, // children[8].compressedKey
|
|
// children[8].id
|
|
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
|
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
|
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
|
|
0x01, // children[8].hasValue
|
|
0x09, // children[9].index
|
|
0x08, // len(children[9].compressedKey)
|
|
0x09, // children[9].compressedKey
|
|
// children[9].id
|
|
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
|
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
|
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
|
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
|
0x00, // children[9].hasValue
|
|
0x0a, // children[10].index
|
|
0x08, // len(children[10].compressedKey)
|
|
0x0a, // children[10].compressedKey
|
|
// children[10].id
|
|
0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
|
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
|
|
0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
|
0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
|
|
0x01, // children[10].hasValue
|
|
0x0b, // children[11].index
|
|
0x08, // len(children[11].compressedKey)
|
|
0x0b, // children[11].compressedKey
|
|
// children[11].id
|
|
0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12,
|
|
0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
|
|
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22,
|
|
0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
|
0x00, // children[11].hasValue
|
|
0x0c, // children[12].index
|
|
0x08, // len(children[12].compressedKey)
|
|
0x0c, // children[12].compressedKey
|
|
// children[12].id
|
|
0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
|
|
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
|
0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
|
|
0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
|
|
0x01, // children[12].hasValue
|
|
0x0d, // children[13].index
|
|
0x08, // len(children[13].compressedKey)
|
|
0x0d, // children[13].compressedKey
|
|
// children[13].id
|
|
0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
|
|
0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
|
|
0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24,
|
|
0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
|
|
0x00, // children[13].hasValue
|
|
0x0e, // children[14].index
|
|
0x08, // len(children[14].compressedKey)
|
|
0x0e, // children[14].compressedKey
|
|
// children[14].id
|
|
0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
|
|
0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
|
|
0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
|
|
0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
|
|
0x01, // children[14].hasValue
|
|
0x0f, // children[15].index
|
|
0x08, // len(children[15].compressedKey)
|
|
0x0f, // children[15].compressedKey
|
|
// children[15].id
|
|
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
|
|
0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e,
|
|
0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
|
|
0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e,
|
|
0x00, // children[15].hasValue
|
|
},
|
|
},
|
|
}
|
|
encodeKeyTests = []struct {
|
|
name string
|
|
key Key
|
|
expectedBytes []byte
|
|
}{
|
|
{
|
|
name: "empty",
|
|
key: ToKey([]byte{}),
|
|
expectedBytes: []byte{
|
|
0x00, // length
|
|
},
|
|
},
|
|
{
|
|
name: "1 byte",
|
|
key: ToKey([]byte{0}),
|
|
expectedBytes: []byte{
|
|
0x08, // length
|
|
0x00, // key
|
|
},
|
|
},
|
|
{
|
|
name: "2 bytes",
|
|
key: ToKey([]byte{0, 1}),
|
|
expectedBytes: []byte{
|
|
0x10, // length
|
|
0x00, 0x01, // key
|
|
},
|
|
},
|
|
{
|
|
name: "4 bytes",
|
|
key: ToKey([]byte{0, 1, 2, 3}),
|
|
expectedBytes: []byte{
|
|
0x20, // length
|
|
0x00, 0x01, 0x02, 0x03, // key
|
|
},
|
|
},
|
|
{
|
|
name: "8 bytes",
|
|
key: ToKey([]byte{0, 1, 2, 3, 4, 5, 6, 7}),
|
|
expectedBytes: []byte{
|
|
0x40, // length
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // key
|
|
},
|
|
},
|
|
{
|
|
name: "32 bytes",
|
|
key: ToKey(make([]byte, 32)),
|
|
expectedBytes: append(
|
|
[]byte{
|
|
0x80, 0x02, // length
|
|
},
|
|
make([]byte, 32)..., // key
|
|
),
|
|
},
|
|
{
|
|
name: "64 bytes",
|
|
key: ToKey(make([]byte, 64)),
|
|
expectedBytes: append(
|
|
[]byte{
|
|
0x80, 0x04, // length
|
|
},
|
|
make([]byte, 64)..., // key
|
|
),
|
|
},
|
|
{
|
|
name: "1024 bytes",
|
|
key: ToKey(make([]byte, 1024)),
|
|
expectedBytes: append(
|
|
[]byte{
|
|
0x80, 0x40, // length
|
|
},
|
|
make([]byte, 1024)..., // key
|
|
),
|
|
},
|
|
}
|
|
)
|
|
|
|
func FuzzCodecBool(f *testing.F) {
|
|
f.Fuzz(
|
|
func(
|
|
t *testing.T,
|
|
b []byte,
|
|
) {
|
|
require := require.New(t)
|
|
|
|
r := codecReader{
|
|
b: b,
|
|
}
|
|
startLen := len(r.b)
|
|
got, err := r.Bool()
|
|
if err != nil {
|
|
// Invalid input - test should continue with error handling
|
|
return
|
|
}
|
|
endLen := len(r.b)
|
|
numRead := startLen - endLen
|
|
|
|
// Encoding [got] should be the same as [b].
|
|
w := codecWriter{}
|
|
w.Bool(got)
|
|
require.Len(w.b, numRead)
|
|
require.Equal(b[:numRead], w.b)
|
|
},
|
|
)
|
|
}
|
|
|
|
func FuzzCodecInt(f *testing.F) {
|
|
f.Fuzz(
|
|
func(
|
|
t *testing.T,
|
|
b []byte,
|
|
) {
|
|
require := require.New(t)
|
|
|
|
c := codecReader{
|
|
b: b,
|
|
}
|
|
startLen := len(c.b)
|
|
got, err := c.Uvarint()
|
|
if err != nil {
|
|
// Invalid input - test should continue with error handling
|
|
return
|
|
}
|
|
endLen := len(c.b)
|
|
numRead := startLen - endLen
|
|
|
|
// Encoding [got] should be the same as [b].
|
|
w := codecWriter{}
|
|
w.Uvarint(got)
|
|
require.Len(w.b, numRead)
|
|
require.Equal(b[:numRead], w.b)
|
|
},
|
|
)
|
|
}
|
|
|
|
func FuzzCodecKey(f *testing.F) {
|
|
f.Fuzz(
|
|
func(
|
|
t *testing.T,
|
|
b []byte,
|
|
) {
|
|
require := require.New(t)
|
|
got, err := decodeKey(b)
|
|
if err != nil {
|
|
// Invalid input - test should continue with error handling
|
|
return
|
|
}
|
|
|
|
// Encoding [got] should be the same as [b].
|
|
gotBytes := encodeKey(got)
|
|
require.Equal(b, gotBytes)
|
|
},
|
|
)
|
|
}
|
|
|
|
func FuzzCodecDBNodeCanonical(f *testing.F) {
|
|
f.Fuzz(
|
|
func(
|
|
t *testing.T,
|
|
b []byte,
|
|
) {
|
|
require := require.New(t)
|
|
node := &dbNode{}
|
|
if err := decodeDBNode(b, node); err != nil {
|
|
// Invalid input - test should continue with error handling
|
|
return
|
|
}
|
|
|
|
// Encoding [node] should be the same as [b].
|
|
buf := encodeDBNode(node)
|
|
require.Equal(b, buf)
|
|
},
|
|
)
|
|
}
|
|
|
|
func FuzzCodecDBNodeDeterministic(f *testing.F) {
|
|
f.Fuzz(
|
|
func(
|
|
t *testing.T,
|
|
randSeed int,
|
|
hasValue bool,
|
|
valueBytes []byte,
|
|
) {
|
|
require := require.New(t)
|
|
for _, bf := range validBranchFactors {
|
|
r := rand.New(rand.NewSource(int64(randSeed))) // #nosec G404
|
|
|
|
value := maybe.Nothing[[]byte]()
|
|
if hasValue {
|
|
value = maybe.Some(valueBytes)
|
|
}
|
|
|
|
numChildren := r.Intn(int(bf)) // #nosec G404
|
|
|
|
children := map[byte]*child{}
|
|
for i := 0; i < numChildren; i++ {
|
|
var childID ids.ID
|
|
_, _ = r.Read(childID[:]) // #nosec G404
|
|
|
|
childKeyBytes := make([]byte, r.Intn(32)) // #nosec G404
|
|
_, _ = r.Read(childKeyBytes) // #nosec G404
|
|
|
|
children[byte(i)] = &child{
|
|
compressedKey: ToKey(childKeyBytes),
|
|
id: childID,
|
|
}
|
|
}
|
|
node := dbNode{
|
|
value: value,
|
|
children: children,
|
|
}
|
|
|
|
nodeBytes := encodeDBNode(&node)
|
|
require.Len(nodeBytes, encodedDBNodeSize(&node))
|
|
var gotNode dbNode
|
|
require.NoError(decodeDBNode(nodeBytes, &gotNode))
|
|
require.Equal(node, gotNode)
|
|
|
|
nodeBytes2 := encodeDBNode(&gotNode)
|
|
require.Equal(nodeBytes, nodeBytes2)
|
|
|
|
// Enforce that modifying bytes after decodeDBNode doesn't
|
|
// modify the populated struct.
|
|
clear(nodeBytes)
|
|
require.Equal(node, gotNode)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
func TestCodecDecodeDBNode_TooShort(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
var (
|
|
parsedDBNode dbNode
|
|
tooShortBytes = make([]byte, 1)
|
|
)
|
|
err := decodeDBNode(tooShortBytes, &parsedDBNode)
|
|
require.ErrorIs(err, io.ErrUnexpectedEOF)
|
|
}
|
|
|
|
func TestEncodeDBNode(t *testing.T) {
|
|
for _, test := range encodeDBNodeTests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
bytes := encodeDBNode(test.n)
|
|
require.Equal(t, test.expectedBytes, bytes)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDecodeDBNode(t *testing.T) {
|
|
for _, test := range encodeDBNodeTests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
var n dbNode
|
|
require.NoError(decodeDBNode(test.expectedBytes, &n))
|
|
require.Equal(test.n, &n)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEncodeKey(t *testing.T) {
|
|
for _, test := range encodeKeyTests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
bytes := encodeKey(test.key)
|
|
require.Equal(t, test.expectedBytes, bytes)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDecodeKey(t *testing.T) {
|
|
for _, test := range encodeKeyTests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
key, err := decodeKey(test.expectedBytes)
|
|
require.NoError(err)
|
|
require.Equal(test.key, key)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCodecDecodeKeyLengthOverflowRegression(t *testing.T) {
|
|
_, err := decodeKey(binary.AppendUvarint(nil, math.MaxInt))
|
|
require.ErrorIs(t, err, io.ErrUnexpectedEOF)
|
|
}
|
|
|
|
func TestUintSize(t *testing.T) {
|
|
// Test lower bound
|
|
expectedSize := uintSize(0)
|
|
actualSize := binary.PutUvarint(make([]byte, binary.MaxVarintLen64), 0)
|
|
require.Equal(t, expectedSize, actualSize)
|
|
|
|
// Test upper bound
|
|
expectedSize = uintSize(math.MaxUint64)
|
|
actualSize = binary.PutUvarint(make([]byte, binary.MaxVarintLen64), math.MaxUint64)
|
|
require.Equal(t, expectedSize, actualSize)
|
|
|
|
// Test powers of 2
|
|
for power := 0; power < 64; power++ {
|
|
n := uint64(1) << uint(power)
|
|
expectedSize := uintSize(n)
|
|
actualSize := binary.PutUvarint(make([]byte, binary.MaxVarintLen64), n)
|
|
require.Equal(t, expectedSize, actualSize, power)
|
|
}
|
|
}
|
|
|
|
func Benchmark_EncodeDBNode(b *testing.B) {
|
|
for _, benchmark := range encodeDBNodeTests {
|
|
b.Run(benchmark.name, func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
encodeDBNode(benchmark.n)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Benchmark_DecodeDBNode(b *testing.B) {
|
|
for _, benchmark := range encodeDBNodeTests {
|
|
b.Run(benchmark.name, func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
var n dbNode
|
|
err := decodeDBNode(benchmark.expectedBytes, &n)
|
|
require.NoError(b, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Benchmark_EncodeKey(b *testing.B) {
|
|
for _, benchmark := range encodeKeyTests {
|
|
b.Run(benchmark.name, func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
encodeKey(benchmark.key)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Benchmark_DecodeKey(b *testing.B) {
|
|
for _, benchmark := range encodeKeyTests {
|
|
b.Run(benchmark.name, func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := decodeKey(benchmark.expectedBytes)
|
|
require.NoError(b, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Benchmark_EncodeUint(b *testing.B) {
|
|
w := codecWriter{
|
|
b: make([]byte, 0, binary.MaxVarintLen64),
|
|
}
|
|
|
|
for _, v := range []uint64{0, 1, 2, 32, 1024, 32768} {
|
|
b.Run(strconv.FormatUint(v, 10), func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
w.Uvarint(v)
|
|
w.b = w.b[:0]
|
|
}
|
|
})
|
|
}
|
|
}
|