From 09d8264b781a1191fc12a9a87999a2f3cc59e3e3 Mon Sep 17 00:00:00 2001 From: Mikhail Elhimov Date: Fri, 17 Oct 2025 12:43:27 +0300 Subject: [PATCH] test: add benchmarks Closes TNTP-3733 --- .github/workflows/testing.yaml | 10 +- Makefile | 5 + bench_ext_test.go | 372 ++++++++++++++++++++++++++++++ bench_generic_over_ptr_test.go | 60 +++++ bench_generic_over_slice_test.go | 59 +++++ bench_test.go | 373 +++++++++++++++++++++++++++++++ bench_typed_test.go | 125 +++++++++++ bench_utils_test.go | 7 + go.mod | 2 +- 9 files changed, 1011 insertions(+), 2 deletions(-) create mode 100644 bench_ext_test.go create mode 100644 bench_generic_over_ptr_test.go create mode 100644 bench_generic_over_slice_test.go create mode 100644 bench_test.go create mode 100644 bench_typed_test.go create mode 100644 bench_utils_test.go diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml index 0281a0b..7f7ec3c 100644 --- a/.github/workflows/testing.yaml +++ b/.github/workflows/testing.yaml @@ -51,10 +51,18 @@ jobs: run: make coverage coveralls-deps coveralls run-benchmarks: - if: false + if: (github.event_name == 'push') || + (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name != github.repository) || + (github.event_name == 'workflow_dispatch') runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + golang: ['1.24', 'stable'] + steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 diff --git a/Makefile b/Makefile index 0e6d57d..2a7bc6d 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,11 @@ testrace: @echo "Running tests with race flag" @go test ./... -count=100 -race +.PHONY: bench +bench: + @echo "Running benchmarks" + @go test ./... -count=1 -bench=. -benchmem + .PHONY: coverage coverage: @echo "Running tests with coveralls" diff --git a/bench_ext_test.go b/bench_ext_test.go new file mode 100644 index 0000000..19e221e --- /dev/null +++ b/bench_ext_test.go @@ -0,0 +1,372 @@ +//nolint:varnamelen,gocognit,funlen +package option_test + +import ( + "bytes" + "fmt" + "slices" + "testing" + + "github.com/tarantool/go-option" + "github.com/vmihailenco/msgpack/v5" + "github.com/vmihailenco/msgpack/v5/msgpcode" +) + +type BenchExt struct { + data []byte +} + +func (e *BenchExt) MarshalMsgpack() ([]byte, error) { + return e.data, nil +} + +func (e *BenchExt) UnmarshalMsgpack(bytes []byte) error { + e.data = slices.Clone(bytes) + return nil +} + +func (e *BenchExt) ExtType() int8 { + return 8 +} + +type Optional1BenchExt struct { + value BenchExt + exists bool +} + +func SomeOptional1BenchExt(value BenchExt) Optional1BenchExt { + return Optional1BenchExt{value: value, exists: true} +} + +func NoneOptional1BenchExt() Optional1BenchExt { + return Optional1BenchExt{value: zero[BenchExt](), exists: false} +} + +var ( + //nolint: gochecknoglobals + NilBytes = []byte{msgpcode.Nil} +) + +func (opt *Optional1BenchExt) MarshalMsgpack() ([]byte, error) { + if opt.exists { + return opt.value.MarshalMsgpack() + } + + return NilBytes, nil +} + +func (opt *Optional1BenchExt) UnmarshalMsgpack(bytes []byte) error { + if bytes[0] == msgpcode.Nil { + opt.exists = false + return nil + } + + opt.exists = true + + return opt.value.UnmarshalMsgpack(bytes) +} + +func (opt *Optional1BenchExt) EncodeMsgpack(enc *msgpack.Encoder) error { + switch { + case !opt.exists: + return enc.EncodeNil() + default: + mpdata, err := opt.value.MarshalMsgpack() + if err != nil { + return err + } + + err = enc.EncodeExtHeader(opt.value.ExtType(), len(mpdata)) + if err != nil { + return err + } + + mpdataLen, err := enc.Writer().Write(mpdata) + switch { + case err != nil: + return err + case mpdataLen != len(mpdata): + return fmt.Errorf("%w: failed to write mpdata", errEncWrite) + } + + return nil + } +} + +func (opt *Optional1BenchExt) DecodeMsgpack(dec *msgpack.Decoder) error { + code, err := dec.PeekCode() + if err != nil { + return err + } + + switch { + case code == msgpcode.Nil: + opt.exists = false + case msgpcode.IsExt(code): + extID, extLen, err := dec.DecodeExtHeader() + switch { + case err != nil: + return err + case extID != opt.value.ExtType(): + return fmt.Errorf("%w: %d", errDecUnexpectedExtType, extID) + default: + ext := make([]byte, extLen) + + err := dec.ReadFull(ext) + if err != nil { + return err + } + + err = opt.value.UnmarshalMsgpack(ext) + if err != nil { + return err + } + } + default: + return fmt.Errorf("%w: %x", errDecUnexpectedCode, code) + } + + return nil +} + +type Optional2BenchExt struct { + value BenchExt + exists bool +} + +func SomeOptional2BenchExt(value BenchExt) Optional2BenchExt { + return Optional2BenchExt{value: value, exists: true} +} + +func NoneOptional2BenchExt() Optional2BenchExt { + return Optional2BenchExt{value: zero[BenchExt](), exists: false} +} + +func (opt *Optional2BenchExt) MarshalMsgpack() ([]byte, error) { + if opt.exists { + return opt.value.MarshalMsgpack() + } + + return NilBytes, nil +} + +func (opt *Optional2BenchExt) UnmarshalMsgpack(b []byte) error { + if b[0] == msgpcode.Nil { + opt.exists = false + return nil + } + + opt.exists = true + + return opt.value.UnmarshalMsgpack(b) +} + +func (opt *Optional2BenchExt) EncodeMsgpack(enc *msgpack.Encoder) error { + switch { + case !opt.exists: + return enc.EncodeNil() + default: + return enc.Encode(&opt.value) + } +} + +func (opt *Optional2BenchExt) DecodeMsgpack(dec *msgpack.Decoder) error { + code, err := dec.PeekCode() + if err != nil { + return err + } + + switch { + case code == msgpcode.Nil: + opt.exists = false + return nil + case msgpcode.IsExt(code): + return dec.Decode(&opt.value) + default: + return fmt.Errorf("%w: %x", errDecUnexpectedCode, code) + } +} + +type MsgpackExtInterface interface { + ExtType() int8 + msgpack.Marshaler + msgpack.Unmarshaler +} + +type OptionalGenericStructWithInterface[T MsgpackExtInterface] struct { + value T + exists bool +} + +func SomeOptionalGenericStructWithInterface[T MsgpackExtInterface](value T) *OptionalGenericStructWithInterface[T] { + return &OptionalGenericStructWithInterface[T]{ + value: value, + exists: true, + } +} + +func NoneOptionalGenericStructWithInterface[T MsgpackExtInterface]() *OptionalGenericStructWithInterface[T] { + return &OptionalGenericStructWithInterface[T]{ + value: zero[T](), + exists: false, + } +} + +func (opt *OptionalGenericStructWithInterface[T]) DecodeMsgpack(dec *msgpack.Decoder) error { + code, err := dec.PeekCode() + if err != nil { + return err + } + + switch { + case code == msgpcode.Nil: + opt.exists = false + case msgpcode.IsExt(code): + opt.exists = true + + extID, extLen, err := dec.DecodeExtHeader() + switch { + case err != nil: + return err + case extID != opt.value.ExtType(): + return fmt.Errorf("%w: %d", errDecUnexpectedExtType, extID) + default: + ext := make([]byte, extLen) + + err := dec.ReadFull(ext) + if err != nil { + return err + } + + err = opt.value.UnmarshalMsgpack(ext) + if err != nil { + return err + } + } + + default: + return fmt.Errorf("%w: %x", errDecUnexpectedCode, code) + } + + return nil +} + +func (opt *OptionalGenericStructWithInterface[T]) EncodeMsgpack(enc *msgpack.Encoder) error { + switch { + case !opt.exists: + return enc.EncodeNil() + default: + mpdata, err := opt.value.MarshalMsgpack() + if err != nil { + return err + } + + err = enc.EncodeExtHeader(opt.value.ExtType(), len(mpdata)) + if err != nil { + return err + } + + mpdataLen, err := enc.Writer().Write(mpdata) + switch { + case err != nil: + return err + case mpdataLen != len(mpdata): + return fmt.Errorf("%w: failed to write mpdata", errEncWrite) + } + + return nil + } +} + +func BenchmarkExtension(b *testing.B) { + msgpack.RegisterExt(8, &BenchExt{nil}) + + var buf bytes.Buffer + buf.Grow(4096) + + enc := msgpack.GetEncoder() + enc.Reset(&buf) + + dec := msgpack.GetDecoder() + dec.Reset(&buf) + + b.Run("Optional1Bench", func(b *testing.B) { + for b.Loop() { + opt := SomeOptional1BenchExt(BenchExt{[]byte{1, 2, 3}}) + + err := opt.EncodeMsgpack(enc) + if err != nil { + b.Fatal(err) + } + + err = opt.DecodeMsgpack(dec) + if err != nil { + b.Fatal(err) + } + } + }) + + b.Run("Optional2Bench", func(b *testing.B) { + for b.Loop() { + opt := SomeOptional2BenchExt(BenchExt{[]byte{1, 2, 3}}) + + err := opt.EncodeMsgpack(enc) + if err != nil { + b.Fatal(err) + } + + err = opt.DecodeMsgpack(dec) + if err != nil { + b.Fatal(err) + } + } + }) + + b.Run("OptionalBenchGeneric", func(b *testing.B) { + for b.Loop() { + opt := option.Some(BenchExt{[]byte{1, 2, 3}}) + + err := opt.EncodeMsgpack(enc) + if err != nil { + b.Fatal(err) + } + + err = opt.DecodeMsgpack(dec) + if err != nil { + b.Fatal(err) + } + } + }) + + b.Run("OptionalGenericStructWithInterface", func(b *testing.B) { + for b.Loop() { + opt := SomeOptionalGenericStructWithInterface(&BenchExt{[]byte{1, 2, 3}}) + + err := opt.EncodeMsgpack(enc) + if err != nil { + b.Fatal(err) + } + + err = opt.DecodeMsgpack(dec) + if err != nil { + b.Fatal(err) + } + } + }) + + b.Run("Default", func(b *testing.B) { + for b.Loop() { + opt := BenchExt{[]byte{1, 2, 3}} + + err := enc.Encode(&opt) + if err != nil { + b.Fatal(err) + } + + err = dec.Decode(&opt) + if err != nil { + b.Fatal(err) + } + } + }) +} diff --git a/bench_generic_over_ptr_test.go b/bench_generic_over_ptr_test.go new file mode 100644 index 0000000..c6d0d4f --- /dev/null +++ b/bench_generic_over_ptr_test.go @@ -0,0 +1,60 @@ +package option_test + +import ( + "github.com/vmihailenco/msgpack/v5" + "github.com/vmihailenco/msgpack/v5/msgpcode" +) + +type GenericOverPtr[T any] struct { + val *T +} + +func SomeOverPtr[T any](value T) GenericOverPtr[T] { + return GenericOverPtr[T]{val: &value} +} + +func NoneOverPtr[T any]() GenericOverPtr[T] { + return GenericOverPtr[T]{val: nil} +} + +func (opt *GenericOverPtr[T]) Get() (T, bool) { + if opt.val == nil { + return zero[T](), false + } + + return *opt.val, true +} + +func (opt *GenericOverPtr[T]) IsSome() bool { + return opt.val != nil +} + +func (opt *GenericOverPtr[T]) EncodeMsgpack(encoder *msgpack.Encoder) error { + if !opt.IsSome() { + return encoder.EncodeNil() + } + + return encoder.Encode(*opt.val) +} + +func (opt *GenericOverPtr[T]) DecodeMsgpack(decoder *msgpack.Decoder) error { + code, err := decoder.PeekCode() + switch { + case err != nil: + return err + case code == msgpcode.Nil: + opt.val = nil + return nil + } + + var val T + + err = decoder.Decode(&val) + if err != nil { + return err + } + + opt.val = &val + + return nil +} diff --git a/bench_generic_over_slice_test.go b/bench_generic_over_slice_test.go new file mode 100644 index 0000000..e5aa0df --- /dev/null +++ b/bench_generic_over_slice_test.go @@ -0,0 +1,59 @@ +package option_test + +import ( + "github.com/vmihailenco/msgpack/v5" + "github.com/vmihailenco/msgpack/v5/msgpcode" +) + +type GenericOverSlice[T any] []T + +func SomeOverSlice[T any](value T) GenericOverSlice[T] { + return []T{value} +} + +func NoneOverSlice[T any]() GenericOverSlice[T] { + return []T{} +} + +func (opt *GenericOverSlice[T]) Get() (T, bool) { + if len(*opt) == 0 { + return zero[T](), false + } + + return (*opt)[0], true +} + +func (opt *GenericOverSlice[T]) IsSome() bool { + return len(*opt) > 0 +} + +func (opt *GenericOverSlice[T]) EncodeMsgpack(encoder *msgpack.Encoder) error { + if !opt.IsSome() { + return encoder.EncodeNil() + } + + return encoder.Encode((*opt)[0]) +} + +func (opt *GenericOverSlice[T]) DecodeMsgpack(decoder *msgpack.Decoder) error { + code, err := decoder.PeekCode() + if err != nil { + return err + } + + if code == msgpcode.Nil { + *opt = nil + return nil + } + + var val T + + err = decoder.Decode(&val) + if err != nil { + return err + } + + *opt = []T{val} + + return nil +} diff --git a/bench_test.go b/bench_test.go new file mode 100644 index 0000000..c6c2b20 --- /dev/null +++ b/bench_test.go @@ -0,0 +1,373 @@ +//nolint:wsl_v5,varnamelen,gocognit,forbidigo,funlen +package option_test + +import ( + "bytes" + "errors" + "fmt" + "testing" + + "github.com/tarantool/go-option" + "github.com/vmihailenco/msgpack/v5" +) + +var ( + errDecUnexpected = errors.New("unexpected") + errDecUnexpectedCode = errors.New("unexpected code") + errDecUnexpectedExtType = errors.New("unexpected extension type") + errEncWrite = errors.New("write failure") +) + +func BenchmarkNoneInt(b *testing.B) { + var val int + var ok bool + + b.Run("option.Int", func(b *testing.B) { + for b.Loop() { + opt := option.NoneInt() + val, ok = opt.Get() + if ok { + b.Fatal("Get() returned true") + } + } + }) + + b.Run("option.Generic[int]", func(b *testing.B) { + for b.Loop() { + opt := option.None[int]() + val, ok = opt.Get() + if ok { + b.Fatal("Get() returned true") + } + } + }) + + b.Run("GenericOverPtr[int]", func(b *testing.B) { + for b.Loop() { + opt := NoneOverPtr[int]() + val, ok = opt.Get() + if ok { + b.Fatal("Get() returned true") + } + } + }) + + b.Run("GenericOverSlice[int]", func(b *testing.B) { + for b.Loop() { + opt := NoneOverSlice[int]() + val, ok = opt.Get() + if ok { + b.Fatal("Get() returned true") + } + } + }) + + fmt.Println(val, ok) +} + +func BenchmarkNoneStruct(b *testing.B) { + var val ValueType + var ok bool + + b.Run("option.Generic[struct]", func(b *testing.B) { + for b.Loop() { + opt := option.None[ValueType]() + val, ok = opt.Get() + if ok { + b.Fatal("Get() returned true") + } + } + }) + + b.Run("GenericOverPtr[struct]", func(b *testing.B) { + for b.Loop() { + opt := NoneOverPtr[ValueType]() + val, ok = opt.Get() + if ok { + b.Fatal("Get() returned true") + } + } + }) + + b.Run("GenericOverSlice[struct]", func(b *testing.B) { + for b.Loop() { + opt := NoneOverSlice[ValueType]() + val, ok = opt.Get() + if ok { + b.Fatal("Get() returned true") + } + } + }) + + b.Run("OptionalStruct", func(b *testing.B) { + for b.Loop() { + opt := NoneOptionalStruct() + val, ok = opt.Get() + if ok { + b.Fatal("Get() returned true") + } + } + }) + + fmt.Println(val, ok) +} + +func BenchmarkSomeInt(b *testing.B) { + var val int + var ok bool + + b.Run("option.Int", func(b *testing.B) { + for b.Loop() { + opt := option.SomeInt(42) + val, ok = opt.Get() + if !ok { + b.Fatal("Get() returned false") + } + } + }) + + b.Run("option.Generic[int]", func(b *testing.B) { + for b.Loop() { + opt := option.Some[int](42) + val, ok = opt.Get() + if !ok { + b.Fatal("Get() returned false") + } + } + }) + + b.Run("GenericOverPtr[int]", func(b *testing.B) { + for b.Loop() { + opt := SomeOverPtr[int](42) + val, ok = opt.Get() + if !ok { + b.Fatal("Get() returned false") + } + } + }) + + b.Run("GenericOverSlice[int]", func(b *testing.B) { + for b.Loop() { + opt := SomeOverSlice[int](42) + val, ok = opt.Get() + if !ok { + b.Fatal("Get() returned false") + } + } + }) + + fmt.Println(val, ok) +} + +func BenchmarkSomeStruct(b *testing.B) { + var val ValueType + var ok bool + + b.Run("option.Generic[struct]", func(b *testing.B) { + for b.Loop() { + opt := option.Some[ValueType](ValueType{"foo", 42}) + val, ok = opt.Get() + if !ok { + b.Fatal("Get() returned false") + } + } + }) + + b.Run("GenericOverPtr[struct]", func(b *testing.B) { + for b.Loop() { + opt := SomeOverPtr[ValueType](ValueType{"foo", 42}) + val, ok = opt.Get() + if !ok { + b.Fatal("Get() returned false") + } + } + }) + + b.Run("GenericOverSlice[struct]", func(b *testing.B) { + for b.Loop() { + opt := SomeOverSlice[ValueType](ValueType{"foo", 42}) + val, ok = opt.Get() + if !ok { + b.Fatal("Get() returned false") + } + } + }) + + b.Run("OptionalStruct", func(b *testing.B) { + for b.Loop() { + opt := SomeOptionalStruct(ValueType{"foo", 42}) + val, ok = opt.Get() + if !ok { + b.Fatal("Get() returned false") + } + } + }) + + fmt.Println(val, ok) +} + +func BenchmarkEncodeDecodeInt(b *testing.B) { + var buf bytes.Buffer + buf.Grow(4096) + + enc := msgpack.GetEncoder() + enc.Reset(&buf) + + dec := msgpack.GetDecoder() + dec.Reset(&buf) + + b.Run("option.Int", func(b *testing.B) { + for b.Loop() { + opt := option.SomeInt(42) + + err := opt.EncodeMsgpack(enc) + if err != nil { + b.Errorf("EncodeMsgpack() failed: %v", err) + } + + err = opt.DecodeMsgpack(dec) + if err != nil { + b.Errorf("DecodeMsgpack() failed: %v", err) + } + + buf.Reset() + } + }) + + b.Run("option.Generic[int]", func(b *testing.B) { + for b.Loop() { + opt := option.Some[int](42) + + err := opt.EncodeMsgpack(enc) + if err != nil { + b.Errorf("EncodeMsgpack() failed: %v", err) + } + + err = opt.DecodeMsgpack(dec) + if err != nil { + b.Errorf("DecodeMsgpack() failed: %v", err) + } + + buf.Reset() + } + }) + + b.Run("GenericOverSlice[int]", func(b *testing.B) { + for b.Loop() { + opt := SomeOverSlice[int](42) + + err := opt.EncodeMsgpack(enc) + if err != nil { + b.Errorf("EncodeMsgpack() failed: %v", err) + } + + err = opt.DecodeMsgpack(dec) + if err != nil { + b.Errorf("DecodeMsgpack() failed: %v", err) + } + + buf.Reset() + } + }) + + b.Run("GenericOverPtr[int]", func(b *testing.B) { + for b.Loop() { + opt := SomeOverPtr[int](42) + + err := opt.EncodeMsgpack(enc) + if err != nil { + b.Errorf("EncodeMsgpack() failed: %v", err) + } + + err = opt.DecodeMsgpack(dec) + if err != nil { + b.Errorf("DecodeMsgpack() failed: %v", err) + } + + buf.Reset() + } + }) +} + +func BenchmarkEncodeDecodeStruct(b *testing.B) { + var buf bytes.Buffer + buf.Grow(4096) + + enc := msgpack.GetEncoder() + enc.Reset(&buf) + + dec := msgpack.GetDecoder() + dec.Reset(&buf) + + b.Run("option.Generic[struct]", func(b *testing.B) { + for b.Loop() { + opt := option.Some[ValueType](ValueType{"foo", 42}) + + err := opt.EncodeMsgpack(enc) + if err != nil { + b.Errorf("EncodeMsgpack() failed: %v", err) + } + + err = opt.DecodeMsgpack(dec) + if err != nil { + b.Errorf("DecodeMsgpack() failed: %v", err) + } + + buf.Reset() + } + }) + + b.Run("GenericOverSlice[struct]", func(b *testing.B) { + for b.Loop() { + opt := SomeOverSlice[ValueType](ValueType{"foo", 42}) + + err := opt.EncodeMsgpack(enc) + if err != nil { + b.Errorf("EncodeMsgpack() failed: %v", err) + } + + err = opt.DecodeMsgpack(dec) + if err != nil { + b.Errorf("DecodeMsgpack() failed: %v", err) + } + + buf.Reset() + } + }) + + b.Run("GenericOverPtr[struct]", func(b *testing.B) { + for b.Loop() { + opt := SomeOverPtr[ValueType](ValueType{"foo", 42}) + + err := opt.EncodeMsgpack(enc) + if err != nil { + b.Errorf("EncodeMsgpack() failed: %v", err) + } + + err = opt.DecodeMsgpack(dec) + if err != nil { + b.Errorf("DecodeMsgpack() failed: %v", err) + } + + buf.Reset() + } + }) + + b.Run("OptionalStruct", func(b *testing.B) { + for b.Loop() { + opt := SomeOptionalStruct(ValueType{"foo", 42}) + + err := opt.EncodeMsgpack(enc) + if err != nil { + b.Errorf("EncodeMsgpack() failed: %v", err) + } + + err = opt.DecodeMsgpack(dec) + if err != nil { + b.Errorf("DecodeMsgpack() failed: %v", err) + } + + buf.Reset() + } + }) +} diff --git a/bench_typed_test.go b/bench_typed_test.go new file mode 100644 index 0000000..c4fce99 --- /dev/null +++ b/bench_typed_test.go @@ -0,0 +1,125 @@ +package option_test + +import ( + "fmt" + + "github.com/vmihailenco/msgpack/v5" +) + +type OptionalInt struct { + value int + exists bool +} + +func SomeOptionalInt(value int) OptionalInt { + return OptionalInt{value: value, exists: true} +} + +func NoneInt() OptionalInt { + return OptionalInt{value: zero[int](), exists: false} +} + +func (opt *OptionalInt) Get() (int, bool) { + return opt.value, opt.exists +} + +func (opt *OptionalInt) IsSome() bool { + return opt.exists +} + +func (opt *OptionalInt) EncodeMsgpack(encoder *msgpack.Encoder) error { + if !opt.exists { + return encoder.EncodeNil() + } + + return encoder.EncodeInt(int64(opt.value)) +} + +func (opt *OptionalInt) DecodeMsgpack(decoder *msgpack.Decoder) error { + val, err := decoder.DecodeInt() + if err != nil { + return err + } + + opt.value = val + opt.exists = true + + return nil +} + +type ValueType struct { + Value1 string + Value2 int +} + +type OptionalStruct struct { + value ValueType + exists bool +} + +func SomeOptionalStruct(value ValueType) OptionalStruct { + return OptionalStruct{value: value, exists: true} +} + +func NoneOptionalStruct() OptionalStruct { + return OptionalStruct{value: zero[ValueType](), exists: false} +} + +func (opt *OptionalStruct) Get() (ValueType, bool) { + return opt.value, opt.exists +} + +func (opt *OptionalStruct) HasValue() bool { + return opt.exists +} + +func (opt *OptionalStruct) EncodeMsgpack(encoder *msgpack.Encoder) error { + var err error + + if !opt.exists { + return encoder.EncodeNil() + } + + err = encoder.EncodeArrayLen(2) + if err != nil { + return err + } + + err = encoder.EncodeString(opt.value.Value1) + if err != nil { + return err + } + + err = encoder.EncodeInt(int64(opt.value.Value2)) + if err != nil { + return err + } + + return nil +} + +func (opt *OptionalStruct) DecodeMsgpack(decoder *msgpack.Decoder) error { + arrLen, err := decoder.DecodeArrayLen() + switch { + case err != nil: + return err + case arrLen == -1: + opt.exists = false + case arrLen != 2: + return fmt.Errorf("%w: unexpected array length: %d", errDecUnexpected, arrLen) + } + + opt.value.Value1, err = decoder.DecodeString() + if err != nil { + return err + } + + opt.value.Value2, err = decoder.DecodeInt() + if err != nil { + return err + } + + opt.exists = true + + return nil +} diff --git a/bench_utils_test.go b/bench_utils_test.go new file mode 100644 index 0000000..7183112 --- /dev/null +++ b/bench_utils_test.go @@ -0,0 +1,7 @@ +package option_test + +func zero[T any]() T { + var zero T + + return zero +} diff --git a/go.mod b/go.mod index 3dcb760..3faf874 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/tarantool/go-option -go 1.23.0 +go 1.24.0 require ( github.com/stretchr/testify v1.11.1