protobuf-go/proto/merge_test.go
Joe Tsai 387873dd53 all: implement support for proto3 optional semantics
In the upcoming 3.12.x release of protoc, the proto3 language will be
amended to support true presence for scalars. This CL adds support
to both the generator and runtime to support these semantics.

Newly added public API:
	protogen.Plugin.SupportedFeatures
	protoreflect.FieldDescriptor.HasPresence
	protoreflect.FieldDescriptor.HasOptionalKeyword
	protoreflect.OneofDescriptor.IsSynthetic

Change-Id: I7c86bf66d0ae56642109beb5f2132184593747ad
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/230698
Reviewed-by: Damien Neil <dneil@google.com>
2020-04-29 20:02:24 +00:00

906 lines
30 KiB
Go

// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package proto_test
import (
"fmt"
"reflect"
"sync"
"testing"
"github.com/google/go-cmp/cmp"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/internal/protobuild"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/testing/protopack"
"google.golang.org/protobuf/types/dynamicpb"
legacypb "google.golang.org/protobuf/internal/testprotos/legacy"
testpb "google.golang.org/protobuf/internal/testprotos/test"
test3pb "google.golang.org/protobuf/internal/testprotos/test3"
)
type testMerge struct {
desc string
dst protobuild.Message
src protobuild.Message
want protobuild.Message // if dst and want are nil, want = src
types []proto.Message
}
var testMerges = []testMerge{{
desc: "clone a large message",
src: protobuild.Message{
"optional_int32": 1001,
"optional_int64": 1002,
"optional_uint32": 1003,
"optional_uint64": 1004,
"optional_sint32": 1005,
"optional_sint64": 1006,
"optional_fixed32": 1007,
"optional_fixed64": 1008,
"optional_sfixed32": 1009,
"optional_sfixed64": 1010,
"optional_float": 1011.5,
"optional_double": 1012.5,
"optional_bool": true,
"optional_string": "string",
"optional_bytes": []byte("bytes"),
"optional_nested_enum": 1,
"optional_nested_message": protobuild.Message{
"a": 100,
},
"repeated_int32": []int32{1001, 2001},
"repeated_int64": []int64{1002, 2002},
"repeated_uint32": []uint32{1003, 2003},
"repeated_uint64": []uint64{1004, 2004},
"repeated_sint32": []int32{1005, 2005},
"repeated_sint64": []int64{1006, 2006},
"repeated_fixed32": []uint32{1007, 2007},
"repeated_fixed64": []uint64{1008, 2008},
"repeated_sfixed32": []int32{1009, 2009},
"repeated_sfixed64": []int64{1010, 2010},
"repeated_float": []float32{1011.5, 2011.5},
"repeated_double": []float64{1012.5, 2012.5},
"repeated_bool": []bool{true, false},
"repeated_string": []string{"foo", "bar"},
"repeated_bytes": []string{"FOO", "BAR"},
"repeated_nested_enum": []string{"FOO", "BAR"},
"repeated_nested_message": []protobuild.Message{
{"a": 200},
{"a": 300},
},
},
}, {
desc: "clone maps",
src: protobuild.Message{
"map_int32_int32": map[int32]int32{1056: 1156, 2056: 2156},
"map_int64_int64": map[int64]int64{1057: 1157, 2057: 2157},
"map_uint32_uint32": map[uint32]uint32{1058: 1158, 2058: 2158},
"map_uint64_uint64": map[uint64]uint64{1059: 1159, 2059: 2159},
"map_sint32_sint32": map[int32]int32{1060: 1160, 2060: 2160},
"map_sint64_sint64": map[int64]int64{1061: 1161, 2061: 2161},
"map_fixed32_fixed32": map[uint32]uint32{1062: 1162, 2062: 2162},
"map_fixed64_fixed64": map[uint64]uint64{1063: 1163, 2063: 2163},
"map_sfixed32_sfixed32": map[int32]int32{1064: 1164, 2064: 2164},
"map_sfixed64_sfixed64": map[int64]int64{1065: 1165, 2065: 2165},
"map_int32_float": map[int32]float32{1066: 1166.5, 2066: 2166.5},
"map_int32_double": map[int32]float64{1067: 1167.5, 2067: 2167.5},
"map_bool_bool": map[bool]bool{true: false, false: true},
"map_string_string": map[string]string{"69.1.key": "69.1.val", "69.2.key": "69.2.val"},
"map_string_bytes": map[string][]byte{"70.1.key": []byte("70.1.val"), "70.2.key": []byte("70.2.val")},
"map_string_nested_message": map[string]protobuild.Message{
"71.1.key": {"a": 1171},
"71.2.key": {"a": 2171},
},
"map_string_nested_enum": map[string]string{"73.1.key": "FOO", "73.2.key": "BAR"},
},
types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
}, {
desc: "clone oneof uint32",
src: protobuild.Message{
"oneof_uint32": 1111,
},
types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
}, {
desc: "clone oneof string",
src: protobuild.Message{
"oneof_string": "string",
},
types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
}, {
desc: "clone oneof bytes",
src: protobuild.Message{
"oneof_bytes": "bytes",
},
types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
}, {
desc: "clone oneof bool",
src: protobuild.Message{
"oneof_bool": true,
},
types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
}, {
desc: "clone oneof uint64",
src: protobuild.Message{
"oneof_uint64": 100,
},
types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
}, {
desc: "clone oneof float",
src: protobuild.Message{
"oneof_float": 100,
},
types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
}, {
desc: "clone oneof double",
src: protobuild.Message{
"oneof_double": 1111,
},
types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
}, {
desc: "clone oneof enum",
src: protobuild.Message{
"oneof_enum": 1,
},
types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
}, {
desc: "clone oneof message",
src: protobuild.Message{
"oneof_nested_message": protobuild.Message{
"a": 1,
},
},
types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
}, {
desc: "clone oneof group",
src: protobuild.Message{
"oneofgroup": protobuild.Message{
"a": 1,
},
},
types: []proto.Message{&testpb.TestAllTypes{}},
}, {
desc: "merge bytes",
dst: protobuild.Message{
"optional_bytes": []byte{1, 2, 3},
"repeated_bytes": [][]byte{{1, 2}, {3, 4}},
"map_string_bytes": map[string][]byte{"alpha": {1, 2, 3}},
},
src: protobuild.Message{
"optional_bytes": []byte{4, 5, 6},
"repeated_bytes": [][]byte{{5, 6}, {7, 8}},
"map_string_bytes": map[string][]byte{"alpha": {4, 5, 6}, "bravo": {1, 2, 3}},
},
want: protobuild.Message{
"optional_bytes": []byte{4, 5, 6},
"repeated_bytes": [][]byte{{1, 2}, {3, 4}, {5, 6}, {7, 8}},
"map_string_bytes": map[string][]byte{"alpha": {4, 5, 6}, "bravo": {1, 2, 3}},
},
types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
}, {
desc: "merge singular fields",
dst: protobuild.Message{
"optional_int32": 1,
"optional_int64": 1,
"optional_uint32": 1,
"optional_uint64": 1,
"optional_sint32": 1,
"optional_sint64": 1,
"optional_fixed32": 1,
"optional_fixed64": 1,
"optional_sfixed32": 1,
"optional_sfixed64": 1,
"optional_float": 1,
"optional_double": 1,
"optional_bool": false,
"optional_string": "1",
"optional_bytes": "1",
"optional_nested_enum": 1,
"optional_nested_message": protobuild.Message{
"a": 1,
"corecursive": protobuild.Message{
"optional_int64": 1,
},
},
},
src: protobuild.Message{
"optional_int32": 2,
"optional_int64": 2,
"optional_uint32": 2,
"optional_uint64": 2,
"optional_sint32": 2,
"optional_sint64": 2,
"optional_fixed32": 2,
"optional_fixed64": 2,
"optional_sfixed32": 2,
"optional_sfixed64": 2,
"optional_float": 2,
"optional_double": 2,
"optional_bool": true,
"optional_string": "2",
"optional_bytes": "2",
"optional_nested_enum": 2,
"optional_nested_message": protobuild.Message{
"a": 2,
"corecursive": protobuild.Message{
"optional_int64": 2,
},
},
},
want: protobuild.Message{
"optional_int32": 2,
"optional_int64": 2,
"optional_uint32": 2,
"optional_uint64": 2,
"optional_sint32": 2,
"optional_sint64": 2,
"optional_fixed32": 2,
"optional_fixed64": 2,
"optional_sfixed32": 2,
"optional_sfixed64": 2,
"optional_float": 2,
"optional_double": 2,
"optional_bool": true,
"optional_string": "2",
"optional_bytes": "2",
"optional_nested_enum": 2,
"optional_nested_message": protobuild.Message{
"a": 2,
"corecursive": protobuild.Message{
"optional_int64": 2,
},
},
},
}, {
desc: "no merge of empty singular fields",
dst: protobuild.Message{
"optional_int32": 1,
"optional_int64": 1,
"optional_uint32": 1,
"optional_uint64": 1,
"optional_sint32": 1,
"optional_sint64": 1,
"optional_fixed32": 1,
"optional_fixed64": 1,
"optional_sfixed32": 1,
"optional_sfixed64": 1,
"optional_float": 1,
"optional_double": 1,
"optional_bool": false,
"optional_string": "1",
"optional_bytes": "1",
"optional_nested_enum": 1,
"optional_nested_message": protobuild.Message{
"a": 1,
"corecursive": protobuild.Message{
"optional_int64": 1,
},
},
},
src: protobuild.Message{
"optional_nested_message": protobuild.Message{
"a": 1,
"corecursive": protobuild.Message{
"optional_int32": 2,
},
},
},
want: protobuild.Message{
"optional_int32": 1,
"optional_int64": 1,
"optional_uint32": 1,
"optional_uint64": 1,
"optional_sint32": 1,
"optional_sint64": 1,
"optional_fixed32": 1,
"optional_fixed64": 1,
"optional_sfixed32": 1,
"optional_sfixed64": 1,
"optional_float": 1,
"optional_double": 1,
"optional_bool": false,
"optional_string": "1",
"optional_bytes": "1",
"optional_nested_enum": 1,
"optional_nested_message": protobuild.Message{
"a": 1,
"corecursive": protobuild.Message{
"optional_int32": 2,
"optional_int64": 1,
},
},
},
}, {
desc: "merge list fields",
dst: protobuild.Message{
"repeated_int32": []int32{1, 2, 3},
"repeated_int64": []int64{1, 2, 3},
"repeated_uint32": []uint32{1, 2, 3},
"repeated_uint64": []uint64{1, 2, 3},
"repeated_sint32": []int32{1, 2, 3},
"repeated_sint64": []int64{1, 2, 3},
"repeated_fixed32": []uint32{1, 2, 3},
"repeated_fixed64": []uint64{1, 2, 3},
"repeated_sfixed32": []int32{1, 2, 3},
"repeated_sfixed64": []int64{1, 2, 3},
"repeated_float": []float32{1, 2, 3},
"repeated_double": []float64{1, 2, 3},
"repeated_bool": []bool{true},
"repeated_string": []string{"a", "b", "c"},
"repeated_bytes": []string{"a", "b", "c"},
"repeated_nested_enum": []int{1, 2, 3},
"repeated_nested_message": []protobuild.Message{
{"a": 100},
{"a": 200},
},
},
src: protobuild.Message{
"repeated_int32": []int32{4, 5, 6},
"repeated_int64": []int64{4, 5, 6},
"repeated_uint32": []uint32{4, 5, 6},
"repeated_uint64": []uint64{4, 5, 6},
"repeated_sint32": []int32{4, 5, 6},
"repeated_sint64": []int64{4, 5, 6},
"repeated_fixed32": []uint32{4, 5, 6},
"repeated_fixed64": []uint64{4, 5, 6},
"repeated_sfixed32": []int32{4, 5, 6},
"repeated_sfixed64": []int64{4, 5, 6},
"repeated_float": []float32{4, 5, 6},
"repeated_double": []float64{4, 5, 6},
"repeated_bool": []bool{false},
"repeated_string": []string{"d", "e", "f"},
"repeated_bytes": []string{"d", "e", "f"},
"repeated_nested_enum": []int{4, 5, 6},
"repeated_nested_message": []protobuild.Message{
{"a": 300},
{"a": 400},
},
},
want: protobuild.Message{
"repeated_int32": []int32{1, 2, 3, 4, 5, 6},
"repeated_int64": []int64{1, 2, 3, 4, 5, 6},
"repeated_uint32": []uint32{1, 2, 3, 4, 5, 6},
"repeated_uint64": []uint64{1, 2, 3, 4, 5, 6},
"repeated_sint32": []int32{1, 2, 3, 4, 5, 6},
"repeated_sint64": []int64{1, 2, 3, 4, 5, 6},
"repeated_fixed32": []uint32{1, 2, 3, 4, 5, 6},
"repeated_fixed64": []uint64{1, 2, 3, 4, 5, 6},
"repeated_sfixed32": []int32{1, 2, 3, 4, 5, 6},
"repeated_sfixed64": []int64{1, 2, 3, 4, 5, 6},
"repeated_float": []float32{1, 2, 3, 4, 5, 6},
"repeated_double": []float64{1, 2, 3, 4, 5, 6},
"repeated_bool": []bool{true, false},
"repeated_string": []string{"a", "b", "c", "d", "e", "f"},
"repeated_bytes": []string{"a", "b", "c", "d", "e", "f"},
"repeated_nested_enum": []int{1, 2, 3, 4, 5, 6},
"repeated_nested_message": []protobuild.Message{
{"a": 100},
{"a": 200},
{"a": 300},
{"a": 400},
},
},
}, {
desc: "merge map fields",
dst: protobuild.Message{
"map_int32_int32": map[int]int{1: 1, 3: 1},
"map_int64_int64": map[int]int{1: 1, 3: 1},
"map_uint32_uint32": map[int]int{1: 1, 3: 1},
"map_uint64_uint64": map[int]int{1: 1, 3: 1},
"map_sint32_sint32": map[int]int{1: 1, 3: 1},
"map_sint64_sint64": map[int]int{1: 1, 3: 1},
"map_fixed32_fixed32": map[int]int{1: 1, 3: 1},
"map_fixed64_fixed64": map[int]int{1: 1, 3: 1},
"map_sfixed32_sfixed32": map[int]int{1: 1, 3: 1},
"map_sfixed64_sfixed64": map[int]int{1: 1, 3: 1},
"map_int32_float": map[int]int{1: 1, 3: 1},
"map_int32_double": map[int]int{1: 1, 3: 1},
"map_bool_bool": map[bool]bool{true: true},
"map_string_string": map[string]string{"a": "1", "ab": "1"},
"map_string_bytes": map[string]string{"a": "1", "ab": "1"},
"map_string_nested_message": map[string]protobuild.Message{
"a": {"a": 1},
"ab": {
"a": 1,
"corecursive": protobuild.Message{
"map_int32_int32": map[int]int{1: 1, 3: 1},
},
},
},
"map_string_nested_enum": map[string]int{"a": 1, "ab": 1},
},
src: protobuild.Message{
"map_int32_int32": map[int]int{2: 2, 3: 2},
"map_int64_int64": map[int]int{2: 2, 3: 2},
"map_uint32_uint32": map[int]int{2: 2, 3: 2},
"map_uint64_uint64": map[int]int{2: 2, 3: 2},
"map_sint32_sint32": map[int]int{2: 2, 3: 2},
"map_sint64_sint64": map[int]int{2: 2, 3: 2},
"map_fixed32_fixed32": map[int]int{2: 2, 3: 2},
"map_fixed64_fixed64": map[int]int{2: 2, 3: 2},
"map_sfixed32_sfixed32": map[int]int{2: 2, 3: 2},
"map_sfixed64_sfixed64": map[int]int{2: 2, 3: 2},
"map_int32_float": map[int]int{2: 2, 3: 2},
"map_int32_double": map[int]int{2: 2, 3: 2},
"map_bool_bool": map[bool]bool{false: false},
"map_string_string": map[string]string{"b": "2", "ab": "2"},
"map_string_bytes": map[string]string{"b": "2", "ab": "2"},
"map_string_nested_message": map[string]protobuild.Message{
"b": {"a": 2},
"ab": {
"a": 2,
"corecursive": protobuild.Message{
"map_int32_int32": map[int]int{2: 2, 3: 2},
},
},
},
"map_string_nested_enum": map[string]int{"b": 2, "ab": 2},
},
want: protobuild.Message{
"map_int32_int32": map[int]int{1: 1, 2: 2, 3: 2},
"map_int64_int64": map[int]int{1: 1, 2: 2, 3: 2},
"map_uint32_uint32": map[int]int{1: 1, 2: 2, 3: 2},
"map_uint64_uint64": map[int]int{1: 1, 2: 2, 3: 2},
"map_sint32_sint32": map[int]int{1: 1, 2: 2, 3: 2},
"map_sint64_sint64": map[int]int{1: 1, 2: 2, 3: 2},
"map_fixed32_fixed32": map[int]int{1: 1, 2: 2, 3: 2},
"map_fixed64_fixed64": map[int]int{1: 1, 2: 2, 3: 2},
"map_sfixed32_sfixed32": map[int]int{1: 1, 2: 2, 3: 2},
"map_sfixed64_sfixed64": map[int]int{1: 1, 2: 2, 3: 2},
"map_int32_float": map[int]int{1: 1, 2: 2, 3: 2},
"map_int32_double": map[int]int{1: 1, 2: 2, 3: 2},
"map_bool_bool": map[bool]bool{true: true, false: false},
"map_string_string": map[string]string{"a": "1", "b": "2", "ab": "2"},
"map_string_bytes": map[string]string{"a": "1", "b": "2", "ab": "2"},
"map_string_nested_message": map[string]protobuild.Message{
"a": {"a": 1},
"b": {"a": 2},
"ab": {
"a": 2,
"corecursive": protobuild.Message{
// The map item "ab" was entirely replaced, so
// this does not contain 1:1 from dst.
"map_int32_int32": map[int]int{2: 2, 3: 2},
},
},
},
"map_string_nested_enum": map[string]int{"a": 1, "b": 2, "ab": 2},
},
types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
}, {
desc: "merge oneof message fields",
dst: protobuild.Message{
"oneof_nested_message": protobuild.Message{
"a": 100,
},
},
src: protobuild.Message{
"oneof_nested_message": protobuild.Message{
"corecursive": protobuild.Message{
"optional_int64": 1000,
},
},
},
want: protobuild.Message{
"oneof_nested_message": protobuild.Message{
"a": 100,
"corecursive": protobuild.Message{
"optional_int64": 1000,
},
},
},
types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
}, {
desc: "merge oneof scalar fields",
dst: protobuild.Message{
"oneof_uint32": 100,
},
src: protobuild.Message{
"oneof_float": 3.14152,
},
want: protobuild.Message{
"oneof_float": 3.14152,
},
types: []proto.Message{&testpb.TestAllTypes{}, &test3pb.TestAllTypes{}},
}, {
desc: "merge unknown fields",
dst: protobuild.Message{
protobuild.Unknown: protopack.Message{
protopack.Tag{Number: 50000, Type: protopack.VarintType}, protopack.Svarint(-5),
}.Marshal(),
},
src: protobuild.Message{
protobuild.Unknown: protopack.Message{
protopack.Tag{Number: 500000, Type: protopack.VarintType}, protopack.Svarint(-50),
}.Marshal(),
},
want: protobuild.Message{
protobuild.Unknown: protopack.Message{
protopack.Tag{Number: 50000, Type: protopack.VarintType}, protopack.Svarint(-5),
protopack.Tag{Number: 500000, Type: protopack.VarintType}, protopack.Svarint(-50),
}.Marshal(),
},
}, {
desc: "clone legacy message",
src: protobuild.Message{"f1": protobuild.Message{
"optional_int32": 1,
"optional_int64": 1,
"optional_uint32": 1,
"optional_uint64": 1,
"optional_sint32": 1,
"optional_sint64": 1,
"optional_fixed32": 1,
"optional_fixed64": 1,
"optional_sfixed32": 1,
"optional_sfixed64": 1,
"optional_float": 1,
"optional_double": 1,
"optional_bool": true,
"optional_string": "string",
"optional_bytes": "bytes",
"optional_sibling_enum": 1,
"optional_sibling_message": protobuild.Message{
"f1": "value",
},
"repeated_int32": []int32{1},
"repeated_int64": []int64{1},
"repeated_uint32": []uint32{1},
"repeated_uint64": []uint64{1},
"repeated_sint32": []int32{1},
"repeated_sint64": []int64{1},
"repeated_fixed32": []uint32{1},
"repeated_fixed64": []uint64{1},
"repeated_sfixed32": []int32{1},
"repeated_sfixed64": []int64{1},
"repeated_float": []float32{1},
"repeated_double": []float64{1},
"repeated_bool": []bool{true},
"repeated_string": []string{"string"},
"repeated_bytes": []string{"bytes"},
"repeated_sibling_enum": []int{1},
"repeated_sibling_message": []protobuild.Message{
{"f1": "1"},
},
"map_bool_int32": map[bool]int{true: 1},
"map_bool_int64": map[bool]int{true: 1},
"map_bool_uint32": map[bool]int{true: 1},
"map_bool_uint64": map[bool]int{true: 1},
"map_bool_sint32": map[bool]int{true: 1},
"map_bool_sint64": map[bool]int{true: 1},
"map_bool_fixed32": map[bool]int{true: 1},
"map_bool_fixed64": map[bool]int{true: 1},
"map_bool_sfixed32": map[bool]int{true: 1},
"map_bool_sfixed64": map[bool]int{true: 1},
"map_bool_float": map[bool]int{true: 1},
"map_bool_double": map[bool]int{true: 1},
"map_bool_bool": map[bool]bool{true: false},
"map_bool_string": map[bool]string{true: "1"},
"map_bool_bytes": map[bool]string{true: "1"},
"map_bool_sibling_message": map[bool]protobuild.Message{
true: {"f1": "1"},
},
"map_bool_sibling_enum": map[bool]int{true: 1},
"oneof_sibling_message": protobuild.Message{
"f1": "1",
},
}},
types: []proto.Message{&legacypb.Legacy{}},
}}
func TestMerge(t *testing.T) {
for _, tt := range testMerges {
for _, mt := range templateMessages(tt.types...) {
t.Run(fmt.Sprintf("%s (%v)", tt.desc, mt.Descriptor().FullName()), func(t *testing.T) {
dst := mt.New().Interface()
tt.dst.Build(dst.ProtoReflect())
src := mt.New().Interface()
tt.src.Build(src.ProtoReflect())
want := mt.New().Interface()
if tt.dst == nil && tt.want == nil {
tt.src.Build(want.ProtoReflect())
} else {
tt.want.Build(want.ProtoReflect())
}
// Merge should be semantically equivalent to unmarshaling the
// encoded form of src into the current dst.
b1, err := proto.MarshalOptions{AllowPartial: true}.Marshal(dst)
if err != nil {
t.Fatalf("Marshal(dst) error: %v", err)
}
b2, err := proto.MarshalOptions{AllowPartial: true}.Marshal(src)
if err != nil {
t.Fatalf("Marshal(src) error: %v", err)
}
unmarshaled := dst.ProtoReflect().New().Interface()
err = proto.UnmarshalOptions{AllowPartial: true}.Unmarshal(append(b1, b2...), unmarshaled)
if err != nil {
t.Fatalf("Unmarshal() error: %v", err)
}
if !proto.Equal(unmarshaled, want) {
t.Fatalf("Unmarshal(Marshal(dst)+Marshal(src)) mismatch:\n got %v\nwant %v\ndiff (-want,+got):\n%v", unmarshaled, want, cmp.Diff(want, unmarshaled, protocmp.Transform()))
}
// Test heterogeneous MessageTypes by merging into a
// dynamic message.
ddst := dynamicpb.NewMessage(mt.Descriptor())
tt.dst.Build(ddst.ProtoReflect())
proto.Merge(ddst, src)
if !proto.Equal(ddst, want) {
t.Fatalf("Merge() into dynamic message mismatch:\n got %v\nwant %v\ndiff (-want,+got):\n%v", ddst, want, cmp.Diff(want, ddst, protocmp.Transform()))
}
proto.Merge(dst, src)
if !proto.Equal(dst, want) {
t.Fatalf("Merge() mismatch:\n got %v\nwant %v\ndiff (-want,+got):\n%v", dst, want, cmp.Diff(want, dst, protocmp.Transform()))
}
mutateValue(protoreflect.ValueOfMessage(src.ProtoReflect()))
if !proto.Equal(dst, want) {
t.Fatalf("mutation observed after modifying source:\n got %v\nwant %v\ndiff (-want,+got):\n%v", dst, want, cmp.Diff(want, dst, protocmp.Transform()))
}
})
}
}
}
func TestMergeFromNil(t *testing.T) {
dst := &testpb.TestAllTypes{}
proto.Merge(dst, (*testpb.TestAllTypes)(nil))
if !proto.Equal(dst, &testpb.TestAllTypes{}) {
t.Errorf("destination should be empty after merging from nil message; got:\n%v", prototext.Format(dst))
}
}
// TestMergeAberrant tests inputs that are beyond the protobuf data model.
// Just because there is a test for the current behavior does not mean that
// this will behave the same way in the future.
func TestMergeAberrant(t *testing.T) {
tests := []struct {
label string
dst proto.Message
src proto.Message
check func(proto.Message) bool
}{{
label: "Proto2EmptyBytes",
dst: &testpb.TestAllTypes{OptionalBytes: nil},
src: &testpb.TestAllTypes{OptionalBytes: []byte{}},
check: func(m proto.Message) bool {
return m.(*testpb.TestAllTypes).OptionalBytes != nil
},
}, {
label: "Proto3EmptyBytes",
dst: &test3pb.TestAllTypes{SingularBytes: nil},
src: &test3pb.TestAllTypes{SingularBytes: []byte{}},
check: func(m proto.Message) bool {
return m.(*test3pb.TestAllTypes).SingularBytes == nil
},
}, {
label: "EmptyList",
dst: &testpb.TestAllTypes{RepeatedInt32: nil},
src: &testpb.TestAllTypes{RepeatedInt32: []int32{}},
check: func(m proto.Message) bool {
return m.(*testpb.TestAllTypes).RepeatedInt32 == nil
},
}, {
label: "ListWithNilBytes",
dst: &testpb.TestAllTypes{RepeatedBytes: nil},
src: &testpb.TestAllTypes{RepeatedBytes: [][]byte{nil}},
check: func(m proto.Message) bool {
return reflect.DeepEqual(m.(*testpb.TestAllTypes).RepeatedBytes, [][]byte{{}})
},
}, {
label: "ListWithEmptyBytes",
dst: &testpb.TestAllTypes{RepeatedBytes: nil},
src: &testpb.TestAllTypes{RepeatedBytes: [][]byte{{}}},
check: func(m proto.Message) bool {
return reflect.DeepEqual(m.(*testpb.TestAllTypes).RepeatedBytes, [][]byte{{}})
},
}, {
label: "ListWithNilMessage",
dst: &testpb.TestAllTypes{RepeatedNestedMessage: nil},
src: &testpb.TestAllTypes{RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{nil}},
check: func(m proto.Message) bool {
return m.(*testpb.TestAllTypes).RepeatedNestedMessage[0] != nil
},
}, {
label: "EmptyMap",
dst: &testpb.TestAllTypes{MapStringString: nil},
src: &testpb.TestAllTypes{MapStringString: map[string]string{}},
check: func(m proto.Message) bool {
return m.(*testpb.TestAllTypes).MapStringString == nil
},
}, {
label: "MapWithNilBytes",
dst: &testpb.TestAllTypes{MapStringBytes: nil},
src: &testpb.TestAllTypes{MapStringBytes: map[string][]byte{"k": nil}},
check: func(m proto.Message) bool {
return reflect.DeepEqual(m.(*testpb.TestAllTypes).MapStringBytes, map[string][]byte{"k": {}})
},
}, {
label: "MapWithEmptyBytes",
dst: &testpb.TestAllTypes{MapStringBytes: nil},
src: &testpb.TestAllTypes{MapStringBytes: map[string][]byte{"k": {}}},
check: func(m proto.Message) bool {
return reflect.DeepEqual(m.(*testpb.TestAllTypes).MapStringBytes, map[string][]byte{"k": {}})
},
}, {
label: "MapWithNilMessage",
dst: &testpb.TestAllTypes{MapStringNestedMessage: nil},
src: &testpb.TestAllTypes{MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{"k": nil}},
check: func(m proto.Message) bool {
return m.(*testpb.TestAllTypes).MapStringNestedMessage["k"] != nil
},
}, {
label: "OneofWithTypedNilWrapper",
dst: &testpb.TestAllTypes{OneofField: nil},
src: &testpb.TestAllTypes{OneofField: (*testpb.TestAllTypes_OneofNestedMessage)(nil)},
check: func(m proto.Message) bool {
return m.(*testpb.TestAllTypes).OneofField == nil
},
}, {
label: "OneofWithNilMessage",
dst: &testpb.TestAllTypes{OneofField: nil},
src: &testpb.TestAllTypes{OneofField: &testpb.TestAllTypes_OneofNestedMessage{OneofNestedMessage: nil}},
check: func(m proto.Message) bool {
return m.(*testpb.TestAllTypes).OneofField.(*testpb.TestAllTypes_OneofNestedMessage).OneofNestedMessage != nil
},
// TODO: extension, nil message
// TODO: repeated extension, nil
// TODO: extension bytes
// TODO: repeated extension, nil message
}}
for _, tt := range tests {
t.Run(tt.label, func(t *testing.T) {
var pass bool
func() {
defer func() { recover() }()
proto.Merge(tt.dst, tt.src)
pass = tt.check(tt.dst)
}()
if !pass {
t.Error("check failed")
}
})
}
}
func TestMergeRace(t *testing.T) {
dst := new(testpb.TestAllTypes)
srcs := []*testpb.TestAllTypes{
{OptionalInt32: proto.Int32(1)},
{OptionalString: proto.String("hello")},
{RepeatedInt32: []int32{2, 3, 4}},
{RepeatedString: []string{"goodbye"}},
{MapStringString: map[string]string{"key": "value"}},
{OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{
A: proto.Int32(5),
}},
func() *testpb.TestAllTypes {
m := new(testpb.TestAllTypes)
m.ProtoReflect().SetUnknown(protopack.Message{
protopack.Tag{Number: 50000, Type: protopack.VarintType}, protopack.Svarint(-5),
}.Marshal())
return m
}(),
}
// It should be safe to concurrently merge non-overlapping fields.
var wg sync.WaitGroup
defer wg.Wait()
for _, src := range srcs {
wg.Add(1)
go func(src proto.Message) {
defer wg.Done()
proto.Merge(dst, src)
}(src)
}
}
func TestMergeSelf(t *testing.T) {
got := &testpb.TestAllTypes{
OptionalInt32: proto.Int32(1),
OptionalString: proto.String("hello"),
RepeatedInt32: []int32{2, 3, 4},
RepeatedString: []string{"goodbye"},
MapStringString: map[string]string{"key": "value"},
OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{
A: proto.Int32(5),
},
}
got.ProtoReflect().SetUnknown(protopack.Message{
protopack.Tag{Number: 50000, Type: protopack.VarintType}, protopack.Svarint(-5),
}.Marshal())
proto.Merge(got, got)
// The main impact of merging to self is that repeated fields and
// unknown fields are doubled.
want := &testpb.TestAllTypes{
OptionalInt32: proto.Int32(1),
OptionalString: proto.String("hello"),
RepeatedInt32: []int32{2, 3, 4, 2, 3, 4},
RepeatedString: []string{"goodbye", "goodbye"},
MapStringString: map[string]string{"key": "value"},
OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{
A: proto.Int32(5),
},
}
want.ProtoReflect().SetUnknown(protopack.Message{
protopack.Tag{Number: 50000, Type: protopack.VarintType}, protopack.Svarint(-5),
protopack.Tag{Number: 50000, Type: protopack.VarintType}, protopack.Svarint(-5),
}.Marshal())
if !proto.Equal(got, want) {
t.Errorf("Equal mismatch:\ngot %v\nwant %v", got, want)
}
}
func TestClone(t *testing.T) {
want := &testpb.TestAllTypes{
OptionalInt32: proto.Int32(1),
}
got := proto.Clone(want).(*testpb.TestAllTypes)
if !proto.Equal(got, want) {
t.Errorf("Clone(src) != src:\n got %v\nwant %v", got, want)
}
}
// mutateValue changes a Value, returning a new value.
//
// For scalar values, it returns a value different from the input.
// For Message, List, and Map values, it mutates the input and returns it.
func mutateValue(v protoreflect.Value) protoreflect.Value {
switch v := v.Interface().(type) {
case bool:
return protoreflect.ValueOfBool(!v)
case protoreflect.EnumNumber:
return protoreflect.ValueOfEnum(v + 1)
case int32:
return protoreflect.ValueOfInt32(v + 1)
case int64:
return protoreflect.ValueOfInt64(v + 1)
case uint32:
return protoreflect.ValueOfUint32(v + 1)
case uint64:
return protoreflect.ValueOfUint64(v + 1)
case float32:
return protoreflect.ValueOfFloat32(v + 1)
case float64:
return protoreflect.ValueOfFloat64(v + 1)
case []byte:
for i := range v {
v[i]++
}
return protoreflect.ValueOfBytes(v)
case string:
return protoreflect.ValueOfString("_" + v)
case protoreflect.Message:
v.Range(func(fd protoreflect.FieldDescriptor, val protoreflect.Value) bool {
v.Set(fd, mutateValue(val))
return true
})
return protoreflect.ValueOfMessage(v)
case protoreflect.List:
for i := 0; i < v.Len(); i++ {
v.Set(i, mutateValue(v.Get(i)))
}
return protoreflect.ValueOfList(v)
case protoreflect.Map:
v.Range(func(mk protoreflect.MapKey, mv protoreflect.Value) bool {
v.Set(mk, mutateValue(mv))
return true
})
return protoreflect.ValueOfMap(v)
default:
panic(fmt.Sprintf("unknown value type %T", v))
}
}