dcrd/wire/error_test.go
Dave Collins c1722eb7b2
wire: Improve error handling.
This updates the error handling in the wire package to be more
consistent with the rest of the code base, tightens some of the error
handling in message parsing to harden the protocol, and adds tests to
ensure the error implementations works correctly with the standard
library errors.Is and errors.As functions introduced in Go 1.13.

In particular, the error code semantics have been changed so the first
error starts at 0 instead of treating 0 is a sentinel "no error" value
because it goes against Go best practices which designed the errors
specifically to avoid the use of sentinel values.  Further, the use of 0
as a sentinel means that there are two different ways to indicate "no
error", namely nil and an error code of zero.  Thus callers have to know
that they need to check for both conditions which is incredibly error
prone.  In fact, there is already at least one instance in the code base
that has that mistake in the tests.

Finally, while here, this hardens the protocol by enforcing more strict
parsing requirements lower in the software stack instead of relying on
upper layers to take care of it.
2020-05-01 20:24:15 -05:00

174 lines
5.1 KiB
Go

// Copyright (c) 2017 The btcsuite developers
// Copyright (c) 2015-2020 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package wire
import (
"errors"
"testing"
)
// TestMessageErrorCodeStringer tests the stringized output for
// the ErrorCode type.
func TestMessageErrorCodeStringer(t *testing.T) {
t.Parallel()
tests := []struct {
in ErrorCode
want string
}{
{ErrNonCanonicalVarInt, "ErrNonCanonicalVarInt"},
{ErrVarStringTooLong, "ErrVarStringTooLong"},
{ErrVarBytesTooLong, "ErrVarBytesTooLong"},
{ErrCmdTooLong, "ErrCmdTooLong"},
{ErrPayloadTooLarge, "ErrPayloadTooLarge"},
{ErrWrongNetwork, "ErrWrongNetwork"},
{ErrMalformedCmd, "ErrMalformedCmd"},
{ErrUnknownCmd, "ErrUnknownCmd"},
{ErrPayloadChecksum, "ErrPayloadChecksum"},
{ErrTooManyAddrs, "ErrTooManyAddrs"},
{ErrTooManyTxs, "ErrTooManyTxs"},
{ErrMsgInvalidForPVer, "ErrMsgInvalidForPVer"},
{ErrFilterTooLarge, "ErrFilterTooLarge"},
{ErrTooManyProofs, "ErrTooManyProofs"},
{ErrTooManyFilterTypes, "ErrTooManyFilterTypes"},
{ErrTooManyLocators, "ErrTooManyLocators"},
{ErrTooManyVectors, "ErrTooManyVectors"},
{ErrTooManyHeaders, "ErrTooManyHeaders"},
{ErrHeaderContainsTxs, "ErrHeaderContainsTxs"},
{ErrTooManyVotes, "ErrTooManyVotes"},
{ErrTooManyBlocks, "ErrTooManyBlocks"},
{ErrMismatchedWitnessCount, "ErrMismatchedWitnessCount"},
{ErrUnknownTxType, "ErrUnknownTxType"},
{ErrReadInPrefixFromWitnessOnlyTx, "ErrReadInPrefixFromWitnessOnlyTx"},
{ErrInvalidMsg, "ErrInvalidMsg"},
{ErrUserAgentTooLong, "ErrUserAgentTooLong"},
{ErrTooManyFilterHeaders, "ErrTooManyFilterHeaders"},
{ErrMalformedStrictString, "ErrMalformedStrictString"},
{0xffff, "Unknown ErrorCode (65535)"},
}
t.Logf("Running %d tests", len(tests))
for i, test := range tests {
result := test.in.String()
if result != test.want {
t.Errorf("String #%d\n got: %s want: %s", i, result,
test.want)
continue
}
}
}
// TestMessageError tests the error output for the MessageError type.
func TestMessageError(t *testing.T) {
t.Parallel()
tests := []struct {
in MessageError
want string
}{{
MessageError{Description: "some error"},
"some error",
}, {
MessageError{Description: "human-readable error"},
"human-readable error",
}, {
MessageError{Func: "foo", Description: "something bad happened"},
"foo: something bad happened",
}}
t.Logf("Running %d tests", len(tests))
for i, test := range tests {
result := test.in.Error()
if result != test.want {
t.Errorf("#%d: got: %s want: %s", i, result, test.want)
continue
}
}
}
// TestErrorCodeIsAs ensures both ErrorCode and MessageError can be identified
// as being a specific error code via errors.Is and unwrapped via errors.As.
func TestErrorCodeIsAs(t *testing.T) {
tests := []struct {
name string
err error
target error
wantMatch bool
wantAs ErrorCode
}{{
name: "ErrTooManyAddrs == ErrTooManyAddrs",
err: ErrTooManyAddrs,
target: ErrTooManyAddrs,
wantMatch: true,
wantAs: ErrTooManyAddrs,
}, {
name: "MessageError.ErrTooManyAddrs == ErrTooManyAddrs",
err: messageError("", ErrTooManyAddrs, ""),
target: ErrTooManyAddrs,
wantMatch: true,
wantAs: ErrTooManyAddrs,
}, {
name: "ErrTooManyAddrs == MessageError.ErrTooManyAddrs",
err: ErrTooManyAddrs,
target: messageError("", ErrTooManyAddrs, ""),
wantMatch: true,
wantAs: ErrTooManyAddrs,
}, {
name: "MessageError.ErrTooManyAddrs == MessageError.ErrTooManyAddrs",
err: messageError("", ErrTooManyAddrs, ""),
target: messageError("", ErrTooManyAddrs, ""),
wantMatch: true,
wantAs: ErrTooManyAddrs,
}, {
name: "ErrTooManyTxs != ErrTooManyAddrs",
err: ErrTooManyTxs,
target: ErrTooManyAddrs,
wantMatch: false,
wantAs: ErrTooManyTxs,
}, {
name: "MessageError.ErrTooManyTxs != ErrTooManyAddrs",
err: messageError("", ErrTooManyTxs, ""),
target: ErrTooManyAddrs,
wantMatch: false,
wantAs: ErrTooManyTxs,
}, {
name: "ErrTooManyTxs != MessageError.ErrTooManyAddrs",
err: ErrTooManyTxs,
target: messageError("", ErrTooManyAddrs, ""),
wantMatch: false,
wantAs: ErrTooManyTxs,
}, {
name: "MessageError.ErrTooManyTxs != MessageError.ErrTooManyAddrs",
err: messageError("", ErrTooManyTxs, ""),
target: messageError("", ErrTooManyAddrs, ""),
wantMatch: false,
wantAs: ErrTooManyTxs,
}}
for _, test := range tests {
// Ensure the error matches or not depending on the expected result.
result := errors.Is(test.err, test.target)
if result != test.wantMatch {
t.Errorf("%s: incorrect error identification -- got %v, want %v",
test.name, result, test.wantMatch)
continue
}
// Ensure the underlying error code can be unwrapped and is the expected
// code.
var code ErrorCode
if !errors.As(test.err, &code) {
t.Errorf("%s: unable to unwrap to error code", test.name)
continue
}
if code != test.wantAs {
t.Errorf("%s: unexpected unwrapped error code -- got %v, want %v",
test.name, code, test.wantAs)
continue
}
}
}