Home | History | Annotate | Download | only in proto
      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