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 "fmt" 36 "math" 37 "testing" 38 39 . "github.com/golang/protobuf/proto" 40 proto3pb "github.com/golang/protobuf/proto/proto3_proto" 41 . "github.com/golang/protobuf/proto/test_proto" 42) 43 44type UnmarshalTextTest struct { 45 in string 46 err string // if "", no error expected 47 out *MyMessage 48} 49 50func buildExtStructTest(text string) UnmarshalTextTest { 51 msg := &MyMessage{ 52 Count: Int32(42), 53 } 54 SetExtension(msg, E_Ext_More, &Ext{ 55 Data: String("Hello, world!"), 56 }) 57 return UnmarshalTextTest{in: text, out: msg} 58} 59 60func buildExtDataTest(text string) UnmarshalTextTest { 61 msg := &MyMessage{ 62 Count: Int32(42), 63 } 64 SetExtension(msg, E_Ext_Text, String("Hello, world!")) 65 SetExtension(msg, E_Ext_Number, Int32(1729)) 66 return UnmarshalTextTest{in: text, out: msg} 67} 68 69func buildExtRepStringTest(text string) UnmarshalTextTest { 70 msg := &MyMessage{ 71 Count: Int32(42), 72 } 73 if err := SetExtension(msg, E_Greeting, []string{"bula", "hola"}); err != nil { 74 panic(err) 75 } 76 return UnmarshalTextTest{in: text, out: msg} 77} 78 79var unMarshalTextTests = []UnmarshalTextTest{ 80 // Basic 81 { 82 in: " count:42\n name:\"Dave\" ", 83 out: &MyMessage{ 84 Count: Int32(42), 85 Name: String("Dave"), 86 }, 87 }, 88 89 // Empty quoted string 90 { 91 in: `count:42 name:""`, 92 out: &MyMessage{ 93 Count: Int32(42), 94 Name: String(""), 95 }, 96 }, 97 98 // Quoted string concatenation with double quotes 99 { 100 in: `count:42 name: "My name is "` + "\n" + `"elsewhere"`, 101 out: &MyMessage{ 102 Count: Int32(42), 103 Name: String("My name is elsewhere"), 104 }, 105 }, 106 107 // Quoted string concatenation with single quotes 108 { 109 in: "count:42 name: 'My name is '\n'elsewhere'", 110 out: &MyMessage{ 111 Count: Int32(42), 112 Name: String("My name is elsewhere"), 113 }, 114 }, 115 116 // Quoted string concatenations with mixed quotes 117 { 118 in: "count:42 name: 'My name is '\n\"elsewhere\"", 119 out: &MyMessage{ 120 Count: Int32(42), 121 Name: String("My name is elsewhere"), 122 }, 123 }, 124 { 125 in: "count:42 name: \"My name is \"\n'elsewhere'", 126 out: &MyMessage{ 127 Count: Int32(42), 128 Name: String("My name is elsewhere"), 129 }, 130 }, 131 132 // Quoted string with escaped apostrophe 133 { 134 in: `count:42 name: "HOLIDAY - New Year\'s Day"`, 135 out: &MyMessage{ 136 Count: Int32(42), 137 Name: String("HOLIDAY - New Year's Day"), 138 }, 139 }, 140 141 // Quoted string with single quote 142 { 143 in: `count:42 name: 'Roger "The Ramster" Ramjet'`, 144 out: &MyMessage{ 145 Count: Int32(42), 146 Name: String(`Roger "The Ramster" Ramjet`), 147 }, 148 }, 149 150 // Quoted string with all the accepted special characters from the C++ test 151 { 152 in: `count:42 name: ` + "\"\\\"A string with \\' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"", 153 out: &MyMessage{ 154 Count: Int32(42), 155 Name: String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces"), 156 }, 157 }, 158 159 // Quoted string with quoted backslash 160 { 161 in: `count:42 name: "\\'xyz"`, 162 out: &MyMessage{ 163 Count: Int32(42), 164 Name: String(`\'xyz`), 165 }, 166 }, 167 168 // Quoted string with UTF-8 bytes. 169 { 170 in: "count:42 name: '\303\277\302\201\x00\xAB\xCD\xEF'", 171 out: &MyMessage{ 172 Count: Int32(42), 173 Name: String("\303\277\302\201\x00\xAB\xCD\xEF"), 174 }, 175 }, 176 177 // Quoted string with unicode escapes. 178 { 179 in: `count: 42 name: "\u0047\U00000047\uffff\U0010ffff"`, 180 out: &MyMessage{ 181 Count: Int32(42), 182 Name: String("GG\uffff\U0010ffff"), 183 }, 184 }, 185 186 // Bad quoted string 187 { 188 in: `inner: < host: "\0" >` + "\n", 189 err: `line 1.15: invalid quoted string "\0": \0 requires 2 following digits`, 190 }, 191 192 // Bad \u escape 193 { 194 in: `count: 42 name: "\u000"`, 195 err: `line 1.16: invalid quoted string "\u000": \u requires 4 following digits`, 196 }, 197 198 // Bad \U escape 199 { 200 in: `count: 42 name: "\U0000000"`, 201 err: `line 1.16: invalid quoted string "\U0000000": \U requires 8 following digits`, 202 }, 203 204 // Bad \U escape 205 { 206 in: `count: 42 name: "\xxx"`, 207 err: `line 1.16: invalid quoted string "\xxx": \xxx contains non-hexadecimal digits`, 208 }, 209 210 // Number too large for int64 211 { 212 in: "count: 1 others { key: 123456789012345678901 }", 213 err: "line 1.23: invalid int64: 123456789012345678901", 214 }, 215 216 // Number too large for int32 217 { 218 in: "count: 1234567890123", 219 err: "line 1.7: invalid int32: 1234567890123", 220 }, 221 222 // Number in hexadecimal 223 { 224 in: "count: 0x2beef", 225 out: &MyMessage{ 226 Count: Int32(0x2beef), 227 }, 228 }, 229 230 // Number in octal 231 { 232 in: "count: 024601", 233 out: &MyMessage{ 234 Count: Int32(024601), 235 }, 236 }, 237 238 // Floating point number with "f" suffix 239 { 240 in: "count: 4 others:< weight: 17.0f >", 241 out: &MyMessage{ 242 Count: Int32(4), 243 Others: []*OtherMessage{ 244 { 245 Weight: Float32(17), 246 }, 247 }, 248 }, 249 }, 250 251 // Floating point positive infinity 252 { 253 in: "count: 4 bigfloat: inf", 254 out: &MyMessage{ 255 Count: Int32(4), 256 Bigfloat: Float64(math.Inf(1)), 257 }, 258 }, 259 260 // Floating point negative infinity 261 { 262 in: "count: 4 bigfloat: -inf", 263 out: &MyMessage{ 264 Count: Int32(4), 265 Bigfloat: Float64(math.Inf(-1)), 266 }, 267 }, 268 269 // Number too large for float32 270 { 271 in: "others:< weight: 12345678901234567890123456789012345678901234567890 >", 272 err: "line 1.17: invalid float32: 12345678901234567890123456789012345678901234567890", 273 }, 274 275 // Number posing as a quoted string 276 { 277 in: `inner: < host: 12 >` + "\n", 278 err: `line 1.15: invalid string: 12`, 279 }, 280 281 // Quoted string posing as int32 282 { 283 in: `count: "12"`, 284 err: `line 1.7: invalid int32: "12"`, 285 }, 286 287 // Quoted string posing a float32 288 { 289 in: `others:< weight: "17.4" >`, 290 err: `line 1.17: invalid float32: "17.4"`, 291 }, 292 293 // unclosed bracket doesn't cause infinite loop 294 { 295 in: `[`, 296 err: `line 1.0: unclosed type_url or extension name`, 297 }, 298 299 // Enum 300 { 301 in: `count:42 bikeshed: BLUE`, 302 out: &MyMessage{ 303 Count: Int32(42), 304 Bikeshed: MyMessage_BLUE.Enum(), 305 }, 306 }, 307 308 // Repeated field 309 { 310 in: `count:42 pet: "horsey" pet:"bunny"`, 311 out: &MyMessage{ 312 Count: Int32(42), 313 Pet: []string{"horsey", "bunny"}, 314 }, 315 }, 316 317 // Repeated field with list notation 318 { 319 in: `count:42 pet: ["horsey", "bunny"]`, 320 out: &MyMessage{ 321 Count: Int32(42), 322 Pet: []string{"horsey", "bunny"}, 323 }, 324 }, 325 326 // Repeated message with/without colon and <>/{} 327 { 328 in: `count:42 others:{} others{} others:<> others:{}`, 329 out: &MyMessage{ 330 Count: Int32(42), 331 Others: []*OtherMessage{ 332 {}, 333 {}, 334 {}, 335 {}, 336 }, 337 }, 338 }, 339 340 // Missing colon for inner message 341 { 342 in: `count:42 inner < host: "cauchy.syd" >`, 343 out: &MyMessage{ 344 Count: Int32(42), 345 Inner: &InnerMessage{ 346 Host: String("cauchy.syd"), 347 }, 348 }, 349 }, 350 351 // Missing colon for string field 352 { 353 in: `name "Dave"`, 354 err: `line 1.5: expected ':', found "\"Dave\""`, 355 }, 356 357 // Missing colon for int32 field 358 { 359 in: `count 42`, 360 err: `line 1.6: expected ':', found "42"`, 361 }, 362 363 // Missing required field 364 { 365 in: `name: "Pawel"`, 366 err: fmt.Sprintf(`proto: required field "%T.count" not set`, MyMessage{}), 367 out: &MyMessage{ 368 Name: String("Pawel"), 369 }, 370 }, 371 372 // Missing required field in a required submessage 373 { 374 in: `count: 42 we_must_go_deeper < leo_finally_won_an_oscar <> >`, 375 err: fmt.Sprintf(`proto: required field "%T.host" not set`, InnerMessage{}), 376 out: &MyMessage{ 377 Count: Int32(42), 378 WeMustGoDeeper: &RequiredInnerMessage{LeoFinallyWonAnOscar: &InnerMessage{}}, 379 }, 380 }, 381 382 // Repeated non-repeated field 383 { 384 in: `name: "Rob" name: "Russ"`, 385 err: `line 1.12: non-repeated field "name" was repeated`, 386 }, 387 388 // Group 389 { 390 in: `count: 17 SomeGroup { group_field: 12 }`, 391 out: &MyMessage{ 392 Count: Int32(17), 393 Somegroup: &MyMessage_SomeGroup{ 394 GroupField: Int32(12), 395 }, 396 }, 397 }, 398 399 // Semicolon between fields 400 { 401 in: `count:3;name:"Calvin"`, 402 out: &MyMessage{ 403 Count: Int32(3), 404 Name: String("Calvin"), 405 }, 406 }, 407 // Comma between fields 408 { 409 in: `count:4,name:"Ezekiel"`, 410 out: &MyMessage{ 411 Count: Int32(4), 412 Name: String("Ezekiel"), 413 }, 414 }, 415 416 // Boolean false 417 { 418 in: `count:42 inner { host: "example.com" connected: false }`, 419 out: &MyMessage{ 420 Count: Int32(42), 421 Inner: &InnerMessage{ 422 Host: String("example.com"), 423 Connected: Bool(false), 424 }, 425 }, 426 }, 427 // Boolean true 428 { 429 in: `count:42 inner { host: "example.com" connected: true }`, 430 out: &MyMessage{ 431 Count: Int32(42), 432 Inner: &InnerMessage{ 433 Host: String("example.com"), 434 Connected: Bool(true), 435 }, 436 }, 437 }, 438 // Boolean 0 439 { 440 in: `count:42 inner { host: "example.com" connected: 0 }`, 441 out: &MyMessage{ 442 Count: Int32(42), 443 Inner: &InnerMessage{ 444 Host: String("example.com"), 445 Connected: Bool(false), 446 }, 447 }, 448 }, 449 // Boolean 1 450 { 451 in: `count:42 inner { host: "example.com" connected: 1 }`, 452 out: &MyMessage{ 453 Count: Int32(42), 454 Inner: &InnerMessage{ 455 Host: String("example.com"), 456 Connected: Bool(true), 457 }, 458 }, 459 }, 460 // Boolean f 461 { 462 in: `count:42 inner { host: "example.com" connected: f }`, 463 out: &MyMessage{ 464 Count: Int32(42), 465 Inner: &InnerMessage{ 466 Host: String("example.com"), 467 Connected: Bool(false), 468 }, 469 }, 470 }, 471 // Boolean t 472 { 473 in: `count:42 inner { host: "example.com" connected: t }`, 474 out: &MyMessage{ 475 Count: Int32(42), 476 Inner: &InnerMessage{ 477 Host: String("example.com"), 478 Connected: Bool(true), 479 }, 480 }, 481 }, 482 // Boolean False 483 { 484 in: `count:42 inner { host: "example.com" connected: False }`, 485 out: &MyMessage{ 486 Count: Int32(42), 487 Inner: &InnerMessage{ 488 Host: String("example.com"), 489 Connected: Bool(false), 490 }, 491 }, 492 }, 493 // Boolean True 494 { 495 in: `count:42 inner { host: "example.com" connected: True }`, 496 out: &MyMessage{ 497 Count: Int32(42), 498 Inner: &InnerMessage{ 499 Host: String("example.com"), 500 Connected: Bool(true), 501 }, 502 }, 503 }, 504 505 // Extension 506 buildExtStructTest(`count: 42 [test_proto.Ext.more]:<data:"Hello, world!" >`), 507 buildExtStructTest(`count: 42 [test_proto.Ext.more] {data:"Hello, world!"}`), 508 buildExtDataTest(`count: 42 [test_proto.Ext.text]:"Hello, world!" [test_proto.Ext.number]:1729`), 509 buildExtRepStringTest(`count: 42 [test_proto.greeting]:"bula" [test_proto.greeting]:"hola"`), 510 511 // Big all-in-one 512 { 513 in: "count:42 # Meaning\n" + 514 `name:"Dave" ` + 515 `quote:"\"I didn't want to go.\"" ` + 516 `pet:"bunny" ` + 517 `pet:"kitty" ` + 518 `pet:"horsey" ` + 519 `inner:<` + 520 ` host:"footrest.syd" ` + 521 ` port:7001 ` + 522 ` connected:true ` + 523 `> ` + 524 `others:<` + 525 ` key:3735928559 ` + 526 ` value:"\x01A\a\f" ` + 527 `> ` + 528 `others:<` + 529 " weight:58.9 # Atomic weight of Co\n" + 530 ` inner:<` + 531 ` host:"lesha.mtv" ` + 532 ` port:8002 ` + 533 ` >` + 534 `>`, 535 out: &MyMessage{ 536 Count: Int32(42), 537 Name: String("Dave"), 538 Quote: String(`"I didn't want to go."`), 539 Pet: []string{"bunny", "kitty", "horsey"}, 540 Inner: &InnerMessage{ 541 Host: String("footrest.syd"), 542 Port: Int32(7001), 543 Connected: Bool(true), 544 }, 545 Others: []*OtherMessage{ 546 { 547 Key: Int64(3735928559), 548 Value: []byte{0x1, 'A', '\a', '\f'}, 549 }, 550 { 551 Weight: Float32(58.9), 552 Inner: &InnerMessage{ 553 Host: String("lesha.mtv"), 554 Port: Int32(8002), 555 }, 556 }, 557 }, 558 }, 559 }, 560} 561 562func TestUnmarshalText(t *testing.T) { 563 for i, test := range unMarshalTextTests { 564 pb := new(MyMessage) 565 err := UnmarshalText(test.in, pb) 566 if test.err == "" { 567 // We don't expect failure. 568 if err != nil { 569 t.Errorf("Test %d: Unexpected error: %v", i, err) 570 } else if !Equal(pb, test.out) { 571 t.Errorf("Test %d: Incorrect populated \nHave: %v\nWant: %v", 572 i, pb, test.out) 573 } 574 } else { 575 // We do expect failure. 576 if err == nil { 577 t.Errorf("Test %d: Didn't get expected error: %v", i, test.err) 578 } else if err.Error() != test.err { 579 t.Errorf("Test %d: Incorrect error.\nHave: %v\nWant: %v", 580 i, err.Error(), test.err) 581 } else if _, ok := err.(*RequiredNotSetError); ok && test.out != nil && !Equal(pb, test.out) { 582 t.Errorf("Test %d: Incorrect populated \nHave: %v\nWant: %v", 583 i, pb, test.out) 584 } 585 } 586 } 587} 588 589func TestUnmarshalTextCustomMessage(t *testing.T) { 590 msg := &textMessage{} 591 if err := UnmarshalText("custom", msg); err != nil { 592 t.Errorf("Unexpected error from custom unmarshal: %v", err) 593 } 594 if UnmarshalText("not custom", msg) == nil { 595 t.Errorf("Didn't get expected error from custom unmarshal") 596 } 597} 598 599// Regression test; this caused a panic. 600func TestRepeatedEnum(t *testing.T) { 601 pb := new(RepeatedEnum) 602 if err := UnmarshalText("color: RED", pb); err != nil { 603 t.Fatal(err) 604 } 605 exp := &RepeatedEnum{ 606 Color: []RepeatedEnum_Color{RepeatedEnum_RED}, 607 } 608 if !Equal(pb, exp) { 609 t.Errorf("Incorrect populated \nHave: %v\nWant: %v", pb, exp) 610 } 611} 612 613func TestProto3TextParsing(t *testing.T) { 614 m := new(proto3pb.Message) 615 const in = `name: "Wallace" true_scotsman: true` 616 want := &proto3pb.Message{ 617 Name: "Wallace", 618 TrueScotsman: true, 619 } 620 if err := UnmarshalText(in, m); err != nil { 621 t.Fatal(err) 622 } 623 if !Equal(m, want) { 624 t.Errorf("\n got %v\nwant %v", m, want) 625 } 626} 627 628func TestMapParsing(t *testing.T) { 629 m := new(MessageWithMap) 630 const in = `name_mapping:<key:1234 value:"Feist"> name_mapping:<key:1 value:"Beatles">` + 631 `msg_mapping:<key:-4, value:<f: 2.0>,>` + // separating commas are okay 632 `msg_mapping<key:-2 value<f: 4.0>>` + // no colon after "value" 633 `msg_mapping:<value:<f: 5.0>>` + // omitted key 634 `msg_mapping:<key:1>` + // omitted value 635 `byte_mapping:<key:true value:"so be it">` + 636 `byte_mapping:<>` // omitted key and value 637 want := &MessageWithMap{ 638 NameMapping: map[int32]string{ 639 1: "Beatles", 640 1234: "Feist", 641 }, 642 MsgMapping: map[int64]*FloatingPoint{ 643 -4: {F: Float64(2.0)}, 644 -2: {F: Float64(4.0)}, 645 0: {F: Float64(5.0)}, 646 1: nil, 647 }, 648 ByteMapping: map[bool][]byte{ 649 false: nil, 650 true: []byte("so be it"), 651 }, 652 } 653 if err := UnmarshalText(in, m); err != nil { 654 t.Fatal(err) 655 } 656 if !Equal(m, want) { 657 t.Errorf("\n got %v\nwant %v", m, want) 658 } 659} 660 661func TestOneofParsing(t *testing.T) { 662 const in = `name:"Shrek"` 663 m := new(Communique) 664 want := &Communique{Union: &Communique_Name{"Shrek"}} 665 if err := UnmarshalText(in, m); err != nil { 666 t.Fatal(err) 667 } 668 if !Equal(m, want) { 669 t.Errorf("\n got %v\nwant %v", m, want) 670 } 671 672 const inOverwrite = `name:"Shrek" number:42` 673 m = new(Communique) 674 testErr := "line 1.13: field 'number' would overwrite already parsed oneof 'Union'" 675 if err := UnmarshalText(inOverwrite, m); err == nil { 676 t.Errorf("TestOneofParsing: Didn't get expected error: %v", testErr) 677 } else if err.Error() != testErr { 678 t.Errorf("TestOneofParsing: Incorrect error.\nHave: %v\nWant: %v", 679 err.Error(), testErr) 680 } 681 682} 683 684var benchInput string 685 686func init() { 687 benchInput = "count: 4\n" 688 for i := 0; i < 1000; i++ { 689 benchInput += "pet: \"fido\"\n" 690 } 691 692 // Check it is valid input. 693 pb := new(MyMessage) 694 err := UnmarshalText(benchInput, pb) 695 if err != nil { 696 panic("Bad benchmark input: " + err.Error()) 697 } 698} 699 700func BenchmarkUnmarshalText(b *testing.B) { 701 pb := new(MyMessage) 702 for i := 0; i < b.N; i++ { 703 UnmarshalText(benchInput, pb) 704 } 705 b.SetBytes(int64(len(benchInput))) 706} 707