1// Go support for Protocol Buffers - Google's data interchange format 2// 3// Copyright 2016 The Go Authors. All rights reserved. 4// https://github.com/golang/protobuf 5// 6// Redistribution and use in source and binary forms, with or without 7// modification, are permitted provided that the following conditions are 8// met: 9// 10// * Redistributions of source code must retain the above copyright 11// notice, this list of conditions and the following disclaimer. 12// * Redistributions in binary form must reproduce the above 13// copyright notice, this list of conditions and the following disclaimer 14// in the documentation and/or other materials provided with the 15// distribution. 16// * Neither the name of Google Inc. nor the names of its 17// contributors may be used to endorse or promote products derived from 18// this software without specific prior written permission. 19// 20// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 32package proto_test 33 34import ( 35 "strings" 36 "testing" 37 38 "github.com/golang/protobuf/proto" 39 40 pb "github.com/golang/protobuf/proto/proto3_proto" 41 testpb "github.com/golang/protobuf/proto/test_proto" 42 anypb "github.com/golang/protobuf/ptypes/any" 43) 44 45var ( 46 expandedMarshaler = proto.TextMarshaler{ExpandAny: true} 47 expandedCompactMarshaler = proto.TextMarshaler{Compact: true, ExpandAny: true} 48) 49 50// anyEqual reports whether two messages which may be google.protobuf.Any or may 51// contain google.protobuf.Any fields are equal. We can't use proto.Equal for 52// comparison, because semantically equivalent messages may be marshaled to 53// binary in different tag order. Instead, trust that TextMarshaler with 54// ExpandAny option works and compare the text marshaling results. 55func anyEqual(got, want proto.Message) bool { 56 // if messages are proto.Equal, no need to marshal. 57 if proto.Equal(got, want) { 58 return true 59 } 60 g := expandedMarshaler.Text(got) 61 w := expandedMarshaler.Text(want) 62 return g == w 63} 64 65type golden struct { 66 m proto.Message 67 t, c string 68} 69 70var goldenMessages = makeGolden() 71 72func makeGolden() []golden { 73 nested := &pb.Nested{Bunny: "Monty"} 74 nb, err := proto.Marshal(nested) 75 if err != nil { 76 panic(err) 77 } 78 m1 := &pb.Message{ 79 Name: "David", 80 ResultCount: 47, 81 Anything: &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(nested), Value: nb}, 82 } 83 m2 := &pb.Message{ 84 Name: "David", 85 ResultCount: 47, 86 Anything: &anypb.Any{TypeUrl: "http://[::1]/type.googleapis.com/" + proto.MessageName(nested), Value: nb}, 87 } 88 m3 := &pb.Message{ 89 Name: "David", 90 ResultCount: 47, 91 Anything: &anypb.Any{TypeUrl: `type.googleapis.com/"/` + proto.MessageName(nested), Value: nb}, 92 } 93 m4 := &pb.Message{ 94 Name: "David", 95 ResultCount: 47, 96 Anything: &anypb.Any{TypeUrl: "type.googleapis.com/a/path/" + proto.MessageName(nested), Value: nb}, 97 } 98 m5 := &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(nested), Value: nb} 99 100 any1 := &testpb.MyMessage{Count: proto.Int32(47), Name: proto.String("David")} 101 proto.SetExtension(any1, testpb.E_Ext_More, &testpb.Ext{Data: proto.String("foo")}) 102 proto.SetExtension(any1, testpb.E_Ext_Text, proto.String("bar")) 103 any1b, err := proto.Marshal(any1) 104 if err != nil { 105 panic(err) 106 } 107 any2 := &testpb.MyMessage{Count: proto.Int32(42), Bikeshed: testpb.MyMessage_GREEN.Enum(), RepBytes: [][]byte{[]byte("roboto")}} 108 proto.SetExtension(any2, testpb.E_Ext_More, &testpb.Ext{Data: proto.String("baz")}) 109 any2b, err := proto.Marshal(any2) 110 if err != nil { 111 panic(err) 112 } 113 m6 := &pb.Message{ 114 Name: "David", 115 ResultCount: 47, 116 Anything: &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any1), Value: any1b}, 117 ManyThings: []*anypb.Any{ 118 &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any2), Value: any2b}, 119 &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any1), Value: any1b}, 120 }, 121 } 122 123 const ( 124 m1Golden = ` 125name: "David" 126result_count: 47 127anything: < 128 [type.googleapis.com/proto3_proto.Nested]: < 129 bunny: "Monty" 130 > 131> 132` 133 m2Golden = ` 134name: "David" 135result_count: 47 136anything: < 137 ["http://[::1]/type.googleapis.com/proto3_proto.Nested"]: < 138 bunny: "Monty" 139 > 140> 141` 142 m3Golden = ` 143name: "David" 144result_count: 47 145anything: < 146 ["type.googleapis.com/\"/proto3_proto.Nested"]: < 147 bunny: "Monty" 148 > 149> 150` 151 m4Golden = ` 152name: "David" 153result_count: 47 154anything: < 155 [type.googleapis.com/a/path/proto3_proto.Nested]: < 156 bunny: "Monty" 157 > 158> 159` 160 m5Golden = ` 161[type.googleapis.com/proto3_proto.Nested]: < 162 bunny: "Monty" 163> 164` 165 m6Golden = ` 166name: "David" 167result_count: 47 168anything: < 169 [type.googleapis.com/test_proto.MyMessage]: < 170 count: 47 171 name: "David" 172 [test_proto.Ext.more]: < 173 data: "foo" 174 > 175 [test_proto.Ext.text]: "bar" 176 > 177> 178many_things: < 179 [type.googleapis.com/test_proto.MyMessage]: < 180 count: 42 181 bikeshed: GREEN 182 rep_bytes: "roboto" 183 [test_proto.Ext.more]: < 184 data: "baz" 185 > 186 > 187> 188many_things: < 189 [type.googleapis.com/test_proto.MyMessage]: < 190 count: 47 191 name: "David" 192 [test_proto.Ext.more]: < 193 data: "foo" 194 > 195 [test_proto.Ext.text]: "bar" 196 > 197> 198` 199 ) 200 return []golden{ 201 {m1, strings.TrimSpace(m1Golden) + "\n", strings.TrimSpace(compact(m1Golden)) + " "}, 202 {m2, strings.TrimSpace(m2Golden) + "\n", strings.TrimSpace(compact(m2Golden)) + " "}, 203 {m3, strings.TrimSpace(m3Golden) + "\n", strings.TrimSpace(compact(m3Golden)) + " "}, 204 {m4, strings.TrimSpace(m4Golden) + "\n", strings.TrimSpace(compact(m4Golden)) + " "}, 205 {m5, strings.TrimSpace(m5Golden) + "\n", strings.TrimSpace(compact(m5Golden)) + " "}, 206 {m6, strings.TrimSpace(m6Golden) + "\n", strings.TrimSpace(compact(m6Golden)) + " "}, 207 } 208} 209 210func TestMarshalGolden(t *testing.T) { 211 for _, tt := range goldenMessages { 212 if got, want := expandedMarshaler.Text(tt.m), tt.t; got != want { 213 t.Errorf("message %v: got:\n%s\nwant:\n%s", tt.m, got, want) 214 } 215 if got, want := expandedCompactMarshaler.Text(tt.m), tt.c; got != want { 216 t.Errorf("message %v: got:\n`%s`\nwant:\n`%s`", tt.m, got, want) 217 } 218 } 219} 220 221func TestUnmarshalGolden(t *testing.T) { 222 for _, tt := range goldenMessages { 223 want := tt.m 224 got := proto.Clone(tt.m) 225 got.Reset() 226 if err := proto.UnmarshalText(tt.t, got); err != nil { 227 t.Errorf("failed to unmarshal\n%s\nerror: %v", tt.t, err) 228 } 229 if !anyEqual(got, want) { 230 t.Errorf("message:\n%s\ngot:\n%s\nwant:\n%s", tt.t, got, want) 231 } 232 got.Reset() 233 if err := proto.UnmarshalText(tt.c, got); err != nil { 234 t.Errorf("failed to unmarshal\n%s\nerror: %v", tt.c, err) 235 } 236 if !anyEqual(got, want) { 237 t.Errorf("message:\n%s\ngot:\n%s\nwant:\n%s", tt.c, got, want) 238 } 239 } 240} 241 242func TestMarshalUnknownAny(t *testing.T) { 243 m := &pb.Message{ 244 Anything: &anypb.Any{ 245 TypeUrl: "foo", 246 Value: []byte("bar"), 247 }, 248 } 249 want := `anything: < 250 type_url: "foo" 251 value: "bar" 252> 253` 254 got := expandedMarshaler.Text(m) 255 if got != want { 256 t.Errorf("got\n`%s`\nwant\n`%s`", got, want) 257 } 258} 259 260func TestAmbiguousAny(t *testing.T) { 261 pb := &anypb.Any{} 262 err := proto.UnmarshalText(` 263 type_url: "ttt/proto3_proto.Nested" 264 value: "\n\x05Monty" 265 `, pb) 266 t.Logf("result: %v (error: %v)", expandedMarshaler.Text(pb), err) 267 if err != nil { 268 t.Errorf("failed to parse ambiguous Any message: %v", err) 269 } 270} 271 272func TestUnmarshalOverwriteAny(t *testing.T) { 273 pb := &anypb.Any{} 274 err := proto.UnmarshalText(` 275 [type.googleapis.com/a/path/proto3_proto.Nested]: < 276 bunny: "Monty" 277 > 278 [type.googleapis.com/a/path/proto3_proto.Nested]: < 279 bunny: "Rabbit of Caerbannog" 280 > 281 `, pb) 282 want := `line 7: Any message unpacked multiple times, or "type_url" already set` 283 if err.Error() != want { 284 t.Errorf("incorrect error.\nHave: %v\nWant: %v", err.Error(), want) 285 } 286} 287 288func TestUnmarshalAnyMixAndMatch(t *testing.T) { 289 pb := &anypb.Any{} 290 err := proto.UnmarshalText(` 291 value: "\n\x05Monty" 292 [type.googleapis.com/a/path/proto3_proto.Nested]: < 293 bunny: "Rabbit of Caerbannog" 294 > 295 `, pb) 296 want := `line 5: Any message unpacked multiple times, or "value" already set` 297 if err.Error() != want { 298 t.Errorf("incorrect error.\nHave: %v\nWant: %v", err.Error(), want) 299 } 300} 301