1// Copyright 2016 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package proto_test 6 7import ( 8 "bytes" 9 "errors" 10 "math" 11 "strings" 12 "sync" 13 "testing" 14 15 "github.com/golang/protobuf/proto" 16 "github.com/google/go-cmp/cmp" 17 18 pb2 "github.com/golang/protobuf/internal/testprotos/proto2_proto" 19 pb3 "github.com/golang/protobuf/internal/testprotos/proto3_proto" 20 anypb "github.com/golang/protobuf/ptypes/any" 21) 22 23var ( 24 expandedMarshaler = proto.TextMarshaler{ExpandAny: true} 25 expandedCompactMarshaler = proto.TextMarshaler{Compact: true, ExpandAny: true} 26) 27 28// anyEqual reports whether two messages which may be google.protobuf.Any or may 29// contain google.protobuf.Any fields are equal. We can't use proto.Equal for 30// comparison, because semantically equivalent messages may be marshaled to 31// binary in different tag order. Instead, trust that TextMarshaler with 32// ExpandAny option works and compare the text marshaling results. 33func anyEqual(got, want proto.Message) bool { 34 // if messages are proto.Equal, no need to marshal. 35 if proto.Equal(got, want) { 36 return true 37 } 38 g := expandedMarshaler.Text(got) 39 w := expandedMarshaler.Text(want) 40 return g == w 41} 42 43type golden struct { 44 m proto.Message 45 t, c string 46} 47 48var goldenMessages = makeGolden() 49 50func makeGolden() []golden { 51 nested := &pb3.Nested{Bunny: "Monty"} 52 nb, err := proto.Marshal(nested) 53 if err != nil { 54 panic(err) 55 } 56 m1 := &pb3.Message{ 57 Name: "David", 58 ResultCount: 47, 59 Anything: &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(nested), Value: nb}, 60 } 61 m2 := &pb3.Message{ 62 Name: "David", 63 ResultCount: 47, 64 Anything: &anypb.Any{TypeUrl: "http://[::1]/type.googleapis.com/" + proto.MessageName(nested), Value: nb}, 65 } 66 m3 := &pb3.Message{ 67 Name: "David", 68 ResultCount: 47, 69 Anything: &anypb.Any{TypeUrl: `type.googleapis.com/"/` + proto.MessageName(nested), Value: nb}, 70 } 71 m4 := &pb3.Message{ 72 Name: "David", 73 ResultCount: 47, 74 Anything: &anypb.Any{TypeUrl: "type.googleapis.com/a/path/" + proto.MessageName(nested), Value: nb}, 75 } 76 m5 := &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(nested), Value: nb} 77 78 any1 := &pb2.MyMessage{Count: proto.Int32(47), Name: proto.String("David")} 79 proto.SetExtension(any1, pb2.E_Ext_More, &pb2.Ext{Data: proto.String("foo")}) 80 proto.SetExtension(any1, pb2.E_Ext_Text, proto.String("bar")) 81 any1b, err := proto.Marshal(any1) 82 if err != nil { 83 panic(err) 84 } 85 any2 := &pb2.MyMessage{Count: proto.Int32(42), Bikeshed: pb2.MyMessage_GREEN.Enum(), RepBytes: [][]byte{[]byte("roboto")}} 86 proto.SetExtension(any2, pb2.E_Ext_More, &pb2.Ext{Data: proto.String("baz")}) 87 any2b, err := proto.Marshal(any2) 88 if err != nil { 89 panic(err) 90 } 91 m6 := &pb3.Message{ 92 Name: "David", 93 ResultCount: 47, 94 Anything: &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any1), Value: any1b}, 95 ManyThings: []*anypb.Any{ 96 &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any2), Value: any2b}, 97 &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any1), Value: any1b}, 98 }, 99 } 100 101 const ( 102 m1Golden = ` 103name: "David" 104result_count: 47 105anything: < 106 [type.googleapis.com/proto3_test.Nested]: < 107 bunny: "Monty" 108 > 109> 110` 111 m2Golden = ` 112name: "David" 113result_count: 47 114anything: < 115 ["http://[::1]/type.googleapis.com/proto3_test.Nested"]: < 116 bunny: "Monty" 117 > 118> 119` 120 m3Golden = ` 121name: "David" 122result_count: 47 123anything: < 124 ["type.googleapis.com/\"/proto3_test.Nested"]: < 125 bunny: "Monty" 126 > 127> 128` 129 m4Golden = ` 130name: "David" 131result_count: 47 132anything: < 133 [type.googleapis.com/a/path/proto3_test.Nested]: < 134 bunny: "Monty" 135 > 136> 137` 138 m5Golden = ` 139[type.googleapis.com/proto3_test.Nested]: < 140 bunny: "Monty" 141> 142` 143 m6Golden = ` 144name: "David" 145result_count: 47 146anything: < 147 [type.googleapis.com/proto2_test.MyMessage]: < 148 count: 47 149 name: "David" 150 [proto2_test.Ext.more]: < 151 data: "foo" 152 > 153 [proto2_test.Ext.text]: "bar" 154 > 155> 156many_things: < 157 [type.googleapis.com/proto2_test.MyMessage]: < 158 count: 42 159 bikeshed: GREEN 160 rep_bytes: "roboto" 161 [proto2_test.Ext.more]: < 162 data: "baz" 163 > 164 > 165> 166many_things: < 167 [type.googleapis.com/proto2_test.MyMessage]: < 168 count: 47 169 name: "David" 170 [proto2_test.Ext.more]: < 171 data: "foo" 172 > 173 [proto2_test.Ext.text]: "bar" 174 > 175> 176` 177 ) 178 return []golden{ 179 {m1, strings.TrimSpace(m1Golden) + "\n", strings.TrimSpace(compact(m1Golden)) + " "}, 180 {m2, strings.TrimSpace(m2Golden) + "\n", strings.TrimSpace(compact(m2Golden)) + " "}, 181 {m3, strings.TrimSpace(m3Golden) + "\n", strings.TrimSpace(compact(m3Golden)) + " "}, 182 {m4, strings.TrimSpace(m4Golden) + "\n", strings.TrimSpace(compact(m4Golden)) + " "}, 183 {m5, strings.TrimSpace(m5Golden) + "\n", strings.TrimSpace(compact(m5Golden)) + " "}, 184 {m6, strings.TrimSpace(m6Golden) + "\n", strings.TrimSpace(compact(m6Golden)) + " "}, 185 } 186} 187 188func TestMarshalGolden(t *testing.T) { 189 for _, tt := range goldenMessages { 190 t.Run("", func(t *testing.T) { 191 if got, want := expandedMarshaler.Text(tt.m), tt.t; got != want { 192 t.Errorf("message %v: got:\n%s\nwant:\n%s", tt.m, got, want) 193 } 194 if got, want := expandedCompactMarshaler.Text(tt.m), tt.c; got != want { 195 t.Errorf("message %v: got:\n`%s`\nwant:\n`%s`", tt.m, got, want) 196 } 197 }) 198 } 199} 200 201func TestUnmarshalGolden(t *testing.T) { 202 for _, tt := range goldenMessages { 203 t.Run("", func(t *testing.T) { 204 want := tt.m 205 got := proto.Clone(tt.m) 206 got.Reset() 207 if err := proto.UnmarshalText(tt.t, got); err != nil { 208 t.Errorf("failed to unmarshal\n%s\nerror: %v", tt.t, err) 209 } 210 if !anyEqual(got, want) { 211 t.Errorf("message:\n%s\ngot:\n%s\nwant:\n%s", tt.t, got, want) 212 } 213 got.Reset() 214 if err := proto.UnmarshalText(tt.c, got); err != nil { 215 t.Errorf("failed to unmarshal\n%s\nerror: %v", tt.c, err) 216 } 217 if !anyEqual(got, want) { 218 t.Errorf("message:\n%s\ngot:\n%s\nwant:\n%s", tt.c, got, want) 219 } 220 }) 221 } 222} 223 224func TestMarshalUnknownAny(t *testing.T) { 225 m := &pb3.Message{ 226 Anything: &anypb.Any{ 227 TypeUrl: "foo", 228 Value: []byte("bar"), 229 }, 230 } 231 want := `anything: < 232 type_url: "foo" 233 value: "bar" 234> 235` 236 got := expandedMarshaler.Text(m) 237 if got != want { 238 t.Errorf("got:\n%s\nwant:\n%s", got, want) 239 } 240} 241 242func TestAmbiguousAny(t *testing.T) { 243 pb := &anypb.Any{} 244 err := proto.UnmarshalText(` 245 type_url: "ttt/proto3_test.Nested" 246 value: "\n\x05Monty" 247 `, pb) 248 if err != nil { 249 t.Errorf("unexpected proto.UnmarshalText error: %v", err) 250 } 251} 252 253func TestUnmarshalOverwriteAny(t *testing.T) { 254 pb := &anypb.Any{} 255 err := proto.UnmarshalText(` 256 [type.googleapis.com/a/path/proto3_test.Nested]: < 257 bunny: "Monty" 258 > 259 [type.googleapis.com/a/path/proto3_test.Nested]: < 260 bunny: "Rabbit of Caerbannog" 261 > 262 `, pb) 263 want := `line 7: Any message unpacked multiple times, or "type_url" already set` 264 if err.Error() != want { 265 t.Errorf("incorrect error:\ngot: %v\nwant: %v", err.Error(), want) 266 } 267} 268 269func TestUnmarshalAnyMixAndMatch(t *testing.T) { 270 pb := &anypb.Any{} 271 err := proto.UnmarshalText(` 272 value: "\n\x05Monty" 273 [type.googleapis.com/a/path/proto3_test.Nested]: < 274 bunny: "Rabbit of Caerbannog" 275 > 276 `, pb) 277 want := `line 5: Any message unpacked multiple times, or "value" already set` 278 if err.Error() != want { 279 t.Errorf("incorrect error:\ngot: %v\nwant: %v", err.Error(), want) 280 } 281} 282 283// textMessage implements the methods that allow it to marshal and unmarshal 284// itself as text. 285type textMessage struct { 286} 287 288func (*textMessage) MarshalText() ([]byte, error) { 289 return []byte("custom"), nil 290} 291 292func (*textMessage) UnmarshalText(bytes []byte) error { 293 if string(bytes) != "custom" { 294 return errors.New("expected 'custom'") 295 } 296 return nil 297} 298 299func (*textMessage) Reset() {} 300func (*textMessage) String() string { return "" } 301func (*textMessage) ProtoMessage() {} 302 303func newTestMessage() *pb2.MyMessage { 304 msg := &pb2.MyMessage{ 305 Count: proto.Int32(42), 306 Name: proto.String("Dave"), 307 Quote: proto.String(`"I didn't want to go."`), 308 Pet: []string{"bunny", "kitty", "horsey"}, 309 Inner: &pb2.InnerMessage{ 310 Host: proto.String("footrest.syd"), 311 Port: proto.Int32(7001), 312 Connected: proto.Bool(true), 313 }, 314 Others: []*pb2.OtherMessage{ 315 { 316 Key: proto.Int64(0xdeadbeef), 317 Value: []byte{1, 65, 7, 12}, 318 }, 319 { 320 Weight: proto.Float32(6.022), 321 Inner: &pb2.InnerMessage{ 322 Host: proto.String("lesha.mtv"), 323 Port: proto.Int32(8002), 324 }, 325 }, 326 }, 327 Bikeshed: pb2.MyMessage_BLUE.Enum(), 328 Somegroup: &pb2.MyMessage_SomeGroup{ 329 GroupField: proto.Int32(8), 330 }, 331 // One normally wouldn't do this. 332 // This is an undeclared tag 13, as a varint (wire type 0) with value 4. 333 XXX_unrecognized: []byte{13<<3 | 0, 4}, 334 } 335 ext := &pb2.Ext{ 336 Data: proto.String("Big gobs for big rats"), 337 } 338 if err := proto.SetExtension(msg, pb2.E_Ext_More, ext); err != nil { 339 panic(err) 340 } 341 greetings := []string{"adg", "easy", "cow"} 342 if err := proto.SetExtension(msg, pb2.E_Greeting, greetings); err != nil { 343 panic(err) 344 } 345 346 // Add an unknown extension. We marshal a pb2.Ext, and fake the ID. 347 b, err := proto.Marshal(&pb2.Ext{Data: proto.String("3G skiing")}) 348 if err != nil { 349 panic(err) 350 } 351 b = append(proto.EncodeVarint(201<<3|proto.WireBytes), b...) 352 proto.SetRawExtension(msg, 201, b) 353 354 // Extensions can be plain fields, too, so let's test that. 355 b = append(proto.EncodeVarint(202<<3|proto.WireVarint), 19) 356 proto.SetRawExtension(msg, 202, b) 357 358 return msg 359} 360 361const text = `count: 42 362name: "Dave" 363quote: "\"I didn't want to go.\"" 364pet: "bunny" 365pet: "kitty" 366pet: "horsey" 367inner: < 368 host: "footrest.syd" 369 port: 7001 370 connected: true 371> 372others: < 373 key: 3735928559 374 value: "\001A\007\014" 375> 376others: < 377 weight: 6.022 378 inner: < 379 host: "lesha.mtv" 380 port: 8002 381 > 382> 383bikeshed: BLUE 384SomeGroup { 385 group_field: 8 386} 387/* 18 unknown bytes */ 38813: 4 389201: "\t3G skiing" 390202: 19 391[proto2_test.Ext.more]: < 392 data: "Big gobs for big rats" 393> 394[proto2_test.greeting]: "adg" 395[proto2_test.greeting]: "easy" 396[proto2_test.greeting]: "cow" 397` 398 399func TestMarshalText(t *testing.T) { 400 buf := new(bytes.Buffer) 401 if err := proto.MarshalText(buf, newTestMessage()); err != nil { 402 t.Fatalf("proto.MarshalText: %v", err) 403 } 404 got := buf.String() 405 if diff := cmp.Diff(text, got); got != text { 406 t.Errorf("diff (-want +got):\n%v\n\ngot:\n%v\n\nwant:\n%v", diff, got, text) 407 } 408} 409 410func TestMarshalTextCustomMessage(t *testing.T) { 411 buf := new(bytes.Buffer) 412 if err := proto.MarshalText(buf, &textMessage{}); err != nil { 413 t.Fatalf("proto.MarshalText: %v", err) 414 } 415 got := buf.String() 416 if got != "custom" { 417 t.Errorf("got:\n%v\n\nwant:\n%v", got, "custom") 418 } 419} 420func TestMarshalTextNil(t *testing.T) { 421 want := "<nil>" 422 tests := []proto.Message{nil, (*pb2.MyMessage)(nil)} 423 for i, test := range tests { 424 buf := new(bytes.Buffer) 425 if err := proto.MarshalText(buf, test); err != nil { 426 t.Fatal(err) 427 } 428 if got := buf.String(); got != want { 429 t.Errorf("%d: got %q want %q", i, got, want) 430 } 431 } 432} 433 434func TestMarshalTextUnknownEnum(t *testing.T) { 435 // The Color enum only specifies values 0-2. 436 m := &pb2.MyMessage{Bikeshed: pb2.MyMessage_Color(3).Enum()} 437 got := m.String() 438 const want = `bikeshed:3 ` 439 if got != want { 440 t.Errorf("\n got %q\nwant %q", got, want) 441 } 442} 443 444func TestTextOneof(t *testing.T) { 445 tests := []struct { 446 m proto.Message 447 want string 448 }{ 449 // zero message 450 {&pb2.Communique{}, ``}, 451 // scalar field 452 {&pb2.Communique{Union: &pb2.Communique_Number{4}}, `number:4`}, 453 // message field 454 {&pb2.Communique{Union: &pb2.Communique_Msg{ 455 &pb2.Strings{StringField: proto.String("why hello!")}, 456 }}, `msg:<string_field:"why hello!" >`}, 457 // bad oneof (should not panic) 458 {&pb2.Communique{Union: &pb2.Communique_Msg{nil}}, `msg:<>`}, 459 } 460 for _, test := range tests { 461 got := strings.TrimSpace(test.m.String()) 462 if got != test.want { 463 t.Errorf("got:\n%s\n\nwant:\n%s", got, test.want) 464 } 465 } 466} 467 468func compact(src string) string { 469 // s/[ \n]+/ /g; s/ $//; 470 dst := make([]byte, len(src)) 471 space, comment := false, false 472 j := 0 473 for i := 0; i < len(src); i++ { 474 if strings.HasPrefix(src[i:], "/*") { 475 comment = true 476 i++ 477 continue 478 } 479 if comment && strings.HasPrefix(src[i:], "*/") { 480 comment = false 481 i++ 482 continue 483 } 484 if comment { 485 continue 486 } 487 c := src[i] 488 if c == ' ' || c == '\n' { 489 space = true 490 continue 491 } 492 if j > 0 && (dst[j-1] == ':' || dst[j-1] == '<' || dst[j-1] == '{') { 493 space = false 494 } 495 if c == '{' { 496 space = false 497 } 498 if space { 499 dst[j] = ' ' 500 j++ 501 space = false 502 } 503 dst[j] = c 504 j++ 505 } 506 if space { 507 dst[j] = ' ' 508 j++ 509 } 510 return string(dst[0:j]) 511} 512 513func TestCompactText(t *testing.T) { 514 got := proto.CompactTextString(newTestMessage()) 515 if got != compact(text) { 516 t.Errorf("got:\n%v\n\nwant:\n%v", got, compact(text)) 517 } 518} 519 520func TestStringEscaping(t *testing.T) { 521 testCases := []struct { 522 in *pb2.Strings 523 out string 524 }{ 525 { 526 // Test data from C++ test (TextFormatTest.StringEscape). 527 // Single divergence: we don't escape apostrophes. 528 &pb2.Strings{StringField: proto.String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces")}, 529 "string_field: \"\\\"A string with ' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"\n", 530 }, 531 { 532 // Test data from the same C++ test. 533 &pb2.Strings{StringField: proto.String("\350\260\267\346\255\214")}, 534 "string_field: \"\\350\\260\\267\\346\\255\\214\"\n", 535 }, 536 { 537 // Some UTF-8. 538 &pb2.Strings{StringField: proto.String("\x00\x01\xff\x81")}, 539 `string_field: "\000\001\377\201"` + "\n", 540 }, 541 } 542 543 for _, tc := range testCases { 544 t.Run("", func(t *testing.T) { 545 var buf bytes.Buffer 546 if err := proto.MarshalText(&buf, tc.in); err != nil { 547 t.Fatalf("proto.MarsalText error: %v", err) 548 } 549 got := buf.String() 550 if got != tc.out { 551 t.Fatalf("want:\n%s\n\nwant:\n%s", got, tc.out) 552 } 553 554 // Check round-trip. 555 pb := new(pb2.Strings) 556 if err := proto.UnmarshalText(got, pb); err != nil { 557 t.Fatalf("proto.UnmarshalText error: %v", err) 558 } 559 if !proto.Equal(pb, tc.in) { 560 t.Fatalf("proto.Equal mismatch:\ngot:\n%v\n\nwant:\n%v", pb, tc.in) 561 } 562 }) 563 } 564} 565 566// A limitedWriter accepts some output before it fails. 567// This is a proxy for something like a nearly-full or imminently-failing disk, 568// or a network connection that is about to die. 569type limitedWriter struct { 570 b bytes.Buffer 571 limit int 572} 573 574var outOfSpace = errors.New("proto: insufficient space") 575 576func (w *limitedWriter) Write(p []byte) (n int, err error) { 577 var avail = w.limit - w.b.Len() 578 if avail <= 0 { 579 return 0, outOfSpace 580 } 581 if len(p) <= avail { 582 return w.b.Write(p) 583 } 584 n, _ = w.b.Write(p[:avail]) 585 return n, outOfSpace 586} 587 588func TestMarshalTextFailing(t *testing.T) { 589 // Try lots of different sizes to exercise more error code-paths. 590 for lim := 0; lim < len(text); lim++ { 591 buf := new(limitedWriter) 592 buf.limit = lim 593 err := proto.MarshalText(buf, newTestMessage()) 594 // We expect a certain error, but also some partial results in the buffer. 595 if err != outOfSpace { 596 t.Errorf("error mismatch: got %v, want %v", err, outOfSpace) 597 } 598 got := buf.b.String() 599 want := text[:buf.limit] 600 if got != want { 601 t.Errorf("text mismatch:\n\ngot:\n%v\n\nwant:\n%v", got, want) 602 } 603 } 604} 605 606func TestFloats(t *testing.T) { 607 tests := []struct { 608 f float64 609 want string 610 }{ 611 {0, "0"}, 612 {4.7, "4.7"}, 613 {math.Inf(1), "inf"}, 614 {math.Inf(-1), "-inf"}, 615 {math.NaN(), "nan"}, 616 } 617 for _, test := range tests { 618 msg := &pb2.FloatingPoint{F: &test.f} 619 got := strings.TrimSpace(msg.String()) 620 want := `f:` + test.want 621 if got != want { 622 t.Errorf("f=%f: got %q, want %q", test.f, got, want) 623 } 624 } 625} 626 627func TestRepeatedNilText(t *testing.T) { 628 m := &pb2.MessageList{ 629 Message: []*pb2.MessageList_Message{ 630 nil, 631 &pb2.MessageList_Message{ 632 Name: proto.String("Horse"), 633 }, 634 nil, 635 }, 636 } 637 want := `Message { 638} 639Message { 640 name: "Horse" 641} 642Message { 643} 644` 645 if got := proto.MarshalTextString(m); got != want { 646 t.Errorf("got:\n%s\n\nwant:\n%s", got, want) 647 } 648} 649 650func TestProto3Text(t *testing.T) { 651 tests := []struct { 652 m proto.Message 653 want string 654 }{ 655 // zero message 656 {&pb3.Message{}, ``}, 657 // zero message except for an empty byte slice 658 {&pb3.Message{Data: []byte{}}, ``}, 659 // trivial case 660 {&pb3.Message{Name: "Rob", HeightInCm: 175}, `name:"Rob" height_in_cm:175`}, 661 // empty map 662 {&pb2.MessageWithMap{}, ``}, 663 // non-empty map; map format is the same as a repeated struct, 664 // and they are sorted by key (numerically for numeric keys). 665 { 666 &pb2.MessageWithMap{NameMapping: map[int32]string{ 667 -1: "Negatory", 668 7: "Lucky", 669 1234: "Feist", 670 6345789: "Otis", 671 }}, 672 `name_mapping:<key:-1 value:"Negatory" > ` + 673 `name_mapping:<key:7 value:"Lucky" > ` + 674 `name_mapping:<key:1234 value:"Feist" > ` + 675 `name_mapping:<key:6345789 value:"Otis" >`, 676 }, 677 // map with nil value; not well-defined, but we shouldn't crash 678 { 679 &pb2.MessageWithMap{MsgMapping: map[int64]*pb2.FloatingPoint{7: nil}}, 680 `msg_mapping:<key:7 value:<> >`, 681 }, 682 } 683 for _, test := range tests { 684 got := strings.TrimSpace(test.m.String()) 685 if got != test.want { 686 t.Errorf("got:\n%s\n\nwant:\n%s", got, test.want) 687 } 688 } 689} 690 691func TestRacyMarshal(t *testing.T) { 692 // This test should be run with the race detector. 693 694 any := &pb2.MyMessage{Count: proto.Int32(47), Name: proto.String("David")} 695 proto.SetExtension(any, pb2.E_Ext_Text, proto.String("bar")) 696 b, err := proto.Marshal(any) 697 if err != nil { 698 panic(err) 699 } 700 m := &pb3.Message{ 701 Name: "David", 702 ResultCount: 47, 703 Anything: &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any), Value: b}, 704 } 705 706 wantText := proto.MarshalTextString(m) 707 wantBytes, err := proto.Marshal(m) 708 if err != nil { 709 t.Fatalf("proto.Marshal error: %v", err) 710 } 711 712 var wg sync.WaitGroup 713 defer wg.Wait() 714 wg.Add(20) 715 for i := 0; i < 10; i++ { 716 go func() { 717 defer wg.Done() 718 got := proto.MarshalTextString(m) 719 if got != wantText { 720 t.Errorf("proto.MarshalTextString = %q, want %q", got, wantText) 721 } 722 }() 723 go func() { 724 defer wg.Done() 725 got, err := proto.Marshal(m) 726 if !bytes.Equal(got, wantBytes) || err != nil { 727 t.Errorf("proto.Marshal = (%x, %v), want (%x, nil)", got, err, wantBytes) 728 } 729 }() 730 } 731} 732 733type UnmarshalTextTest struct { 734 in string 735 err string // if "", no error expected 736 out *pb2.MyMessage 737} 738 739func buildExtStructTest(text string) UnmarshalTextTest { 740 msg := &pb2.MyMessage{ 741 Count: proto.Int32(42), 742 } 743 proto.SetExtension(msg, pb2.E_Ext_More, &pb2.Ext{ 744 Data: proto.String("Hello, world!"), 745 }) 746 return UnmarshalTextTest{in: text, out: msg} 747} 748 749func buildExtDataTest(text string) UnmarshalTextTest { 750 msg := &pb2.MyMessage{ 751 Count: proto.Int32(42), 752 } 753 proto.SetExtension(msg, pb2.E_Ext_Text, proto.String("Hello, world!")) 754 proto.SetExtension(msg, pb2.E_Ext_Number, proto.Int32(1729)) 755 return UnmarshalTextTest{in: text, out: msg} 756} 757 758func buildExtRepStringTest(text string) UnmarshalTextTest { 759 msg := &pb2.MyMessage{ 760 Count: proto.Int32(42), 761 } 762 if err := proto.SetExtension(msg, pb2.E_Greeting, []string{"bula", "hola"}); err != nil { 763 panic(err) 764 } 765 return UnmarshalTextTest{in: text, out: msg} 766} 767 768var unmarshalTextTests = []UnmarshalTextTest{ 769 // Basic 770 { 771 in: " count:42\n name:\"Dave\" ", 772 out: &pb2.MyMessage{ 773 Count: proto.Int32(42), 774 Name: proto.String("Dave"), 775 }, 776 }, 777 778 // Empty quoted string 779 { 780 in: `count:42 name:""`, 781 out: &pb2.MyMessage{ 782 Count: proto.Int32(42), 783 Name: proto.String(""), 784 }, 785 }, 786 787 // Quoted string concatenation with double quotes 788 { 789 in: `count:42 name: "My name is "` + "\n" + `"elsewhere"`, 790 out: &pb2.MyMessage{ 791 Count: proto.Int32(42), 792 Name: proto.String("My name is elsewhere"), 793 }, 794 }, 795 796 // Quoted string concatenation with single quotes 797 { 798 in: "count:42 name: 'My name is '\n'elsewhere'", 799 out: &pb2.MyMessage{ 800 Count: proto.Int32(42), 801 Name: proto.String("My name is elsewhere"), 802 }, 803 }, 804 805 // Quoted string concatenations with mixed quotes 806 { 807 in: "count:42 name: 'My name is '\n\"elsewhere\"", 808 out: &pb2.MyMessage{ 809 Count: proto.Int32(42), 810 Name: proto.String("My name is elsewhere"), 811 }, 812 }, 813 { 814 in: "count:42 name: \"My name is \"\n'elsewhere'", 815 out: &pb2.MyMessage{ 816 Count: proto.Int32(42), 817 Name: proto.String("My name is elsewhere"), 818 }, 819 }, 820 821 // Quoted string with escaped apostrophe 822 { 823 in: `count:42 name: "HOLIDAY - New Year\'s Day"`, 824 out: &pb2.MyMessage{ 825 Count: proto.Int32(42), 826 Name: proto.String("HOLIDAY - New Year's Day"), 827 }, 828 }, 829 830 // Quoted string with single quote 831 { 832 in: `count:42 name: 'Roger "The Ramster" Ramjet'`, 833 out: &pb2.MyMessage{ 834 Count: proto.Int32(42), 835 Name: proto.String(`Roger "The Ramster" Ramjet`), 836 }, 837 }, 838 839 // Quoted string with all the accepted special characters from the C++ test 840 { 841 in: `count:42 name: ` + "\"\\\"A string with \\' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"", 842 out: &pb2.MyMessage{ 843 Count: proto.Int32(42), 844 Name: proto.String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces"), 845 }, 846 }, 847 848 // Quoted string with quoted backslash 849 { 850 in: `count:42 name: "\\'xyz"`, 851 out: &pb2.MyMessage{ 852 Count: proto.Int32(42), 853 Name: proto.String(`\'xyz`), 854 }, 855 }, 856 857 // Quoted string with UTF-8 bytes. 858 { 859 in: "count:42 name: '\303\277\302\201\x00\xAB\xCD\xEF'", 860 out: &pb2.MyMessage{ 861 Count: proto.Int32(42), 862 Name: proto.String("\303\277\302\201\x00\xAB\xCD\xEF"), 863 }, 864 }, 865 866 // Quoted string with unicode escapes. 867 { 868 in: `count: 42 name: "\u0047\U00000047\uffff\U0010ffff"`, 869 out: &pb2.MyMessage{ 870 Count: proto.Int32(42), 871 Name: proto.String("GG\uffff\U0010ffff"), 872 }, 873 }, 874 875 // Bad quoted string 876 { 877 in: `inner: < host: "\0" >` + "\n", 878 err: `line 1.15: invalid quoted string "\0": \0 requires 2 following digits`, 879 }, 880 881 // Bad \u escape 882 { 883 in: `count: 42 name: "\u000"`, 884 err: `line 1.16: invalid quoted string "\u000": \u requires 4 following digits`, 885 }, 886 887 // Bad \U escape 888 { 889 in: `count: 42 name: "\U0000000"`, 890 err: `line 1.16: invalid quoted string "\U0000000": \U requires 8 following digits`, 891 }, 892 893 // Bad \U escape 894 { 895 in: `count: 42 name: "\xxx"`, 896 err: `line 1.16: invalid quoted string "\xxx": \xxx contains non-hexadecimal digits`, 897 }, 898 899 // Number too large for int64 900 { 901 in: "count: 1 others { key: 123456789012345678901 }", 902 err: "line 1.23: invalid int64: 123456789012345678901", 903 }, 904 905 // Number too large for int32 906 { 907 in: "count: 1234567890123", 908 err: "line 1.7: invalid int32: 1234567890123", 909 }, 910 911 // Number in hexadecimal 912 { 913 in: "count: 0x2beef", 914 out: &pb2.MyMessage{ 915 Count: proto.Int32(0x2beef), 916 }, 917 }, 918 919 // Number in octal 920 { 921 in: "count: 024601", 922 out: &pb2.MyMessage{ 923 Count: proto.Int32(024601), 924 }, 925 }, 926 927 // Floating point number with "f" suffix 928 { 929 in: "count: 4 others:< weight: 17.0f >", 930 out: &pb2.MyMessage{ 931 Count: proto.Int32(4), 932 Others: []*pb2.OtherMessage{ 933 { 934 Weight: proto.Float32(17), 935 }, 936 }, 937 }, 938 }, 939 940 // Floating point positive infinity 941 { 942 in: "count: 4 bigfloat: inf", 943 out: &pb2.MyMessage{ 944 Count: proto.Int32(4), 945 Bigfloat: proto.Float64(math.Inf(1)), 946 }, 947 }, 948 949 // Floating point negative infinity 950 { 951 in: "count: 4 bigfloat: -inf", 952 out: &pb2.MyMessage{ 953 Count: proto.Int32(4), 954 Bigfloat: proto.Float64(math.Inf(-1)), 955 }, 956 }, 957 958 // Number too large for float32 959 { 960 in: "others:< weight: 12345678901234567890123456789012345678901234567890 >", 961 err: "line 1.17: invalid float: 12345678901234567890123456789012345678901234567890", 962 }, 963 964 // Number posing as a quoted string 965 { 966 in: `inner: < host: 12 >` + "\n", 967 err: `line 1.15: invalid string: 12`, 968 }, 969 970 // Quoted string posing as int32 971 { 972 in: `count: "12"`, 973 err: `line 1.7: invalid int32: "12"`, 974 }, 975 976 // Quoted string posing a float32 977 { 978 in: `others:< weight: "17.4" >`, 979 err: `line 1.17: invalid float: "17.4"`, 980 }, 981 982 // unclosed bracket doesn't cause infinite loop 983 { 984 in: `[`, 985 err: `line 1.0: unclosed type_url or extension name`, 986 }, 987 988 // Enum 989 { 990 in: `count:42 bikeshed: BLUE`, 991 out: &pb2.MyMessage{ 992 Count: proto.Int32(42), 993 Bikeshed: pb2.MyMessage_BLUE.Enum(), 994 }, 995 }, 996 997 // Repeated field 998 { 999 in: `count:42 pet: "horsey" pet:"bunny"`, 1000 out: &pb2.MyMessage{ 1001 Count: proto.Int32(42), 1002 Pet: []string{"horsey", "bunny"}, 1003 }, 1004 }, 1005 1006 // Repeated field with list notation 1007 { 1008 in: `count:42 pet: ["horsey", "bunny"]`, 1009 out: &pb2.MyMessage{ 1010 Count: proto.Int32(42), 1011 Pet: []string{"horsey", "bunny"}, 1012 }, 1013 }, 1014 1015 // Repeated message with/without colon and <>/{} 1016 { 1017 in: `count:42 others:{} others{} others:<> others:{}`, 1018 out: &pb2.MyMessage{ 1019 Count: proto.Int32(42), 1020 Others: []*pb2.OtherMessage{ 1021 {}, 1022 {}, 1023 {}, 1024 {}, 1025 }, 1026 }, 1027 }, 1028 1029 // Missing colon for inner message 1030 { 1031 in: `count:42 inner < host: "cauchy.syd" >`, 1032 out: &pb2.MyMessage{ 1033 Count: proto.Int32(42), 1034 Inner: &pb2.InnerMessage{ 1035 Host: proto.String("cauchy.syd"), 1036 }, 1037 }, 1038 }, 1039 1040 // Missing colon for string field 1041 { 1042 in: `name "Dave"`, 1043 err: `line 1.5: expected ':', found "\"Dave\""`, 1044 }, 1045 1046 // Missing colon for int32 field 1047 { 1048 in: `count 42`, 1049 err: `line 1.6: expected ':', found "42"`, 1050 }, 1051 1052 // Missing required field 1053 { 1054 in: `name: "Pawel"`, 1055 err: `required field proto2_test.MyMessage.count not set`, 1056 out: &pb2.MyMessage{ 1057 Name: proto.String("Pawel"), 1058 }, 1059 }, 1060 1061 // Missing required field in a required submessage 1062 { 1063 in: `count: 42 we_must_go_deeper < leo_finally_won_an_oscar <> >`, 1064 err: `required field proto2_test.InnerMessage.host not set`, 1065 out: &pb2.MyMessage{ 1066 Count: proto.Int32(42), 1067 WeMustGoDeeper: &pb2.RequiredInnerMessage{LeoFinallyWonAnOscar: &pb2.InnerMessage{}}, 1068 }, 1069 }, 1070 1071 // Repeated non-repeated field 1072 { 1073 in: `name: "Rob" name: "Russ"`, 1074 err: `line 1.12: non-repeated field "name" was repeated`, 1075 }, 1076 1077 // Group 1078 { 1079 in: `count: 17 SomeGroup { group_field: 12 }`, 1080 out: &pb2.MyMessage{ 1081 Count: proto.Int32(17), 1082 Somegroup: &pb2.MyMessage_SomeGroup{ 1083 GroupField: proto.Int32(12), 1084 }, 1085 }, 1086 }, 1087 1088 // Semicolon between fields 1089 { 1090 in: `count:3;name:"Calvin"`, 1091 out: &pb2.MyMessage{ 1092 Count: proto.Int32(3), 1093 Name: proto.String("Calvin"), 1094 }, 1095 }, 1096 // Comma between fields 1097 { 1098 in: `count:4,name:"Ezekiel"`, 1099 out: &pb2.MyMessage{ 1100 Count: proto.Int32(4), 1101 Name: proto.String("Ezekiel"), 1102 }, 1103 }, 1104 1105 // Boolean false 1106 { 1107 in: `count:42 inner { host: "example.com" connected: false }`, 1108 out: &pb2.MyMessage{ 1109 Count: proto.Int32(42), 1110 Inner: &pb2.InnerMessage{ 1111 Host: proto.String("example.com"), 1112 Connected: proto.Bool(false), 1113 }, 1114 }, 1115 }, 1116 // Boolean true 1117 { 1118 in: `count:42 inner { host: "example.com" connected: true }`, 1119 out: &pb2.MyMessage{ 1120 Count: proto.Int32(42), 1121 Inner: &pb2.InnerMessage{ 1122 Host: proto.String("example.com"), 1123 Connected: proto.Bool(true), 1124 }, 1125 }, 1126 }, 1127 // Boolean 0 1128 { 1129 in: `count:42 inner { host: "example.com" connected: 0 }`, 1130 out: &pb2.MyMessage{ 1131 Count: proto.Int32(42), 1132 Inner: &pb2.InnerMessage{ 1133 Host: proto.String("example.com"), 1134 Connected: proto.Bool(false), 1135 }, 1136 }, 1137 }, 1138 // Boolean 1 1139 { 1140 in: `count:42 inner { host: "example.com" connected: 1 }`, 1141 out: &pb2.MyMessage{ 1142 Count: proto.Int32(42), 1143 Inner: &pb2.InnerMessage{ 1144 Host: proto.String("example.com"), 1145 Connected: proto.Bool(true), 1146 }, 1147 }, 1148 }, 1149 // Boolean f 1150 { 1151 in: `count:42 inner { host: "example.com" connected: f }`, 1152 out: &pb2.MyMessage{ 1153 Count: proto.Int32(42), 1154 Inner: &pb2.InnerMessage{ 1155 Host: proto.String("example.com"), 1156 Connected: proto.Bool(false), 1157 }, 1158 }, 1159 }, 1160 // Boolean t 1161 { 1162 in: `count:42 inner { host: "example.com" connected: t }`, 1163 out: &pb2.MyMessage{ 1164 Count: proto.Int32(42), 1165 Inner: &pb2.InnerMessage{ 1166 Host: proto.String("example.com"), 1167 Connected: proto.Bool(true), 1168 }, 1169 }, 1170 }, 1171 // Boolean False 1172 { 1173 in: `count:42 inner { host: "example.com" connected: False }`, 1174 out: &pb2.MyMessage{ 1175 Count: proto.Int32(42), 1176 Inner: &pb2.InnerMessage{ 1177 Host: proto.String("example.com"), 1178 Connected: proto.Bool(false), 1179 }, 1180 }, 1181 }, 1182 // Boolean True 1183 { 1184 in: `count:42 inner { host: "example.com" connected: True }`, 1185 out: &pb2.MyMessage{ 1186 Count: proto.Int32(42), 1187 Inner: &pb2.InnerMessage{ 1188 Host: proto.String("example.com"), 1189 Connected: proto.Bool(true), 1190 }, 1191 }, 1192 }, 1193 1194 // Extension 1195 buildExtStructTest(`count: 42 [proto2_test.Ext.more]:<data:"Hello, world!" >`), 1196 buildExtStructTest(`count: 42 [proto2_test.Ext.more] {data:"Hello, world!"}`), 1197 buildExtDataTest(`count: 42 [proto2_test.Ext.text]:"Hello, world!" [proto2_test.Ext.number]:1729`), 1198 buildExtRepStringTest(`count: 42 [proto2_test.greeting]:"bula" [proto2_test.greeting]:"hola"`), 1199 { 1200 in: `[proto2_test.complex]:<>`, 1201 err: `line 1.20: extension field "proto2_test.complex" does not extend message "proto2_test.MyMessage"`, 1202 }, 1203 1204 // Big all-in-one 1205 { 1206 in: "count:42 # Meaning\n" + 1207 `name:"Dave" ` + 1208 `quote:"\"I didn't want to go.\"" ` + 1209 `pet:"bunny" ` + 1210 `pet:"kitty" ` + 1211 `pet:"horsey" ` + 1212 `inner:<` + 1213 ` host:"footrest.syd" ` + 1214 ` port:7001 ` + 1215 ` connected:true ` + 1216 `> ` + 1217 `others:<` + 1218 ` key:3735928559 ` + 1219 ` value:"\x01A\a\f" ` + 1220 `> ` + 1221 `others:<` + 1222 " weight:58.9 # Atomic weight of Co\n" + 1223 ` inner:<` + 1224 ` host:"lesha.mtv" ` + 1225 ` port:8002 ` + 1226 ` >` + 1227 `>`, 1228 out: &pb2.MyMessage{ 1229 Count: proto.Int32(42), 1230 Name: proto.String("Dave"), 1231 Quote: proto.String(`"I didn't want to go."`), 1232 Pet: []string{"bunny", "kitty", "horsey"}, 1233 Inner: &pb2.InnerMessage{ 1234 Host: proto.String("footrest.syd"), 1235 Port: proto.Int32(7001), 1236 Connected: proto.Bool(true), 1237 }, 1238 Others: []*pb2.OtherMessage{ 1239 { 1240 Key: proto.Int64(3735928559), 1241 Value: []byte{0x1, 'A', '\a', '\f'}, 1242 }, 1243 { 1244 Weight: proto.Float32(58.9), 1245 Inner: &pb2.InnerMessage{ 1246 Host: proto.String("lesha.mtv"), 1247 Port: proto.Int32(8002), 1248 }, 1249 }, 1250 }, 1251 }, 1252 }, 1253} 1254 1255func TestUnmarshalText(t *testing.T) { 1256 for _, test := range unmarshalTextTests { 1257 t.Run("", func(t *testing.T) { 1258 pb := new(pb2.MyMessage) 1259 err := proto.UnmarshalText(test.in, pb) 1260 if test.err == "" { 1261 // We don't expect failure. 1262 if err != nil { 1263 t.Errorf("proto.UnmarshalText error: %v", err) 1264 } else if !proto.Equal(pb, test.out) { 1265 t.Errorf("proto.Equal mismatch:\ngot: %v\nwant: %v", pb, test.out) 1266 } 1267 } else { 1268 // We do expect failure. 1269 if err == nil { 1270 t.Errorf("proto.UnmarshalText: got nil error, want %v", test.err) 1271 } else if !strings.Contains(err.Error(), test.err) { 1272 t.Errorf("proto.UnmarshalText error mismatch:\ngot: %v\nwant: %v", err.Error(), test.err) 1273 } else if _, ok := err.(*proto.RequiredNotSetError); ok && test.out != nil && !proto.Equal(pb, test.out) { 1274 t.Errorf("proto.Equal mismatch:\ngot %v\nwant: %v", pb, test.out) 1275 } 1276 } 1277 }) 1278 } 1279} 1280 1281func TestUnmarshalTextCustomMessage(t *testing.T) { 1282 msg := &textMessage{} 1283 if err := proto.UnmarshalText("custom", msg); err != nil { 1284 t.Errorf("proto.UnmarshalText error: %v", err) 1285 } 1286 if err := proto.UnmarshalText("not custom", msg); err == nil { 1287 t.Errorf("proto.UnmarshalText: got nil error, want non-nil") 1288 } 1289} 1290 1291// Regression test; this caused a panic. 1292func TestRepeatedEnum(t *testing.T) { 1293 pb := new(pb2.RepeatedEnum) 1294 if err := proto.UnmarshalText("color: RED", pb); err != nil { 1295 t.Fatal(err) 1296 } 1297 exp := &pb2.RepeatedEnum{ 1298 Color: []pb2.RepeatedEnum_Color{pb2.RepeatedEnum_RED}, 1299 } 1300 if !proto.Equal(pb, exp) { 1301 t.Errorf("proto.Equal mismatch:\ngot: %v\nwant %v", pb, exp) 1302 } 1303} 1304 1305func TestProto3TextParsing(t *testing.T) { 1306 m := new(pb3.Message) 1307 const in = `name: "Wallace" true_scotsman: true` 1308 want := &pb3.Message{ 1309 Name: "Wallace", 1310 TrueScotsman: true, 1311 } 1312 if err := proto.UnmarshalText(in, m); err != nil { 1313 t.Fatal(err) 1314 } 1315 if !proto.Equal(m, want) { 1316 t.Errorf("proto.Equal mismatch:\ngot: %v\nwant %v", m, want) 1317 } 1318} 1319 1320func TestMapParsing(t *testing.T) { 1321 m := new(pb2.MessageWithMap) 1322 const in = `name_mapping:<key:1234 value:"Feist"> name_mapping:<key:1 value:"Beatles">` + 1323 `msg_mapping:<key:-4, value:<f: 2.0>,>` + // separating commas are okay 1324 `msg_mapping<key:-2 value<f: 4.0>>` + // no colon after "value" 1325 `msg_mapping:<value:<f: 5.0>>` + // omitted key 1326 `byte_mapping:<key:true value:"so be it">` + 1327 `byte_mapping:<>` // omitted key and value 1328 want := &pb2.MessageWithMap{ 1329 NameMapping: map[int32]string{ 1330 1: "Beatles", 1331 1234: "Feist", 1332 }, 1333 MsgMapping: map[int64]*pb2.FloatingPoint{ 1334 -4: {F: proto.Float64(2.0)}, 1335 -2: {F: proto.Float64(4.0)}, 1336 0: {F: proto.Float64(5.0)}, 1337 }, 1338 ByteMapping: map[bool][]byte{ 1339 false: nil, 1340 true: []byte("so be it"), 1341 }, 1342 } 1343 if err := proto.UnmarshalText(in, m); err != nil { 1344 t.Fatal(err) 1345 } 1346 if !proto.Equal(m, want) { 1347 t.Errorf("proto.Equal mismatch:\ngot: %v\nwant %v", m, want) 1348 } 1349} 1350 1351func TestOneofParsing(t *testing.T) { 1352 const in = `name:"Shrek"` 1353 m := new(pb2.Communique) 1354 want := &pb2.Communique{Union: &pb2.Communique_Name{"Shrek"}} 1355 if err := proto.UnmarshalText(in, m); err != nil { 1356 t.Fatal(err) 1357 } 1358 if !proto.Equal(m, want) { 1359 t.Errorf("\n got %v\nwant %v", m, want) 1360 } 1361 1362 const inOverwrite = `name:"Shrek" number:42` 1363 m = new(pb2.Communique) 1364 testErr := "line 1.13: field 'number' would overwrite already parsed oneof 'union'" 1365 if err := proto.UnmarshalText(inOverwrite, m); err == nil { 1366 t.Errorf("proto.UnmarshalText: got nil error, want %v", testErr) 1367 } else if err.Error() != testErr { 1368 t.Errorf("error mismatch:\ngot: %v\nwant: %v", err.Error(), testErr) 1369 } 1370} 1371