proto: refactor merge tests
Use protobuild to generate test messages, both to simplify generating
proto2/proto3/extension variants of each test where appropriate and
to make it easier to test alternative message generators in the future.
Add various additional merge tests.
Change-Id: I4ba3ce232304e1d8325543680e2b6aae61de7364
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/219146
Reviewed-by: Joe Tsai <joetsai@google.com>
diff --git a/internal/protobuild/build.go b/internal/protobuild/build.go
index 645f0c1..8790d5f 100644
--- a/internal/protobuild/build.go
+++ b/internal/protobuild/build.go
@@ -144,6 +144,8 @@
case pref.EnumKind:
v = fd.Enum().Values().ByName(pref.Name(o)).Number()
}
+ case []byte:
+ return pref.ValueOf(append([]byte{}, o...))
}
return pref.ValueOf(v)
}
diff --git a/proto/merge_test.go b/proto/merge_test.go
index 01b998d..48d8a41 100644
--- a/proto/merge_test.go
+++ b/proto/merge_test.go
@@ -5,377 +5,657 @@
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/encoding/pack"
+ "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/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: pack.Message{
+ pack.Tag{Number: 50000, Type: pack.VarintType}, pack.Svarint(-5),
+ }.Marshal(),
+ },
+ src: protobuild.Message{
+ protobuild.Unknown: pack.Message{
+ pack.Tag{Number: 500000, Type: pack.VarintType}, pack.Svarint(-50),
+ }.Marshal(),
+ },
+ want: protobuild.Message{
+ protobuild.Unknown: pack.Message{
+ pack.Tag{Number: 50000, Type: pack.VarintType}, pack.Svarint(-5),
+ pack.Tag{Number: 500000, Type: pack.VarintType}, pack.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) {
- tests := []struct {
- desc string
- dst proto.Message
- src proto.Message
- want proto.Message
+ 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())
- // If provided, mutator is run on src after merging.
- // It reports whether a mutation is expected to be observable in dst
- // if Shallow is enabled.
- mutator func(proto.Message) bool
- }{{
- desc: "merge from nil message",
- dst: new(testpb.TestAllTypes),
- src: (*testpb.TestAllTypes)(nil),
- want: new(testpb.TestAllTypes),
- }, {
- desc: "clone a large message",
- dst: new(testpb.TestAllTypes),
- src: &testpb.TestAllTypes{
- OptionalInt64: proto.Int64(0),
- OptionalNestedEnum: testpb.TestAllTypes_NestedEnum(1).Enum(),
- OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{
- A: proto.Int32(100),
- },
- RepeatedSfixed32: []int32{1, 2, 3},
- RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{
- {A: proto.Int32(200)},
- {A: proto.Int32(300)},
- },
- MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{
- "fizz": 400,
- "buzz": 500,
- },
- MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{
- "foo": {A: proto.Int32(600)},
- "bar": {A: proto.Int32(700)},
- },
- OneofField: &testpb.TestAllTypes_OneofNestedMessage{
- &testpb.TestAllTypes_NestedMessage{
- A: proto.Int32(800),
- },
- },
- },
- want: &testpb.TestAllTypes{
- OptionalInt64: proto.Int64(0),
- OptionalNestedEnum: testpb.TestAllTypes_NestedEnum(1).Enum(),
- OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{
- A: proto.Int32(100),
- },
- RepeatedSfixed32: []int32{1, 2, 3},
- RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{
- {A: proto.Int32(200)},
- {A: proto.Int32(300)},
- },
- MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{
- "fizz": 400,
- "buzz": 500,
- },
- MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{
- "foo": {A: proto.Int32(600)},
- "bar": {A: proto.Int32(700)},
- },
- OneofField: &testpb.TestAllTypes_OneofNestedMessage{
- &testpb.TestAllTypes_NestedMessage{
- A: proto.Int32(800),
- },
- },
- },
- mutator: func(mi proto.Message) bool {
- m := mi.(*testpb.TestAllTypes)
- *m.OptionalInt64++
- *m.OptionalNestedEnum++
- *m.OptionalNestedMessage.A++
- m.RepeatedSfixed32[0]++
- *m.RepeatedNestedMessage[0].A++
- delete(m.MapStringNestedEnum, "fizz")
- *m.MapStringNestedMessage["foo"].A++
- *m.OneofField.(*testpb.TestAllTypes_OneofNestedMessage).OneofNestedMessage.A++
- return true
- },
- }, {
- desc: "merge bytes",
- dst: &testpb.TestAllTypes{
- OptionalBytes: []byte{1, 2, 3},
- RepeatedBytes: [][]byte{{1, 2}, {3, 4}},
- MapStringBytes: map[string][]byte{"alpha": {1, 2, 3}},
- },
- src: &testpb.TestAllTypes{
- OptionalBytes: []byte{4, 5, 6},
- RepeatedBytes: [][]byte{{5, 6}, {7, 8}},
- MapStringBytes: map[string][]byte{"alpha": {4, 5, 6}, "bravo": {1, 2, 3}},
- },
- want: &testpb.TestAllTypes{
- OptionalBytes: []byte{4, 5, 6},
- RepeatedBytes: [][]byte{{1, 2}, {3, 4}, {5, 6}, {7, 8}},
- MapStringBytes: map[string][]byte{"alpha": {4, 5, 6}, "bravo": {1, 2, 3}},
- },
- mutator: func(mi proto.Message) bool {
- m := mi.(*testpb.TestAllTypes)
- m.OptionalBytes[0]++
- m.RepeatedBytes[0][0]++
- m.MapStringBytes["alpha"][0]++
- return true
- },
- }, {
- desc: "merge singular fields",
- dst: &testpb.TestAllTypes{
- OptionalInt32: proto.Int32(1),
- OptionalInt64: proto.Int64(1),
- OptionalNestedEnum: testpb.TestAllTypes_NestedEnum(10).Enum(),
- OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{
- A: proto.Int32(100),
- Corecursive: &testpb.TestAllTypes{
- OptionalInt64: proto.Int64(1000),
- },
- },
- },
- src: &testpb.TestAllTypes{
- OptionalInt64: proto.Int64(2),
- OptionalNestedEnum: testpb.TestAllTypes_NestedEnum(20).Enum(),
- OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{
- A: proto.Int32(200),
- },
- },
- want: &testpb.TestAllTypes{
- OptionalInt32: proto.Int32(1),
- OptionalInt64: proto.Int64(2),
- OptionalNestedEnum: testpb.TestAllTypes_NestedEnum(20).Enum(),
- OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{
- A: proto.Int32(200),
- Corecursive: &testpb.TestAllTypes{
- OptionalInt64: proto.Int64(1000),
- },
- },
- },
- mutator: func(mi proto.Message) bool {
- m := mi.(*testpb.TestAllTypes)
- *m.OptionalInt64++
- *m.OptionalNestedEnum++
- *m.OptionalNestedMessage.A++
- return false // scalar mutations are not observable in shallow copy
- },
- }, {
- desc: "merge list fields",
- dst: &testpb.TestAllTypes{
- RepeatedSfixed32: []int32{1, 2, 3},
- RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{
- {A: proto.Int32(100)},
- {A: proto.Int32(200)},
- },
- },
- src: &testpb.TestAllTypes{
- RepeatedSfixed32: []int32{4, 5, 6},
- RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{
- {A: proto.Int32(300)},
- {A: proto.Int32(400)},
- },
- },
- want: &testpb.TestAllTypes{
- RepeatedSfixed32: []int32{1, 2, 3, 4, 5, 6},
- RepeatedNestedMessage: []*testpb.TestAllTypes_NestedMessage{
- {A: proto.Int32(100)},
- {A: proto.Int32(200)},
- {A: proto.Int32(300)},
- {A: proto.Int32(400)},
- },
- },
- mutator: func(mi proto.Message) bool {
- m := mi.(*testpb.TestAllTypes)
- m.RepeatedSfixed32[0]++
- *m.RepeatedNestedMessage[0].A++
- return true
- },
- }, {
- desc: "merge map fields",
- dst: &testpb.TestAllTypes{
- MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{
- "fizz": 100,
- "buzz": 200,
- "guzz": 300,
- },
- MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{
- "foo": {A: proto.Int32(400)},
- },
- },
- src: &testpb.TestAllTypes{
- MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{
- "fizz": 1000,
- "buzz": 2000,
- },
- MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{
- "foo": {A: proto.Int32(3000)},
- "bar": {},
- },
- },
- want: &testpb.TestAllTypes{
- MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{
- "fizz": 1000,
- "buzz": 2000,
- "guzz": 300,
- },
- MapStringNestedMessage: map[string]*testpb.TestAllTypes_NestedMessage{
- "foo": {A: proto.Int32(3000)},
- "bar": {},
- },
- },
- mutator: func(mi proto.Message) bool {
- m := mi.(*testpb.TestAllTypes)
- delete(m.MapStringNestedEnum, "fizz")
- m.MapStringNestedMessage["bar"].A = proto.Int32(1)
- return true
- },
- }, {
- desc: "merge oneof message fields",
- dst: &testpb.TestAllTypes{
- OneofField: &testpb.TestAllTypes_OneofNestedMessage{
- &testpb.TestAllTypes_NestedMessage{
- A: proto.Int32(100),
- },
- },
- },
- src: &testpb.TestAllTypes{
- OneofField: &testpb.TestAllTypes_OneofNestedMessage{
- &testpb.TestAllTypes_NestedMessage{
- Corecursive: &testpb.TestAllTypes{
- OptionalInt64: proto.Int64(1000),
- },
- },
- },
- },
- want: &testpb.TestAllTypes{
- OneofField: &testpb.TestAllTypes_OneofNestedMessage{
- &testpb.TestAllTypes_NestedMessage{
- A: proto.Int32(100),
- Corecursive: &testpb.TestAllTypes{
- OptionalInt64: proto.Int64(1000),
- },
- },
- },
- },
- mutator: func(mi proto.Message) bool {
- m := mi.(*testpb.TestAllTypes)
- *m.OneofField.(*testpb.TestAllTypes_OneofNestedMessage).OneofNestedMessage.Corecursive.OptionalInt64++
- return true
- },
- }, {
- desc: "merge oneof scalar fields",
- dst: &testpb.TestAllTypes{
- OneofField: &testpb.TestAllTypes_OneofUint32{100},
- },
- src: &testpb.TestAllTypes{
- OneofField: &testpb.TestAllTypes_OneofFloat{3.14152},
- },
- want: &testpb.TestAllTypes{
- OneofField: &testpb.TestAllTypes_OneofFloat{3.14152},
- },
- mutator: func(mi proto.Message) bool {
- m := mi.(*testpb.TestAllTypes)
- m.OneofField.(*testpb.TestAllTypes_OneofFloat).OneofFloat++
- return false // scalar mutations are not observable in shallow copy
- },
- }, {
- desc: "merge extension fields",
- dst: func() proto.Message {
- m := new(testpb.TestAllExtensions)
- proto.SetExtension(m, testpb.E_OptionalInt32, int32(32))
- proto.SetExtension(m, testpb.E_OptionalNestedMessage,
- &testpb.TestAllExtensions_NestedMessage{
- A: proto.Int32(50),
- },
- )
- proto.SetExtension(m, testpb.E_RepeatedFixed32, []uint32{1, 2, 3})
- return m
- }(),
- src: func() proto.Message {
- m2 := new(testpb.TestAllExtensions)
- proto.SetExtension(m2, testpb.E_OptionalInt64, int64(1000))
- m := new(testpb.TestAllExtensions)
- proto.SetExtension(m, testpb.E_OptionalInt64, int64(64))
- proto.SetExtension(m, testpb.E_OptionalNestedMessage,
- &testpb.TestAllExtensions_NestedMessage{
- Corecursive: m2,
- },
- )
- proto.SetExtension(m, testpb.E_RepeatedFixed32, []uint32{4, 5, 6})
- return m
- }(),
- want: func() proto.Message {
- m2 := new(testpb.TestAllExtensions)
- proto.SetExtension(m2, testpb.E_OptionalInt64, int64(1000))
- m := new(testpb.TestAllExtensions)
- proto.SetExtension(m, testpb.E_OptionalInt32, int32(32))
- proto.SetExtension(m, testpb.E_OptionalInt64, int64(64))
- proto.SetExtension(m, testpb.E_OptionalNestedMessage,
- &testpb.TestAllExtensions_NestedMessage{
- A: proto.Int32(50),
- Corecursive: m2,
- },
- )
- proto.SetExtension(m, testpb.E_RepeatedFixed32, []uint32{1, 2, 3, 4, 5, 6})
- return m
- }(),
- }, {
- desc: "merge unknown fields",
- dst: func() proto.Message {
- m := new(testpb.TestAllTypes)
- m.ProtoReflect().SetUnknown(pack.Message{
- pack.Tag{Number: 50000, Type: pack.VarintType}, pack.Svarint(-5),
- }.Marshal())
- return m
- }(),
- src: func() proto.Message {
- m := new(testpb.TestAllTypes)
- m.ProtoReflect().SetUnknown(pack.Message{
- pack.Tag{Number: 500000, Type: pack.VarintType}, pack.Svarint(-50),
- }.Marshal())
- return m
- }(),
- want: func() proto.Message {
- m := new(testpb.TestAllTypes)
- m.ProtoReflect().SetUnknown(pack.Message{
- pack.Tag{Number: 50000, Type: pack.VarintType}, pack.Svarint(-5),
- pack.Tag{Number: 500000, Type: pack.VarintType}, pack.Svarint(-50),
- }.Marshal())
- return m
- }(),
- }}
+ src := mt.New().Interface()
+ tt.src.Build(src.ProtoReflect())
- for _, tt := range tests {
- t.Run(tt.desc, func(t *testing.T) {
- // Merge should be semantically equivalent to unmarshaling the
- // encoded form of src into the current dst.
- b1, err := proto.MarshalOptions{AllowPartial: true}.Marshal(tt.dst)
- if err != nil {
- t.Fatalf("Marshal(dst) error: %v", err)
- }
- b2, err := proto.MarshalOptions{AllowPartial: true}.Marshal(tt.src)
- if err != nil {
- t.Fatalf("Marshal(src) error: %v", err)
- }
- dst := tt.dst.ProtoReflect().New().Interface()
- err = proto.UnmarshalOptions{AllowPartial: true}.Unmarshal(append(b1, b2...), dst)
- if err != nil {
- t.Fatalf("Unmarshal() error: %v", err)
- }
- if !proto.Equal(dst, tt.want) {
- t.Fatalf("Unmarshal(Marshal(dst)+Marshal(src)) mismatch: got %v, want %v", dst, tt.want)
- }
-
- proto.Merge(tt.dst, tt.src)
- if !proto.Equal(tt.dst, tt.want) {
- t.Fatalf("Merge() mismatch:\n got %v\nwant %v", tt.dst, tt.want)
- }
- if tt.mutator != nil {
- if !proto.Equal(tt.dst, tt.want) {
- t.Fatalf("mutation observed in dest after modifying merge source:\n got %v\nwant %v", tt.dst, tt.want)
+ 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))
}
}
@@ -562,3 +842,64 @@
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))
+ }
+}
diff --git a/proto/testmessages_test.go b/proto/testmessages_test.go
index ca3ce3c..cfa3cd7 100644
--- a/proto/testmessages_test.go
+++ b/proto/testmessages_test.go
@@ -45,6 +45,22 @@
return messages
}
+func templateMessages(messages ...proto.Message) []protoreflect.MessageType {
+ if len(messages) == 0 {
+ messages = []proto.Message{
+ (*testpb.TestAllTypes)(nil),
+ (*test3pb.TestAllTypes)(nil),
+ (*testpb.TestAllExtensions)(nil),
+ }
+ }
+ var out []protoreflect.MessageType
+ for _, m := range messages {
+ out = append(out, m.ProtoReflect().Type())
+ }
+ return out
+
+}
+
var testValidMessages = []testProto{
{
desc: "basic scalar types",
diff --git a/proto/weak_test.go b/proto/weak_test.go
index 15a18fb..0b582ba 100644
--- a/proto/weak_test.go
+++ b/proto/weak_test.go
@@ -9,6 +9,7 @@
"google.golang.org/protobuf/internal/encoding/pack"
"google.golang.org/protobuf/internal/flags"
+ "google.golang.org/protobuf/internal/protobuild"
"google.golang.org/protobuf/proto"
testpb "google.golang.org/protobuf/internal/testprotos/test"
@@ -19,6 +20,7 @@
if flags.ProtoLegacy {
testValidMessages = append(testValidMessages, testWeakValidMessages...)
testInvalidMessages = append(testInvalidMessages, testWeakInvalidMessages...)
+ testMerges = append(testMerges, testWeakMerges...)
}
}
@@ -65,6 +67,36 @@
},
}
+var testWeakMerges = []testMerge{
+ {
+ desc: "clone weak message",
+ src: protobuild.Message{
+ "weak_message1": protobuild.Message{
+ "a": 1,
+ },
+ },
+ types: []proto.Message{&testpb.TestWeak{}},
+ }, {
+ desc: "merge weak message",
+ dst: protobuild.Message{
+ "weak_message1": protobuild.Message{
+ "a": 1,
+ },
+ },
+ src: protobuild.Message{
+ "weak_message1": protobuild.Message{
+ "a": 2,
+ },
+ },
+ want: protobuild.Message{
+ "weak_message1": protobuild.Message{
+ "a": 2,
+ },
+ },
+ types: []proto.Message{&testpb.TestWeak{}},
+ },
+}
+
func TestWeakNil(t *testing.T) {
if !flags.ProtoLegacy {
t.SkipNow()