diff --git a/internal/common/testutil/testutil.go b/internal/common/testutil/testutil.go index d38d0e5..1c67cf7 100644 --- a/internal/common/testutil/testutil.go +++ b/internal/common/testutil/testutil.go @@ -2,6 +2,7 @@ package testutil import ( "errors" + "reflect" "strings" "testing" ) @@ -37,9 +38,9 @@ func ErrorContains(t *testing.T, err error, errStr string) { } } -func Equal[T comparable](t *testing.T, actual, expected T) { +func Equal[T any](t *testing.T, actual, expected T) { t.Helper() - if actual != expected { + if !reflect.DeepEqual(actual, expected) { t.Fatalf("not equal. actual: %v, expected: %v", actual, expected) } } diff --git a/internal/stream/decoding/decoding_test.go b/internal/stream/decoding/decoding_test.go index 0ea5c9b..2429cca 100644 --- a/internal/stream/decoding/decoding_test.go +++ b/internal/stream/decoding/decoding_test.go @@ -10,7 +10,7 @@ import ( tu "github.com/shamaton/msgpack/v2/internal/common/testutil" ) -type AsXXXTestCase[T comparable] struct { +type AsXXXTestCase[T any] struct { Name string Code byte Data []byte @@ -23,7 +23,7 @@ type AsXXXTestCase[T comparable] struct { MethodAsCustom func(d *decoder) (T, error) } -type AsXXXTestCases[T comparable] []AsXXXTestCase[T] +type AsXXXTestCases[T any] []AsXXXTestCase[T] func (tc *AsXXXTestCase[T]) Run(t *testing.T) { const kind = reflect.String diff --git a/internal/stream/decoding/interface.go b/internal/stream/decoding/interface.go index c2c2af7..d0a0a11 100644 --- a/internal/stream/decoding/interface.go +++ b/internal/stream/decoding/interface.go @@ -1,6 +1,7 @@ package decoding import ( + "errors" "fmt" "reflect" @@ -10,7 +11,7 @@ import ( func (d *decoder) asInterface(k reflect.Kind) (interface{}, error) { code, err := d.readSize1() if err != nil { - return 0, err + return nil, err } return d.asInterfaceWithCode(code, k) } @@ -133,7 +134,7 @@ func (d *decoder) asInterfaceWithCode(code byte, k reflect.Kind) (interface{}, e return 0, err } - if d.canSetAsMapKey(keyCode) != nil { + if err := d.canSetAsMapKey(keyCode); err != nil { return nil, err } key, err := d.asInterfaceWithCode(keyCode, k) @@ -166,12 +167,15 @@ func (d *decoder) asInterfaceWithCode(code byte, k reflect.Kind) (interface{}, e return nil, d.errorTemplate(code, k) } +var ErrCanNotSetSliceAsMapKey = errors.New("can not set slice as map key") +var ErrCanNotSetMapAsMapKey = errors.New("can not set map as map key") + func (d *decoder) canSetAsMapKey(code byte) error { switch { case d.isFixSlice(code), code == def.Array16, code == def.Array32: - return fmt.Errorf("can not use slice code for map key/ code: %x", code) + return fmt.Errorf("%w. code: %x", ErrCanNotSetSliceAsMapKey, code) case d.isFixMap(code), code == def.Map16, code == def.Map32: - return fmt.Errorf("can not use map code for map key/ code: %x", code) + return fmt.Errorf("%w. code: %x", ErrCanNotSetMapAsMapKey, code) } return nil } diff --git a/internal/stream/decoding/interface_test.go b/internal/stream/decoding/interface_test.go new file mode 100644 index 0000000..f7ed807 --- /dev/null +++ b/internal/stream/decoding/interface_test.go @@ -0,0 +1,220 @@ +package decoding + +import ( + "fmt" + "io" + "reflect" + "testing" + + "github.com/shamaton/msgpack/v2/def" + "github.com/shamaton/msgpack/v2/ext" +) + +func Test_asInterface(t *testing.T) { + method := func(d *decoder) func(reflect.Kind) (any, error) { + return d.asInterface + } + testcases := AsXXXTestCases[any]{ + { + Name: "error", + Data: []byte{}, + Error: io.EOF, + ReadCount: 0, + MethodAs: method, + }, + { + Name: "ok", + Data: []byte{def.Nil}, + Expected: nil, + ReadCount: 1, + MethodAs: method, + }, + } + + for _, tc := range testcases { + tc.Run(t) + } +} + +func Test_asInterfaceWithCode(t *testing.T) { + dec := testExt2StreamDecoder{} + AddExtDecoder(&dec) + defer RemoveExtDecoder(&dec) + + method := func(d *decoder) func(byte, reflect.Kind) (any, error) { + return d.asInterfaceWithCode + } + testcases := AsXXXTestCases[any]{ + { + Name: "Uint8.error", + Code: def.Uint8, + Error: io.EOF, + MethodAsWithCode: method, + }, + { + Name: "Uint16.error", + Code: def.Uint16, + Error: io.EOF, + MethodAsWithCode: method, + }, + { + Name: "Uint32.error", + Code: def.Uint32, + Error: io.EOF, + MethodAsWithCode: method, + }, + { + Name: "Uint64.error", + Code: def.Uint64, + Error: io.EOF, + MethodAsWithCode: method, + }, + { + Name: "Int8.error", + Code: def.Int8, + Error: io.EOF, + MethodAsWithCode: method, + }, + { + Name: "Int16.error", + Code: def.Int16, + Error: io.EOF, + MethodAsWithCode: method, + }, + { + Name: "Int32.error", + Code: def.Int32, + Error: io.EOF, + MethodAsWithCode: method, + }, + { + Name: "Int64.error", + Code: def.Int64, + Error: io.EOF, + MethodAsWithCode: method, + }, + { + Name: "Float32.error", + Code: def.Float32, + Error: io.EOF, + MethodAsWithCode: method, + }, + { + Name: "Float64.error", + Code: def.Float64, + Error: io.EOF, + MethodAsWithCode: method, + }, + { + Name: "Str.error", + Code: def.Str8, + Error: io.EOF, + MethodAsWithCode: method, + }, + { + Name: "Bin.error", + Code: def.Bin8, + Error: io.EOF, + MethodAsWithCode: method, + }, + { + Name: "Array.error.length", + Code: def.Array16, + Error: io.EOF, + MethodAsWithCode: method, + }, + { + Name: "Array.error.set", + Code: def.Array16, + Data: []byte{0, 1}, + ReadCount: 1, + Error: io.EOF, + MethodAsWithCode: method, + }, + { + Name: "Map.error.length", + Code: def.Map16, + Error: io.EOF, + MethodAsWithCode: method, + }, + { + Name: "Map.error.set.key_code", + Code: def.Map16, + Data: []byte{0, 1}, + ReadCount: 1, + Error: io.EOF, + MethodAsWithCode: method, + }, + { + Name: "Map.error.set.can.slice", + Code: def.Map16, + Data: []byte{0, 1, def.Array16}, + ReadCount: 2, + Error: ErrCanNotSetSliceAsMapKey, + MethodAsWithCode: method, + }, + { + Name: "Map.error.set.can.map", + Code: def.Map16, + Data: []byte{0, 1, def.Map16}, + ReadCount: 2, + Error: ErrCanNotSetMapAsMapKey, + MethodAsWithCode: method, + }, + { + Name: "Map.error.set.key", + Code: def.Map16, + Data: []byte{0, 1, def.FixStr + 1}, + ReadCount: 2, + Error: io.EOF, + MethodAsWithCode: method, + }, + { + Name: "Map.error.set.value", + Code: def.Map16, + Data: []byte{0, 1, def.FixStr + 1, 'a'}, + ReadCount: 3, + Error: io.EOF, + MethodAsWithCode: method, + }, + { + Name: "Ext.error", + Code: def.Fixext1, + Data: []byte{3, 0}, + ReadCount: 2, + Error: ErrTestExtStreamDecoder, + MethodAsWithCode: method, + }, + { + Name: "Unexpected", + Code: def.Fixext1, + Data: []byte{4, 0}, + ReadCount: 2, + IsTemplateError: true, + MethodAsWithCode: method, + }, + } + + for _, tc := range testcases { + tc.Run(t) + } +} + +// TODO: to testutil +type testExt2StreamDecoder struct{} + +var _ ext.StreamDecoder = (*testExt2StreamDecoder)(nil) + +func (td *testExt2StreamDecoder) Code() int8 { + return 3 +} + +func (td *testExt2StreamDecoder) IsType(_ byte, code int8, _ int) bool { + return code == td.Code() +} + +var ErrTestExtStreamDecoder = fmt.Errorf("testExtStreamDecoder") + +func (td *testExt2StreamDecoder) ToValue(_ byte, _ []byte, k reflect.Kind) (any, error) { + return nil, ErrTestExtStreamDecoder +} diff --git a/internal/stream/decoding/string_test.go b/internal/stream/decoding/string_test.go index 9875af6..db33cfc 100644 --- a/internal/stream/decoding/string_test.go +++ b/internal/stream/decoding/string_test.go @@ -1,15 +1,12 @@ package decoding import ( - "bytes" "io" "math" "reflect" "testing" "github.com/shamaton/msgpack/v2/def" - "github.com/shamaton/msgpack/v2/internal/common" - tu "github.com/shamaton/msgpack/v2/internal/common/testutil" ) func Test_stringByteLength(t *testing.T) { @@ -87,22 +84,27 @@ func Test_asString(t *testing.T) { } func Test_asStringByte(t *testing.T) { - t.Run("read error", func(t *testing.T) { - d := decoder{ - r: tu.NewErrReader(), - buf: common.GetBuffer(), - } - v, err := d.asStringByte(reflect.String) - tu.IsError(t, err, tu.ErrReaderErr) - tu.EqualSlice(t, v, emptyBytes) - }) - t.Run("ok", func(t *testing.T) { - d := decoder{ - r: bytes.NewReader([]byte{def.FixStr + 1, 'a'}), - buf: common.GetBuffer(), - } - v, err := d.asStringByte(reflect.String) - tu.NoError(t, err) - tu.EqualSlice(t, v, []byte("a")) - }) + method := func(d *decoder) func(reflect.Kind) ([]byte, error) { + return d.asStringByte + } + testcases := AsXXXTestCases[[]byte]{ + { + Name: "error", + Data: []byte{def.FixStr + 1}, + Error: io.EOF, + ReadCount: 1, + MethodAs: method, + }, + { + Name: "ok", + Data: []byte{def.FixStr + 1, 'a'}, + Expected: []byte{'a'}, + ReadCount: 2, + MethodAs: method, + }, + } + + for _, tc := range testcases { + tc.Run(t) + } }