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