1 // Go support for Protocol Buffers - Google's data interchange format
3 // Copyright 2010 The Go Authors. All rights reserved.
4 // https://github.com/golang/protobuf
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are
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
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.
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.
39 . "github.com/gogo/protobuf/proto"
40 proto3pb "github.com/gogo/protobuf/proto/proto3_proto"
41 . "github.com/gogo/protobuf/proto/test_proto"
44 type UnmarshalTextTest struct {
46 err string // if "", no error expected
50 func buildExtStructTest(text string) UnmarshalTextTest {
54 SetExtension(msg, E_Ext_More, &Ext{
55 Data: String("Hello, world!"),
57 return UnmarshalTextTest{in: text, out: msg}
60 func buildExtDataTest(text string) UnmarshalTextTest {
64 SetExtension(msg, E_Ext_Text, String("Hello, world!"))
65 SetExtension(msg, E_Ext_Number, Int32(1729))
66 return UnmarshalTextTest{in: text, out: msg}
69 func buildExtRepStringTest(text string) UnmarshalTextTest {
73 if err := SetExtension(msg, E_Greeting, []string{"bula", "hola"}); err != nil {
76 return UnmarshalTextTest{in: text, out: msg}
79 var unMarshalTextTests = []UnmarshalTextTest{
82 in: " count:42\n name:\"Dave\" ",
89 // Empty quoted string
91 in: `count:42 name:""`,
98 // Quoted string concatenation with double quotes
100 in: `count:42 name: "My name is "` + "\n" + `"elsewhere"`,
103 Name: String("My name is elsewhere"),
107 // Quoted string concatenation with single quotes
109 in: "count:42 name: 'My name is '\n'elsewhere'",
112 Name: String("My name is elsewhere"),
116 // Quoted string concatenations with mixed quotes
118 in: "count:42 name: 'My name is '\n\"elsewhere\"",
121 Name: String("My name is elsewhere"),
125 in: "count:42 name: \"My name is \"\n'elsewhere'",
128 Name: String("My name is elsewhere"),
132 // Quoted string with escaped apostrophe
134 in: `count:42 name: "HOLIDAY - New Year\'s Day"`,
137 Name: String("HOLIDAY - New Year's Day"),
141 // Quoted string with single quote
143 in: `count:42 name: 'Roger "The Ramster" Ramjet'`,
146 Name: String(`Roger "The Ramster" Ramjet`),
150 // Quoted string with all the accepted special characters from the C++ test
152 in: `count:42 name: ` + "\"\\\"A string with \\' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"",
155 Name: String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces"),
159 // Quoted string with quoted backslash
161 in: `count:42 name: "\\'xyz"`,
164 Name: String(`\'xyz`),
168 // Quoted string with UTF-8 bytes.
170 in: "count:42 name: '\303\277\302\201\x00\xAB\xCD\xEF'",
173 Name: String("\303\277\302\201\x00\xAB\xCD\xEF"),
177 // Quoted string with unicode escapes.
179 in: `count: 42 name: "\u0047\U00000047\uffff\U0010ffff"`,
182 Name: String("GG\uffff\U0010ffff"),
188 in: `inner: < host: "\0" >` + "\n",
189 err: `line 1.15: invalid quoted string "\0": \0 requires 2 following digits`,
194 in: `count: 42 name: "\u000"`,
195 err: `line 1.16: invalid quoted string "\u000": \u requires 4 following digits`,
200 in: `count: 42 name: "\U0000000"`,
201 err: `line 1.16: invalid quoted string "\U0000000": \U requires 8 following digits`,
206 in: `count: 42 name: "\xxx"`,
207 err: `line 1.16: invalid quoted string "\xxx": \xxx contains non-hexadecimal digits`,
210 // Number too large for int64
212 in: "count: 1 others { key: 123456789012345678901 }",
213 err: "line 1.23: invalid int64: 123456789012345678901",
216 // Number too large for int32
218 in: "count: 1234567890123",
219 err: "line 1.7: invalid int32: 1234567890123",
222 // Number in hexadecimal
224 in: "count: 0x2beef",
226 Count: Int32(0x2beef),
234 Count: Int32(024601),
238 // Floating point number with "f" suffix
240 in: "count: 4 others:< weight: 17.0f >",
243 Others: []*OtherMessage{
251 // Floating point positive infinity
253 in: "count: 4 bigfloat: inf",
256 Bigfloat: Float64(math.Inf(1)),
260 // Floating point negative infinity
262 in: "count: 4 bigfloat: -inf",
265 Bigfloat: Float64(math.Inf(-1)),
269 // Number too large for float32
271 in: "others:< weight: 12345678901234567890123456789012345678901234567890 >",
272 err: "line 1.17: invalid float32: 12345678901234567890123456789012345678901234567890",
275 // Number posing as a quoted string
277 in: `inner: < host: 12 >` + "\n",
278 err: `line 1.15: invalid string: 12`,
281 // Quoted string posing as int32
284 err: `line 1.7: invalid int32: "12"`,
287 // Quoted string posing a float32
289 in: `others:< weight: "17.4" >`,
290 err: `line 1.17: invalid float32: "17.4"`,
293 // unclosed bracket doesn't cause infinite loop
296 err: `line 1.0: unclosed type_url or extension name`,
301 in: `count:42 bikeshed: BLUE`,
304 Bikeshed: MyMessage_BLUE.Enum(),
310 in: `count:42 pet: "horsey" pet:"bunny"`,
313 Pet: []string{"horsey", "bunny"},
317 // Repeated field with list notation
319 in: `count:42 pet: ["horsey", "bunny"]`,
322 Pet: []string{"horsey", "bunny"},
326 // Repeated message with/without colon and <>/{}
328 in: `count:42 others:{} others{} others:<> others:{}`,
331 Others: []*OtherMessage{
340 // Missing colon for inner message
342 in: `count:42 inner < host: "cauchy.syd" >`,
345 Inner: &InnerMessage{
346 Host: String("cauchy.syd"),
351 // Missing colon for string field
354 err: `line 1.5: expected ':', found "\"Dave\""`,
357 // Missing colon for int32 field
360 err: `line 1.6: expected ':', found "42"`,
363 // Missing required field
366 err: fmt.Sprintf(`proto: required field "%T.count" not set`, MyMessage{}),
368 Name: String("Pawel"),
372 // Missing required field in a required submessage
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{}),
378 WeMustGoDeeper: &RequiredInnerMessage{LeoFinallyWonAnOscar: &InnerMessage{}},
382 // Repeated non-repeated field
384 in: `name: "Rob" name: "Russ"`,
385 err: `line 1.12: non-repeated field "name" was repeated`,
390 in: `count: 17 SomeGroup { group_field: 12 }`,
393 Somegroup: &MyMessage_SomeGroup{
394 GroupField: Int32(12),
399 // Semicolon between fields
401 in: `count:3;name:"Calvin"`,
404 Name: String("Calvin"),
407 // Comma between fields
409 in: `count:4,name:"Ezekiel"`,
412 Name: String("Ezekiel"),
418 in: `count:42 inner { host: "example.com" connected: false }`,
421 Inner: &InnerMessage{
422 Host: String("example.com"),
423 Connected: Bool(false),
429 in: `count:42 inner { host: "example.com" connected: true }`,
432 Inner: &InnerMessage{
433 Host: String("example.com"),
434 Connected: Bool(true),
440 in: `count:42 inner { host: "example.com" connected: 0 }`,
443 Inner: &InnerMessage{
444 Host: String("example.com"),
445 Connected: Bool(false),
451 in: `count:42 inner { host: "example.com" connected: 1 }`,
454 Inner: &InnerMessage{
455 Host: String("example.com"),
456 Connected: Bool(true),
462 in: `count:42 inner { host: "example.com" connected: f }`,
465 Inner: &InnerMessage{
466 Host: String("example.com"),
467 Connected: Bool(false),
473 in: `count:42 inner { host: "example.com" connected: t }`,
476 Inner: &InnerMessage{
477 Host: String("example.com"),
478 Connected: Bool(true),
484 in: `count:42 inner { host: "example.com" connected: False }`,
487 Inner: &InnerMessage{
488 Host: String("example.com"),
489 Connected: Bool(false),
495 in: `count:42 inner { host: "example.com" connected: True }`,
498 Inner: &InnerMessage{
499 Host: String("example.com"),
500 Connected: Bool(true),
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"`),
513 in: "count:42 # Meaning\n" +
515 `quote:"\"I didn't want to go.\"" ` +
520 ` host:"footrest.syd" ` +
526 ` value:"\x01A\a\f" ` +
529 " weight:58.9 # Atomic weight of Co\n" +
531 ` host:"lesha.mtv" ` +
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"),
543 Connected: Bool(true),
545 Others: []*OtherMessage{
547 Key: Int64(3735928559),
548 Value: []byte{0x1, 'A', '\a', '\f'},
551 Weight: Float32(58.9),
552 Inner: &InnerMessage{
553 Host: String("lesha.mtv"),
562 func TestUnmarshalText(t *testing.T) {
563 for i, test := range unMarshalTextTests {
565 err := UnmarshalText(test.in, pb)
567 // We don't expect failure.
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",
575 // We do expect failure.
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",
589 func 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)
594 if UnmarshalText("not custom", msg) == nil {
595 t.Errorf("Didn't get expected error from custom unmarshal")
599 // Regression test; this caused a panic.
600 func TestRepeatedEnum(t *testing.T) {
601 pb := new(RepeatedEnum)
602 if err := UnmarshalText("color: RED", pb); err != nil {
605 exp := &RepeatedEnum{
606 Color: []RepeatedEnum_Color{RepeatedEnum_RED},
609 t.Errorf("Incorrect populated \nHave: %v\nWant: %v", pb, exp)
613 func TestProto3TextParsing(t *testing.T) {
614 m := new(proto3pb.Message)
615 const in = `name: "Wallace" true_scotsman: true`
616 want := &proto3pb.Message{
620 if err := UnmarshalText(in, m); err != nil {
624 t.Errorf("\n got %v\nwant %v", m, want)
628 func 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{
642 MsgMapping: map[int64]*FloatingPoint{
643 -4: {F: Float64(2.0)},
644 -2: {F: Float64(4.0)},
645 0: {F: Float64(5.0)},
648 ByteMapping: map[bool][]byte{
650 true: []byte("so be it"),
653 if err := UnmarshalText(in, m); err != nil {
657 t.Errorf("\n got %v\nwant %v", m, want)
661 func TestOneofParsing(t *testing.T) {
662 const in = `name:"Shrek"`
664 want := &Communique{Union: &Communique_Name{Name: "Shrek"}}
665 if err := UnmarshalText(in, m); err != nil {
669 t.Errorf("\n got %v\nwant %v", m, want)
672 const inOverwrite = `name:"Shrek" number:42`
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)
684 var benchInput string
687 benchInput = "count: 4\n"
688 for i := 0; i < 1000; i++ {
689 benchInput += "pet: \"fido\"\n"
692 // Check it is valid input.
694 err := UnmarshalText(benchInput, pb)
696 panic("Bad benchmark input: " + err.Error())
700 func BenchmarkUnmarshalText(b *testing.B) {
702 for i := 0; i < b.N; i++ {
703 UnmarshalText(benchInput, pb)
705 b.SetBytes(int64(len(benchInput)))