From c89d50ccab641589733f622ed09d2b8fa8f7ef22 Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Thu, 25 Nov 2021 16:53:06 +0100 Subject: [PATCH] chore(trie): `lib/trie/hash.go` tests (#2049) --- lib/trie/bytesBuffer_mock_test.go | 77 +++ lib/trie/hash.go | 123 ++-- lib/trie/hash_test.go | 1025 +++++++++++++++++++++++++++-- lib/trie/node.go | 4 +- lib/trie/node_mock_test.go | 183 +++++ lib/trie/node_test.go | 11 + lib/trie/readwriter_mock_test.go | 64 ++ lib/trie/writer_mock_test.go | 49 ++ 8 files changed, 1446 insertions(+), 90 deletions(-) create mode 100644 lib/trie/bytesBuffer_mock_test.go create mode 100644 lib/trie/node_mock_test.go create mode 100644 lib/trie/readwriter_mock_test.go create mode 100644 lib/trie/writer_mock_test.go diff --git a/lib/trie/bytesBuffer_mock_test.go b/lib/trie/bytesBuffer_mock_test.go new file mode 100644 index 0000000000..c59f7dd4a9 --- /dev/null +++ b/lib/trie/bytesBuffer_mock_test.go @@ -0,0 +1,77 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: hash.go + +// Package trie is a generated GoMock package. +package trie + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockbytesBuffer is a mock of bytesBuffer interface. +type MockbytesBuffer struct { + ctrl *gomock.Controller + recorder *MockbytesBufferMockRecorder +} + +// MockbytesBufferMockRecorder is the mock recorder for MockbytesBuffer. +type MockbytesBufferMockRecorder struct { + mock *MockbytesBuffer +} + +// NewMockbytesBuffer creates a new mock instance. +func NewMockbytesBuffer(ctrl *gomock.Controller) *MockbytesBuffer { + mock := &MockbytesBuffer{ctrl: ctrl} + mock.recorder = &MockbytesBufferMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockbytesBuffer) EXPECT() *MockbytesBufferMockRecorder { + return m.recorder +} + +// Bytes mocks base method. +func (m *MockbytesBuffer) Bytes() []byte { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Bytes") + ret0, _ := ret[0].([]byte) + return ret0 +} + +// Bytes indicates an expected call of Bytes. +func (mr *MockbytesBufferMockRecorder) Bytes() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bytes", reflect.TypeOf((*MockbytesBuffer)(nil).Bytes)) +} + +// Len mocks base method. +func (m *MockbytesBuffer) Len() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Len") + ret0, _ := ret[0].(int) + return ret0 +} + +// Len indicates an expected call of Len. +func (mr *MockbytesBufferMockRecorder) Len() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Len", reflect.TypeOf((*MockbytesBuffer)(nil).Len)) +} + +// Write mocks base method. +func (m *MockbytesBuffer) Write(p []byte) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Write", p) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Write indicates an expected call of Write. +func (mr *MockbytesBufferMockRecorder) Write(p interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockbytesBuffer)(nil).Write), p) +} diff --git a/lib/trie/hash.go b/lib/trie/hash.go index 887ed83731..ecb674ea82 100644 --- a/lib/trie/hash.go +++ b/lib/trie/hash.go @@ -57,7 +57,10 @@ func hashNode(n node, digestBuffer io.Writer) (err error) { // if length of encoded leaf is less than 32 bytes, do not hash if encodingBuffer.Len() < 32 { _, err = digestBuffer.Write(encodingBuffer.Bytes()) - return err + if err != nil { + return fmt.Errorf("cannot write encoded node to buffer: %w", err) + } + return nil } // otherwise, hash encoded node @@ -72,16 +75,26 @@ func hashNode(n node, digestBuffer io.Writer) (err error) { } _, err = digestBuffer.Write(hasher.Sum(nil)) - return err + if err != nil { + return fmt.Errorf("cannot write hash sum of node to buffer: %w", err) + } + return nil } var ErrNodeTypeUnsupported = errors.New("node type is not supported") +type bytesBuffer interface { + // note: cannot compose with io.Writer for mock generation + Write(p []byte) (n int, err error) + Len() int + Bytes() []byte +} + // encodeNode writes the encoding of the node to the buffer given. // It is the high-level function wrapping the encoding for different // node types. The encoding has the following format: // NodeHeader | Extra partial key length | Partial Key | Value -func encodeNode(n node, buffer *bytes.Buffer, parallel bool) (err error) { +func encodeNode(n node, buffer bytesBuffer, parallel bool) (err error) { switch n := n.(type) { case *branch: err := encodeBranch(n, buffer, parallel) @@ -104,70 +117,58 @@ func encodeNode(n node, buffer *bytes.Buffer, parallel bool) (err error) { copy(n.encoding, buffer.Bytes()) return nil case nil: - buffer.Write([]byte{0}) + _, err := buffer.Write([]byte{0}) + if err != nil { + return fmt.Errorf("cannot encode nil node: %w", err) + } return nil default: return fmt.Errorf("%w: %T", ErrNodeTypeUnsupported, n) } } -func encodeAndHash(n node) ([]byte, error) { - buffer := digestBufferPool.Get().(*bytes.Buffer) - buffer.Reset() - defer digestBufferPool.Put(buffer) - - err := hashNode(n, buffer) - if err != nil { - return nil, err - } - - scEncChild, err := scale.Marshal(buffer.Bytes()) - if err != nil { - return nil, err - } - return scEncChild, nil -} - // encodeBranch encodes a branch with the encoding specified at the top of this package // to the buffer given. func encodeBranch(b *branch, buffer io.Writer, parallel bool) (err error) { if !b.dirty && b.encoding != nil { _, err = buffer.Write(b.encoding) if err != nil { - return fmt.Errorf("cannot write stored encoded branch to buffer: %w", err) + return fmt.Errorf("cannot write stored encoding to buffer: %w", err) } return nil } - encoding, err := b.header() + encodedHeader, err := b.header() if err != nil { - return fmt.Errorf("cannot encode branch header: %w", err) + return fmt.Errorf("cannot encode header: %w", err) } - _, err = buffer.Write(encoding) + _, err = buffer.Write(encodedHeader) if err != nil { - return fmt.Errorf("cannot write encoded branch header to buffer: %w", err) + return fmt.Errorf("cannot write encoded header to buffer: %w", err) } - _, err = buffer.Write(nibblesToKeyLE(b.key)) + keyLE := nibblesToKeyLE(b.key) + _, err = buffer.Write(keyLE) if err != nil { - return fmt.Errorf("cannot write encoded branch key to buffer: %w", err) + return fmt.Errorf("cannot write encoded key to buffer: %w", err) } - _, err = buffer.Write(common.Uint16ToBytes(b.childrenBitmap())) + childrenBitmap := common.Uint16ToBytes(b.childrenBitmap()) + _, err = buffer.Write(childrenBitmap) if err != nil { - return fmt.Errorf("cannot write branch children bitmap to buffer: %w", err) + return fmt.Errorf("cannot write children bitmap to buffer: %w", err) } if b.value != nil { bytes, err := scale.Marshal(b.value) if err != nil { - return fmt.Errorf("cannot scale encode branch value: %w", err) + return fmt.Errorf("cannot scale encode value: %w", err) } _, err = buffer.Write(bytes) if err != nil { - return fmt.Errorf("cannot write encoded branch value to buffer: %w", err) + return fmt.Errorf("cannot write encoded value to buffer: %w", err) } } @@ -222,10 +223,15 @@ func encodeChildrenInParallel(children [16]node, buffer io.Writer) (err error) { // write as many completed buffers to the result buffer. for currentIndex < len(children) && resultBuffers[currentIndex] != nil { - // note buffer.Write copies the byte slice given as argument - _, writeErr := buffer.Write(resultBuffers[currentIndex].Bytes()) - if writeErr != nil && err == nil { - err = writeErr + bufferSlice := resultBuffers[currentIndex].Bytes() + if len(bufferSlice) > 0 { + // note buffer.Write copies the byte slice given as argument + _, writeErr := buffer.Write(bufferSlice) + if writeErr != nil && err == nil { + err = fmt.Errorf( + "cannot write encoding of child at index %d: %w", + currentIndex, writeErr) + } } encodingBufferPool.Put(resultBuffers[currentIndex]) @@ -246,17 +252,26 @@ func encodeChildrenInParallel(children [16]node, buffer io.Writer) (err error) { } func encodeChildrenSequentially(children [16]node, buffer io.Writer) (err error) { - for _, child := range children { + for i, child := range children { err = encodeChild(child, buffer) if err != nil { - return err + return fmt.Errorf("cannot encode child at index %d: %w", i, err) } } return nil } func encodeChild(child node, buffer io.Writer) (err error) { - if child == nil { + var isNil bool + switch impl := child.(type) { + case *branch: + isNil = impl == nil + case *leaf: + isNil = impl == nil + default: + isNil = child == nil + } + if isNil { return nil } @@ -273,6 +288,23 @@ func encodeChild(child node, buffer io.Writer) (err error) { return nil } +func encodeAndHash(n node) (b []byte, err error) { + buffer := digestBufferPool.Get().(*bytes.Buffer) + buffer.Reset() + defer digestBufferPool.Put(buffer) + + err = hashNode(n, buffer) + if err != nil { + return nil, fmt.Errorf("cannot hash node: %w", err) + } + + scEncChild, err := scale.Marshal(buffer.Bytes()) + if err != nil { + return nil, fmt.Errorf("cannot scale encode hashed node: %w", err) + } + return scEncChild, nil +} + // encodeLeaf encodes a leaf to the buffer given, with the encoding // specified at the top of this package. func encodeLeaf(l *leaf, buffer io.Writer) (err error) { @@ -286,27 +318,28 @@ func encodeLeaf(l *leaf, buffer io.Writer) (err error) { return nil } - encoding, err := l.header() + encodedHeader, err := l.header() if err != nil { return fmt.Errorf("cannot encode header: %w", err) } - _, err = buffer.Write(encoding) + _, err = buffer.Write(encodedHeader) if err != nil { return fmt.Errorf("cannot write encoded header to buffer: %w", err) } - _, err = buffer.Write(nibblesToKeyLE(l.key)) + keyLE := nibblesToKeyLE(l.key) + _, err = buffer.Write(keyLE) if err != nil { return fmt.Errorf("cannot write LE key to buffer: %w", err) } - bytes, err := scale.Marshal(l.value) // TODO scale encoder to write to buffer + encodedValue, err := scale.Marshal(l.value) // TODO scale encoder to write to buffer if err != nil { - return err + return fmt.Errorf("cannot scale marshal value: %w", err) } - _, err = buffer.Write(bytes) + _, err = buffer.Write(encodedValue) if err != nil { return fmt.Errorf("cannot write scale encoded value to buffer: %w", err) } diff --git a/lib/trie/hash_test.go b/lib/trie/hash_test.go index ffe46403f9..23265fdf57 100644 --- a/lib/trie/hash_test.go +++ b/lib/trie/hash_test.go @@ -4,72 +4,1009 @@ package trie import ( - "bytes" - "math/rand" + "errors" "testing" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func generateRandBytes(size int) []byte { - buf := make([]byte, rand.Intn(size)+1) - rand.Read(buf) - return buf +type writeCall struct { + written []byte + n int + err error } -func generateRand(size int) [][]byte { - rt := make([][]byte, size) - for i := range rt { - buf := make([]byte, rand.Intn(379)+1) - rand.Read(buf) - rt[i] = buf +var errTest = errors.New("test error") + +//go:generate mockgen -destination=bytesBuffer_mock_test.go -package $GOPACKAGE -source=hash.go . bytesBuffer +//go:generate mockgen -destination=node_mock_test.go -package $GOPACKAGE -source=node.go . node + +func Test_hashNode(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + n node + writeCall bool + write writeCall + wrappedErr error + errMessage string + }{ + "node encoding error": { + n: NewMocknode(nil), + wrappedErr: ErrNodeTypeUnsupported, + errMessage: "cannot encode node: " + + "node type is not supported: " + + "*trie.Mocknode", + }, + "small leaf buffer write error": { + n: &leaf{ + encoding: []byte{1, 2, 3}, + }, + writeCall: true, + write: writeCall{ + written: []byte{1, 2, 3}, + err: errTest, + }, + wrappedErr: errTest, + errMessage: "cannot write encoded node to buffer: " + + "test error", + }, + "small leaf success": { + n: &leaf{ + encoding: []byte{1, 2, 3}, + }, + writeCall: true, + write: writeCall{ + written: []byte{1, 2, 3}, + }, + }, + "leaf hash sum buffer write error": { + n: &leaf{ + encoding: []byte{ + 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, + }, + }, + writeCall: true, + write: writeCall{ + written: []byte{ + 107, 105, 154, 175, 253, 170, 232, + 135, 240, 21, 207, 148, 82, 117, + 249, 230, 80, 197, 254, 17, 149, + 108, 50, 7, 80, 56, 114, 176, + 84, 114, 125, 234}, + err: errTest, + }, + wrappedErr: errTest, + errMessage: "cannot write hash sum of node to buffer: " + + "test error", + }, + "leaf hash sum success": { + n: &leaf{ + encoding: []byte{ + 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, + }, + }, + writeCall: true, + write: writeCall{ + written: []byte{ + 107, 105, 154, 175, 253, 170, 232, + 135, 240, 21, 207, 148, 82, 117, + 249, 230, 80, 197, 254, 17, 149, + 108, 50, 7, 80, 56, 114, 176, + 84, 114, 125, 234}, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + + buffer := NewMockWriter(ctrl) + if testCase.writeCall { + buffer.EXPECT(). + Write(testCase.write.written). + Return(testCase.write.n, testCase.write.err) + } + + err := hashNode(testCase.n, buffer) + + if testCase.wrappedErr != nil { + assert.ErrorIs(t, err, testCase.wrappedErr) + assert.EqualError(t, err, testCase.errMessage) + } else { + require.NoError(t, err) + } + }) } - return rt } -func TestHashLeaf(t *testing.T) { - n := &leaf{key: generateRandBytes(380), value: generateRandBytes(64)} +func Test_encodeNode(t *testing.T) { + t.Parallel() - buffer := bytes.NewBuffer(nil) - const parallel = false - err := encodeNode(n, buffer, parallel) + testCases := map[string]struct { + n node + writes []writeCall + leafEncodingCopy bool + leafBufferLen int + leafBufferBytes []byte + parallel bool + wrappedErr error + errMessage string + }{ + "branch error": { + n: &branch{ + encoding: []byte{1, 2, 3}, + }, + writes: []writeCall{ + {written: []byte{1, 2, 3}, err: errTest}, + }, + wrappedErr: errTest, + errMessage: "cannot encode branch: " + + "cannot write stored encoding to buffer: " + + "test error", + }, + "branch success": { + n: &branch{ + encoding: []byte{1, 2, 3}, + }, + writes: []writeCall{ + {written: []byte{1, 2, 3}}, + }, + }, + "leaf error": { + n: &leaf{ + encoding: []byte{1, 2, 3}, + }, + writes: []writeCall{ + {written: []byte{1, 2, 3}, err: errTest}, + }, + wrappedErr: errTest, + errMessage: "cannot encode leaf: " + + "cannot write stored encoding to buffer: " + + "test error", + }, + "leaf success": { + n: &leaf{ + encoding: []byte{1, 2, 3}, + }, + writes: []writeCall{ + {written: []byte{1, 2, 3}}, + }, + leafEncodingCopy: true, + leafBufferLen: 3, + leafBufferBytes: []byte{1, 2, 3}, + }, + "nil node error": { + writes: []writeCall{ + {written: []byte{0}, err: errTest}, + }, + wrappedErr: errTest, + errMessage: "cannot encode nil node: test error", + }, + "nil node success": { + writes: []writeCall{ + {written: []byte{0}}, + }, + }, + "unsupported node type": { + n: NewMocknode(nil), + wrappedErr: ErrNodeTypeUnsupported, + errMessage: "node type is not supported: *trie.Mocknode", + }, + } - if err != nil { - t.Errorf("did not hash leaf node: %s", err) - } else if buffer.Len() == 0 { - t.Errorf("did not hash leaf node: nil") + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + + buffer := NewMockbytesBuffer(ctrl) + var previousCall *gomock.Call + for _, write := range testCase.writes { + call := buffer.EXPECT(). + Write(write.written). + Return(write.n, write.err) + + if previousCall != nil { + call.After(previousCall) + } + previousCall = call + } + + if testCase.leafEncodingCopy { + previousCall = buffer.EXPECT().Len(). + Return(testCase.leafBufferLen). + After(previousCall) + buffer.EXPECT().Bytes(). + Return(testCase.leafBufferBytes). + After(previousCall) + } + + err := encodeNode(testCase.n, buffer, testCase.parallel) + + if testCase.wrappedErr != nil { + assert.ErrorIs(t, err, testCase.wrappedErr) + assert.EqualError(t, err, testCase.errMessage) + } else { + require.NoError(t, err) + } + }) } } -func TestHashBranch(t *testing.T) { - n := &branch{key: generateRandBytes(380), value: generateRandBytes(380)} - n.children[3] = &leaf{key: generateRandBytes(380), value: generateRandBytes(380)} +func Test_encodeBranch(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + branch *branch + writes []writeCall + parallel bool + wrappedErr error + errMessage string + }{ + "clean branch with encoding": { + branch: &branch{ + encoding: []byte{1, 2, 3}, + }, + writes: []writeCall{ + { // stored encoding + written: []byte{1, 2, 3}, + }, + }, + }, + "write error for clean branch with encoding": { + branch: &branch{ + encoding: []byte{1, 2, 3}, + }, + writes: []writeCall{ + { // stored encoding + written: []byte{1, 2, 3}, + err: errTest, + }, + }, + wrappedErr: errTest, + errMessage: "cannot write stored encoding to buffer: test error", + }, + "header encoding error": { + branch: &branch{ + key: make([]byte, 63+(1<<16)), + }, + wrappedErr: ErrPartialKeyTooBig, + errMessage: "cannot encode header: partial key length greater than or equal to 2^16", + }, + "buffer write error for encoded header": { + branch: &branch{ + key: []byte{1, 2, 3}, + value: []byte{100}, + }, + writes: []writeCall{ + { // header + written: []byte{195}, + err: errTest, + }, + }, + wrappedErr: errTest, + errMessage: "cannot write encoded header to buffer: test error", + }, + "buffer write error for encoded key": { + branch: &branch{ + key: []byte{1, 2, 3}, + value: []byte{100}, + }, + writes: []writeCall{ + { // header + written: []byte{195}, + }, + { // key LE + written: []byte{1, 35}, + err: errTest, + }, + }, + wrappedErr: errTest, + errMessage: "cannot write encoded key to buffer: test error", + }, + "buffer write error for children bitmap": { + branch: &branch{ + key: []byte{1, 2, 3}, + value: []byte{100}, + children: [16]node{ + nil, nil, nil, &leaf{key: []byte{9}}, + nil, nil, nil, &leaf{key: []byte{11}}, + }, + }, + writes: []writeCall{ + { // header + written: []byte{195}, + }, + { // key LE + written: []byte{1, 35}, + }, + { // children bitmap + written: []byte{136, 0}, + err: errTest, + }, + }, + wrappedErr: errTest, + errMessage: "cannot write children bitmap to buffer: test error", + }, + "buffer write error for value": { + branch: &branch{ + key: []byte{1, 2, 3}, + value: []byte{100}, + children: [16]node{ + nil, nil, nil, &leaf{key: []byte{9}}, + nil, nil, nil, &leaf{key: []byte{11}}, + }, + }, + writes: []writeCall{ + { // header + written: []byte{195}, + }, + { // key LE + written: []byte{1, 35}, + }, + { // children bitmap + written: []byte{136, 0}, + }, + { // value + written: []byte{4, 100}, + err: errTest, + }, + }, + wrappedErr: errTest, + errMessage: "cannot write encoded value to buffer: test error", + }, + "buffer write error for children encoded sequentially": { + branch: &branch{ + key: []byte{1, 2, 3}, + value: []byte{100}, + children: [16]node{ + nil, nil, nil, &leaf{key: []byte{9}}, + nil, nil, nil, &leaf{key: []byte{11}}, + }, + }, + writes: []writeCall{ + { // header + written: []byte{195}, + }, + { // key LE + written: []byte{1, 35}, + }, + { // children bitmap + written: []byte{136, 0}, + }, + { // value + written: []byte{4, 100}, + }, + { // children + written: []byte{12, 65, 9, 0}, + err: errTest, + }, + }, + wrappedErr: errTest, + errMessage: "cannot encode children of branch: " + + "cannot encode child at index 3: " + + "failed to write child to buffer: test error", + }, + "buffer write error for children encoded in parallel": { + branch: &branch{ + key: []byte{1, 2, 3}, + value: []byte{100}, + children: [16]node{ + nil, nil, nil, &leaf{key: []byte{9}}, + nil, nil, nil, &leaf{key: []byte{11}}, + }, + }, + writes: []writeCall{ + { // header + written: []byte{195}, + }, + { // key LE + written: []byte{1, 35}, + }, + { // children bitmap + written: []byte{136, 0}, + }, + { // value + written: []byte{4, 100}, + }, + { // first children + written: []byte{12, 65, 9, 0}, + err: errTest, + }, + { // second children + written: []byte{12, 65, 11, 0}, + }, + }, + parallel: true, + wrappedErr: errTest, + errMessage: "cannot encode children of branch: " + + "cannot write encoding of child at index 3: " + + "test error", + }, + "success with parallel children encoding": { + branch: &branch{ + key: []byte{1, 2, 3}, + value: []byte{100}, + children: [16]node{ + nil, nil, nil, &leaf{key: []byte{9}}, + nil, nil, nil, &leaf{key: []byte{11}}, + }, + }, + writes: []writeCall{ + { // header + written: []byte{195}, + }, + { // key LE + written: []byte{1, 35}, + }, + { // children bitmap + written: []byte{136, 0}, + }, + { // value + written: []byte{4, 100}, + }, + { // first children + written: []byte{12, 65, 9, 0}, + }, + { // second children + written: []byte{12, 65, 11, 0}, + }, + }, + parallel: true, + }, + "success with sequential children encoding": { + branch: &branch{ + key: []byte{1, 2, 3}, + value: []byte{100}, + children: [16]node{ + nil, nil, nil, &leaf{key: []byte{9}}, + nil, nil, nil, &leaf{key: []byte{11}}, + }, + }, + writes: []writeCall{ + { // header + written: []byte{195}, + }, + { // key LE + written: []byte{1, 35}, + }, + { // children bitmap + written: []byte{136, 0}, + }, + { // value + written: []byte{4, 100}, + }, + { // first children + written: []byte{12, 65, 9, 0}, + }, + { // second children + written: []byte{12, 65, 11, 0}, + }, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) - buffer := bytes.NewBuffer(nil) - const parallel = false - err := encodeNode(n, buffer, parallel) + buffer := NewMockReadWriter(ctrl) + var previousCall *gomock.Call + for _, write := range testCase.writes { + call := buffer.EXPECT(). + Write(write.written). + Return(write.n, write.err) - if err != nil { - t.Errorf("did not hash branch node: %s", err) - } else if buffer.Len() == 0 { - t.Errorf("did not hash branch node: nil") + if previousCall != nil { + call.After(previousCall) + } + previousCall = call + } + + err := encodeBranch(testCase.branch, buffer, testCase.parallel) + + if testCase.wrappedErr != nil { + assert.ErrorIs(t, err, testCase.wrappedErr) + assert.EqualError(t, err, testCase.errMessage) + } else { + require.NoError(t, err) + } + }) + } +} + +//go:generate mockgen -destination=readwriter_mock_test.go -package $GOPACKAGE io ReadWriter + +func Test_encodeChildrenInParallel(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + children [16]node + writes []writeCall + wrappedErr error + errMessage string + }{ + "no children": {}, + "first child not nil": { + children: [16]node{ + &leaf{key: []byte{1}}, + }, + writes: []writeCall{ + { + written: []byte{12, 65, 1, 0}, + }, + }, + }, + "last child not nil": { + children: [16]node{ + nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, + &leaf{key: []byte{1}}, + }, + writes: []writeCall{ + { + written: []byte{12, 65, 1, 0}, + }, + }, + }, + "first two children not nil": { + children: [16]node{ + &leaf{key: []byte{1}}, + &leaf{key: []byte{2}}, + }, + writes: []writeCall{ + { + written: []byte{12, 65, 1, 0}, + }, + { + written: []byte{12, 65, 2, 0}, + }, + }, + }, + "encoding error": { + children: [16]node{ + nil, nil, nil, nil, + nil, nil, nil, nil, + nil, nil, nil, + &leaf{ + key: []byte{1}, + }, + nil, nil, nil, nil, + }, + writes: []writeCall{ + { + written: []byte{12, 65, 1, 0}, + err: errTest, + }, + }, + wrappedErr: errTest, + errMessage: "cannot write encoding of child at index 11: " + + "test error", + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + + buffer := NewMockReadWriter(ctrl) + var previousCall *gomock.Call + for _, write := range testCase.writes { + call := buffer.EXPECT(). + Write(write.written). + Return(write.n, write.err) + + if previousCall != nil { + call.After(previousCall) + } + previousCall = call + } + + err := encodeChildrenInParallel(testCase.children, buffer) + + if testCase.wrappedErr != nil { + assert.ErrorIs(t, err, testCase.wrappedErr) + assert.EqualError(t, err, testCase.errMessage) + } else { + require.NoError(t, err) + } + }) } } -func TestHashShort(t *testing.T) { - n := &leaf{ - key: generateRandBytes(2), - value: generateRandBytes(3), +func Test_encodeChildrenSequentially(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + children [16]node + writes []writeCall + wrappedErr error + errMessage string + }{ + "no children": {}, + "first child not nil": { + children: [16]node{ + &leaf{key: []byte{1}}, + }, + writes: []writeCall{ + { + written: []byte{12, 65, 1, 0}, + }, + }, + }, + "last child not nil": { + children: [16]node{ + nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, + &leaf{key: []byte{1}}, + }, + writes: []writeCall{ + { + written: []byte{12, 65, 1, 0}, + }, + }, + }, + "first two children not nil": { + children: [16]node{ + &leaf{key: []byte{1}}, + &leaf{key: []byte{2}}, + }, + writes: []writeCall{ + { + written: []byte{12, 65, 1, 0}, + }, + { + written: []byte{12, 65, 2, 0}, + }, + }, + }, + "encoding error": { + children: [16]node{ + nil, nil, nil, nil, + nil, nil, nil, nil, + nil, nil, nil, + &leaf{ + key: []byte{1}, + }, + nil, nil, nil, nil, + }, + writes: []writeCall{ + { + written: []byte{12, 65, 1, 0}, + err: errTest, + }, + }, + wrappedErr: errTest, + errMessage: "cannot encode child at index 11: " + + "failed to write child to buffer: test error", + }, } - encodingBuffer := bytes.NewBuffer(nil) - const parallel = false - err := encodeNode(n, encodingBuffer, parallel) - require.NoError(t, err) + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + + buffer := NewMockReadWriter(ctrl) + var previousCall *gomock.Call + for _, write := range testCase.writes { + call := buffer.EXPECT(). + Write(write.written). + Return(write.n, write.err) + + if previousCall != nil { + call.After(previousCall) + } + previousCall = call + } - digestBuffer := bytes.NewBuffer(nil) - err = hashNode(n, digestBuffer) - require.NoError(t, err) - assert.Equal(t, encodingBuffer.Bytes(), digestBuffer.Bytes()) + err := encodeChildrenSequentially(testCase.children, buffer) + + if testCase.wrappedErr != nil { + assert.ErrorIs(t, err, testCase.wrappedErr) + assert.EqualError(t, err, testCase.errMessage) + } else { + require.NoError(t, err) + } + }) + } +} + +//go:generate mockgen -destination=writer_mock_test.go -package $GOPACKAGE io Writer + +func Test_encodeChild(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + child node + writeCall bool + write writeCall + wrappedErr error + errMessage string + }{ + "nil node": {}, + "nil leaf": { + child: (*leaf)(nil), + }, + "nil branch": { + child: (*branch)(nil), + }, + "empty leaf child": { + child: &leaf{}, + writeCall: true, + write: writeCall{ + written: []byte{8, 64, 0}, + }, + }, + "empty branch child": { + child: &branch{}, + writeCall: true, + write: writeCall{ + written: []byte{12, 128, 0, 0}, + }, + }, + "buffer write error": { + child: &branch{}, + writeCall: true, + write: writeCall{ + written: []byte{12, 128, 0, 0}, + err: errTest, + }, + wrappedErr: errTest, + errMessage: "failed to write child to buffer: test error", + }, + "leaf child": { + child: &leaf{ + key: []byte{1}, + value: []byte{2}, + }, + writeCall: true, + write: writeCall{ + written: []byte{16, 65, 1, 4, 2}, + }, + }, + "branch child": { + child: &branch{ + key: []byte{1}, + value: []byte{2}, + children: [16]node{ + nil, nil, &leaf{ + key: []byte{5}, + value: []byte{6}, + }, + }, + }, + writeCall: true, + write: writeCall{ + written: []byte{44, 193, 1, 4, 0, 4, 2, 16, 65, 5, 4, 6}, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + + buffer := NewMockWriter(ctrl) + + if testCase.writeCall { + buffer.EXPECT(). + Write(testCase.write.written). + Return(testCase.write.n, testCase.write.err) + } + + err := encodeChild(testCase.child, buffer) + + if testCase.wrappedErr != nil { + assert.ErrorIs(t, err, testCase.wrappedErr) + assert.EqualError(t, err, testCase.errMessage) + } else { + require.NoError(t, err) + } + }) + } +} + +func Test_encodeAndHash(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + n node + b []byte + wrappedErr error + errMessage string + }{ + "node encoding error": { + n: NewMocknode(nil), + wrappedErr: ErrNodeTypeUnsupported, + errMessage: "cannot hash node: " + + "cannot encode node: " + + "node type is not supported: " + + "*trie.Mocknode", + }, + "leaf": { + n: &leaf{}, + b: []byte{0x8, 0x40, 0}, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + + b, err := encodeAndHash(testCase.n) + + 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.b, b) + }) + } +} + +func Test_encodeLeaf(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + leaf *leaf + writes []writeCall + wrappedErr error + errMessage string + }{ + "clean leaf with encoding": { + leaf: &leaf{ + encoding: []byte{1, 2, 3}, + }, + writes: []writeCall{ + { + written: []byte{1, 2, 3}, + }, + }, + }, + "write error for clean leaf with encoding": { + leaf: &leaf{ + encoding: []byte{1, 2, 3}, + }, + writes: []writeCall{ + { + written: []byte{1, 2, 3}, + err: errTest, + }, + }, + wrappedErr: errTest, + errMessage: "cannot write stored encoding to buffer: test error", + }, + "header encoding error": { + leaf: &leaf{ + key: make([]byte, 63+(1<<16)), + }, + wrappedErr: ErrPartialKeyTooBig, + errMessage: "cannot encode header: partial key length greater than or equal to 2^16", + }, + "buffer write error for encoded header": { + leaf: &leaf{ + key: []byte{1, 2, 3}, + }, + writes: []writeCall{ + { + written: []byte{67}, + err: errTest, + }, + }, + wrappedErr: errTest, + errMessage: "cannot write encoded header to buffer: test error", + }, + "buffer write error for encoded key": { + leaf: &leaf{ + key: []byte{1, 2, 3}, + }, + writes: []writeCall{ + { + written: []byte{67}, + }, + { + written: []byte{1, 35}, + err: errTest, + }, + }, + wrappedErr: errTest, + errMessage: "cannot write LE key to buffer: test error", + }, + "buffer write error for encoded value": { + leaf: &leaf{ + key: []byte{1, 2, 3}, + value: []byte{4, 5, 6}, + }, + writes: []writeCall{ + { + written: []byte{67}, + }, + { + written: []byte{1, 35}, + }, + { + written: []byte{12, 4, 5, 6}, + err: errTest, + }, + }, + wrappedErr: errTest, + errMessage: "cannot write scale encoded value to buffer: test error", + }, + "success": { + leaf: &leaf{ + key: []byte{1, 2, 3}, + value: []byte{4, 5, 6}, + }, + writes: []writeCall{ + { + written: []byte{67}, + }, + { + written: []byte{1, 35}, + }, + { + written: []byte{12, 4, 5, 6}, + }, + }, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + + buffer := NewMockReadWriter(ctrl) + var previousCall *gomock.Call + for _, write := range testCase.writes { + call := buffer.EXPECT(). + Write(write.written). + Return(write.n, write.err) + + if previousCall != nil { + call.After(previousCall) + } + previousCall = call + } + + err := encodeLeaf(testCase.leaf, buffer) + + if testCase.wrappedErr != nil { + assert.ErrorIs(t, err, testCase.wrappedErr) + assert.EqualError(t, err, testCase.errMessage) + } else { + require.NoError(t, err) + } + }) + } } diff --git a/lib/trie/node.go b/lib/trie/node.go index f685447779..1def45a4c5 100644 --- a/lib/trie/node.go +++ b/lib/trie/node.go @@ -470,12 +470,14 @@ func (l *leaf) header() ([]byte, error) { return fullHeader, nil } +var ErrPartialKeyTooBig = errors.New("partial key length greater than or equal to 2^16") + func encodeExtraPartialKeyLength(pkLen int) ([]byte, error) { pkLen -= 63 fullHeader := []byte{} if pkLen >= 1<<16 { - return nil, errors.New("partial key length greater than or equal to 2^16") + return nil, ErrPartialKeyTooBig } for i := 0; i < 1<<16; i++ { diff --git a/lib/trie/node_mock_test.go b/lib/trie/node_mock_test.go new file mode 100644 index 0000000000..0235377728 --- /dev/null +++ b/lib/trie/node_mock_test.go @@ -0,0 +1,183 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: node.go + +// Package trie is a generated GoMock package. +package trie + +import ( + io "io" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// Mocknode is a mock of node interface. +type Mocknode struct { + ctrl *gomock.Controller + recorder *MocknodeMockRecorder +} + +// MocknodeMockRecorder is the mock recorder for Mocknode. +type MocknodeMockRecorder struct { + mock *Mocknode +} + +// NewMocknode creates a new mock instance. +func NewMocknode(ctrl *gomock.Controller) *Mocknode { + mock := &Mocknode{ctrl: ctrl} + mock.recorder = &MocknodeMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *Mocknode) EXPECT() *MocknodeMockRecorder { + return m.recorder +} + +// String mocks base method. +func (m *Mocknode) String() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "String") + ret0, _ := ret[0].(string) + return ret0 +} + +// String indicates an expected call of String. +func (mr *MocknodeMockRecorder) String() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*Mocknode)(nil).String)) +} + +// copy mocks base method. +func (m *Mocknode) copy() node { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "copy") + ret0, _ := ret[0].(node) + return ret0 +} + +// copy indicates an expected call of copy. +func (mr *MocknodeMockRecorder) copy() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "copy", reflect.TypeOf((*Mocknode)(nil).copy)) +} + +// decode mocks base method. +func (m *Mocknode) decode(r io.Reader, h byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "decode", r, h) + ret0, _ := ret[0].(error) + return ret0 +} + +// decode indicates an expected call of decode. +func (mr *MocknodeMockRecorder) decode(r, h interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "decode", reflect.TypeOf((*Mocknode)(nil).decode), r, h) +} + +// encodeAndHash mocks base method. +func (m *Mocknode) encodeAndHash() ([]byte, []byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "encodeAndHash") + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].([]byte) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// encodeAndHash indicates an expected call of encodeAndHash. +func (mr *MocknodeMockRecorder) encodeAndHash() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "encodeAndHash", reflect.TypeOf((*Mocknode)(nil).encodeAndHash)) +} + +// getGeneration mocks base method. +func (m *Mocknode) getGeneration() uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getGeneration") + ret0, _ := ret[0].(uint64) + return ret0 +} + +// getGeneration indicates an expected call of getGeneration. +func (mr *MocknodeMockRecorder) getGeneration() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getGeneration", reflect.TypeOf((*Mocknode)(nil).getGeneration)) +} + +// getHash mocks base method. +func (m *Mocknode) getHash() []byte { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getHash") + ret0, _ := ret[0].([]byte) + return ret0 +} + +// getHash indicates an expected call of getHash. +func (mr *MocknodeMockRecorder) getHash() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getHash", reflect.TypeOf((*Mocknode)(nil).getHash)) +} + +// isDirty mocks base method. +func (m *Mocknode) isDirty() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "isDirty") + ret0, _ := ret[0].(bool) + return ret0 +} + +// isDirty indicates an expected call of isDirty. +func (mr *MocknodeMockRecorder) isDirty() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "isDirty", reflect.TypeOf((*Mocknode)(nil).isDirty)) +} + +// setDirty mocks base method. +func (m *Mocknode) setDirty(dirty bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "setDirty", dirty) +} + +// setDirty indicates an expected call of setDirty. +func (mr *MocknodeMockRecorder) setDirty(dirty interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "setDirty", reflect.TypeOf((*Mocknode)(nil).setDirty), dirty) +} + +// setEncodingAndHash mocks base method. +func (m *Mocknode) setEncodingAndHash(arg0, arg1 []byte) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "setEncodingAndHash", arg0, arg1) +} + +// setEncodingAndHash indicates an expected call of setEncodingAndHash. +func (mr *MocknodeMockRecorder) setEncodingAndHash(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "setEncodingAndHash", reflect.TypeOf((*Mocknode)(nil).setEncodingAndHash), arg0, arg1) +} + +// setGeneration mocks base method. +func (m *Mocknode) setGeneration(arg0 uint64) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "setGeneration", arg0) +} + +// setGeneration indicates an expected call of setGeneration. +func (mr *MocknodeMockRecorder) setGeneration(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "setGeneration", reflect.TypeOf((*Mocknode)(nil).setGeneration), arg0) +} + +// setKey mocks base method. +func (m *Mocknode) setKey(key []byte) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "setKey", key) +} + +// setKey indicates an expected call of setKey. +func (mr *MocknodeMockRecorder) setKey(key interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "setKey", reflect.TypeOf((*Mocknode)(nil).setKey), key) +} diff --git a/lib/trie/node_test.go b/lib/trie/node_test.go index 030a9af1b2..04e69a796d 100644 --- a/lib/trie/node_test.go +++ b/lib/trie/node_test.go @@ -5,6 +5,7 @@ package trie import ( "bytes" + "math/rand" "strconv" "testing" @@ -24,6 +25,16 @@ func byteArray(length int) []byte { return b } +func generateRand(size int) [][]byte { + rt := make([][]byte, size) + for i := range rt { + buf := make([]byte, rand.Intn(379)+1) + rand.Read(buf) + rt[i] = buf + } + return rt +} + func TestChildrenBitmap(t *testing.T) { b := &branch{children: [16]node{}} res := b.childrenBitmap() diff --git a/lib/trie/readwriter_mock_test.go b/lib/trie/readwriter_mock_test.go new file mode 100644 index 0000000000..6d1affa288 --- /dev/null +++ b/lib/trie/readwriter_mock_test.go @@ -0,0 +1,64 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: io (interfaces: ReadWriter) + +// Package trie is a generated GoMock package. +package trie + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockReadWriter is a mock of ReadWriter interface. +type MockReadWriter struct { + ctrl *gomock.Controller + recorder *MockReadWriterMockRecorder +} + +// MockReadWriterMockRecorder is the mock recorder for MockReadWriter. +type MockReadWriterMockRecorder struct { + mock *MockReadWriter +} + +// NewMockReadWriter creates a new mock instance. +func NewMockReadWriter(ctrl *gomock.Controller) *MockReadWriter { + mock := &MockReadWriter{ctrl: ctrl} + mock.recorder = &MockReadWriterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockReadWriter) EXPECT() *MockReadWriterMockRecorder { + return m.recorder +} + +// Read mocks base method. +func (m *MockReadWriter) 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 *MockReadWriterMockRecorder) Read(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockReadWriter)(nil).Read), arg0) +} + +// Write mocks base method. +func (m *MockReadWriter) Write(arg0 []byte) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Write", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Write indicates an expected call of Write. +func (mr *MockReadWriterMockRecorder) Write(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockReadWriter)(nil).Write), arg0) +} diff --git a/lib/trie/writer_mock_test.go b/lib/trie/writer_mock_test.go new file mode 100644 index 0000000000..b1009272f2 --- /dev/null +++ b/lib/trie/writer_mock_test.go @@ -0,0 +1,49 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: io (interfaces: Writer) + +// Package trie is a generated GoMock package. +package trie + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockWriter is a mock of Writer interface. +type MockWriter struct { + ctrl *gomock.Controller + recorder *MockWriterMockRecorder +} + +// MockWriterMockRecorder is the mock recorder for MockWriter. +type MockWriterMockRecorder struct { + mock *MockWriter +} + +// NewMockWriter creates a new mock instance. +func NewMockWriter(ctrl *gomock.Controller) *MockWriter { + mock := &MockWriter{ctrl: ctrl} + mock.recorder = &MockWriterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockWriter) EXPECT() *MockWriterMockRecorder { + return m.recorder +} + +// Write mocks base method. +func (m *MockWriter) Write(arg0 []byte) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Write", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Write indicates an expected call of Write. +func (mr *MockWriterMockRecorder) Write(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockWriter)(nil).Write), arg0) +}