From c7aa53a3e0e697061ca81cc4289ea0dc85be11f1 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Wed, 12 Feb 2020 10:09:24 -0800 Subject: [PATCH] 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 --- internal/protobuild/build.go | 2 + proto/merge_test.go | 1053 ++++++++++++++++++++++------------ proto/testmessages_test.go | 16 + proto/weak_test.go | 32 ++ 4 files changed, 747 insertions(+), 356 deletions(-) diff --git a/internal/protobuild/build.go b/internal/protobuild/build.go index 645f0c1a..8790d5f1 100644 --- a/internal/protobuild/build.go +++ b/internal/protobuild/build.go @@ -144,6 +144,8 @@ func fieldValue(fd pref.FieldDescriptor, v interface{}) pref.Value { 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 01b998d4..48d8a41d 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 @@ func TestMergeSelf(t *testing.T) { 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 ca3ce3c4..cfa3cd7c 100644 --- a/proto/testmessages_test.go +++ b/proto/testmessages_test.go @@ -45,6 +45,22 @@ func makeMessages(in protobuild.Message, messages ...proto.Message) []proto.Mess 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 15a18fb2..0b582ba7 100644 --- a/proto/weak_test.go +++ b/proto/weak_test.go @@ -9,6 +9,7 @@ import ( "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 @@ func init() { if flags.ProtoLegacy { testValidMessages = append(testValidMessages, testWeakValidMessages...) testInvalidMessages = append(testInvalidMessages, testWeakInvalidMessages...) + testMerges = append(testMerges, testWeakMerges...) } } @@ -65,6 +67,36 @@ var testWeakInvalidMessages = []testProto{ }, } +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()