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 32 package proto_test 33 34 import ( 35 "math" 36 "reflect" 37 "testing" 38 39 . "github.com/golang/protobuf/proto" 40 proto3pb "github.com/golang/protobuf/proto/proto3_proto" 41 . "github.com/golang/protobuf/proto/testdata" 42 ) 43 44 type UnmarshalTextTest struct { 45 in string 46 err string // if "", no error expected 47 out *MyMessage 48 } 49 50 func 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 60 func 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 69 func 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 79 var 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\xAB'", 171 out: &MyMessage{ 172 Count: Int32(42), 173 Name: String("\303\277\302\201\xAB"), 174 }, 175 }, 176 177 // Bad quoted string 178 { 179 in: `inner: < host: "\0" >` + "\n", 180 err: `line 1.15: invalid quoted string "\0": \0 requires 2 following digits`, 181 }, 182 183 // Number too large for int64 184 { 185 in: "count: 1 others { key: 123456789012345678901 }", 186 err: "line 1.23: invalid int64: 123456789012345678901", 187 }, 188 189 // Number too large for int32 190 { 191 in: "count: 1234567890123", 192 err: "line 1.7: invalid int32: 1234567890123", 193 }, 194 195 // Number in hexadecimal 196 { 197 in: "count: 0x2beef", 198 out: &MyMessage{ 199 Count: Int32(0x2beef), 200 }, 201 }, 202 203 // Number in octal 204 { 205 in: "count: 024601", 206 out: &MyMessage{ 207 Count: Int32(024601), 208 }, 209 }, 210 211 // Floating point number with "f" suffix 212 { 213 in: "count: 4 others:< weight: 17.0f >", 214 out: &MyMessage{ 215 Count: Int32(4), 216 Others: []*OtherMessage{ 217 { 218 Weight: Float32(17), 219 }, 220 }, 221 }, 222 }, 223 224 // Floating point positive infinity 225 { 226 in: "count: 4 bigfloat: inf", 227 out: &MyMessage{ 228 Count: Int32(4), 229 Bigfloat: Float64(math.Inf(1)), 230 }, 231 }, 232 233 // Floating point negative infinity 234 { 235 in: "count: 4 bigfloat: -inf", 236 out: &MyMessage{ 237 Count: Int32(4), 238 Bigfloat: Float64(math.Inf(-1)), 239 }, 240 }, 241 242 // Number too large for float32 243 { 244 in: "others:< weight: 12345678901234567890123456789012345678901234567890 >", 245 err: "line 1.17: invalid float32: 12345678901234567890123456789012345678901234567890", 246 }, 247 248 // Number posing as a quoted string 249 { 250 in: `inner: < host: 12 >` + "\n", 251 err: `line 1.15: invalid string: 12`, 252 }, 253 254 // Quoted string posing as int32 255 { 256 in: `count: "12"`, 257 err: `line 1.7: invalid int32: "12"`, 258 }, 259 260 // Quoted string posing a float32 261 { 262 in: `others:< weight: "17.4" >`, 263 err: `line 1.17: invalid float32: "17.4"`, 264 }, 265 266 // Enum 267 { 268 in: `count:42 bikeshed: BLUE`, 269 out: &MyMessage{ 270 Count: Int32(42), 271 Bikeshed: MyMessage_BLUE.Enum(), 272 }, 273 }, 274 275 // Repeated field 276 { 277 in: `count:42 pet: "horsey" pet:"bunny"`, 278 out: &MyMessage{ 279 Count: Int32(42), 280 Pet: []string{"horsey", "bunny"}, 281 }, 282 }, 283 284 // Repeated field with list notation 285 { 286 in: `count:42 pet: ["horsey", "bunny"]`, 287 out: &MyMessage{ 288 Count: Int32(42), 289 Pet: []string{"horsey", "bunny"}, 290 }, 291 }, 292 293 // Repeated message with/without colon and <>/{} 294 { 295 in: `count:42 others:{} others{} others:<> others:{}`, 296 out: &MyMessage{ 297 Count: Int32(42), 298 Others: []*OtherMessage{ 299 {}, 300 {}, 301 {}, 302 {}, 303 }, 304 }, 305 }, 306 307 // Missing colon for inner message 308 { 309 in: `count:42 inner < host: "cauchy.syd" >`, 310 out: &MyMessage{ 311 Count: Int32(42), 312 Inner: &InnerMessage{ 313 Host: String("cauchy.syd"), 314 }, 315 }, 316 }, 317 318 // Missing colon for string field 319 { 320 in: `name "Dave"`, 321 err: `line 1.5: expected ':', found "\"Dave\""`, 322 }, 323 324 // Missing colon for int32 field 325 { 326 in: `count 42`, 327 err: `line 1.6: expected ':', found "42"`, 328 }, 329 330 // Missing required field 331 { 332 in: `name: "Pawel"`, 333 err: `proto: required field "testdata.MyMessage.count" not set`, 334 out: &MyMessage{ 335 Name: String("Pawel"), 336 }, 337 }, 338 339 // Missing required field in a required submessage 340 { 341 in: `count: 42 we_must_go_deeper < leo_finally_won_an_oscar <> >`, 342 err: `proto: required field "testdata.InnerMessage.host" not set`, 343 out: &MyMessage{ 344 Count: Int32(42), 345 WeMustGoDeeper: &RequiredInnerMessage{LeoFinallyWonAnOscar: &InnerMessage{}}, 346 }, 347 }, 348 349 // Repeated non-repeated field 350 { 351 in: `name: "Rob" name: "Russ"`, 352 err: `line 1.12: non-repeated field "name" was repeated`, 353 }, 354 355 // Group 356 { 357 in: `count: 17 SomeGroup { group_field: 12 }`, 358 out: &MyMessage{ 359 Count: Int32(17), 360 Somegroup: &MyMessage_SomeGroup{ 361 GroupField: Int32(12), 362 }, 363 }, 364 }, 365 366 // Semicolon between fields 367 { 368 in: `count:3;name:"Calvin"`, 369 out: &MyMessage{ 370 Count: Int32(3), 371 Name: String("Calvin"), 372 }, 373 }, 374 // Comma between fields 375 { 376 in: `count:4,name:"Ezekiel"`, 377 out: &MyMessage{ 378 Count: Int32(4), 379 Name: String("Ezekiel"), 380 }, 381 }, 382 383 // Boolean false 384 { 385 in: `count:42 inner { host: "example.com" connected: false }`, 386 out: &MyMessage{ 387 Count: Int32(42), 388 Inner: &InnerMessage{ 389 Host: String("example.com"), 390 Connected: Bool(false), 391 }, 392 }, 393 }, 394 // Boolean true 395 { 396 in: `count:42 inner { host: "example.com" connected: true }`, 397 out: &MyMessage{ 398 Count: Int32(42), 399 Inner: &InnerMessage{ 400 Host: String("example.com"), 401 Connected: Bool(true), 402 }, 403 }, 404 }, 405 // Boolean 0 406 { 407 in: `count:42 inner { host: "example.com" connected: 0 }`, 408 out: &MyMessage{ 409 Count: Int32(42), 410 Inner: &InnerMessage{ 411 Host: String("example.com"), 412 Connected: Bool(false), 413 }, 414 }, 415 }, 416 // Boolean 1 417 { 418 in: `count:42 inner { host: "example.com" connected: 1 }`, 419 out: &MyMessage{ 420 Count: Int32(42), 421 Inner: &InnerMessage{ 422 Host: String("example.com"), 423 Connected: Bool(true), 424 }, 425 }, 426 }, 427 // Boolean f 428 { 429 in: `count:42 inner { host: "example.com" connected: f }`, 430 out: &MyMessage{ 431 Count: Int32(42), 432 Inner: &InnerMessage{ 433 Host: String("example.com"), 434 Connected: Bool(false), 435 }, 436 }, 437 }, 438 // Boolean t 439 { 440 in: `count:42 inner { host: "example.com" connected: t }`, 441 out: &MyMessage{ 442 Count: Int32(42), 443 Inner: &InnerMessage{ 444 Host: String("example.com"), 445 Connected: Bool(true), 446 }, 447 }, 448 }, 449 // Boolean False 450 { 451 in: `count:42 inner { host: "example.com" connected: False }`, 452 out: &MyMessage{ 453 Count: Int32(42), 454 Inner: &InnerMessage{ 455 Host: String("example.com"), 456 Connected: Bool(false), 457 }, 458 }, 459 }, 460 // Boolean True 461 { 462 in: `count:42 inner { host: "example.com" connected: True }`, 463 out: &MyMessage{ 464 Count: Int32(42), 465 Inner: &InnerMessage{ 466 Host: String("example.com"), 467 Connected: Bool(true), 468 }, 469 }, 470 }, 471 472 // Extension 473 buildExtStructTest(`count: 42 [testdata.Ext.more]:<data:"Hello, world!" >`), 474 buildExtStructTest(`count: 42 [testdata.Ext.more] {data:"Hello, world!"}`), 475 buildExtDataTest(`count: 42 [testdata.Ext.text]:"Hello, world!" [testdata.Ext.number]:1729`), 476 buildExtRepStringTest(`count: 42 [testdata.greeting]:"bula" [testdata.greeting]:"hola"`), 477 478 // Big all-in-one 479 { 480 in: "count:42 # Meaning\n" + 481 `name:"Dave" ` + 482 `quote:"\"I didn't want to go.\"" ` + 483 `pet:"bunny" ` + 484 `pet:"kitty" ` + 485 `pet:"horsey" ` + 486 `inner:<` + 487 ` host:"footrest.syd" ` + 488 ` port:7001 ` + 489 ` connected:true ` + 490 `> ` + 491 `others:<` + 492 ` key:3735928559 ` + 493 ` value:"\x01A\a\f" ` + 494 `> ` + 495 `others:<` + 496 " weight:58.9 # Atomic weight of Co\n" + 497 ` inner:<` + 498 ` host:"lesha.mtv" ` + 499 ` port:8002 ` + 500 ` >` + 501 `>`, 502 out: &MyMessage{ 503 Count: Int32(42), 504 Name: String("Dave"), 505 Quote: String(`"I didn't want to go."`), 506 Pet: []string{"bunny", "kitty", "horsey"}, 507 Inner: &InnerMessage{ 508 Host: String("footrest.syd"), 509 Port: Int32(7001), 510 Connected: Bool(true), 511 }, 512 Others: []*OtherMessage{ 513 { 514 Key: Int64(3735928559), 515 Value: []byte{0x1, 'A', '\a', '\f'}, 516 }, 517 { 518 Weight: Float32(58.9), 519 Inner: &InnerMessage{ 520 Host: String("lesha.mtv"), 521 Port: Int32(8002), 522 }, 523 }, 524 }, 525 }, 526 }, 527 } 528 529 func TestUnmarshalText(t *testing.T) { 530 for i, test := range unMarshalTextTests { 531 pb := new(MyMessage) 532 err := UnmarshalText(test.in, pb) 533 if test.err == "" { 534 // We don't expect failure. 535 if err != nil { 536 t.Errorf("Test %d: Unexpected error: %v", i, err) 537 } else if !reflect.DeepEqual(pb, test.out) { 538 t.Errorf("Test %d: Incorrect populated \nHave: %v\nWant: %v", 539 i, pb, test.out) 540 } 541 } else { 542 // We do expect failure. 543 if err == nil { 544 t.Errorf("Test %d: Didn't get expected error: %v", i, test.err) 545 } else if err.Error() != test.err { 546 t.Errorf("Test %d: Incorrect error.\nHave: %v\nWant: %v", 547 i, err.Error(), test.err) 548 } else if _, ok := err.(*RequiredNotSetError); ok && test.out != nil && !reflect.DeepEqual(pb, test.out) { 549 t.Errorf("Test %d: Incorrect populated \nHave: %v\nWant: %v", 550 i, pb, test.out) 551 } 552 } 553 } 554 } 555 556 func TestUnmarshalTextCustomMessage(t *testing.T) { 557 msg := &textMessage{} 558 if err := UnmarshalText("custom", msg); err != nil { 559 t.Errorf("Unexpected error from custom unmarshal: %v", err) 560 } 561 if UnmarshalText("not custom", msg) == nil { 562 t.Errorf("Didn't get expected error from custom unmarshal") 563 } 564 } 565 566 // Regression test; this caused a panic. 567 func TestRepeatedEnum(t *testing.T) { 568 pb := new(RepeatedEnum) 569 if err := UnmarshalText("color: RED", pb); err != nil { 570 t.Fatal(err) 571 } 572 exp := &RepeatedEnum{ 573 Color: []RepeatedEnum_Color{RepeatedEnum_RED}, 574 } 575 if !Equal(pb, exp) { 576 t.Errorf("Incorrect populated \nHave: %v\nWant: %v", pb, exp) 577 } 578 } 579 580 func TestProto3TextParsing(t *testing.T) { 581 m := new(proto3pb.Message) 582 const in = `name: "Wallace" true_scotsman: true` 583 want := &proto3pb.Message{ 584 Name: "Wallace", 585 TrueScotsman: true, 586 } 587 if err := UnmarshalText(in, m); err != nil { 588 t.Fatal(err) 589 } 590 if !Equal(m, want) { 591 t.Errorf("\n got %v\nwant %v", m, want) 592 } 593 } 594 595 func TestMapParsing(t *testing.T) { 596 m := new(MessageWithMap) 597 const in = `name_mapping:<key:1234 value:"Feist"> name_mapping:<key:1 value:"Beatles">` + 598 `msg_mapping:<key:-4, value:<f: 2.0>,>` + // separating commas are okay 599 `msg_mapping<key:-2 value<f: 4.0>>` + // no colon after "value" 600 `msg_mapping:<value:<f: 5.0>>` + // omitted key 601 `msg_mapping:<key:1>` + // omitted value 602 `byte_mapping:<key:true value:"so be it">` + 603 `byte_mapping:<>` // omitted key and value 604 want := &MessageWithMap{ 605 NameMapping: map[int32]string{ 606 1: "Beatles", 607 1234: "Feist", 608 }, 609 MsgMapping: map[int64]*FloatingPoint{ 610 -4: {F: Float64(2.0)}, 611 -2: {F: Float64(4.0)}, 612 0: {F: Float64(5.0)}, 613 1: nil, 614 }, 615 ByteMapping: map[bool][]byte{ 616 false: nil, 617 true: []byte("so be it"), 618 }, 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 628 func TestOneofParsing(t *testing.T) { 629 const in = `name:"Shrek"` 630 m := new(Communique) 631 want := &Communique{Union: &Communique_Name{"Shrek"}} 632 if err := UnmarshalText(in, m); err != nil { 633 t.Fatal(err) 634 } 635 if !Equal(m, want) { 636 t.Errorf("\n got %v\nwant %v", m, want) 637 } 638 639 const inOverwrite = `name:"Shrek" number:42` 640 m = new(Communique) 641 testErr := "line 1.13: field 'number' would overwrite already parsed oneof 'Union'" 642 if err := UnmarshalText(inOverwrite, m); err == nil { 643 t.Errorf("TestOneofParsing: Didn't get expected error: %v", testErr) 644 } else if err.Error() != testErr { 645 t.Errorf("TestOneofParsing: Incorrect error.\nHave: %v\nWant: %v", 646 err.Error(), testErr) 647 } 648 649 } 650 651 var benchInput string 652 653 func init() { 654 benchInput = "count: 4\n" 655 for i := 0; i < 1000; i++ { 656 benchInput += "pet: \"fido\"\n" 657 } 658 659 // Check it is valid input. 660 pb := new(MyMessage) 661 err := UnmarshalText(benchInput, pb) 662 if err != nil { 663 panic("Bad benchmark input: " + err.Error()) 664 } 665 } 666 667 func BenchmarkUnmarshalText(b *testing.B) { 668 pb := new(MyMessage) 669 for i := 0; i < b.N; i++ { 670 UnmarshalText(benchInput, pb) 671 } 672 b.SetBytes(int64(len(benchInput))) 673 } 674