1// Go support for Protocol Buffers - Google's data interchange format 2// 3// Copyright 2010 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 "bytes" 36 "errors" 37 "io/ioutil" 38 "math" 39 "strings" 40 "sync" 41 "testing" 42 43 "github.com/golang/protobuf/proto" 44 45 proto3pb "github.com/golang/protobuf/proto/proto3_proto" 46 pb "github.com/golang/protobuf/proto/test_proto" 47 anypb "github.com/golang/protobuf/ptypes/any" 48) 49 50// textMessage implements the methods that allow it to marshal and unmarshal 51// itself as text. 52type textMessage struct { 53} 54 55func (*textMessage) MarshalText() ([]byte, error) { 56 return []byte("custom"), nil 57} 58 59func (*textMessage) UnmarshalText(bytes []byte) error { 60 if string(bytes) != "custom" { 61 return errors.New("expected 'custom'") 62 } 63 return nil 64} 65 66func (*textMessage) Reset() {} 67func (*textMessage) String() string { return "" } 68func (*textMessage) ProtoMessage() {} 69 70func newTestMessage() *pb.MyMessage { 71 msg := &pb.MyMessage{ 72 Count: proto.Int32(42), 73 Name: proto.String("Dave"), 74 Quote: proto.String(`"I didn't want to go."`), 75 Pet: []string{"bunny", "kitty", "horsey"}, 76 Inner: &pb.InnerMessage{ 77 Host: proto.String("footrest.syd"), 78 Port: proto.Int32(7001), 79 Connected: proto.Bool(true), 80 }, 81 Others: []*pb.OtherMessage{ 82 { 83 Key: proto.Int64(0xdeadbeef), 84 Value: []byte{1, 65, 7, 12}, 85 }, 86 { 87 Weight: proto.Float32(6.022), 88 Inner: &pb.InnerMessage{ 89 Host: proto.String("lesha.mtv"), 90 Port: proto.Int32(8002), 91 }, 92 }, 93 }, 94 Bikeshed: pb.MyMessage_BLUE.Enum(), 95 Somegroup: &pb.MyMessage_SomeGroup{ 96 GroupField: proto.Int32(8), 97 }, 98 // One normally wouldn't do this. 99 // This is an undeclared tag 13, as a varint (wire type 0) with value 4. 100 XXX_unrecognized: []byte{13<<3 | 0, 4}, 101 } 102 ext := &pb.Ext{ 103 Data: proto.String("Big gobs for big rats"), 104 } 105 if err := proto.SetExtension(msg, pb.E_Ext_More, ext); err != nil { 106 panic(err) 107 } 108 greetings := []string{"adg", "easy", "cow"} 109 if err := proto.SetExtension(msg, pb.E_Greeting, greetings); err != nil { 110 panic(err) 111 } 112 113 // Add an unknown extension. We marshal a pb.Ext, and fake the ID. 114 b, err := proto.Marshal(&pb.Ext{Data: proto.String("3G skiing")}) 115 if err != nil { 116 panic(err) 117 } 118 b = append(proto.EncodeVarint(201<<3|proto.WireBytes), b...) 119 proto.SetRawExtension(msg, 201, b) 120 121 // Extensions can be plain fields, too, so let's test that. 122 b = append(proto.EncodeVarint(202<<3|proto.WireVarint), 19) 123 proto.SetRawExtension(msg, 202, b) 124 125 return msg 126} 127 128const text = `count: 42 129name: "Dave" 130quote: "\"I didn't want to go.\"" 131pet: "bunny" 132pet: "kitty" 133pet: "horsey" 134inner: < 135 host: "footrest.syd" 136 port: 7001 137 connected: true 138> 139others: < 140 key: 3735928559 141 value: "\001A\007\014" 142> 143others: < 144 weight: 6.022 145 inner: < 146 host: "lesha.mtv" 147 port: 8002 148 > 149> 150bikeshed: BLUE 151SomeGroup { 152 group_field: 8 153} 154/* 2 unknown bytes */ 15513: 4 156[test_proto.Ext.more]: < 157 data: "Big gobs for big rats" 158> 159[test_proto.greeting]: "adg" 160[test_proto.greeting]: "easy" 161[test_proto.greeting]: "cow" 162/* 13 unknown bytes */ 163201: "\t3G skiing" 164/* 3 unknown bytes */ 165202: 19 166` 167 168func TestMarshalText(t *testing.T) { 169 buf := new(bytes.Buffer) 170 if err := proto.MarshalText(buf, newTestMessage()); err != nil { 171 t.Fatalf("proto.MarshalText: %v", err) 172 } 173 s := buf.String() 174 if s != text { 175 t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, text) 176 } 177} 178 179func TestMarshalTextCustomMessage(t *testing.T) { 180 buf := new(bytes.Buffer) 181 if err := proto.MarshalText(buf, &textMessage{}); err != nil { 182 t.Fatalf("proto.MarshalText: %v", err) 183 } 184 s := buf.String() 185 if s != "custom" { 186 t.Errorf("Got %q, expected %q", s, "custom") 187 } 188} 189func TestMarshalTextNil(t *testing.T) { 190 want := "<nil>" 191 tests := []proto.Message{nil, (*pb.MyMessage)(nil)} 192 for i, test := range tests { 193 buf := new(bytes.Buffer) 194 if err := proto.MarshalText(buf, test); err != nil { 195 t.Fatal(err) 196 } 197 if got := buf.String(); got != want { 198 t.Errorf("%d: got %q want %q", i, got, want) 199 } 200 } 201} 202 203func TestMarshalTextUnknownEnum(t *testing.T) { 204 // The Color enum only specifies values 0-2. 205 m := &pb.MyMessage{Bikeshed: pb.MyMessage_Color(3).Enum()} 206 got := m.String() 207 const want = `bikeshed:3 ` 208 if got != want { 209 t.Errorf("\n got %q\nwant %q", got, want) 210 } 211} 212 213func TestTextOneof(t *testing.T) { 214 tests := []struct { 215 m proto.Message 216 want string 217 }{ 218 // zero message 219 {&pb.Communique{}, ``}, 220 // scalar field 221 {&pb.Communique{Union: &pb.Communique_Number{4}}, `number:4`}, 222 // message field 223 {&pb.Communique{Union: &pb.Communique_Msg{ 224 &pb.Strings{StringField: proto.String("why hello!")}, 225 }}, `msg:<string_field:"why hello!" >`}, 226 // bad oneof (should not panic) 227 {&pb.Communique{Union: &pb.Communique_Msg{nil}}, `msg:/* nil */`}, 228 } 229 for _, test := range tests { 230 got := strings.TrimSpace(test.m.String()) 231 if got != test.want { 232 t.Errorf("\n got %s\nwant %s", got, test.want) 233 } 234 } 235} 236 237func BenchmarkMarshalTextBuffered(b *testing.B) { 238 buf := new(bytes.Buffer) 239 m := newTestMessage() 240 for i := 0; i < b.N; i++ { 241 buf.Reset() 242 proto.MarshalText(buf, m) 243 } 244} 245 246func BenchmarkMarshalTextUnbuffered(b *testing.B) { 247 w := ioutil.Discard 248 m := newTestMessage() 249 for i := 0; i < b.N; i++ { 250 proto.MarshalText(w, m) 251 } 252} 253 254func compact(src string) string { 255 // s/[ \n]+/ /g; s/ $//; 256 dst := make([]byte, len(src)) 257 space, comment := false, false 258 j := 0 259 for i := 0; i < len(src); i++ { 260 if strings.HasPrefix(src[i:], "/*") { 261 comment = true 262 i++ 263 continue 264 } 265 if comment && strings.HasPrefix(src[i:], "*/") { 266 comment = false 267 i++ 268 continue 269 } 270 if comment { 271 continue 272 } 273 c := src[i] 274 if c == ' ' || c == '\n' { 275 space = true 276 continue 277 } 278 if j > 0 && (dst[j-1] == ':' || dst[j-1] == '<' || dst[j-1] == '{') { 279 space = false 280 } 281 if c == '{' { 282 space = false 283 } 284 if space { 285 dst[j] = ' ' 286 j++ 287 space = false 288 } 289 dst[j] = c 290 j++ 291 } 292 if space { 293 dst[j] = ' ' 294 j++ 295 } 296 return string(dst[0:j]) 297} 298 299var compactText = compact(text) 300 301func TestCompactText(t *testing.T) { 302 s := proto.CompactTextString(newTestMessage()) 303 if s != compactText { 304 t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v\n===\n", s, compactText) 305 } 306} 307 308func TestStringEscaping(t *testing.T) { 309 testCases := []struct { 310 in *pb.Strings 311 out string 312 }{ 313 { 314 // Test data from C++ test (TextFormatTest.StringEscape). 315 // Single divergence: we don't escape apostrophes. 316 &pb.Strings{StringField: proto.String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces")}, 317 "string_field: \"\\\"A string with ' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"\n", 318 }, 319 { 320 // Test data from the same C++ test. 321 &pb.Strings{StringField: proto.String("\350\260\267\346\255\214")}, 322 "string_field: \"\\350\\260\\267\\346\\255\\214\"\n", 323 }, 324 { 325 // Some UTF-8. 326 &pb.Strings{StringField: proto.String("\x00\x01\xff\x81")}, 327 `string_field: "\000\001\377\201"` + "\n", 328 }, 329 } 330 331 for i, tc := range testCases { 332 var buf bytes.Buffer 333 if err := proto.MarshalText(&buf, tc.in); err != nil { 334 t.Errorf("proto.MarsalText: %v", err) 335 continue 336 } 337 s := buf.String() 338 if s != tc.out { 339 t.Errorf("#%d: Got:\n%s\nExpected:\n%s\n", i, s, tc.out) 340 continue 341 } 342 343 // Check round-trip. 344 pb := new(pb.Strings) 345 if err := proto.UnmarshalText(s, pb); err != nil { 346 t.Errorf("#%d: UnmarshalText: %v", i, err) 347 continue 348 } 349 if !proto.Equal(pb, tc.in) { 350 t.Errorf("#%d: Round-trip failed:\nstart: %v\n end: %v", i, tc.in, pb) 351 } 352 } 353} 354 355// A limitedWriter accepts some output before it fails. 356// This is a proxy for something like a nearly-full or imminently-failing disk, 357// or a network connection that is about to die. 358type limitedWriter struct { 359 b bytes.Buffer 360 limit int 361} 362 363var outOfSpace = errors.New("proto: insufficient space") 364 365func (w *limitedWriter) Write(p []byte) (n int, err error) { 366 var avail = w.limit - w.b.Len() 367 if avail <= 0 { 368 return 0, outOfSpace 369 } 370 if len(p) <= avail { 371 return w.b.Write(p) 372 } 373 n, _ = w.b.Write(p[:avail]) 374 return n, outOfSpace 375} 376 377func TestMarshalTextFailing(t *testing.T) { 378 // Try lots of different sizes to exercise more error code-paths. 379 for lim := 0; lim < len(text); lim++ { 380 buf := new(limitedWriter) 381 buf.limit = lim 382 err := proto.MarshalText(buf, newTestMessage()) 383 // We expect a certain error, but also some partial results in the buffer. 384 if err != outOfSpace { 385 t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", err, outOfSpace) 386 } 387 s := buf.b.String() 388 x := text[:buf.limit] 389 if s != x { 390 t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, x) 391 } 392 } 393} 394 395func TestFloats(t *testing.T) { 396 tests := []struct { 397 f float64 398 want string 399 }{ 400 {0, "0"}, 401 {4.7, "4.7"}, 402 {math.Inf(1), "inf"}, 403 {math.Inf(-1), "-inf"}, 404 {math.NaN(), "nan"}, 405 } 406 for _, test := range tests { 407 msg := &pb.FloatingPoint{F: &test.f} 408 got := strings.TrimSpace(msg.String()) 409 want := `f:` + test.want 410 if got != want { 411 t.Errorf("f=%f: got %q, want %q", test.f, got, want) 412 } 413 } 414} 415 416func TestRepeatedNilText(t *testing.T) { 417 m := &pb.MessageList{ 418 Message: []*pb.MessageList_Message{ 419 nil, 420 &pb.MessageList_Message{ 421 Name: proto.String("Horse"), 422 }, 423 nil, 424 }, 425 } 426 want := `Message <nil> 427Message { 428 name: "Horse" 429} 430Message <nil> 431` 432 if s := proto.MarshalTextString(m); s != want { 433 t.Errorf(" got: %s\nwant: %s", s, want) 434 } 435} 436 437func TestProto3Text(t *testing.T) { 438 tests := []struct { 439 m proto.Message 440 want string 441 }{ 442 // zero message 443 {&proto3pb.Message{}, ``}, 444 // zero message except for an empty byte slice 445 {&proto3pb.Message{Data: []byte{}}, ``}, 446 // trivial case 447 {&proto3pb.Message{Name: "Rob", HeightInCm: 175}, `name:"Rob" height_in_cm:175`}, 448 // empty map 449 {&pb.MessageWithMap{}, ``}, 450 // non-empty map; map format is the same as a repeated struct, 451 // and they are sorted by key (numerically for numeric keys). 452 { 453 &pb.MessageWithMap{NameMapping: map[int32]string{ 454 -1: "Negatory", 455 7: "Lucky", 456 1234: "Feist", 457 6345789: "Otis", 458 }}, 459 `name_mapping:<key:-1 value:"Negatory" > ` + 460 `name_mapping:<key:7 value:"Lucky" > ` + 461 `name_mapping:<key:1234 value:"Feist" > ` + 462 `name_mapping:<key:6345789 value:"Otis" >`, 463 }, 464 // map with nil value; not well-defined, but we shouldn't crash 465 { 466 &pb.MessageWithMap{MsgMapping: map[int64]*pb.FloatingPoint{7: nil}}, 467 `msg_mapping:<key:7 >`, 468 }, 469 } 470 for _, test := range tests { 471 got := strings.TrimSpace(test.m.String()) 472 if got != test.want { 473 t.Errorf("\n got %s\nwant %s", got, test.want) 474 } 475 } 476} 477 478func TestRacyMarshal(t *testing.T) { 479 // This test should be run with the race detector. 480 481 any := &pb.MyMessage{Count: proto.Int32(47), Name: proto.String("David")} 482 proto.SetExtension(any, pb.E_Ext_Text, proto.String("bar")) 483 b, err := proto.Marshal(any) 484 if err != nil { 485 panic(err) 486 } 487 m := &proto3pb.Message{ 488 Name: "David", 489 ResultCount: 47, 490 Anything: &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any), Value: b}, 491 } 492 493 wantText := proto.MarshalTextString(m) 494 wantBytes, err := proto.Marshal(m) 495 if err != nil { 496 t.Fatalf("proto.Marshal error: %v", err) 497 } 498 499 var wg sync.WaitGroup 500 defer wg.Wait() 501 wg.Add(20) 502 for i := 0; i < 10; i++ { 503 go func() { 504 defer wg.Done() 505 got := proto.MarshalTextString(m) 506 if got != wantText { 507 t.Errorf("proto.MarshalTextString = %q, want %q", got, wantText) 508 } 509 }() 510 go func() { 511 defer wg.Done() 512 got, err := proto.Marshal(m) 513 if !bytes.Equal(got, wantBytes) || err != nil { 514 t.Errorf("proto.Marshal = (%x, %v), want (%x, nil)", got, err, wantBytes) 515 } 516 }() 517 } 518} 519