From ab9b1ffdc4582c3c6f152ba6883568c66326f816 Mon Sep 17 00:00:00 2001 From: Dmitriy Matrenichev Date: Tue, 26 Jul 2022 16:56:45 +0300 Subject: [PATCH] chore: add side-by-side tests with official proto.Marshal and Unmarshal This test ensures that our encoding is compatible with official proto modules. It doesn't verify that binary form is equivalent because we do encode zero values, unlike official encoder. Also add simple fuzzing. Closes #2 Signed-off-by: Dmitriy Matrenichev --- .dockerignore | 3 +- .kres.yaml | 9 + Dockerfile | 29 ++- Makefile | 5 +- marshal.go | 19 +- messages/fuzz_test.go | 67 ++++++ messages/helpers_test.go | 79 +++++++ messages/messages.pb.go | 466 ++++++++++++++++++++++++++++++++++++++ messages/messages.proto | 30 +++ messages/messages_test.go | 294 ++++++++++++++++++++++++ 10 files changed, 991 insertions(+), 10 deletions(-) create mode 100644 .kres.yaml create mode 100644 messages/fuzz_test.go create mode 100644 messages/helpers_test.go create mode 100644 messages/messages.pb.go create mode 100644 messages/messages.proto create mode 100644 messages/messages_test.go diff --git a/.dockerignore b/.dockerignore index 7248838..42e01e4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,8 +1,9 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2022-07-21T21:54:08Z by kres latest. +# Generated on 2022-07-26T15:06:36Z by kres latest. ** +!messages !array_test.go !benchmarks_test.go !example_test.go diff --git a/.kres.yaml b/.kres.yaml new file mode 100644 index 0000000..03b8abe --- /dev/null +++ b/.kres.yaml @@ -0,0 +1,9 @@ +--- +kind: golang.Generate +spec: + vtProtobufEnabled: false + baseSpecPath: /messages/ + specs: + - source: /messages/messages.proto + genGateway: false + external: false diff --git a/Dockerfile b/Dockerfile index 0b265f9..506a45f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,12 +2,13 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2022-07-21T21:54:08Z by kres latest. +# Generated on 2022-07-26T15:20:24Z by kres latest. ARG TOOLCHAIN -# cleaned up specs and compiled versions -FROM scratch AS generate +# collects proto specs +FROM scratch AS proto-specs +ADD /messages/messages.proto /messages/ # base toolchain image FROM ${TOOLCHAIN} AS toolchain @@ -26,6 +27,15 @@ RUN go install mvdan.cc/gofumpt@${GOFUMPT_VERSION} \ ARG GOIMPORTS_VERSION RUN go install golang.org/x/tools/cmd/goimports@${GOIMPORTS_VERSION} \ && mv /go/bin/goimports /bin/goimports +ARG PROTOBUF_GO_VERSION +RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v${PROTOBUF_GO_VERSION} +RUN mv /go/bin/protoc-gen-go /bin +ARG GRPC_GO_VERSION +RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v${GRPC_GO_VERSION} +RUN mv /go/bin/protoc-gen-go-grpc /bin +ARG GRPC_GATEWAY_VERSION +RUN go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v${GRPC_GATEWAY_VERSION} +RUN mv /go/bin/protoc-gen-grpc-gateway /bin ARG DEEPCOPY_VERSION RUN go install github.com/siderolabs/deep-copy@${DEEPCOPY_VERSION} \ && mv /go/bin/deep-copy /bin/deep-copy @@ -37,6 +47,7 @@ COPY ./go.mod . COPY ./go.sum . RUN --mount=type=cache,target=/go/pkg go mod download RUN --mount=type=cache,target=/go/pkg go mod verify +COPY ./messages ./messages COPY ./array_test.go ./array_test.go COPY ./benchmarks_test.go ./benchmarks_test.go COPY ./example_test.go ./example_test.go @@ -54,6 +65,14 @@ COPY ./type_cache.go ./type_cache.go COPY ./unmarshal.go ./unmarshal.go RUN --mount=type=cache,target=/go/pkg go list -mod=readonly all >/dev/null +# runs protobuf compiler +FROM tools AS proto-compile +COPY --from=proto-specs / / +RUN protoc -I/messages/ --go_out=paths=source_relative:/messages/ --go-grpc_out=paths=source_relative:/messages/ /messages/messages.proto +RUN rm /messages/messages.proto +RUN goimports -w -local github.com/siderolabs/protoenc /messages/ +RUN gofumpt -w /messages/ + # runs gofumpt FROM base AS lint-gofumpt RUN FILES="$(gofumpt -l .)" && test -z "${FILES}" || (echo -e "Source code is not formatted with 'gofumpt -w .':\n${FILES}"; exit 1) @@ -78,6 +97,10 @@ FROM base AS unit-tests-run ARG TESTPKGS RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg --mount=type=cache,target=/tmp go test -v -covermode=atomic -coverprofile=coverage.txt -coverpkg=${TESTPKGS} -count 1 ${TESTPKGS} +# cleaned up specs and compiled versions +FROM scratch AS generate +COPY --from=proto-compile /messages/ /messages/ + FROM scratch AS unit-tests COPY --from=unit-tests-run /src/coverage.txt /coverage.txt diff --git a/Makefile b/Makefile index 3d14dd5..35b4ee8 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2022-07-22T07:58:20Z by kres c84ca98-dirty. +# Generated on 2022-07-26T15:06:36Z by kres latest. # common variables @@ -112,6 +112,9 @@ fmt: ## Formats the source code lint-goimports: ## Runs goimports linter. @$(MAKE) target-$@ +generate: ## Generate .proto definitions. + @$(MAKE) local-$@ DEST=./ + .PHONY: base base: ## Prepare base toolchain @$(MAKE) target-$@ diff --git a/marshal.go b/marshal.go index d1ec029..1f9ad87 100644 --- a/marshal.go +++ b/marshal.go @@ -98,21 +98,30 @@ func (m *marshaller) encodeFields(val reflect.Value, fieldsData []FieldData) { noneEncoded := true for _, fieldData = range fieldsData { - field := val.FieldByIndex(fieldData.FieldIndex) - num := fieldData.Num + field := fieldByIndex(val, fieldData) - if field.CanSet() { - m.encodeValue(num, field) + if field.IsValid() { + m.encodeValue(fieldData.Num, field) noneEncoded = false } } if noneEncoded { - panic("struct has no marshallable fields") + panic(fmt.Errorf("struct '%s' has no marshallable fields", val.Type().Name())) } } +// fieldByIndex returns the field of the struct by its index if the field is exported. +// Otherwise, it returns empty reflect.Value. +func fieldByIndex(structVal reflect.Value, data FieldData) reflect.Value { + if data.Field.IsExported() && structVal.IsValid() { + return structVal.FieldByIndex(data.FieldIndex) + } + + return reflect.Value{} +} + //nolint:cyclop func (m *marshaller) encodeValue(num protowire.Number, val reflect.Value) { if m.tryEncodePredefined(num, val) { diff --git a/messages/fuzz_test.go b/messages/fuzz_test.go new file mode 100644 index 0000000..d1928fb --- /dev/null +++ b/messages/fuzz_test.go @@ -0,0 +1,67 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package messages_test + +import ( + "encoding/hex" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/siderolabs/protoenc" +) + +func FuzzBasicMessage(f *testing.F) { + testcases := [][]byte{ + hexToBytes(f, "08 01 18 02 29 03 00 00 00 00 00 00 00 32 0b 73 6f 6d 65 20 73 74 72 69 6e 67 3a 0a 73 6f 6d 65 20 62 79 74 65 73"), + hexToBytes(f, "08 00 18 00 29 00 00 00 00 00 00 00 00 32 00 3a 0a 73 6f 6d 65 20 62 79 74 65 73"), + } + for _, tc := range testcases { + f.Add(tc) // Use f.Add to provide a seed corpus + } + + f.Fuzz(func(t *testing.T, data []byte) { + var ourBasicMessage BasicMessage + err := protoenc.Unmarshal(data, &ourBasicMessage) + if err != nil { + errText := err.Error() + + switch { + case strings.Contains(errText, "index out of range"), + strings.Contains(errText, "assignment to entry in nil map"), + strings.Contains(errText, "invalid memory address or nil pointer dereference"): + t.FailNow() + } + } + }) +} + +// hexToBytes converts a hex string to a byte slice, removing any whitespace. +func hexToBytes(f *testing.F, s string) []byte { + f.Helper() + + s = strings.ReplaceAll(s, "|", "") + s = strings.ReplaceAll(s, "[", "") + s = strings.ReplaceAll(s, "]", "") + s = strings.ReplaceAll(s, " ", "") + + b, err := hex.DecodeString(s) + require.NoError(f, err) + + return b +} + +func TestName(t *testing.T) { + ourBasicMessage := BasicMessage{ + Int64: 0, + UInt64: 0, + Fixed64: protoenc.FixedU64(0), + SomeString: "", + SomeBytes: nil, + } + encoded1 := must(protoenc.Marshal(&ourBasicMessage))(t) + t.Logf("\n%s", hex.Dump(encoded1)) +} diff --git a/messages/helpers_test.go b/messages/helpers_test.go new file mode 100644 index 0000000..17984a2 --- /dev/null +++ b/messages/helpers_test.go @@ -0,0 +1,79 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package messages_test + +import ( + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + "github.com/siderolabs/protoenc" +) + +func shouldBeEqual[T any](t *testing.T, left, right T) { + t.Helper() + + opts := makeOpts[T]() + + if !cmp.Equal(left, right, opts...) { + t.Log(cmp.Diff(left, right, opts...)) + t.FailNow() + } +} + +func makeOpts[T any]() []cmp.Option { + var zero T + + typ := reflect.TypeOf(zero) + for typ.Kind() == reflect.Ptr { + typ = typ.Elem() + } + + if typ.Kind() == reflect.Struct { + return []cmp.Option{cmpopts.IgnoreUnexported(reflect.New(typ).Elem().Interface())} + } + + return nil +} + +type msg[T any] interface { + *T + proto.Message +} + +func protoUnmarshal[T any, V msg[T]](t *testing.T, data []byte) V { + t.Helper() + + var msg T + + err := proto.Unmarshal(data, V(&msg)) + require.NoError(t, err) + + return &msg +} + +func ourUnmarshal[T any](t *testing.T, data []byte) T { + t.Helper() + + var msg T + + err := protoenc.Unmarshal(data, &msg) + require.NoError(t, err) + + return msg +} + +func must[T any](v T, err error) func(t *testing.T) T { + return func(t *testing.T) T { + t.Helper() + require.NoError(t, err) + + return v + } +} diff --git a/messages/messages.pb.go b/messages/messages.pb.go new file mode 100644 index 0000000..3c585b8 --- /dev/null +++ b/messages/messages.pb.go @@ -0,0 +1,466 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.0 +// protoc v3.18.1 +// source: messages.proto + +package messages + +import ( + reflect "reflect" + sync "sync" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type BasicMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Int64 int64 `protobuf:"varint,1,opt,name=int64,proto3" json:"int64,omitempty"` + UInt64 uint64 `protobuf:"varint,3,opt,name=u_int64,json=uInt64,proto3" json:"u_int64,omitempty"` + Fixed64 uint64 `protobuf:"fixed64,5,opt,name=fixed64,proto3" json:"fixed64,omitempty"` + SomeString string `protobuf:"bytes,6,opt,name=some_string,json=someString,proto3" json:"some_string,omitempty"` + SomeBytes []byte `protobuf:"bytes,7,opt,name=some_bytes,json=someBytes,proto3" json:"some_bytes,omitempty"` +} + +func (x *BasicMessage) Reset() { + *x = BasicMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BasicMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BasicMessage) ProtoMessage() {} + +func (x *BasicMessage) ProtoReflect() protoreflect.Message { + mi := &file_messages_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BasicMessage.ProtoReflect.Descriptor instead. +func (*BasicMessage) Descriptor() ([]byte, []int) { + return file_messages_proto_rawDescGZIP(), []int{0} +} + +func (x *BasicMessage) GetInt64() int64 { + if x != nil { + return x.Int64 + } + return 0 +} + +func (x *BasicMessage) GetUInt64() uint64 { + if x != nil { + return x.UInt64 + } + return 0 +} + +func (x *BasicMessage) GetFixed64() uint64 { + if x != nil { + return x.Fixed64 + } + return 0 +} + +func (x *BasicMessage) GetSomeString() string { + if x != nil { + return x.SomeString + } + return "" +} + +func (x *BasicMessage) GetSomeBytes() []byte { + if x != nil { + return x.SomeBytes + } + return nil +} + +type MessageRepeatedFields struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Int64 []int64 `protobuf:"varint,1,rep,packed,name=int64,proto3" json:"int64,omitempty"` + UInt64 []uint64 `protobuf:"varint,3,rep,packed,name=u_int64,json=uInt64,proto3" json:"u_int64,omitempty"` + Fixed64 []uint64 `protobuf:"fixed64,5,rep,packed,name=fixed64,proto3" json:"fixed64,omitempty"` + SomeString []string `protobuf:"bytes,6,rep,name=some_string,json=someString,proto3" json:"some_string,omitempty"` + SomeBytes [][]byte `protobuf:"bytes,7,rep,name=some_bytes,json=someBytes,proto3" json:"some_bytes,omitempty"` +} + +func (x *MessageRepeatedFields) Reset() { + *x = MessageRepeatedFields{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageRepeatedFields) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageRepeatedFields) ProtoMessage() {} + +func (x *MessageRepeatedFields) ProtoReflect() protoreflect.Message { + mi := &file_messages_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageRepeatedFields.ProtoReflect.Descriptor instead. +func (*MessageRepeatedFields) Descriptor() ([]byte, []int) { + return file_messages_proto_rawDescGZIP(), []int{1} +} + +func (x *MessageRepeatedFields) GetInt64() []int64 { + if x != nil { + return x.Int64 + } + return nil +} + +func (x *MessageRepeatedFields) GetUInt64() []uint64 { + if x != nil { + return x.UInt64 + } + return nil +} + +func (x *MessageRepeatedFields) GetFixed64() []uint64 { + if x != nil { + return x.Fixed64 + } + return nil +} + +func (x *MessageRepeatedFields) GetSomeString() []string { + if x != nil { + return x.SomeString + } + return nil +} + +func (x *MessageRepeatedFields) GetSomeBytes() [][]byte { + if x != nil { + return x.SomeBytes + } + return nil +} + +type BasicMessageRep struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BasicMessage []*BasicMessage `protobuf:"bytes,1,rep,name=basic_message,json=basicMessage,proto3" json:"basic_message,omitempty"` +} + +func (x *BasicMessageRep) Reset() { + *x = BasicMessageRep{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BasicMessageRep) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BasicMessageRep) ProtoMessage() {} + +func (x *BasicMessageRep) ProtoReflect() protoreflect.Message { + mi := &file_messages_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BasicMessageRep.ProtoReflect.Descriptor instead. +func (*BasicMessageRep) Descriptor() ([]byte, []int) { + return file_messages_proto_rawDescGZIP(), []int{2} +} + +func (x *BasicMessageRep) GetBasicMessage() []*BasicMessage { + if x != nil { + return x.BasicMessage + } + return nil +} + +type MessageComplexFields struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MapToMsg map[string]*BasicMessage `protobuf:"bytes,1,rep,name=mapToMsg,proto3" json:"mapToMsg,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + MapToMsgs map[string]*BasicMessageRep `protobuf:"bytes,2,rep,name=mapToMsgs,proto3" json:"mapToMsgs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + PrimitiveMap map[string]int64 `protobuf:"bytes,3,rep,name=primitiveMap,proto3" json:"primitiveMap,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` +} + +func (x *MessageComplexFields) Reset() { + *x = MessageComplexFields{} + if protoimpl.UnsafeEnabled { + mi := &file_messages_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MessageComplexFields) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MessageComplexFields) ProtoMessage() {} + +func (x *MessageComplexFields) ProtoReflect() protoreflect.Message { + mi := &file_messages_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MessageComplexFields.ProtoReflect.Descriptor instead. +func (*MessageComplexFields) Descriptor() ([]byte, []int) { + return file_messages_proto_rawDescGZIP(), []int{3} +} + +func (x *MessageComplexFields) GetMapToMsg() map[string]*BasicMessage { + if x != nil { + return x.MapToMsg + } + return nil +} + +func (x *MessageComplexFields) GetMapToMsgs() map[string]*BasicMessageRep { + if x != nil { + return x.MapToMsgs + } + return nil +} + +func (x *MessageComplexFields) GetPrimitiveMap() map[string]int64 { + if x != nil { + return x.PrimitiveMap + } + return nil +} + +var File_messages_proto protoreflect.FileDescriptor + +var file_messages_proto_rawDesc = []byte{ + 0x0a, 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x05, 0x73, 0x70, 0x65, 0x63, 0x73, 0x22, 0x97, 0x01, 0x0a, 0x0c, 0x42, 0x61, 0x73, 0x69, + 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x74, 0x36, + 0x34, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x12, 0x17, + 0x0a, 0x07, 0x75, 0x5f, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x06, 0x75, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x69, 0x78, 0x65, 0x64, + 0x36, 0x34, 0x18, 0x05, 0x20, 0x01, 0x28, 0x06, 0x52, 0x07, 0x66, 0x69, 0x78, 0x65, 0x64, 0x36, + 0x34, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6f, 0x6d, 0x65, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x6f, 0x6d, 0x65, 0x42, 0x79, 0x74, 0x65, + 0x73, 0x22, 0xa0, 0x01, 0x0a, 0x15, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x70, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x69, + 0x6e, 0x74, 0x36, 0x34, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x05, 0x69, 0x6e, 0x74, 0x36, + 0x34, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x5f, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x04, 0x52, 0x06, 0x75, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x69, + 0x78, 0x65, 0x64, 0x36, 0x34, 0x18, 0x05, 0x20, 0x03, 0x28, 0x06, 0x52, 0x07, 0x66, 0x69, 0x78, + 0x65, 0x64, 0x36, 0x34, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6f, 0x6d, 0x65, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x62, 0x79, + 0x74, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x6f, 0x6d, 0x65, 0x42, + 0x79, 0x74, 0x65, 0x73, 0x22, 0x4b, 0x0a, 0x0f, 0x42, 0x61, 0x73, 0x69, 0x63, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x70, 0x12, 0x38, 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x69, 0x63, + 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x52, 0x0c, 0x62, 0x61, 0x73, 0x69, 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x22, 0xe3, 0x03, 0x0a, 0x14, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x78, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x45, 0x0a, 0x08, 0x6d, 0x61, + 0x70, 0x54, 0x6f, 0x4d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x73, + 0x70, 0x65, 0x63, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x78, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x2e, 0x4d, 0x61, 0x70, 0x54, 0x6f, 0x4d, + 0x73, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x61, 0x70, 0x54, 0x6f, 0x4d, 0x73, + 0x67, 0x12, 0x48, 0x0a, 0x09, 0x6d, 0x61, 0x70, 0x54, 0x6f, 0x4d, 0x73, 0x67, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x78, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x73, 0x2e, 0x4d, 0x61, 0x70, 0x54, 0x6f, 0x4d, 0x73, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x09, 0x6d, 0x61, 0x70, 0x54, 0x6f, 0x4d, 0x73, 0x67, 0x73, 0x12, 0x51, 0x0a, 0x0c, 0x70, + 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x4d, 0x61, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x2d, 0x2e, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x78, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x2e, 0x50, + 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x0c, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x4d, 0x61, 0x70, 0x1a, 0x50, + 0x0a, 0x0d, 0x4d, 0x61, 0x70, 0x54, 0x6f, 0x4d, 0x73, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x69, 0x63, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x1a, 0x54, 0x0a, 0x0e, 0x4d, 0x61, 0x70, 0x54, 0x6f, 0x4d, 0x73, 0x67, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x69, + 0x63, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x70, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3f, 0x0a, 0x11, 0x50, 0x72, 0x69, 0x6d, 0x69, 0x74, + 0x69, 0x76, 0x65, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x64, 0x65, 0x72, 0x6f, 0x6c, 0x61, 0x62, 0x73, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x65, 0x6e, 0x63, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_messages_proto_rawDescOnce sync.Once + file_messages_proto_rawDescData = file_messages_proto_rawDesc +) + +func file_messages_proto_rawDescGZIP() []byte { + file_messages_proto_rawDescOnce.Do(func() { + file_messages_proto_rawDescData = protoimpl.X.CompressGZIP(file_messages_proto_rawDescData) + }) + return file_messages_proto_rawDescData +} + +var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_messages_proto_goTypes = []interface{}{ + (*BasicMessage)(nil), // 0: specs.BasicMessage + (*MessageRepeatedFields)(nil), // 1: specs.MessageRepeatedFields + (*BasicMessageRep)(nil), // 2: specs.BasicMessageRep + (*MessageComplexFields)(nil), // 3: specs.MessageComplexFields + nil, // 4: specs.MessageComplexFields.MapToMsgEntry + nil, // 5: specs.MessageComplexFields.MapToMsgsEntry + nil, // 6: specs.MessageComplexFields.PrimitiveMapEntry +} +var file_messages_proto_depIdxs = []int32{ + 0, // 0: specs.BasicMessageRep.basic_message:type_name -> specs.BasicMessage + 4, // 1: specs.MessageComplexFields.mapToMsg:type_name -> specs.MessageComplexFields.MapToMsgEntry + 5, // 2: specs.MessageComplexFields.mapToMsgs:type_name -> specs.MessageComplexFields.MapToMsgsEntry + 6, // 3: specs.MessageComplexFields.primitiveMap:type_name -> specs.MessageComplexFields.PrimitiveMapEntry + 0, // 4: specs.MessageComplexFields.MapToMsgEntry.value:type_name -> specs.BasicMessage + 2, // 5: specs.MessageComplexFields.MapToMsgsEntry.value:type_name -> specs.BasicMessageRep + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_messages_proto_init() } +func file_messages_proto_init() { + if File_messages_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_messages_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BasicMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageRepeatedFields); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BasicMessageRep); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_messages_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MessageComplexFields); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_messages_proto_rawDesc, + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_messages_proto_goTypes, + DependencyIndexes: file_messages_proto_depIdxs, + MessageInfos: file_messages_proto_msgTypes, + }.Build() + File_messages_proto = out.File + file_messages_proto_rawDesc = nil + file_messages_proto_goTypes = nil + file_messages_proto_depIdxs = nil +} diff --git a/messages/messages.proto b/messages/messages.proto new file mode 100644 index 0000000..40b58de --- /dev/null +++ b/messages/messages.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; +package specs; + +option go_package = "github.com/siderolabs/protoenc/messages"; + +message BasicMessage { + int64 int64 = 1; + uint64 u_int64 = 3; + fixed64 fixed64 = 5; + string some_string = 6; + bytes some_bytes = 7; +} + +message MessageRepeatedFields { + repeated int64 int64 = 1; + repeated uint64 u_int64 = 3; + repeated fixed64 fixed64 = 5; + repeated string some_string = 6; + repeated bytes some_bytes = 7; +} + +message BasicMessageRep { + repeated BasicMessage basic_message = 1; +} + +message MessageComplexFields { + map mapToMsg = 1; + map mapToMsgs = 2; + map primitiveMap = 3; +} diff --git a/messages/messages_test.go b/messages/messages_test.go new file mode 100644 index 0000000..e339448 --- /dev/null +++ b/messages/messages_test.go @@ -0,0 +1,294 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package messages_test + +import ( + "testing" + + "google.golang.org/protobuf/proto" + + "github.com/siderolabs/protoenc" + "github.com/siderolabs/protoenc/messages" +) + +// TODO: ensure that binary output is also the same + +//nolint:govet +type BasicMessage struct { + Int64 int64 `protobuf:"1"` + UInt64 uint64 `protobuf:"3"` + Fixed64 protoenc.FixedU64 `protobuf:"5"` + SomeString string `protobuf:"6"` + SomeBytes []byte `protobuf:"7"` +} + +func TestBasicMessage(t *testing.T) { + t.Run("check that the outputs of both messages are the same", func(t *testing.T) { + ourBasicMessage := BasicMessage{ + Int64: 1, + UInt64: 2, + Fixed64: protoenc.FixedU64(3), + SomeString: "some string", + SomeBytes: []byte("some bytes"), + } + + encoded1 := must(protoenc.Marshal(&ourBasicMessage))(t) + basicMessage := protoUnmarshal[messages.BasicMessage](t, encoded1) + encoded2 := must(proto.Marshal(basicMessage))(t) + decodedMessage := ourUnmarshal[BasicMessage](t, encoded2) + + shouldBeEqual(t, ourBasicMessage, decodedMessage) + }) + + t.Run("check that the outputs of both zero messages are the same", func(t *testing.T) { + ourBasicMessage := BasicMessage{} + encoded1 := must(protoenc.Marshal(&ourBasicMessage))(t) + basicMessage := protoUnmarshal[messages.BasicMessage](t, encoded1) + encoded2 := must(proto.Marshal(basicMessage))(t) + decodedMessage := ourUnmarshal[BasicMessage](t, encoded2) + + shouldBeEqual(t, ourBasicMessage, decodedMessage) + }) + + t.Run("check that the outputs of both somewhat empty messages are the same", func(t *testing.T) { + ourBasicMessage := BasicMessage{SomeString: "some string"} + encoded1 := must(protoenc.Marshal(&ourBasicMessage))(t) + basicMessage := protoUnmarshal[messages.BasicMessage](t, encoded1) + encoded2 := must(proto.Marshal(basicMessage))(t) + decodedMessage := ourUnmarshal[BasicMessage](t, encoded2) + + shouldBeEqual(t, ourBasicMessage, decodedMessage) + }) +} + +type MessageRepeatedFields struct { + Int64 []int64 `protobuf:"1"` + UInt64 []uint64 `protobuf:"3"` + Fixed64 []protoenc.FixedU64 `protobuf:"5"` + SomeString []string `protobuf:"6"` + SomeBytes [][]byte `protobuf:"7"` +} + +func TestMessageRepeatedFields(t *testing.T) { + t.Run("check that the outputs of both messages are the same", func(t *testing.T) { + originalMsg := MessageRepeatedFields{ + Int64: []int64{1, 2, 3}, + UInt64: []uint64{4, 5, 6}, + Fixed64: []protoenc.FixedU64{7, 8, 9}, + SomeString: []string{"some string", "some string 2"}, + SomeBytes: [][]byte{[]byte("some bytes"), []byte("some bytes 2")}, + } + + encoded1 := must(protoenc.Marshal(&originalMsg))(t) + decodedMsg := protoUnmarshal[messages.MessageRepeatedFields](t, encoded1) + encoded2 := must(proto.Marshal(decodedMsg))(t) + resultMsg := ourUnmarshal[MessageRepeatedFields](t, encoded2) + + shouldBeEqual(t, originalMsg, resultMsg) + }) + + t.Run("check that the outputs of both zero messages are the same", func(t *testing.T) { + ourBasicMessage := MessageRepeatedFields{} + encoded1 := must(protoenc.Marshal(&ourBasicMessage))(t) + basicMessage := protoUnmarshal[messages.MessageRepeatedFields](t, encoded1) + encoded2 := must(proto.Marshal(basicMessage))(t) + decodedMessage := ourUnmarshal[MessageRepeatedFields](t, encoded2) + + shouldBeEqual(t, ourBasicMessage, decodedMessage) + }) + + t.Run("check that the outputs of both somewhat empty messages are the same", func(t *testing.T) { + ourBasicMessage := MessageRepeatedFields{SomeString: []string{"some string"}} + encoded1 := must(protoenc.Marshal(&ourBasicMessage))(t) + basicMessage := protoUnmarshal[messages.MessageRepeatedFields](t, encoded1) + encoded2 := must(proto.Marshal(basicMessage))(t) + decodedMessage := ourUnmarshal[MessageRepeatedFields](t, encoded2) + + shouldBeEqual(t, ourBasicMessage, decodedMessage) + }) +} + +type BasicMessageRep struct { + BasicMessage []BasicMessage `protobuf:"1"` +} + +func TestBasicMessageRep(t *testing.T) { + t.Run("check that the outputs of both messages are the same", func(t *testing.T) { + originalMsg := BasicMessageRep{ + BasicMessage: []BasicMessage{ + { + Int64: 1, + UInt64: 2, + Fixed64: protoenc.FixedU64(3), + SomeString: "some string", + SomeBytes: []byte("some bytes"), + }, + { + Int64: 2, + UInt64: 3, + Fixed64: protoenc.FixedU64(5), + SomeString: "hot string", + SomeBytes: []byte("hot bytes"), + }, + }, + } + encoded1 := must(protoenc.Marshal(&originalMsg))(t) + decodedMsg := protoUnmarshal[messages.BasicMessageRep](t, encoded1) + encoded2 := must(proto.Marshal(decodedMsg))(t) + resultMsg := ourUnmarshal[BasicMessageRep](t, encoded2) + + shouldBeEqual(t, originalMsg, resultMsg) + }) + + t.Run("check that the outputs of both zero messages are the same", func(t *testing.T) { + ourBasicMessage := BasicMessageRep{} + encoded1 := must(protoenc.Marshal(&ourBasicMessage))(t) + basicMessage := protoUnmarshal[messages.BasicMessageRep](t, encoded1) + encoded2 := must(proto.Marshal(basicMessage))(t) + decodedMessage := ourUnmarshal[BasicMessageRep](t, encoded2) + + shouldBeEqual(t, ourBasicMessage, decodedMessage) + }) + + t.Run("check that the outputs of both somewhat empty messages are the same", func(t *testing.T) { + ourBasicMessage := BasicMessageRep{ + BasicMessage: []BasicMessage{ + { + Fixed64: protoenc.FixedU64(3), + }, + }, + } + encoded1 := must(protoenc.Marshal(&ourBasicMessage))(t) + basicMessage := protoUnmarshal[messages.BasicMessageRep](t, encoded1) + encoded2 := must(proto.Marshal(basicMessage))(t) + decodedMessage := ourUnmarshal[BasicMessageRep](t, encoded2) + + shouldBeEqual(t, ourBasicMessage, decodedMessage) + }) +} + +type MessageComplexFields struct { + MapToMsg map[string]BasicMessage `protobuf:"1"` + MapToMsgs map[string]BasicMessageRep `protobuf:"2"` + PrimitiveMap map[string]int64 `protobuf:"3"` +} + +func TestMessageComplexFields(t *testing.T) { + t.Run("check that the outputs of both messages are the same", func(t *testing.T) { + originalMsg := MessageComplexFields{ + MapToMsg: map[string]BasicMessage{ + "key": { + Int64: 1, + UInt64: 2, + Fixed64: protoenc.FixedU64(3), + SomeString: "some string", + SomeBytes: []byte("some bytes"), + }, + }, + MapToMsgs: map[string]BasicMessageRep{ + "key": { + BasicMessage: []BasicMessage{ + { + Int64: 1, + UInt64: 2, + Fixed64: protoenc.FixedU64(3), + SomeString: "some string", + SomeBytes: []byte("some bytes"), + }, + { + Int64: 2, + UInt64: 3, + Fixed64: protoenc.FixedU64(5), + SomeString: "hot string", + SomeBytes: []byte("hot bytes"), + }, + }, + }, + "another key": { + BasicMessage: []BasicMessage{ + { + Int64: 12, + UInt64: 13, + Fixed64: protoenc.FixedU64(15), + SomeString: "another string", + SomeBytes: []byte("another bytes"), + }, + { + Int64: 15, + UInt64: 17, + Fixed64: protoenc.FixedU64(19), + SomeString: "another hot string", + SomeBytes: []byte("another hot bytes"), + }, + }, + }, + }, + PrimitiveMap: map[string]int64{ + "key": 1, + "key2": 2, + "empty": 0, + }, + } + encoded1 := must(protoenc.Marshal(&originalMsg))(t) + decodedMsg := protoUnmarshal[messages.MessageComplexFields](t, encoded1) + encoded2 := must(proto.Marshal(decodedMsg))(t) + resultMsg := ourUnmarshal[MessageComplexFields](t, encoded2) + + shouldBeEqual(t, originalMsg, resultMsg) + }) + + t.Run("check that the outputs of both zero messages are the same", func(t *testing.T) { + ourBasicMessage := MessageComplexFields{} + encoded1 := must(protoenc.Marshal(&ourBasicMessage))(t) + basicMessage := protoUnmarshal[messages.MessageComplexFields](t, encoded1) + encoded2 := must(proto.Marshal(basicMessage))(t) + decodedMessage := ourUnmarshal[MessageComplexFields](t, encoded2) + + shouldBeEqual(t, ourBasicMessage, decodedMessage) + }) + + t.Run("check that the outputs of both somewhat empty messages are the same", func(t *testing.T) { + originalMsg := MessageComplexFields{ + MapToMsg: map[string]BasicMessage{ + "key": { + Int64: 1, + }, + }, + MapToMsgs: map[string]BasicMessageRep{ + "key": { + BasicMessage: []BasicMessage{ + { + Int64: 1, + SomeBytes: []byte("some bytes"), + }, + { + Fixed64: protoenc.FixedU64(5), + }, + }, + }, + "another key": { + BasicMessage: []BasicMessage{ + { + SomeBytes: []byte("another bytes"), + }, + { + Int64: 15, + UInt64: 17, + }, + }, + }, + }, + PrimitiveMap: map[string]int64{ + "key": 1, + }, + } + encoded1 := must(protoenc.Marshal(&originalMsg))(t) + decodedMsg := protoUnmarshal[messages.MessageComplexFields](t, encoded1) + encoded2 := must(proto.Marshal(decodedMsg))(t) + resultMsg := ourUnmarshal[MessageComplexFields](t, encoded2) + + shouldBeEqual(t, originalMsg, resultMsg) + }) +}