diff --git a/internal/trie/node/branch.go b/internal/trie/node/branch.go index 58473023a9..7f3422a6f1 100644 --- a/internal/trie/node/branch.go +++ b/internal/trie/node/branch.go @@ -42,9 +42,9 @@ func NewBranch(key, value []byte, dirty bool, generation uint64) *Branch { func (b *Branch) String() string { if len(b.Value) > 1024 { - return fmt.Sprintf("key=%x childrenBitmap=%16b value (hashed)=%x dirty=%v", + return fmt.Sprintf("branch key=0x%x childrenBitmap=%b value (hashed)=0x%x dirty=%t", b.Key, b.ChildrenBitmap(), common.MustBlake2bHash(b.Value), b.dirty) } - return fmt.Sprintf("key=%x childrenBitmap=%16b value=%v dirty=%v", + return fmt.Sprintf("branch key=0x%x childrenBitmap=%b value=0x%x dirty=%t", b.Key, b.ChildrenBitmap(), b.Value, b.dirty) } diff --git a/internal/trie/node/branch_encode_test.go b/internal/trie/node/branch_encode_test.go index 54504665bf..9c1fc50703 100644 --- a/internal/trie/node/branch_encode_test.go +++ b/internal/trie/node/branch_encode_test.go @@ -11,6 +11,134 @@ import ( "github.com/stretchr/testify/require" ) +func Test_Branch_ScaleEncodeHash(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + branch *Branch + encoding []byte + wrappedErr error + errMessage string + }{ + "empty branch": { + branch: &Branch{}, + encoding: []byte{0xc, 0x80, 0x0, 0x0}, + }, + "non empty branch": { + branch: &Branch{ + Key: []byte{1, 2}, + Value: []byte{3, 4}, + Children: [16]Node{ + nil, nil, &Leaf{Key: []byte{9}}, + }, + }, + encoding: []byte{0x2c, 0xc2, 0x12, 0x4, 0x0, 0x8, 0x3, 0x4, 0xc, 0x41, 0x9, 0x0}, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + encoding, err := testCase.branch.ScaleEncodeHash() + + if testCase.wrappedErr != nil { + assert.ErrorIs(t, err, testCase.wrappedErr) + assert.EqualError(t, err, testCase.errMessage) + } else { + require.NoError(t, err) + } + assert.Equal(t, testCase.encoding, encoding) + }) + } +} + +func Test_Branch_hash(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + branch *Branch + write writeCall + errWrapped error + errMessage string + }{ + "empty branch": { + branch: &Branch{}, + write: writeCall{ + written: []byte{128, 0, 0}, + }, + }, + "less than 32 bytes encoding": { + branch: &Branch{ + Key: []byte{1, 2}, + }, + write: writeCall{ + written: []byte{130, 18, 0, 0}, + }, + }, + "less than 32 bytes encoding write error": { + branch: &Branch{ + Key: []byte{1, 2}, + }, + write: writeCall{ + written: []byte{130, 18, 0, 0}, + err: errTest, + }, + errWrapped: errTest, + errMessage: "cannot write encoded branch to buffer: test error", + }, + "more than 32 bytes encoding": { + branch: &Branch{ + Key: repeatBytes(100, 1), + }, + write: writeCall{ + written: []byte{ + 70, 102, 188, 24, 31, 68, 86, 114, + 95, 156, 225, 138, 175, 254, 176, 251, + 81, 84, 193, 40, 11, 234, 142, 233, + 69, 250, 158, 86, 72, 228, 66, 46}, + }, + }, + "more than 32 bytes encoding write error": { + branch: &Branch{ + Key: repeatBytes(100, 1), + }, + write: writeCall{ + written: []byte{ + 70, 102, 188, 24, 31, 68, 86, 114, + 95, 156, 225, 138, 175, 254, 176, 251, + 81, 84, 193, 40, 11, 234, 142, 233, + 69, 250, 158, 86, 72, 228, 66, 46}, + err: errTest, + }, + errWrapped: errTest, + errMessage: "cannot write hash sum of branch to buffer: test error", + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + + digestBuffer := NewMockWriter(ctrl) + digestBuffer.EXPECT().Write(testCase.write.written). + Return(testCase.write.n, testCase.write.err) + + err := testCase.branch.hash(digestBuffer) + + if testCase.errWrapped != nil { + assert.ErrorIs(t, err, testCase.errWrapped) + assert.EqualError(t, err, testCase.errMessage) + } else { + require.NoError(t, err) + } + }) + } +} + func Test_Branch_Encode(t *testing.T) { t.Parallel() diff --git a/internal/trie/node/branch_test.go b/internal/trie/node/branch_test.go new file mode 100644 index 0000000000..a7d4591c32 --- /dev/null +++ b/internal/trie/node/branch_test.go @@ -0,0 +1,95 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package node + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_NewBranch(t *testing.T) { + t.Parallel() + + key := []byte{1, 2} + value := []byte{3, 4} + const dirty = true + const generation = 9 + + branch := NewBranch(key, value, dirty, generation) + + expectedBranch := &Branch{ + Key: key, + Value: value, + dirty: dirty, + generation: generation, + } + assert.Equal(t, expectedBranch, branch) + + // Check modifying passed slice modifies branch slices + key[0] = 11 + value[0] = 13 + assert.Equal(t, expectedBranch, branch) +} + +func Test_Branch_String(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + branch *Branch + s string + }{ + "empty branch": { + branch: &Branch{}, + s: "branch key=0x childrenBitmap=0 value=0x dirty=false", + }, + "branch with value smaller than 1024": { + branch: &Branch{ + Key: []byte{1, 2}, + Value: []byte{3, 4}, + dirty: true, + Children: [16]Node{ + nil, nil, nil, + &Leaf{}, + nil, nil, nil, + &Branch{}, + nil, nil, nil, + &Leaf{}, + nil, nil, nil, nil, + }, + }, + s: "branch key=0x0102 childrenBitmap=100010001000 value=0x0304 dirty=true", + }, + "branch with value higher than 1024": { + branch: &Branch{ + Key: []byte{1, 2}, + Value: make([]byte, 1025), + dirty: true, + Children: [16]Node{ + nil, nil, nil, + &Leaf{}, + nil, nil, nil, + &Branch{}, + nil, nil, nil, + &Leaf{}, + nil, nil, nil, nil, + }, + }, + s: "branch key=0x0102 childrenBitmap=100010001000 " + + "value (hashed)=0x307861663233363133353361303538646238383034626337353735323831663131663735313265326331346336373032393864306232336630396538386266333066 " + //nolint:lll + "dirty=true", + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + s := testCase.branch.String() + + assert.Equal(t, testCase.s, s) + }) + } +} diff --git a/internal/trie/node/copy_test.go b/internal/trie/node/copy_test.go index 75de5f6284..bff0f409c2 100644 --- a/internal/trie/node/copy_test.go +++ b/internal/trie/node/copy_test.go @@ -1,3 +1,6 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package node import ( diff --git a/internal/trie/node/dirty_test.go b/internal/trie/node/dirty_test.go new file mode 100644 index 0000000000..ebe9c02fa1 --- /dev/null +++ b/internal/trie/node/dirty_test.go @@ -0,0 +1,150 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package node + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Branch_IsDirty(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + branch *Branch + dirty bool + }{ + "not dirty": { + branch: &Branch{}, + }, + "dirty": { + branch: &Branch{ + dirty: true, + }, + dirty: true, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + dirty := testCase.branch.IsDirty() + + assert.Equal(t, testCase.dirty, dirty) + }) + } +} + +func Test_Branch_SetDirty(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + branch *Branch + dirty bool + expected *Branch + }{ + "not dirty to not dirty": { + branch: &Branch{}, + expected: &Branch{}, + }, + "not dirty to dirty": { + branch: &Branch{}, + dirty: true, + expected: &Branch{dirty: true}, + }, + "dirty to not dirty": { + branch: &Branch{dirty: true}, + expected: &Branch{}, + }, + "dirty to dirty": { + branch: &Branch{dirty: true}, + dirty: true, + expected: &Branch{dirty: true}, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + testCase.branch.SetDirty(testCase.dirty) + + assert.Equal(t, testCase.expected, testCase.branch) + }) + } +} + +func Test_Leaf_IsDirty(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + leaf *Leaf + dirty bool + }{ + "not dirty": { + leaf: &Leaf{}, + }, + "dirty": { + leaf: &Leaf{ + dirty: true, + }, + dirty: true, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + dirty := testCase.leaf.IsDirty() + + assert.Equal(t, testCase.dirty, dirty) + }) + } +} + +func Test_Leaf_SetDirty(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + leaf *Leaf + dirty bool + expected *Leaf + }{ + "not dirty to not dirty": { + leaf: &Leaf{}, + expected: &Leaf{}, + }, + "not dirty to dirty": { + leaf: &Leaf{}, + dirty: true, + expected: &Leaf{dirty: true}, + }, + "dirty to not dirty": { + leaf: &Leaf{dirty: true}, + expected: &Leaf{}, + }, + "dirty to dirty": { + leaf: &Leaf{dirty: true}, + dirty: true, + expected: &Leaf{dirty: true}, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + testCase.leaf.SetDirty(testCase.dirty) + + assert.Equal(t, testCase.expected, testCase.leaf) + }) + } +} diff --git a/internal/trie/node/generation_test.go b/internal/trie/node/generation_test.go new file mode 100644 index 0000000000..708d93058e --- /dev/null +++ b/internal/trie/node/generation_test.go @@ -0,0 +1,50 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package node + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Branch_SetGeneration(t *testing.T) { + t.Parallel() + + branch := &Branch{ + generation: 1, + } + branch.SetGeneration(2) + assert.Equal(t, &Branch{generation: 2}, branch) +} + +func Test_Branch_GetGeneration(t *testing.T) { + t.Parallel() + + const generation uint64 = 1 + branch := &Branch{ + generation: generation, + } + assert.Equal(t, branch.GetGeneration(), generation) +} + +func Test_Leaf_SetGeneration(t *testing.T) { + t.Parallel() + + leaf := &Leaf{ + generation: 1, + } + leaf.SetGeneration(2) + assert.Equal(t, &Leaf{generation: 2}, leaf) +} + +func Test_Leaf_GetGeneration(t *testing.T) { + t.Parallel() + + const generation uint64 = 1 + leaf := &Leaf{ + generation: generation, + } + assert.Equal(t, leaf.GetGeneration(), generation) +} diff --git a/internal/trie/node/hash_test.go b/internal/trie/node/hash_test.go new file mode 100644 index 0000000000..26693cd76b --- /dev/null +++ b/internal/trie/node/hash_test.go @@ -0,0 +1,254 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package node + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Branch_SetEncodingAndHash(t *testing.T) { + t.Parallel() + + branch := &Branch{ + encoding: []byte{2}, + hashDigest: []byte{3}, + } + branch.SetEncodingAndHash([]byte{4}, []byte{5}) + + expectedBranch := &Branch{ + encoding: []byte{4}, + hashDigest: []byte{5}, + } + assert.Equal(t, expectedBranch, branch) +} + +func Test_Branch_GetHash(t *testing.T) { + t.Parallel() + + branch := &Branch{ + hashDigest: []byte{3}, + } + hash := branch.GetHash() + + expectedHash := []byte{3} + assert.Equal(t, expectedHash, hash) +} + +func Test_Branch_EncodeAndHash(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + branch *Branch + expectedBranch *Branch + encoding []byte + hash []byte + errWrapped error + errMessage string + }{ + "empty branch": { + branch: &Branch{}, + expectedBranch: &Branch{ + encoding: []byte{0x80, 0x0, 0x0}, + hashDigest: []byte{0x80, 0x0, 0x0}, + }, + encoding: []byte{0x80, 0x0, 0x0}, + hash: []byte{0x80, 0x0, 0x0}, + }, + "small branch encoding": { + branch: &Branch{ + Key: []byte{1}, + Value: []byte{2}, + }, + expectedBranch: &Branch{ + encoding: []byte{0xc1, 0x1, 0x0, 0x0, 0x4, 0x2}, + hashDigest: []byte{0xc1, 0x1, 0x0, 0x0, 0x4, 0x2}, + }, + encoding: []byte{0xc1, 0x1, 0x0, 0x0, 0x4, 0x2}, + hash: []byte{0xc1, 0x1, 0x0, 0x0, 0x4, 0x2}, + }, + "branch dirty with precomputed encoding and hash": { + branch: &Branch{ + Key: []byte{1}, + Value: []byte{2}, + dirty: true, + encoding: []byte{3}, + hashDigest: []byte{4}, + }, + expectedBranch: &Branch{ + encoding: []byte{0xc1, 0x1, 0x0, 0x0, 0x4, 0x2}, + hashDigest: []byte{0xc1, 0x1, 0x0, 0x0, 0x4, 0x2}, + }, + encoding: []byte{0xc1, 0x1, 0x0, 0x0, 0x4, 0x2}, + hash: []byte{0xc1, 0x1, 0x0, 0x0, 0x4, 0x2}, + }, + "branch not dirty with precomputed encoding and hash": { + branch: &Branch{ + Key: []byte{1}, + Value: []byte{2}, + dirty: false, + encoding: []byte{3}, + hashDigest: []byte{4}, + }, + expectedBranch: &Branch{ + Key: []byte{1}, + Value: []byte{2}, + encoding: []byte{3}, + hashDigest: []byte{4}, + }, + encoding: []byte{3}, + hash: []byte{4}, + }, + "large branch encoding": { + branch: &Branch{ + Key: repeatBytes(65, 7), + }, + expectedBranch: &Branch{ + encoding: []byte{0xbf, 0x2, 0x7, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x0, 0x0}, //nolint:lll + hashDigest: []byte{0x6b, 0xd8, 0xcc, 0xac, 0x71, 0x77, 0x44, 0x17, 0xfe, 0xe0, 0xde, 0xda, 0xd5, 0x97, 0x6e, 0x69, 0xeb, 0xe9, 0xdd, 0x80, 0x1d, 0x4b, 0x51, 0xf1, 0x5b, 0xf3, 0x4a, 0x93, 0x27, 0x32, 0x2c, 0xb0}, //nolint:lll + }, + encoding: []byte{0xbf, 0x2, 0x7, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x0, 0x0}, //nolint:lll + hash: []byte{0x6b, 0xd8, 0xcc, 0xac, 0x71, 0x77, 0x44, 0x17, 0xfe, 0xe0, 0xde, 0xda, 0xd5, 0x97, 0x6e, 0x69, 0xeb, 0xe9, 0xdd, 0x80, 0x1d, 0x4b, 0x51, 0xf1, 0x5b, 0xf3, 0x4a, 0x93, 0x27, 0x32, 0x2c, 0xb0}, //nolint:lll + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + encoding, hash, err := testCase.branch.EncodeAndHash() + + assert.ErrorIs(t, err, testCase.errWrapped) + if testCase.errWrapped != nil { + assert.EqualError(t, err, testCase.errMessage) + } + assert.Equal(t, testCase.encoding, encoding) + assert.Equal(t, testCase.hash, hash) + }) + } +} + +func Test_Leaf_SetEncodingAndHash(t *testing.T) { + t.Parallel() + + leaf := &Leaf{ + encoding: []byte{2}, + hashDigest: []byte{3}, + } + leaf.SetEncodingAndHash([]byte{4}, []byte{5}) + + expectedLeaf := &Leaf{ + encoding: []byte{4}, + hashDigest: []byte{5}, + } + assert.Equal(t, expectedLeaf, leaf) +} + +func Test_Leaf_GetHash(t *testing.T) { + t.Parallel() + + leaf := &Leaf{ + hashDigest: []byte{3}, + } + hash := leaf.GetHash() + + expectedHash := []byte{3} + assert.Equal(t, expectedHash, hash) +} + +func Test_Leaf_EncodeAndHash(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + leaf *Leaf + expectedLeaf *Leaf + encoding []byte + hash []byte + errWrapped error + errMessage string + }{ + "empty leaf": { + leaf: &Leaf{}, + expectedLeaf: &Leaf{ + encoding: []byte{0x40, 0x0}, + hashDigest: []byte{0x40, 0x0}, + }, + encoding: []byte{0x40, 0x0}, + hash: []byte{0x40, 0x0}, + }, + "small leaf encoding": { + leaf: &Leaf{ + Key: []byte{1}, + Value: []byte{2}, + }, + expectedLeaf: &Leaf{ + encoding: []byte{0x41, 0x1, 0x4, 0x2}, + hashDigest: []byte{0x41, 0x1, 0x4, 0x2}, + }, + encoding: []byte{0x41, 0x1, 0x4, 0x2}, + hash: []byte{0x41, 0x1, 0x4, 0x2}, + }, + "leaf dirty with precomputed encoding and hash": { + leaf: &Leaf{ + Key: []byte{1}, + Value: []byte{2}, + dirty: true, + encoding: []byte{3}, + hashDigest: []byte{4}, + }, + expectedLeaf: &Leaf{ + encoding: []byte{0x41, 0x1, 0x4, 0x2}, + hashDigest: []byte{0x41, 0x1, 0x4, 0x2}, + }, + encoding: []byte{0x41, 0x1, 0x4, 0x2}, + hash: []byte{0x41, 0x1, 0x4, 0x2}, + }, + "leaf not dirty with precomputed encoding and hash": { + leaf: &Leaf{ + Key: []byte{1}, + Value: []byte{2}, + dirty: false, + encoding: []byte{3}, + hashDigest: []byte{4}, + }, + expectedLeaf: &Leaf{ + Key: []byte{1}, + Value: []byte{2}, + encoding: []byte{3}, + hashDigest: []byte{4}, + }, + encoding: []byte{3}, + hash: []byte{4}, + }, + "large leaf encoding": { + leaf: &Leaf{ + Key: repeatBytes(65, 7), + }, + expectedLeaf: &Leaf{ + encoding: []byte{0x7f, 0x2, 0x7, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x0}, //nolint:lll + hashDigest: []byte{0xfb, 0xae, 0x31, 0x4b, 0xef, 0x31, 0x9, 0xc7, 0x62, 0x99, 0x9d, 0x40, 0x9b, 0xd4, 0xdc, 0x64, 0xe7, 0x39, 0x46, 0x8b, 0xd3, 0xaf, 0xe8, 0x63, 0x9d, 0xf9, 0x41, 0x40, 0x76, 0x40, 0x10, 0xa3}, //nolint:lll + }, + encoding: []byte{0x7f, 0x2, 0x7, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x0}, //nolint:lll + hash: []byte{0xfb, 0xae, 0x31, 0x4b, 0xef, 0x31, 0x9, 0xc7, 0x62, 0x99, 0x9d, 0x40, 0x9b, 0xd4, 0xdc, 0x64, 0xe7, 0x39, 0x46, 0x8b, 0xd3, 0xaf, 0xe8, 0x63, 0x9d, 0xf9, 0x41, 0x40, 0x76, 0x40, 0x10, 0xa3}, //nolint:lll + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + encoding, hash, err := testCase.leaf.EncodeAndHash() + + assert.ErrorIs(t, err, testCase.errWrapped) + if testCase.errWrapped != nil { + assert.EqualError(t, err, testCase.errMessage) + } + assert.Equal(t, testCase.encoding, encoding) + assert.Equal(t, testCase.hash, hash) + }) + } +} diff --git a/internal/trie/node/key_test.go b/internal/trie/node/key_test.go index 4ec4985527..c3413c1628 100644 --- a/internal/trie/node/key_test.go +++ b/internal/trie/node/key_test.go @@ -5,7 +5,7 @@ package node import ( "bytes" - "io" + "fmt" "testing" "github.com/golang/mock/gomock" @@ -13,6 +13,46 @@ import ( "github.com/stretchr/testify/require" ) +func Test_Branch_GetKey(t *testing.T) { + t.Parallel() + + branch := &Branch{ + Key: []byte{2}, + } + key := branch.GetKey() + assert.Equal(t, []byte{2}, key) +} + +func Test_Leaf_GetKey(t *testing.T) { + t.Parallel() + + leaf := &Leaf{ + Key: []byte{2}, + } + key := leaf.GetKey() + assert.Equal(t, []byte{2}, key) +} + +func Test_Branch_SetKey(t *testing.T) { + t.Parallel() + + branch := &Branch{ + Key: []byte{2}, + } + branch.SetKey([]byte{3}) + assert.Equal(t, &Branch{Key: []byte{3}}, branch) +} + +func Test_Leaf_SetKey(t *testing.T) { + t.Parallel() + + leaf := &Leaf{ + Key: []byte{2}, + } + leaf.SetKey([]byte{3}) + assert.Equal(t, &Leaf{Key: []byte{3}}, leaf) +} + func repeatBytes(n int, b byte) (slice []byte) { slice = make([]byte, n) for i := range slice { @@ -144,11 +184,60 @@ func Test_encodeKeyLength(t *testing.T) { }) } +//go:generate mockgen -destination=reader_mock_test.go -package $GOPACKAGE io Reader + +type readCall struct { + buffArgCap int + read []byte + n int // number of bytes read + err error +} + +func repeatReadCalls(rc readCall, length int) (readCalls []readCall) { + readCalls = make([]readCall, length) + for i := range readCalls { + readCalls[i] = readCall{ + buffArgCap: rc.buffArgCap, + n: rc.n, + err: rc.err, + } + if rc.read != nil { + readCalls[i].read = make([]byte, len(rc.read)) + copy(readCalls[i].read, rc.read) + } + } + return readCalls +} + +var _ gomock.Matcher = (*byteSliceCapMatcher)(nil) + +type byteSliceCapMatcher struct { + capacity int +} + +func (b *byteSliceCapMatcher) Matches(x interface{}) bool { + slice, ok := x.([]byte) + if !ok { + return false + } + return cap(slice) == b.capacity +} + +func (b *byteSliceCapMatcher) String() string { + return fmt.Sprintf("capacity of slice is not the expected capacity %d", b.capacity) +} + +func newByteSliceCapMatcher(capacity int) *byteSliceCapMatcher { + return &byteSliceCapMatcher{ + capacity: capacity, + } +} + func Test_decodeKey(t *testing.T) { t.Parallel() testCases := map[string]struct { - reader io.Reader + reads []readCall keyLength byte b []byte errWrapped error @@ -158,42 +247,54 @@ func Test_decodeKey(t *testing.T) { b: []byte{}, }, "short key length": { - reader: bytes.NewBuffer([]byte{1, 2, 3}), + reads: []readCall{ + {buffArgCap: 3, read: []byte{1, 2, 3}, n: 3}, + }, keyLength: 5, b: []byte{0x1, 0x0, 0x2, 0x0, 0x3}, }, "key read error": { - reader: bytes.NewBuffer(nil), + reads: []readCall{ + {buffArgCap: 3, err: errTest}, + }, keyLength: 5, errWrapped: ErrReadKeyData, - errMessage: "cannot read key data: EOF", + errMessage: "cannot read key data: test error", + }, + + "key read bytes count mismatch": { + reads: []readCall{ + {buffArgCap: 3, n: 2}, + }, + keyLength: 5, + errWrapped: ErrReadKeyData, + errMessage: "cannot read key data: read 2 bytes instead of 3", }, "long key length": { - reader: bytes.NewBuffer( - append( - []byte{ - 6, // key length - }, - repeatBytes(64, 7)..., // key data - )), + reads: []readCall{ + {buffArgCap: 1, read: []byte{6}, n: 1}, // key length + {buffArgCap: 35, read: repeatBytes(35, 7), n: 35}, // key data + }, keyLength: 0x3f, b: []byte{ - 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, - 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, - 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, - 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, - 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, - 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, - 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7}, + 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, + 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, + 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, + 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, + 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, + 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, + 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7, 0x0, 0x7}, }, "key length read error": { - reader: bytes.NewBuffer(nil), + reads: []readCall{ + {buffArgCap: 1, err: errTest}, + }, keyLength: 0x3f, errWrapped: ErrReadKeyLength, - errMessage: "cannot read key length: EOF", + errMessage: "cannot read key length: test error", }, "key length too big": { - reader: bytes.NewBuffer(repeatBytes(257, 0xff)), + reads: repeatReadCalls(readCall{buffArgCap: 1, read: []byte{0xff}, n: 1}, 257), keyLength: 0x3f, errWrapped: ErrPartialKeyTooBig, errMessage: "partial key length cannot be larger than or equal to 2^16: 65598", @@ -204,8 +305,24 @@ func Test_decodeKey(t *testing.T) { testCase := testCase t.Run(name, func(t *testing.T) { t.Parallel() + ctrl := gomock.NewController(t) + + reader := NewMockReader(ctrl) + var previousCall *gomock.Call + for _, readCall := range testCase.reads { + byteSliceCapMatcher := newByteSliceCapMatcher(readCall.buffArgCap) + call := reader.EXPECT().Read(byteSliceCapMatcher). + DoAndReturn(func(b []byte) (n int, err error) { + copy(b, readCall.read) + return readCall.n, readCall.err + }) + if previousCall != nil { + call.After(previousCall) + } + previousCall = call + } - b, err := decodeKey(testCase.reader, testCase.keyLength) + b, err := decodeKey(reader, testCase.keyLength) assert.ErrorIs(t, err, testCase.errWrapped) if err != nil { diff --git a/internal/trie/node/leaf.go b/internal/trie/node/leaf.go index 77c884f397..0de16a3881 100644 --- a/internal/trie/node/leaf.go +++ b/internal/trie/node/leaf.go @@ -42,7 +42,7 @@ func NewLeaf(key, value []byte, dirty bool, generation uint64) *Leaf { func (l *Leaf) String() string { if len(l.Value) > 1024 { - return fmt.Sprintf("leaf key=%x value (hashed)=%x dirty=%v", l.Key, common.MustBlake2bHash(l.Value), l.dirty) + return fmt.Sprintf("leaf key=0x%x value (hashed)=0x%x dirty=%t", l.Key, common.MustBlake2bHash(l.Value), l.dirty) } - return fmt.Sprintf("leaf key=%x value=%v dirty=%v", l.Key, l.Value, l.dirty) + return fmt.Sprintf("leaf key=0x%x value=0x%x dirty=%t", l.Key, l.Value, l.dirty) } diff --git a/internal/trie/node/leaf_test.go b/internal/trie/node/leaf_test.go new file mode 100644 index 0000000000..d755eb724d --- /dev/null +++ b/internal/trie/node/leaf_test.go @@ -0,0 +1,77 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package node + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_NewLeaf(t *testing.T) { + t.Parallel() + + key := []byte{1, 2} + value := []byte{3, 4} + const dirty = true + const generation = 9 + + leaf := NewLeaf(key, value, dirty, generation) + + expectedLeaf := &Leaf{ + Key: key, + Value: value, + dirty: dirty, + generation: generation, + } + assert.Equal(t, expectedLeaf, leaf) + + // Check modifying passed slice modifies leaf slices + key[0] = 11 + value[0] = 13 + assert.Equal(t, expectedLeaf, leaf) +} + +func Test_Leaf_String(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + leaf *Leaf + s string + }{ + "empty leaf": { + leaf: &Leaf{}, + s: "leaf key=0x value=0x dirty=false", + }, + "leaf with value smaller than 1024": { + leaf: &Leaf{ + Key: []byte{1, 2}, + Value: []byte{3, 4}, + dirty: true, + }, + s: "leaf key=0x0102 value=0x0304 dirty=true", + }, + "leaf with value higher than 1024": { + leaf: &Leaf{ + Key: []byte{1, 2}, + Value: make([]byte, 1025), + dirty: true, + }, + s: "leaf key=0x0102 " + + "value (hashed)=0x307861663233363133353361303538646238383034626337353735323831663131663735313265326331346336373032393864306232336630396538386266333066 " + //nolint:lll + "dirty=true", + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + s := testCase.leaf.String() + + assert.Equal(t, testCase.s, s) + }) + } +} diff --git a/internal/trie/node/reader_mock_test.go b/internal/trie/node/reader_mock_test.go new file mode 100644 index 0000000000..2aa28d2998 --- /dev/null +++ b/internal/trie/node/reader_mock_test.go @@ -0,0 +1,49 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: io (interfaces: Reader) + +// Package node is a generated GoMock package. +package node + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockReader is a mock of Reader interface. +type MockReader struct { + ctrl *gomock.Controller + recorder *MockReaderMockRecorder +} + +// MockReaderMockRecorder is the mock recorder for MockReader. +type MockReaderMockRecorder struct { + mock *MockReader +} + +// NewMockReader creates a new mock instance. +func NewMockReader(ctrl *gomock.Controller) *MockReader { + mock := &MockReader{ctrl: ctrl} + mock.recorder = &MockReaderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockReader) EXPECT() *MockReaderMockRecorder { + return m.recorder +} + +// Read mocks base method. +func (m *MockReader) Read(arg0 []byte) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Read", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Read indicates an expected call of Read. +func (mr *MockReaderMockRecorder) Read(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockReader)(nil).Read), arg0) +} diff --git a/internal/trie/node/value_test.go b/internal/trie/node/value_test.go new file mode 100644 index 0000000000..f6fe989d1d --- /dev/null +++ b/internal/trie/node/value_test.go @@ -0,0 +1,30 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package node + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Branch_GetValue(t *testing.T) { + t.Parallel() + + branch := &Branch{ + Value: []byte{2}, + } + value := branch.GetValue() + assert.Equal(t, []byte{2}, value) +} + +func Test_Leaf_GetValue(t *testing.T) { + t.Parallel() + + leaf := &Leaf{ + Value: []byte{2}, + } + value := leaf.GetValue() + assert.Equal(t, []byte{2}, value) +}