| // 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)) |
| } |
| } |