1// Copyright 2018 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 prototext_test 6 7import ( 8 "math" 9 "testing" 10 11 "github.com/google/go-cmp/cmp" 12 13 "google.golang.org/protobuf/encoding/prototext" 14 "google.golang.org/protobuf/internal/detrand" 15 "google.golang.org/protobuf/internal/flags" 16 "google.golang.org/protobuf/proto" 17 "google.golang.org/protobuf/reflect/protoregistry" 18 "google.golang.org/protobuf/testing/protopack" 19 20 pb2 "google.golang.org/protobuf/internal/testprotos/textpb2" 21 pb3 "google.golang.org/protobuf/internal/testprotos/textpb3" 22 "google.golang.org/protobuf/types/known/anypb" 23) 24 25func init() { 26 // Disable detrand to enable direct comparisons on outputs. 27 detrand.Disable() 28} 29 30func TestMarshal(t *testing.T) { 31 tests := []struct { 32 desc string 33 mo prototext.MarshalOptions 34 input proto.Message 35 want string 36 wantErr bool // TODO: Verify error message content. 37 skip bool 38 }{{ 39 desc: "proto2 optional scalars not set", 40 input: &pb2.Scalars{}, 41 want: "", 42 }, { 43 desc: "proto3 scalars not set", 44 input: &pb3.Scalars{}, 45 want: "", 46 }, { 47 desc: "proto3 optional not set", 48 input: &pb3.Proto3Optional{}, 49 want: "", 50 }, { 51 desc: "proto2 optional scalars set to zero values", 52 input: &pb2.Scalars{ 53 OptBool: proto.Bool(false), 54 OptInt32: proto.Int32(0), 55 OptInt64: proto.Int64(0), 56 OptUint32: proto.Uint32(0), 57 OptUint64: proto.Uint64(0), 58 OptSint32: proto.Int32(0), 59 OptSint64: proto.Int64(0), 60 OptFixed32: proto.Uint32(0), 61 OptFixed64: proto.Uint64(0), 62 OptSfixed32: proto.Int32(0), 63 OptSfixed64: proto.Int64(0), 64 OptFloat: proto.Float32(0), 65 OptDouble: proto.Float64(0), 66 OptBytes: []byte{}, 67 OptString: proto.String(""), 68 }, 69 want: `opt_bool: false 70opt_int32: 0 71opt_int64: 0 72opt_uint32: 0 73opt_uint64: 0 74opt_sint32: 0 75opt_sint64: 0 76opt_fixed32: 0 77opt_fixed64: 0 78opt_sfixed32: 0 79opt_sfixed64: 0 80opt_float: 0 81opt_double: 0 82opt_bytes: "" 83opt_string: "" 84`, 85 }, { 86 desc: "proto3 optional set to zero values", 87 input: &pb3.Proto3Optional{ 88 OptBool: proto.Bool(false), 89 OptInt32: proto.Int32(0), 90 OptInt64: proto.Int64(0), 91 OptUint32: proto.Uint32(0), 92 OptUint64: proto.Uint64(0), 93 OptFloat: proto.Float32(0), 94 OptDouble: proto.Float64(0), 95 OptString: proto.String(""), 96 OptBytes: []byte{}, 97 OptEnum: pb3.Enum_ZERO.Enum(), 98 OptMessage: &pb3.Nested{}, 99 }, 100 want: `opt_bool: false 101opt_int32: 0 102opt_int64: 0 103opt_uint32: 0 104opt_uint64: 0 105opt_float: 0 106opt_double: 0 107opt_string: "" 108opt_bytes: "" 109opt_enum: ZERO 110opt_message: {} 111`, 112 }, { 113 desc: "proto3 scalars set to zero values", 114 input: &pb3.Scalars{ 115 SBool: false, 116 SInt32: 0, 117 SInt64: 0, 118 SUint32: 0, 119 SUint64: 0, 120 SSint32: 0, 121 SSint64: 0, 122 SFixed32: 0, 123 SFixed64: 0, 124 SSfixed32: 0, 125 SSfixed64: 0, 126 SFloat: 0, 127 SDouble: 0, 128 SBytes: []byte{}, 129 SString: "", 130 }, 131 want: "", 132 }, { 133 desc: "proto2 optional scalars set to some values", 134 input: &pb2.Scalars{ 135 OptBool: proto.Bool(true), 136 OptInt32: proto.Int32(0xff), 137 OptInt64: proto.Int64(0xdeadbeef), 138 OptUint32: proto.Uint32(47), 139 OptUint64: proto.Uint64(0xdeadbeef), 140 OptSint32: proto.Int32(-1001), 141 OptSint64: proto.Int64(-0xffff), 142 OptFixed64: proto.Uint64(64), 143 OptSfixed32: proto.Int32(-32), 144 OptFloat: proto.Float32(1.02), 145 OptDouble: proto.Float64(1.0199999809265137), 146 OptBytes: []byte("\xe8\xb0\xb7\xe6\xad\x8c"), 147 OptString: proto.String("谷歌"), 148 }, 149 want: `opt_bool: true 150opt_int32: 255 151opt_int64: 3735928559 152opt_uint32: 47 153opt_uint64: 3735928559 154opt_sint32: -1001 155opt_sint64: -65535 156opt_fixed64: 64 157opt_sfixed32: -32 158opt_float: 1.02 159opt_double: 1.0199999809265137 160opt_bytes: "谷歌" 161opt_string: "谷歌" 162`, 163 }, { 164 desc: "proto2 string with invalid UTF-8", 165 input: &pb2.Scalars{ 166 OptString: proto.String("abc\xff"), 167 }, 168 want: `opt_string: "abc\xff" 169`, 170 }, { 171 desc: "proto3 string with invalid UTF-8", 172 input: &pb3.Scalars{ 173 SString: "abc\xff", 174 }, 175 wantErr: true, 176 }, { 177 desc: "float nan", 178 input: &pb3.Scalars{ 179 SFloat: float32(math.NaN()), 180 }, 181 want: "s_float: nan\n", 182 }, { 183 desc: "float positive infinity", 184 input: &pb3.Scalars{ 185 SFloat: float32(math.Inf(1)), 186 }, 187 want: "s_float: inf\n", 188 }, { 189 desc: "float negative infinity", 190 input: &pb3.Scalars{ 191 SFloat: float32(math.Inf(-1)), 192 }, 193 want: "s_float: -inf\n", 194 }, { 195 desc: "double nan", 196 input: &pb3.Scalars{ 197 SDouble: math.NaN(), 198 }, 199 want: "s_double: nan\n", 200 }, { 201 desc: "double positive infinity", 202 input: &pb3.Scalars{ 203 SDouble: math.Inf(1), 204 }, 205 want: "s_double: inf\n", 206 }, { 207 desc: "double negative infinity", 208 input: &pb3.Scalars{ 209 SDouble: math.Inf(-1), 210 }, 211 want: "s_double: -inf\n", 212 }, { 213 desc: "proto2 enum not set", 214 input: &pb2.Enums{}, 215 want: "", 216 }, { 217 desc: "proto2 enum set to zero value", 218 input: &pb2.Enums{ 219 OptEnum: pb2.Enum(0).Enum(), 220 OptNestedEnum: pb2.Enums_NestedEnum(0).Enum(), 221 }, 222 want: `opt_enum: 0 223opt_nested_enum: 0 224`, 225 }, { 226 desc: "proto2 enum", 227 input: &pb2.Enums{ 228 OptEnum: pb2.Enum_ONE.Enum(), 229 OptNestedEnum: pb2.Enums_UNO.Enum(), 230 }, 231 want: `opt_enum: ONE 232opt_nested_enum: UNO 233`, 234 }, { 235 desc: "proto2 enum set to numeric values", 236 input: &pb2.Enums{ 237 OptEnum: pb2.Enum(2).Enum(), 238 OptNestedEnum: pb2.Enums_NestedEnum(2).Enum(), 239 }, 240 want: `opt_enum: TWO 241opt_nested_enum: DOS 242`, 243 }, { 244 desc: "proto2 enum set to unnamed numeric values", 245 input: &pb2.Enums{ 246 OptEnum: pb2.Enum(101).Enum(), 247 OptNestedEnum: pb2.Enums_NestedEnum(-101).Enum(), 248 }, 249 want: `opt_enum: 101 250opt_nested_enum: -101 251`, 252 }, { 253 desc: "proto3 enum not set", 254 input: &pb3.Enums{}, 255 want: "", 256 }, { 257 desc: "proto3 enum set to zero value", 258 input: &pb3.Enums{ 259 SEnum: pb3.Enum_ZERO, 260 SNestedEnum: pb3.Enums_CERO, 261 }, 262 want: "", 263 }, { 264 desc: "proto3 enum", 265 input: &pb3.Enums{ 266 SEnum: pb3.Enum_ONE, 267 SNestedEnum: pb3.Enums_UNO, 268 }, 269 want: `s_enum: ONE 270s_nested_enum: UNO 271`, 272 }, { 273 desc: "proto3 enum set to numeric values", 274 input: &pb3.Enums{ 275 SEnum: 2, 276 SNestedEnum: 2, 277 }, 278 want: `s_enum: TWO 279s_nested_enum: DOS 280`, 281 }, { 282 desc: "proto3 enum set to unnamed numeric values", 283 input: &pb3.Enums{ 284 SEnum: -47, 285 SNestedEnum: 47, 286 }, 287 want: `s_enum: -47 288s_nested_enum: 47 289`, 290 }, { 291 desc: "proto2 nested message not set", 292 input: &pb2.Nests{}, 293 want: "", 294 }, { 295 desc: "proto2 nested message set to empty", 296 input: &pb2.Nests{ 297 OptNested: &pb2.Nested{}, 298 Optgroup: &pb2.Nests_OptGroup{}, 299 }, 300 want: `opt_nested: {} 301OptGroup: {} 302`, 303 }, { 304 desc: "proto2 nested messages", 305 input: &pb2.Nests{ 306 OptNested: &pb2.Nested{ 307 OptString: proto.String("nested message"), 308 OptNested: &pb2.Nested{ 309 OptString: proto.String("another nested message"), 310 }, 311 }, 312 }, 313 want: `opt_nested: { 314 opt_string: "nested message" 315 opt_nested: { 316 opt_string: "another nested message" 317 } 318} 319`, 320 }, { 321 desc: "proto2 groups", 322 input: &pb2.Nests{ 323 Optgroup: &pb2.Nests_OptGroup{ 324 OptString: proto.String("inside a group"), 325 OptNested: &pb2.Nested{ 326 OptString: proto.String("nested message inside a group"), 327 }, 328 Optnestedgroup: &pb2.Nests_OptGroup_OptNestedGroup{ 329 OptFixed32: proto.Uint32(47), 330 }, 331 }, 332 }, 333 want: `OptGroup: { 334 opt_string: "inside a group" 335 opt_nested: { 336 opt_string: "nested message inside a group" 337 } 338 OptNestedGroup: { 339 opt_fixed32: 47 340 } 341} 342`, 343 }, { 344 desc: "proto3 nested message not set", 345 input: &pb3.Nests{}, 346 want: "", 347 }, { 348 desc: "proto3 nested message set to empty", 349 input: &pb3.Nests{ 350 SNested: &pb3.Nested{}, 351 }, 352 want: "s_nested: {}\n", 353 }, { 354 desc: "proto3 nested message", 355 input: &pb3.Nests{ 356 SNested: &pb3.Nested{ 357 SString: "nested message", 358 SNested: &pb3.Nested{ 359 SString: "another nested message", 360 }, 361 }, 362 }, 363 want: `s_nested: { 364 s_string: "nested message" 365 s_nested: { 366 s_string: "another nested message" 367 } 368} 369`, 370 }, { 371 desc: "proto3 nested message contains invalid UTF-8", 372 input: &pb3.Nests{ 373 SNested: &pb3.Nested{ 374 SString: "abc\xff", 375 }, 376 }, 377 wantErr: true, 378 }, { 379 desc: "oneof not set", 380 input: &pb3.Oneofs{}, 381 want: "", 382 }, { 383 desc: "oneof set to empty string", 384 input: &pb3.Oneofs{ 385 Union: &pb3.Oneofs_OneofString{}, 386 }, 387 want: `oneof_string: "" 388`, 389 }, { 390 desc: "oneof set to string", 391 input: &pb3.Oneofs{ 392 Union: &pb3.Oneofs_OneofString{ 393 OneofString: "hello", 394 }, 395 }, 396 want: `oneof_string: "hello" 397`, 398 }, { 399 desc: "oneof set to enum", 400 input: &pb3.Oneofs{ 401 Union: &pb3.Oneofs_OneofEnum{ 402 OneofEnum: pb3.Enum_ZERO, 403 }, 404 }, 405 want: `oneof_enum: ZERO 406`, 407 }, { 408 desc: "oneof set to empty message", 409 input: &pb3.Oneofs{ 410 Union: &pb3.Oneofs_OneofNested{ 411 OneofNested: &pb3.Nested{}, 412 }, 413 }, 414 want: "oneof_nested: {}\n", 415 }, { 416 desc: "oneof set to message", 417 input: &pb3.Oneofs{ 418 Union: &pb3.Oneofs_OneofNested{ 419 OneofNested: &pb3.Nested{ 420 SString: "nested message", 421 }, 422 }, 423 }, 424 want: `oneof_nested: { 425 s_string: "nested message" 426} 427`, 428 }, { 429 desc: "repeated fields not set", 430 input: &pb2.Repeats{}, 431 want: "", 432 }, { 433 desc: "repeated fields set to empty slices", 434 input: &pb2.Repeats{ 435 RptBool: []bool{}, 436 RptInt32: []int32{}, 437 RptInt64: []int64{}, 438 RptUint32: []uint32{}, 439 RptUint64: []uint64{}, 440 RptFloat: []float32{}, 441 RptDouble: []float64{}, 442 RptBytes: [][]byte{}, 443 }, 444 want: "", 445 }, { 446 desc: "repeated fields set to some values", 447 input: &pb2.Repeats{ 448 RptBool: []bool{true, false, true, true}, 449 RptInt32: []int32{1, 6, 0, 0}, 450 RptInt64: []int64{-64, 47}, 451 RptUint32: []uint32{0xff, 0xffff}, 452 RptUint64: []uint64{0xdeadbeef}, 453 RptFloat: []float32{float32(math.NaN()), float32(math.Inf(1)), float32(math.Inf(-1)), 1.034}, 454 RptDouble: []float64{math.NaN(), math.Inf(1), math.Inf(-1), 1.23e-308}, 455 RptString: []string{"hello", "世界"}, 456 RptBytes: [][]byte{ 457 []byte("hello"), 458 []byte("\xe4\xb8\x96\xe7\x95\x8c"), 459 }, 460 }, 461 want: `rpt_bool: true 462rpt_bool: false 463rpt_bool: true 464rpt_bool: true 465rpt_int32: 1 466rpt_int32: 6 467rpt_int32: 0 468rpt_int32: 0 469rpt_int64: -64 470rpt_int64: 47 471rpt_uint32: 255 472rpt_uint32: 65535 473rpt_uint64: 3735928559 474rpt_float: nan 475rpt_float: inf 476rpt_float: -inf 477rpt_float: 1.034 478rpt_double: nan 479rpt_double: inf 480rpt_double: -inf 481rpt_double: 1.23e-308 482rpt_string: "hello" 483rpt_string: "世界" 484rpt_bytes: "hello" 485rpt_bytes: "世界" 486`, 487 }, { 488 desc: "repeated proto2 contains invalid UTF-8", 489 input: &pb2.Repeats{ 490 RptString: []string{"abc\xff"}, 491 }, 492 want: `rpt_string: "abc\xff" 493`, 494 }, { 495 desc: "repeated proto3 contains invalid UTF-8", 496 input: &pb3.Repeats{ 497 RptString: []string{"abc\xff"}, 498 }, 499 wantErr: true, 500 }, { 501 desc: "repeated enums", 502 input: &pb2.Enums{ 503 RptEnum: []pb2.Enum{pb2.Enum_ONE, 2, pb2.Enum_TEN, 42}, 504 RptNestedEnum: []pb2.Enums_NestedEnum{2, 47, 10}, 505 }, 506 want: `rpt_enum: ONE 507rpt_enum: TWO 508rpt_enum: TEN 509rpt_enum: 42 510rpt_nested_enum: DOS 511rpt_nested_enum: 47 512rpt_nested_enum: DIEZ 513`, 514 }, { 515 desc: "repeated messages set to empty", 516 input: &pb2.Nests{ 517 RptNested: []*pb2.Nested{}, 518 Rptgroup: []*pb2.Nests_RptGroup{}, 519 }, 520 want: "", 521 }, { 522 desc: "repeated messages", 523 input: &pb2.Nests{ 524 RptNested: []*pb2.Nested{ 525 { 526 OptString: proto.String("repeat nested one"), 527 }, 528 { 529 OptString: proto.String("repeat nested two"), 530 OptNested: &pb2.Nested{ 531 OptString: proto.String("inside repeat nested two"), 532 }, 533 }, 534 {}, 535 }, 536 }, 537 want: `rpt_nested: { 538 opt_string: "repeat nested one" 539} 540rpt_nested: { 541 opt_string: "repeat nested two" 542 opt_nested: { 543 opt_string: "inside repeat nested two" 544 } 545} 546rpt_nested: {} 547`, 548 }, { 549 desc: "repeated messages contains nil value", 550 input: &pb2.Nests{ 551 RptNested: []*pb2.Nested{nil, {}}, 552 }, 553 want: `rpt_nested: {} 554rpt_nested: {} 555`, 556 }, { 557 desc: "repeated groups", 558 input: &pb2.Nests{ 559 Rptgroup: []*pb2.Nests_RptGroup{ 560 { 561 RptString: []string{"hello", "world"}, 562 }, 563 {}, 564 nil, 565 }, 566 }, 567 want: `RptGroup: { 568 rpt_string: "hello" 569 rpt_string: "world" 570} 571RptGroup: {} 572RptGroup: {} 573`, 574 }, { 575 desc: "map fields not set", 576 input: &pb3.Maps{}, 577 want: "", 578 }, { 579 desc: "map fields set to empty", 580 input: &pb3.Maps{ 581 Int32ToStr: map[int32]string{}, 582 BoolToUint32: map[bool]uint32{}, 583 Uint64ToEnum: map[uint64]pb3.Enum{}, 584 StrToNested: map[string]*pb3.Nested{}, 585 StrToOneofs: map[string]*pb3.Oneofs{}, 586 }, 587 want: "", 588 }, { 589 desc: "map fields 1", 590 input: &pb3.Maps{ 591 Int32ToStr: map[int32]string{ 592 -101: "-101", 593 0xff: "0xff", 594 0: "zero", 595 }, 596 BoolToUint32: map[bool]uint32{ 597 true: 42, 598 false: 101, 599 }, 600 }, 601 want: `int32_to_str: { 602 key: -101 603 value: "-101" 604} 605int32_to_str: { 606 key: 0 607 value: "zero" 608} 609int32_to_str: { 610 key: 255 611 value: "0xff" 612} 613bool_to_uint32: { 614 key: false 615 value: 101 616} 617bool_to_uint32: { 618 key: true 619 value: 42 620} 621`, 622 }, { 623 desc: "map fields 2", 624 input: &pb3.Maps{ 625 Uint64ToEnum: map[uint64]pb3.Enum{ 626 1: pb3.Enum_ONE, 627 2: pb3.Enum_TWO, 628 10: pb3.Enum_TEN, 629 47: 47, 630 }, 631 }, 632 want: `uint64_to_enum: { 633 key: 1 634 value: ONE 635} 636uint64_to_enum: { 637 key: 2 638 value: TWO 639} 640uint64_to_enum: { 641 key: 10 642 value: TEN 643} 644uint64_to_enum: { 645 key: 47 646 value: 47 647} 648`, 649 }, { 650 desc: "map fields 3", 651 input: &pb3.Maps{ 652 StrToNested: map[string]*pb3.Nested{ 653 "nested": &pb3.Nested{ 654 SString: "nested in a map", 655 }, 656 }, 657 }, 658 want: `str_to_nested: { 659 key: "nested" 660 value: { 661 s_string: "nested in a map" 662 } 663} 664`, 665 }, { 666 desc: "map fields 4", 667 input: &pb3.Maps{ 668 StrToOneofs: map[string]*pb3.Oneofs{ 669 "string": &pb3.Oneofs{ 670 Union: &pb3.Oneofs_OneofString{ 671 OneofString: "hello", 672 }, 673 }, 674 "nested": &pb3.Oneofs{ 675 Union: &pb3.Oneofs_OneofNested{ 676 OneofNested: &pb3.Nested{ 677 SString: "nested oneof in map field value", 678 }, 679 }, 680 }, 681 }, 682 }, 683 want: `str_to_oneofs: { 684 key: "nested" 685 value: { 686 oneof_nested: { 687 s_string: "nested oneof in map field value" 688 } 689 } 690} 691str_to_oneofs: { 692 key: "string" 693 value: { 694 oneof_string: "hello" 695 } 696} 697`, 698 }, { 699 desc: "proto2 map field value contains invalid UTF-8", 700 input: &pb2.Maps{ 701 Int32ToStr: map[int32]string{ 702 101: "abc\xff", 703 }, 704 }, 705 want: `int32_to_str: { 706 key: 101 707 value: "abc\xff" 708} 709`, 710 }, { 711 desc: "proto2 map field key contains invalid UTF-8", 712 input: &pb2.Maps{ 713 StrToNested: map[string]*pb2.Nested{ 714 "abc\xff": {}, 715 }, 716 }, 717 want: `str_to_nested: { 718 key: "abc\xff" 719 value: {} 720} 721`, 722 }, { 723 desc: "proto3 map field value contains invalid UTF-8", 724 input: &pb3.Maps{ 725 Int32ToStr: map[int32]string{ 726 101: "abc\xff", 727 }, 728 }, 729 wantErr: true, 730 }, { 731 desc: "proto3 map field key contains invalid UTF-8", 732 input: &pb3.Maps{ 733 StrToNested: map[string]*pb3.Nested{ 734 "abc\xff": {}, 735 }, 736 }, 737 wantErr: true, 738 }, { 739 desc: "map field contains nil value", 740 input: &pb3.Maps{ 741 StrToNested: map[string]*pb3.Nested{ 742 "nil": nil, 743 }, 744 }, 745 want: `str_to_nested: { 746 key: "nil" 747 value: {} 748} 749`, 750 }, { 751 desc: "required fields not set", 752 input: &pb2.Requireds{}, 753 want: "", 754 wantErr: true, 755 }, { 756 desc: "required fields partially set", 757 input: &pb2.Requireds{ 758 ReqBool: proto.Bool(false), 759 ReqSfixed64: proto.Int64(0xbeefcafe), 760 ReqDouble: proto.Float64(math.NaN()), 761 ReqString: proto.String("hello"), 762 ReqEnum: pb2.Enum_ONE.Enum(), 763 }, 764 want: `req_bool: false 765req_sfixed64: 3203386110 766req_double: nan 767req_string: "hello" 768req_enum: ONE 769`, 770 wantErr: true, 771 }, { 772 desc: "required fields not set with AllowPartial", 773 mo: prototext.MarshalOptions{AllowPartial: true}, 774 input: &pb2.Requireds{ 775 ReqBool: proto.Bool(false), 776 ReqSfixed64: proto.Int64(0xbeefcafe), 777 ReqDouble: proto.Float64(math.NaN()), 778 ReqString: proto.String("hello"), 779 ReqEnum: pb2.Enum_ONE.Enum(), 780 }, 781 want: `req_bool: false 782req_sfixed64: 3203386110 783req_double: nan 784req_string: "hello" 785req_enum: ONE 786`, 787 }, { 788 desc: "required fields all set", 789 input: &pb2.Requireds{ 790 ReqBool: proto.Bool(false), 791 ReqSfixed64: proto.Int64(0), 792 ReqDouble: proto.Float64(1.23), 793 ReqString: proto.String(""), 794 ReqEnum: pb2.Enum_ONE.Enum(), 795 ReqNested: &pb2.Nested{}, 796 }, 797 want: `req_bool: false 798req_sfixed64: 0 799req_double: 1.23 800req_string: "" 801req_enum: ONE 802req_nested: {} 803`, 804 }, { 805 desc: "indirect required field", 806 input: &pb2.IndirectRequired{ 807 OptNested: &pb2.NestedWithRequired{}, 808 }, 809 want: "opt_nested: {}\n", 810 wantErr: true, 811 }, { 812 desc: "indirect required field with AllowPartial", 813 mo: prototext.MarshalOptions{AllowPartial: true}, 814 input: &pb2.IndirectRequired{ 815 OptNested: &pb2.NestedWithRequired{}, 816 }, 817 want: "opt_nested: {}\n", 818 }, { 819 desc: "indirect required field in empty repeated", 820 input: &pb2.IndirectRequired{ 821 RptNested: []*pb2.NestedWithRequired{}, 822 }, 823 want: "", 824 }, { 825 desc: "indirect required field in repeated", 826 input: &pb2.IndirectRequired{ 827 RptNested: []*pb2.NestedWithRequired{ 828 &pb2.NestedWithRequired{}, 829 }, 830 }, 831 want: "rpt_nested: {}\n", 832 wantErr: true, 833 }, { 834 desc: "indirect required field in repeated with AllowPartial", 835 mo: prototext.MarshalOptions{AllowPartial: true}, 836 input: &pb2.IndirectRequired{ 837 RptNested: []*pb2.NestedWithRequired{ 838 &pb2.NestedWithRequired{}, 839 }, 840 }, 841 want: "rpt_nested: {}\n", 842 }, { 843 desc: "indirect required field in empty map", 844 input: &pb2.IndirectRequired{ 845 StrToNested: map[string]*pb2.NestedWithRequired{}, 846 }, 847 want: "", 848 }, { 849 desc: "indirect required field in map", 850 input: &pb2.IndirectRequired{ 851 StrToNested: map[string]*pb2.NestedWithRequired{ 852 "fail": &pb2.NestedWithRequired{}, 853 }, 854 }, 855 want: `str_to_nested: { 856 key: "fail" 857 value: {} 858} 859`, 860 wantErr: true, 861 }, { 862 desc: "indirect required field in map with AllowPartial", 863 mo: prototext.MarshalOptions{AllowPartial: true}, 864 input: &pb2.IndirectRequired{ 865 StrToNested: map[string]*pb2.NestedWithRequired{ 866 "fail": &pb2.NestedWithRequired{}, 867 }, 868 }, 869 want: `str_to_nested: { 870 key: "fail" 871 value: {} 872} 873`, 874 }, { 875 desc: "indirect required field in oneof", 876 input: &pb2.IndirectRequired{ 877 Union: &pb2.IndirectRequired_OneofNested{ 878 OneofNested: &pb2.NestedWithRequired{}, 879 }, 880 }, 881 want: "oneof_nested: {}\n", 882 wantErr: true, 883 }, { 884 desc: "indirect required field in oneof with AllowPartial", 885 mo: prototext.MarshalOptions{AllowPartial: true}, 886 input: &pb2.IndirectRequired{ 887 Union: &pb2.IndirectRequired_OneofNested{ 888 OneofNested: &pb2.NestedWithRequired{}, 889 }, 890 }, 891 want: "oneof_nested: {}\n", 892 }, { 893 desc: "unknown fields not printed", 894 input: func() proto.Message { 895 m := &pb2.Scalars{ 896 OptString: proto.String("this message contains unknown fields"), 897 } 898 m.ProtoReflect().SetUnknown(protopack.Message{ 899 protopack.Tag{101, protopack.VarintType}, protopack.Bool(true), 900 protopack.Tag{102, protopack.VarintType}, protopack.Varint(0xff), 901 protopack.Tag{103, protopack.Fixed32Type}, protopack.Uint32(47), 902 protopack.Tag{104, protopack.Fixed64Type}, protopack.Int64(0xdeadbeef), 903 }.Marshal()) 904 return m 905 }(), 906 want: `opt_string: "this message contains unknown fields" 907`, 908 }, { 909 desc: "unknown varint and fixed types", 910 mo: prototext.MarshalOptions{EmitUnknown: true}, 911 input: func() proto.Message { 912 m := &pb2.Scalars{ 913 OptString: proto.String("this message contains unknown fields"), 914 } 915 m.ProtoReflect().SetUnknown(protopack.Message{ 916 protopack.Tag{101, protopack.VarintType}, protopack.Bool(true), 917 protopack.Tag{102, protopack.VarintType}, protopack.Varint(0xff), 918 protopack.Tag{103, protopack.Fixed32Type}, protopack.Uint32(0x47), 919 protopack.Tag{104, protopack.Fixed64Type}, protopack.Int64(0xdeadbeef), 920 }.Marshal()) 921 return m 922 }(), 923 want: `opt_string: "this message contains unknown fields" 924101: 1 925102: 255 926103: 0x47 927104: 0xdeadbeef 928`, 929 }, { 930 desc: "unknown length-delimited", 931 mo: prototext.MarshalOptions{EmitUnknown: true}, 932 input: func() proto.Message { 933 m := new(pb2.Scalars) 934 m.ProtoReflect().SetUnknown(protopack.Message{ 935 protopack.Tag{101, protopack.BytesType}, protopack.LengthPrefix{protopack.Bool(true), protopack.Bool(false)}, 936 protopack.Tag{102, protopack.BytesType}, protopack.String("hello world"), 937 protopack.Tag{103, protopack.BytesType}, protopack.Bytes("\xe4\xb8\x96\xe7\x95\x8c"), 938 }.Marshal()) 939 return m 940 }(), 941 want: `101: "\x01\x00" 942102: "hello world" 943103: "世界" 944`, 945 }, { 946 desc: "unknown group type", 947 mo: prototext.MarshalOptions{EmitUnknown: true}, 948 input: func() proto.Message { 949 m := new(pb2.Scalars) 950 m.ProtoReflect().SetUnknown(protopack.Message{ 951 protopack.Tag{101, protopack.StartGroupType}, protopack.Tag{101, protopack.EndGroupType}, 952 protopack.Tag{102, protopack.StartGroupType}, 953 protopack.Tag{101, protopack.VarintType}, protopack.Bool(false), 954 protopack.Tag{102, protopack.BytesType}, protopack.String("inside a group"), 955 protopack.Tag{102, protopack.EndGroupType}, 956 }.Marshal()) 957 return m 958 }(), 959 want: `101: {} 960102: { 961 101: 0 962 102: "inside a group" 963} 964`, 965 }, { 966 desc: "unknown unpack repeated field", 967 mo: prototext.MarshalOptions{EmitUnknown: true}, 968 input: func() proto.Message { 969 m := new(pb2.Scalars) 970 m.ProtoReflect().SetUnknown(protopack.Message{ 971 protopack.Tag{101, protopack.BytesType}, protopack.LengthPrefix{protopack.Bool(true), protopack.Bool(false), protopack.Bool(true)}, 972 protopack.Tag{102, protopack.BytesType}, protopack.String("hello"), 973 protopack.Tag{101, protopack.VarintType}, protopack.Bool(true), 974 protopack.Tag{102, protopack.BytesType}, protopack.String("世界"), 975 }.Marshal()) 976 return m 977 }(), 978 want: `101: "\x01\x00\x01" 979102: "hello" 980101: 1 981102: "世界" 982`, 983 }, { 984 desc: "extensions of non-repeated fields", 985 input: func() proto.Message { 986 m := &pb2.Extensions{ 987 OptString: proto.String("non-extension field"), 988 OptBool: proto.Bool(true), 989 OptInt32: proto.Int32(42), 990 } 991 proto.SetExtension(m, pb2.E_OptExtBool, true) 992 proto.SetExtension(m, pb2.E_OptExtString, "extension field") 993 proto.SetExtension(m, pb2.E_OptExtEnum, pb2.Enum_TEN) 994 proto.SetExtension(m, pb2.E_OptExtNested, &pb2.Nested{ 995 OptString: proto.String("nested in an extension"), 996 OptNested: &pb2.Nested{ 997 OptString: proto.String("another nested in an extension"), 998 }, 999 }) 1000 return m 1001 }(), 1002 want: `opt_string: "non-extension field" 1003opt_bool: true 1004opt_int32: 42 1005[pb2.opt_ext_bool]: true 1006[pb2.opt_ext_enum]: TEN 1007[pb2.opt_ext_nested]: { 1008 opt_string: "nested in an extension" 1009 opt_nested: { 1010 opt_string: "another nested in an extension" 1011 } 1012} 1013[pb2.opt_ext_string]: "extension field" 1014`, 1015 }, { 1016 desc: "proto2 extension field contains invalid UTF-8", 1017 input: func() proto.Message { 1018 m := &pb2.Extensions{} 1019 proto.SetExtension(m, pb2.E_OptExtString, "abc\xff") 1020 return m 1021 }(), 1022 want: `[pb2.opt_ext_string]: "abc\xff" 1023`, 1024 }, { 1025 desc: "extension partial returns error", 1026 input: func() proto.Message { 1027 m := &pb2.Extensions{} 1028 proto.SetExtension(m, pb2.E_OptExtPartial, &pb2.PartialRequired{ 1029 OptString: proto.String("partial1"), 1030 }) 1031 proto.SetExtension(m, pb2.E_ExtensionsContainer_OptExtPartial, &pb2.PartialRequired{ 1032 OptString: proto.String("partial2"), 1033 }) 1034 return m 1035 }(), 1036 want: `[pb2.ExtensionsContainer.opt_ext_partial]: { 1037 opt_string: "partial2" 1038} 1039[pb2.opt_ext_partial]: { 1040 opt_string: "partial1" 1041} 1042`, 1043 wantErr: true, 1044 }, { 1045 desc: "extension partial with AllowPartial", 1046 mo: prototext.MarshalOptions{AllowPartial: true}, 1047 input: func() proto.Message { 1048 m := &pb2.Extensions{} 1049 proto.SetExtension(m, pb2.E_OptExtPartial, &pb2.PartialRequired{ 1050 OptString: proto.String("partial1"), 1051 }) 1052 return m 1053 }(), 1054 want: `[pb2.opt_ext_partial]: { 1055 opt_string: "partial1" 1056} 1057`, 1058 }, { 1059 desc: "extensions of repeated fields", 1060 input: func() proto.Message { 1061 m := &pb2.Extensions{} 1062 proto.SetExtension(m, pb2.E_RptExtEnum, []pb2.Enum{pb2.Enum_TEN, 101, pb2.Enum_ONE}) 1063 proto.SetExtension(m, pb2.E_RptExtFixed32, []uint32{42, 47}) 1064 proto.SetExtension(m, pb2.E_RptExtNested, []*pb2.Nested{ 1065 &pb2.Nested{OptString: proto.String("one")}, 1066 &pb2.Nested{OptString: proto.String("two")}, 1067 &pb2.Nested{OptString: proto.String("three")}, 1068 }) 1069 return m 1070 }(), 1071 want: `[pb2.rpt_ext_enum]: TEN 1072[pb2.rpt_ext_enum]: 101 1073[pb2.rpt_ext_enum]: ONE 1074[pb2.rpt_ext_fixed32]: 42 1075[pb2.rpt_ext_fixed32]: 47 1076[pb2.rpt_ext_nested]: { 1077 opt_string: "one" 1078} 1079[pb2.rpt_ext_nested]: { 1080 opt_string: "two" 1081} 1082[pb2.rpt_ext_nested]: { 1083 opt_string: "three" 1084} 1085`, 1086 }, { 1087 desc: "extensions of non-repeated fields in another message", 1088 input: func() proto.Message { 1089 m := &pb2.Extensions{} 1090 proto.SetExtension(m, pb2.E_ExtensionsContainer_OptExtBool, true) 1091 proto.SetExtension(m, pb2.E_ExtensionsContainer_OptExtString, "extension field") 1092 proto.SetExtension(m, pb2.E_ExtensionsContainer_OptExtEnum, pb2.Enum_TEN) 1093 proto.SetExtension(m, pb2.E_ExtensionsContainer_OptExtNested, &pb2.Nested{ 1094 OptString: proto.String("nested in an extension"), 1095 OptNested: &pb2.Nested{ 1096 OptString: proto.String("another nested in an extension"), 1097 }, 1098 }) 1099 return m 1100 }(), 1101 want: `[pb2.ExtensionsContainer.opt_ext_bool]: true 1102[pb2.ExtensionsContainer.opt_ext_enum]: TEN 1103[pb2.ExtensionsContainer.opt_ext_nested]: { 1104 opt_string: "nested in an extension" 1105 opt_nested: { 1106 opt_string: "another nested in an extension" 1107 } 1108} 1109[pb2.ExtensionsContainer.opt_ext_string]: "extension field" 1110`, 1111 }, { 1112 desc: "extensions of repeated fields in another message", 1113 input: func() proto.Message { 1114 m := &pb2.Extensions{ 1115 OptString: proto.String("non-extension field"), 1116 OptBool: proto.Bool(true), 1117 OptInt32: proto.Int32(42), 1118 } 1119 proto.SetExtension(m, pb2.E_ExtensionsContainer_RptExtEnum, []pb2.Enum{pb2.Enum_TEN, 101, pb2.Enum_ONE}) 1120 proto.SetExtension(m, pb2.E_ExtensionsContainer_RptExtString, []string{"hello", "world"}) 1121 proto.SetExtension(m, pb2.E_ExtensionsContainer_RptExtNested, []*pb2.Nested{ 1122 &pb2.Nested{OptString: proto.String("one")}, 1123 &pb2.Nested{OptString: proto.String("two")}, 1124 &pb2.Nested{OptString: proto.String("three")}, 1125 }) 1126 return m 1127 }(), 1128 want: `opt_string: "non-extension field" 1129opt_bool: true 1130opt_int32: 42 1131[pb2.ExtensionsContainer.rpt_ext_enum]: TEN 1132[pb2.ExtensionsContainer.rpt_ext_enum]: 101 1133[pb2.ExtensionsContainer.rpt_ext_enum]: ONE 1134[pb2.ExtensionsContainer.rpt_ext_nested]: { 1135 opt_string: "one" 1136} 1137[pb2.ExtensionsContainer.rpt_ext_nested]: { 1138 opt_string: "two" 1139} 1140[pb2.ExtensionsContainer.rpt_ext_nested]: { 1141 opt_string: "three" 1142} 1143[pb2.ExtensionsContainer.rpt_ext_string]: "hello" 1144[pb2.ExtensionsContainer.rpt_ext_string]: "world" 1145`, 1146 }, { 1147 desc: "MessageSet", 1148 input: func() proto.Message { 1149 m := &pb2.MessageSet{} 1150 proto.SetExtension(m, pb2.E_MessageSetExtension_MessageSetExtension, &pb2.MessageSetExtension{ 1151 OptString: proto.String("a messageset extension"), 1152 }) 1153 proto.SetExtension(m, pb2.E_MessageSetExtension_NotMessageSetExtension, &pb2.MessageSetExtension{ 1154 OptString: proto.String("not a messageset extension"), 1155 }) 1156 proto.SetExtension(m, pb2.E_MessageSetExtension_ExtNested, &pb2.Nested{ 1157 OptString: proto.String("just a regular extension"), 1158 }) 1159 return m 1160 }(), 1161 want: `[pb2.MessageSetExtension.ext_nested]: { 1162 opt_string: "just a regular extension" 1163} 1164[pb2.MessageSetExtension]: { 1165 opt_string: "a messageset extension" 1166} 1167[pb2.MessageSetExtension.not_message_set_extension]: { 1168 opt_string: "not a messageset extension" 1169} 1170`, 1171 skip: !flags.ProtoLegacy, 1172 }, { 1173 desc: "not real MessageSet 1", 1174 input: func() proto.Message { 1175 m := &pb2.FakeMessageSet{} 1176 proto.SetExtension(m, pb2.E_FakeMessageSetExtension_MessageSetExtension, &pb2.FakeMessageSetExtension{ 1177 OptString: proto.String("not a messageset extension"), 1178 }) 1179 return m 1180 }(), 1181 want: `[pb2.FakeMessageSetExtension.message_set_extension]: { 1182 opt_string: "not a messageset extension" 1183} 1184`, 1185 skip: !flags.ProtoLegacy, 1186 }, { 1187 desc: "not real MessageSet 2", 1188 input: func() proto.Message { 1189 m := &pb2.MessageSet{} 1190 proto.SetExtension(m, pb2.E_MessageSetExtension, &pb2.FakeMessageSetExtension{ 1191 OptString: proto.String("another not a messageset extension"), 1192 }) 1193 return m 1194 }(), 1195 want: `[pb2.message_set_extension]: { 1196 opt_string: "another not a messageset extension" 1197} 1198`, 1199 skip: !flags.ProtoLegacy, 1200 }, { 1201 desc: "Any not expanded", 1202 mo: prototext.MarshalOptions{ 1203 Resolver: new(protoregistry.Types), 1204 }, 1205 input: func() proto.Message { 1206 m := &pb2.Nested{ 1207 OptString: proto.String("embedded inside Any"), 1208 OptNested: &pb2.Nested{ 1209 OptString: proto.String("inception"), 1210 }, 1211 } 1212 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m) 1213 if err != nil { 1214 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1215 } 1216 return &anypb.Any{ 1217 TypeUrl: "pb2.Nested", 1218 Value: b, 1219 } 1220 }(), 1221 want: `type_url: "pb2.Nested" 1222value: "\n\x13embedded inside Any\x12\x0b\n\tinception" 1223`, 1224 }, { 1225 desc: "Any expanded", 1226 input: func() proto.Message { 1227 m := &pb2.Nested{ 1228 OptString: proto.String("embedded inside Any"), 1229 OptNested: &pb2.Nested{ 1230 OptString: proto.String("inception"), 1231 }, 1232 } 1233 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m) 1234 if err != nil { 1235 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1236 } 1237 return &anypb.Any{ 1238 TypeUrl: "foo/pb2.Nested", 1239 Value: b, 1240 } 1241 }(), 1242 want: `[foo/pb2.Nested]: { 1243 opt_string: "embedded inside Any" 1244 opt_nested: { 1245 opt_string: "inception" 1246 } 1247} 1248`, 1249 }, { 1250 desc: "Any expanded with missing required", 1251 input: func() proto.Message { 1252 m := &pb2.PartialRequired{ 1253 OptString: proto.String("embedded inside Any"), 1254 } 1255 b, err := proto.MarshalOptions{ 1256 AllowPartial: true, 1257 Deterministic: true, 1258 }.Marshal(m) 1259 if err != nil { 1260 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1261 } 1262 return &anypb.Any{ 1263 TypeUrl: string(m.ProtoReflect().Descriptor().FullName()), 1264 Value: b, 1265 } 1266 }(), 1267 want: `[pb2.PartialRequired]: { 1268 opt_string: "embedded inside Any" 1269} 1270`, 1271 }, { 1272 desc: "Any with invalid value", 1273 input: &anypb.Any{ 1274 TypeUrl: "foo/pb2.Nested", 1275 Value: []byte("\x80"), 1276 }, 1277 want: `type_url: "foo/pb2.Nested" 1278value: "\x80" 1279`, 1280 }, { 1281 desc: "Any expanded in another message", 1282 input: func() *pb2.KnownTypes { 1283 m1 := &pb2.Nested{ 1284 OptString: proto.String("message inside Any of another Any field"), 1285 } 1286 b1, err := proto.MarshalOptions{Deterministic: true}.Marshal(m1) 1287 if err != nil { 1288 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1289 } 1290 m2 := &anypb.Any{ 1291 TypeUrl: "pb2.Nested", 1292 Value: b1, 1293 } 1294 b2, err := proto.MarshalOptions{Deterministic: true}.Marshal(m2) 1295 if err != nil { 1296 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1297 } 1298 return &pb2.KnownTypes{ 1299 OptAny: &anypb.Any{ 1300 TypeUrl: "google.protobuf.Any", 1301 Value: b2, 1302 }, 1303 } 1304 }(), 1305 want: `opt_any: { 1306 [google.protobuf.Any]: { 1307 [pb2.Nested]: { 1308 opt_string: "message inside Any of another Any field" 1309 } 1310 } 1311} 1312`, 1313 }, { 1314 desc: "Any expanded with invalid UTF-8 in proto2", 1315 input: func() *pb2.KnownTypes { 1316 m := &pb2.Nested{ 1317 OptString: proto.String("invalid UTF-8 abc\xff"), 1318 } 1319 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m) 1320 if err != nil { 1321 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1322 } 1323 return &pb2.KnownTypes{ 1324 OptAny: &anypb.Any{ 1325 TypeUrl: "pb2.Nested", 1326 Value: b, 1327 }, 1328 } 1329 }(), 1330 want: `opt_any: { 1331 [pb2.Nested]: { 1332 opt_string: "invalid UTF-8 abc\xff" 1333 } 1334} 1335`, 1336 }, { 1337 desc: "Any not expanded due to invalid data", 1338 mo: prototext.MarshalOptions{EmitASCII: true}, 1339 input: func() *pb2.KnownTypes { 1340 return &pb2.KnownTypes{ 1341 OptAny: &anypb.Any{ 1342 TypeUrl: "pb3.Scalar", 1343 Value: []byte("\xde\xad\xbe\xef"), 1344 }, 1345 } 1346 }(), 1347 want: `opt_any: { 1348 type_url: "pb3.Scalar" 1349 value: "\u07ad\xbe\xef" 1350} 1351`, 1352 }, { 1353 desc: "Any inside Any expanded", 1354 input: func() *pb2.KnownTypes { 1355 m1 := &pb2.Nested{ 1356 OptString: proto.String("invalid UTF-8 abc\xff"), 1357 } 1358 b1, err := proto.MarshalOptions{Deterministic: true}.Marshal(m1) 1359 if err != nil { 1360 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1361 } 1362 m2 := &anypb.Any{ 1363 TypeUrl: "pb2.Nested", 1364 Value: b1, 1365 } 1366 b2, err := proto.MarshalOptions{Deterministic: true}.Marshal(m2) 1367 if err != nil { 1368 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1369 } 1370 return &pb2.KnownTypes{ 1371 OptAny: &anypb.Any{ 1372 TypeUrl: "google.protobuf.Any", 1373 Value: b2, 1374 }, 1375 } 1376 }(), 1377 want: `opt_any: { 1378 [google.protobuf.Any]: { 1379 [pb2.Nested]: { 1380 opt_string: "invalid UTF-8 abc\xff" 1381 } 1382 } 1383} 1384`, 1385 }, { 1386 desc: "Any inside Any not expanded due to invalid data", 1387 mo: prototext.MarshalOptions{EmitASCII: true}, 1388 input: func() *pb2.KnownTypes { 1389 m := &anypb.Any{ 1390 TypeUrl: "pb2.Nested", 1391 Value: []byte("\xde\xad\xbe\xef"), 1392 } 1393 b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m) 1394 if err != nil { 1395 t.Fatalf("error in binary marshaling message for Any.value: %v", err) 1396 } 1397 return &pb2.KnownTypes{ 1398 OptAny: &anypb.Any{ 1399 TypeUrl: "google.protobuf.Any", 1400 Value: b, 1401 }, 1402 } 1403 }(), 1404 want: `opt_any: { 1405 [google.protobuf.Any]: { 1406 type_url: "pb2.Nested" 1407 value: "\u07ad\xbe\xef" 1408 } 1409} 1410`, 1411 }} 1412 1413 for _, tt := range tests { 1414 tt := tt 1415 if tt.skip { 1416 continue 1417 } 1418 t.Run(tt.desc, func(t *testing.T) { 1419 // Use 2-space indentation on all MarshalOptions. 1420 tt.mo.Indent = " " 1421 b, err := tt.mo.Marshal(tt.input) 1422 if err != nil && !tt.wantErr { 1423 t.Errorf("Marshal() returned error: %v\n", err) 1424 } 1425 if err == nil && tt.wantErr { 1426 t.Error("Marshal() got nil error, want error\n") 1427 } 1428 got := string(b) 1429 if tt.want != "" && got != tt.want { 1430 t.Errorf("Marshal()\n<got>\n%v\n<want>\n%v\n", got, tt.want) 1431 if diff := cmp.Diff(tt.want, got); diff != "" { 1432 t.Errorf("Marshal() diff -want +got\n%v\n", diff) 1433 } 1434 } 1435 }) 1436 } 1437} 1438