1
2
3
4
5 package proto_test
6
7 import (
8 "bytes"
9 "errors"
10 "math"
11 "strings"
12 "sync"
13 "testing"
14
15 "github.com/golang/protobuf/proto"
16 "github.com/google/go-cmp/cmp"
17
18 pb2 "github.com/golang/protobuf/internal/testprotos/proto2_proto"
19 pb3 "github.com/golang/protobuf/internal/testprotos/proto3_proto"
20 anypb "github.com/golang/protobuf/ptypes/any"
21 )
22
23 var (
24 expandedMarshaler = proto.TextMarshaler{ExpandAny: true}
25 expandedCompactMarshaler = proto.TextMarshaler{Compact: true, ExpandAny: true}
26 )
27
28
29
30
31
32
33 func anyEqual(got, want proto.Message) bool {
34
35 if proto.Equal(got, want) {
36 return true
37 }
38 g := expandedMarshaler.Text(got)
39 w := expandedMarshaler.Text(want)
40 return g == w
41 }
42
43 type golden struct {
44 m proto.Message
45 t, c string
46 }
47
48 var goldenMessages = makeGolden()
49
50 func makeGolden() []golden {
51 nested := &pb3.Nested{Bunny: "Monty"}
52 nb, err := proto.Marshal(nested)
53 if err != nil {
54 panic(err)
55 }
56 m1 := &pb3.Message{
57 Name: "David",
58 ResultCount: 47,
59 Anything: &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(nested), Value: nb},
60 }
61 m2 := &pb3.Message{
62 Name: "David",
63 ResultCount: 47,
64 Anything: &anypb.Any{TypeUrl: "http://[::1]/type.googleapis.com/" + proto.MessageName(nested), Value: nb},
65 }
66 m3 := &pb3.Message{
67 Name: "David",
68 ResultCount: 47,
69 Anything: &anypb.Any{TypeUrl: `type.googleapis.com/"/` + proto.MessageName(nested), Value: nb},
70 }
71 m4 := &pb3.Message{
72 Name: "David",
73 ResultCount: 47,
74 Anything: &anypb.Any{TypeUrl: "type.googleapis.com/a/path/" + proto.MessageName(nested), Value: nb},
75 }
76 m5 := &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(nested), Value: nb}
77
78 any1 := &pb2.MyMessage{Count: proto.Int32(47), Name: proto.String("David")}
79 proto.SetExtension(any1, pb2.E_Ext_More, &pb2.Ext{Data: proto.String("foo")})
80 proto.SetExtension(any1, pb2.E_Ext_Text, proto.String("bar"))
81 any1b, err := proto.Marshal(any1)
82 if err != nil {
83 panic(err)
84 }
85 any2 := &pb2.MyMessage{Count: proto.Int32(42), Bikeshed: pb2.MyMessage_GREEN.Enum(), RepBytes: [][]byte{[]byte("roboto")}}
86 proto.SetExtension(any2, pb2.E_Ext_More, &pb2.Ext{Data: proto.String("baz")})
87 any2b, err := proto.Marshal(any2)
88 if err != nil {
89 panic(err)
90 }
91 m6 := &pb3.Message{
92 Name: "David",
93 ResultCount: 47,
94 Anything: &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any1), Value: any1b},
95 ManyThings: []*anypb.Any{
96 &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any2), Value: any2b},
97 &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any1), Value: any1b},
98 },
99 }
100
101 const (
102 m1Golden = `
103 name: "David"
104 result_count: 47
105 anything: <
106 [type.googleapis.com/proto3_test.Nested]: <
107 bunny: "Monty"
108 >
109 >
110 `
111 m2Golden = `
112 name: "David"
113 result_count: 47
114 anything: <
115 ["http://[::1]/type.googleapis.com/proto3_test.Nested"]: <
116 bunny: "Monty"
117 >
118 >
119 `
120 m3Golden = `
121 name: "David"
122 result_count: 47
123 anything: <
124 ["type.googleapis.com/\"/proto3_test.Nested"]: <
125 bunny: "Monty"
126 >
127 >
128 `
129 m4Golden = `
130 name: "David"
131 result_count: 47
132 anything: <
133 [type.googleapis.com/a/path/proto3_test.Nested]: <
134 bunny: "Monty"
135 >
136 >
137 `
138 m5Golden = `
139 [type.googleapis.com/proto3_test.Nested]: <
140 bunny: "Monty"
141 >
142 `
143 m6Golden = `
144 name: "David"
145 result_count: 47
146 anything: <
147 [type.googleapis.com/proto2_test.MyMessage]: <
148 count: 47
149 name: "David"
150 [proto2_test.Ext.more]: <
151 data: "foo"
152 >
153 [proto2_test.Ext.text]: "bar"
154 >
155 >
156 many_things: <
157 [type.googleapis.com/proto2_test.MyMessage]: <
158 count: 42
159 bikeshed: GREEN
160 rep_bytes: "roboto"
161 [proto2_test.Ext.more]: <
162 data: "baz"
163 >
164 >
165 >
166 many_things: <
167 [type.googleapis.com/proto2_test.MyMessage]: <
168 count: 47
169 name: "David"
170 [proto2_test.Ext.more]: <
171 data: "foo"
172 >
173 [proto2_test.Ext.text]: "bar"
174 >
175 >
176 `
177 )
178 return []golden{
179 {m1, strings.TrimSpace(m1Golden) + "\n", strings.TrimSpace(compact(m1Golden)) + " "},
180 {m2, strings.TrimSpace(m2Golden) + "\n", strings.TrimSpace(compact(m2Golden)) + " "},
181 {m3, strings.TrimSpace(m3Golden) + "\n", strings.TrimSpace(compact(m3Golden)) + " "},
182 {m4, strings.TrimSpace(m4Golden) + "\n", strings.TrimSpace(compact(m4Golden)) + " "},
183 {m5, strings.TrimSpace(m5Golden) + "\n", strings.TrimSpace(compact(m5Golden)) + " "},
184 {m6, strings.TrimSpace(m6Golden) + "\n", strings.TrimSpace(compact(m6Golden)) + " "},
185 }
186 }
187
188 func TestMarshalGolden(t *testing.T) {
189 for _, tt := range goldenMessages {
190 t.Run("", func(t *testing.T) {
191 if got, want := expandedMarshaler.Text(tt.m), tt.t; got != want {
192 t.Errorf("message %v: got:\n%s\nwant:\n%s", tt.m, got, want)
193 }
194 if got, want := expandedCompactMarshaler.Text(tt.m), tt.c; got != want {
195 t.Errorf("message %v: got:\n`%s`\nwant:\n`%s`", tt.m, got, want)
196 }
197 })
198 }
199 }
200
201 func TestUnmarshalGolden(t *testing.T) {
202 for _, tt := range goldenMessages {
203 t.Run("", func(t *testing.T) {
204 want := tt.m
205 got := proto.Clone(tt.m)
206 got.Reset()
207 if err := proto.UnmarshalText(tt.t, got); err != nil {
208 t.Errorf("failed to unmarshal\n%s\nerror: %v", tt.t, err)
209 }
210 if !anyEqual(got, want) {
211 t.Errorf("message:\n%s\ngot:\n%s\nwant:\n%s", tt.t, got, want)
212 }
213 got.Reset()
214 if err := proto.UnmarshalText(tt.c, got); err != nil {
215 t.Errorf("failed to unmarshal\n%s\nerror: %v", tt.c, err)
216 }
217 if !anyEqual(got, want) {
218 t.Errorf("message:\n%s\ngot:\n%s\nwant:\n%s", tt.c, got, want)
219 }
220 })
221 }
222 }
223
224 func TestMarshalUnknownAny(t *testing.T) {
225 m := &pb3.Message{
226 Anything: &anypb.Any{
227 TypeUrl: "foo",
228 Value: []byte("bar"),
229 },
230 }
231 want := `anything: <
232 type_url: "foo"
233 value: "bar"
234 >
235 `
236 got := expandedMarshaler.Text(m)
237 if got != want {
238 t.Errorf("got:\n%s\nwant:\n%s", got, want)
239 }
240 }
241
242 func TestAmbiguousAny(t *testing.T) {
243 pb := &anypb.Any{}
244 err := proto.UnmarshalText(`
245 type_url: "ttt/proto3_test.Nested"
246 value: "\n\x05Monty"
247 `, pb)
248 if err != nil {
249 t.Errorf("unexpected proto.UnmarshalText error: %v", err)
250 }
251 }
252
253 func TestUnmarshalOverwriteAny(t *testing.T) {
254 pb := &anypb.Any{}
255 err := proto.UnmarshalText(`
256 [type.googleapis.com/a/path/proto3_test.Nested]: <
257 bunny: "Monty"
258 >
259 [type.googleapis.com/a/path/proto3_test.Nested]: <
260 bunny: "Rabbit of Caerbannog"
261 >
262 `, pb)
263 want := `line 7: Any message unpacked multiple times, or "type_url" already set`
264 if err.Error() != want {
265 t.Errorf("incorrect error:\ngot: %v\nwant: %v", err.Error(), want)
266 }
267 }
268
269 func TestUnmarshalAnyMixAndMatch(t *testing.T) {
270 pb := &anypb.Any{}
271 err := proto.UnmarshalText(`
272 value: "\n\x05Monty"
273 [type.googleapis.com/a/path/proto3_test.Nested]: <
274 bunny: "Rabbit of Caerbannog"
275 >
276 `, pb)
277 want := `line 5: Any message unpacked multiple times, or "value" already set`
278 if err.Error() != want {
279 t.Errorf("incorrect error:\ngot: %v\nwant: %v", err.Error(), want)
280 }
281 }
282
283
284
285 type textMessage struct {
286 }
287
288 func (*textMessage) MarshalText() ([]byte, error) {
289 return []byte("custom"), nil
290 }
291
292 func (*textMessage) UnmarshalText(bytes []byte) error {
293 if string(bytes) != "custom" {
294 return errors.New("expected 'custom'")
295 }
296 return nil
297 }
298
299 func (*textMessage) Reset() {}
300 func (*textMessage) String() string { return "" }
301 func (*textMessage) ProtoMessage() {}
302
303 func newTestMessage() *pb2.MyMessage {
304 msg := &pb2.MyMessage{
305 Count: proto.Int32(42),
306 Name: proto.String("Dave"),
307 Quote: proto.String(`"I didn't want to go."`),
308 Pet: []string{"bunny", "kitty", "horsey"},
309 Inner: &pb2.InnerMessage{
310 Host: proto.String("footrest.syd"),
311 Port: proto.Int32(7001),
312 Connected: proto.Bool(true),
313 },
314 Others: []*pb2.OtherMessage{
315 {
316 Key: proto.Int64(0xdeadbeef),
317 Value: []byte{1, 65, 7, 12},
318 },
319 {
320 Weight: proto.Float32(6.022),
321 Inner: &pb2.InnerMessage{
322 Host: proto.String("lesha.mtv"),
323 Port: proto.Int32(8002),
324 },
325 },
326 },
327 Bikeshed: pb2.MyMessage_BLUE.Enum(),
328 Somegroup: &pb2.MyMessage_SomeGroup{
329 GroupField: proto.Int32(8),
330 },
331
332
333 XXX_unrecognized: []byte{13<<3 | 0, 4},
334 }
335 ext := &pb2.Ext{
336 Data: proto.String("Big gobs for big rats"),
337 }
338 if err := proto.SetExtension(msg, pb2.E_Ext_More, ext); err != nil {
339 panic(err)
340 }
341 greetings := []string{"adg", "easy", "cow"}
342 if err := proto.SetExtension(msg, pb2.E_Greeting, greetings); err != nil {
343 panic(err)
344 }
345
346
347 b, err := proto.Marshal(&pb2.Ext{Data: proto.String("3G skiing")})
348 if err != nil {
349 panic(err)
350 }
351 b = append(proto.EncodeVarint(201<<3|proto.WireBytes), b...)
352 proto.SetRawExtension(msg, 201, b)
353
354
355 b = append(proto.EncodeVarint(202<<3|proto.WireVarint), 19)
356 proto.SetRawExtension(msg, 202, b)
357
358 return msg
359 }
360
361 const text = `count: 42
362 name: "Dave"
363 quote: "\"I didn't want to go.\""
364 pet: "bunny"
365 pet: "kitty"
366 pet: "horsey"
367 inner: <
368 host: "footrest.syd"
369 port: 7001
370 connected: true
371 >
372 others: <
373 key: 3735928559
374 value: "\001A\007\014"
375 >
376 others: <
377 weight: 6.022
378 inner: <
379 host: "lesha.mtv"
380 port: 8002
381 >
382 >
383 bikeshed: BLUE
384 SomeGroup {
385 group_field: 8
386 }
387 /* 18 unknown bytes */
388 13: 4
389 201: "\t3G skiing"
390 202: 19
391 [proto2_test.Ext.more]: <
392 data: "Big gobs for big rats"
393 >
394 [proto2_test.greeting]: "adg"
395 [proto2_test.greeting]: "easy"
396 [proto2_test.greeting]: "cow"
397 `
398
399 func TestMarshalText(t *testing.T) {
400 buf := new(bytes.Buffer)
401 if err := proto.MarshalText(buf, newTestMessage()); err != nil {
402 t.Fatalf("proto.MarshalText: %v", err)
403 }
404 got := buf.String()
405 if diff := cmp.Diff(text, got); got != text {
406 t.Errorf("diff (-want +got):\n%v\n\ngot:\n%v\n\nwant:\n%v", diff, got, text)
407 }
408 }
409
410 func TestMarshalTextCustomMessage(t *testing.T) {
411 buf := new(bytes.Buffer)
412 if err := proto.MarshalText(buf, &textMessage{}); err != nil {
413 t.Fatalf("proto.MarshalText: %v", err)
414 }
415 got := buf.String()
416 if got != "custom" {
417 t.Errorf("got:\n%v\n\nwant:\n%v", got, "custom")
418 }
419 }
420 func TestMarshalTextNil(t *testing.T) {
421 want := "<nil>"
422 tests := []proto.Message{nil, (*pb2.MyMessage)(nil)}
423 for i, test := range tests {
424 buf := new(bytes.Buffer)
425 if err := proto.MarshalText(buf, test); err != nil {
426 t.Fatal(err)
427 }
428 if got := buf.String(); got != want {
429 t.Errorf("%d: got %q want %q", i, got, want)
430 }
431 }
432 }
433
434 func TestMarshalTextUnknownEnum(t *testing.T) {
435
436 m := &pb2.MyMessage{Bikeshed: pb2.MyMessage_Color(3).Enum()}
437 got := m.String()
438 const want = `bikeshed:3 `
439 if got != want {
440 t.Errorf("\n got %q\nwant %q", got, want)
441 }
442 }
443
444 func TestTextOneof(t *testing.T) {
445 tests := []struct {
446 m proto.Message
447 want string
448 }{
449
450 {&pb2.Communique{}, ``},
451
452 {&pb2.Communique{Union: &pb2.Communique_Number{4}}, `number:4`},
453
454 {&pb2.Communique{Union: &pb2.Communique_Msg{
455 &pb2.Strings{StringField: proto.String("why hello!")},
456 }}, `msg:<string_field:"why hello!" >`},
457
458 {&pb2.Communique{Union: &pb2.Communique_Msg{nil}}, `msg:<>`},
459 }
460 for _, test := range tests {
461 got := strings.TrimSpace(test.m.String())
462 if got != test.want {
463 t.Errorf("got:\n%s\n\nwant:\n%s", got, test.want)
464 }
465 }
466 }
467
468 func compact(src string) string {
469
470 dst := make([]byte, len(src))
471 space, comment := false, false
472 j := 0
473 for i := 0; i < len(src); i++ {
474 if strings.HasPrefix(src[i:], "/*") {
475 comment = true
476 i++
477 continue
478 }
479 if comment && strings.HasPrefix(src[i:], "*/") {
480 comment = false
481 i++
482 continue
483 }
484 if comment {
485 continue
486 }
487 c := src[i]
488 if c == ' ' || c == '\n' {
489 space = true
490 continue
491 }
492 if j > 0 && (dst[j-1] == ':' || dst[j-1] == '<' || dst[j-1] == '{') {
493 space = false
494 }
495 if c == '{' {
496 space = false
497 }
498 if space {
499 dst[j] = ' '
500 j++
501 space = false
502 }
503 dst[j] = c
504 j++
505 }
506 if space {
507 dst[j] = ' '
508 j++
509 }
510 return string(dst[0:j])
511 }
512
513 func TestCompactText(t *testing.T) {
514 got := proto.CompactTextString(newTestMessage())
515 if got != compact(text) {
516 t.Errorf("got:\n%v\n\nwant:\n%v", got, compact(text))
517 }
518 }
519
520 func TestStringEscaping(t *testing.T) {
521 testCases := []struct {
522 in *pb2.Strings
523 out string
524 }{
525 {
526
527
528 &pb2.Strings{StringField: proto.String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces")},
529 "string_field: \"\\\"A string with ' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"\n",
530 },
531 {
532
533 &pb2.Strings{StringField: proto.String("\350\260\267\346\255\214")},
534 "string_field: \"\\350\\260\\267\\346\\255\\214\"\n",
535 },
536 {
537
538 &pb2.Strings{StringField: proto.String("\x00\x01\xff\x81")},
539 `string_field: "\000\001\377\201"` + "\n",
540 },
541 }
542
543 for _, tc := range testCases {
544 t.Run("", func(t *testing.T) {
545 var buf bytes.Buffer
546 if err := proto.MarshalText(&buf, tc.in); err != nil {
547 t.Fatalf("proto.MarsalText error: %v", err)
548 }
549 got := buf.String()
550 if got != tc.out {
551 t.Fatalf("want:\n%s\n\nwant:\n%s", got, tc.out)
552 }
553
554
555 pb := new(pb2.Strings)
556 if err := proto.UnmarshalText(got, pb); err != nil {
557 t.Fatalf("proto.UnmarshalText error: %v", err)
558 }
559 if !proto.Equal(pb, tc.in) {
560 t.Fatalf("proto.Equal mismatch:\ngot:\n%v\n\nwant:\n%v", pb, tc.in)
561 }
562 })
563 }
564 }
565
566
567
568
569 type limitedWriter struct {
570 b bytes.Buffer
571 limit int
572 }
573
574 var outOfSpace = errors.New("proto: insufficient space")
575
576 func (w *limitedWriter) Write(p []byte) (n int, err error) {
577 var avail = w.limit - w.b.Len()
578 if avail <= 0 {
579 return 0, outOfSpace
580 }
581 if len(p) <= avail {
582 return w.b.Write(p)
583 }
584 n, _ = w.b.Write(p[:avail])
585 return n, outOfSpace
586 }
587
588 func TestMarshalTextFailing(t *testing.T) {
589
590 for lim := 0; lim < len(text); lim++ {
591 buf := new(limitedWriter)
592 buf.limit = lim
593 err := proto.MarshalText(buf, newTestMessage())
594
595 if err != outOfSpace {
596 t.Errorf("error mismatch: got %v, want %v", err, outOfSpace)
597 }
598 got := buf.b.String()
599 want := text[:buf.limit]
600 if got != want {
601 t.Errorf("text mismatch:\n\ngot:\n%v\n\nwant:\n%v", got, want)
602 }
603 }
604 }
605
606 func TestFloats(t *testing.T) {
607 tests := []struct {
608 f float64
609 want string
610 }{
611 {0, "0"},
612 {4.7, "4.7"},
613 {math.Inf(1), "inf"},
614 {math.Inf(-1), "-inf"},
615 {math.NaN(), "nan"},
616 }
617 for _, test := range tests {
618 msg := &pb2.FloatingPoint{F: &test.f}
619 got := strings.TrimSpace(msg.String())
620 want := `f:` + test.want
621 if got != want {
622 t.Errorf("f=%f: got %q, want %q", test.f, got, want)
623 }
624 }
625 }
626
627 func TestRepeatedNilText(t *testing.T) {
628 m := &pb2.MessageList{
629 Message: []*pb2.MessageList_Message{
630 nil,
631 &pb2.MessageList_Message{
632 Name: proto.String("Horse"),
633 },
634 nil,
635 },
636 }
637 want := `Message {
638 }
639 Message {
640 name: "Horse"
641 }
642 Message {
643 }
644 `
645 if got := proto.MarshalTextString(m); got != want {
646 t.Errorf("got:\n%s\n\nwant:\n%s", got, want)
647 }
648 }
649
650 func TestProto3Text(t *testing.T) {
651 tests := []struct {
652 m proto.Message
653 want string
654 }{
655
656 {&pb3.Message{}, ``},
657
658 {&pb3.Message{Data: []byte{}}, ``},
659
660 {&pb3.Message{Name: "Rob", HeightInCm: 175}, `name:"Rob" height_in_cm:175`},
661
662 {&pb2.MessageWithMap{}, ``},
663
664
665 {
666 &pb2.MessageWithMap{NameMapping: map[int32]string{
667 -1: "Negatory",
668 7: "Lucky",
669 1234: "Feist",
670 6345789: "Otis",
671 }},
672 `name_mapping:<key:-1 value:"Negatory" > ` +
673 `name_mapping:<key:7 value:"Lucky" > ` +
674 `name_mapping:<key:1234 value:"Feist" > ` +
675 `name_mapping:<key:6345789 value:"Otis" >`,
676 },
677
678 {
679 &pb2.MessageWithMap{MsgMapping: map[int64]*pb2.FloatingPoint{7: nil}},
680 `msg_mapping:<key:7 value:<> >`,
681 },
682 }
683 for _, test := range tests {
684 got := strings.TrimSpace(test.m.String())
685 if got != test.want {
686 t.Errorf("got:\n%s\n\nwant:\n%s", got, test.want)
687 }
688 }
689 }
690
691 func TestRacyMarshal(t *testing.T) {
692
693
694 any := &pb2.MyMessage{Count: proto.Int32(47), Name: proto.String("David")}
695 proto.SetExtension(any, pb2.E_Ext_Text, proto.String("bar"))
696 b, err := proto.Marshal(any)
697 if err != nil {
698 panic(err)
699 }
700 m := &pb3.Message{
701 Name: "David",
702 ResultCount: 47,
703 Anything: &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any), Value: b},
704 }
705
706 wantText := proto.MarshalTextString(m)
707 wantBytes, err := proto.Marshal(m)
708 if err != nil {
709 t.Fatalf("proto.Marshal error: %v", err)
710 }
711
712 var wg sync.WaitGroup
713 defer wg.Wait()
714 wg.Add(20)
715 for i := 0; i < 10; i++ {
716 go func() {
717 defer wg.Done()
718 got := proto.MarshalTextString(m)
719 if got != wantText {
720 t.Errorf("proto.MarshalTextString = %q, want %q", got, wantText)
721 }
722 }()
723 go func() {
724 defer wg.Done()
725 got, err := proto.Marshal(m)
726 if !bytes.Equal(got, wantBytes) || err != nil {
727 t.Errorf("proto.Marshal = (%x, %v), want (%x, nil)", got, err, wantBytes)
728 }
729 }()
730 }
731 }
732
733 type UnmarshalTextTest struct {
734 in string
735 err string
736 out *pb2.MyMessage
737 }
738
739 func buildExtStructTest(text string) UnmarshalTextTest {
740 msg := &pb2.MyMessage{
741 Count: proto.Int32(42),
742 }
743 proto.SetExtension(msg, pb2.E_Ext_More, &pb2.Ext{
744 Data: proto.String("Hello, world!"),
745 })
746 return UnmarshalTextTest{in: text, out: msg}
747 }
748
749 func buildExtDataTest(text string) UnmarshalTextTest {
750 msg := &pb2.MyMessage{
751 Count: proto.Int32(42),
752 }
753 proto.SetExtension(msg, pb2.E_Ext_Text, proto.String("Hello, world!"))
754 proto.SetExtension(msg, pb2.E_Ext_Number, proto.Int32(1729))
755 return UnmarshalTextTest{in: text, out: msg}
756 }
757
758 func buildExtRepStringTest(text string) UnmarshalTextTest {
759 msg := &pb2.MyMessage{
760 Count: proto.Int32(42),
761 }
762 if err := proto.SetExtension(msg, pb2.E_Greeting, []string{"bula", "hola"}); err != nil {
763 panic(err)
764 }
765 return UnmarshalTextTest{in: text, out: msg}
766 }
767
768 var unmarshalTextTests = []UnmarshalTextTest{
769
770 {
771 in: " count:42\n name:\"Dave\" ",
772 out: &pb2.MyMessage{
773 Count: proto.Int32(42),
774 Name: proto.String("Dave"),
775 },
776 },
777
778
779 {
780 in: `count:42 name:""`,
781 out: &pb2.MyMessage{
782 Count: proto.Int32(42),
783 Name: proto.String(""),
784 },
785 },
786
787
788 {
789 in: `count:42 name: "My name is "` + "\n" + `"elsewhere"`,
790 out: &pb2.MyMessage{
791 Count: proto.Int32(42),
792 Name: proto.String("My name is elsewhere"),
793 },
794 },
795
796
797 {
798 in: "count:42 name: 'My name is '\n'elsewhere'",
799 out: &pb2.MyMessage{
800 Count: proto.Int32(42),
801 Name: proto.String("My name is elsewhere"),
802 },
803 },
804
805
806 {
807 in: "count:42 name: 'My name is '\n\"elsewhere\"",
808 out: &pb2.MyMessage{
809 Count: proto.Int32(42),
810 Name: proto.String("My name is elsewhere"),
811 },
812 },
813 {
814 in: "count:42 name: \"My name is \"\n'elsewhere'",
815 out: &pb2.MyMessage{
816 Count: proto.Int32(42),
817 Name: proto.String("My name is elsewhere"),
818 },
819 },
820
821
822 {
823 in: `count:42 name: "HOLIDAY - New Year\'s Day"`,
824 out: &pb2.MyMessage{
825 Count: proto.Int32(42),
826 Name: proto.String("HOLIDAY - New Year's Day"),
827 },
828 },
829
830
831 {
832 in: `count:42 name: 'Roger "The Ramster" Ramjet'`,
833 out: &pb2.MyMessage{
834 Count: proto.Int32(42),
835 Name: proto.String(`Roger "The Ramster" Ramjet`),
836 },
837 },
838
839
840 {
841 in: `count:42 name: ` + "\"\\\"A string with \\' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"",
842 out: &pb2.MyMessage{
843 Count: proto.Int32(42),
844 Name: proto.String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces"),
845 },
846 },
847
848
849 {
850 in: `count:42 name: "\\'xyz"`,
851 out: &pb2.MyMessage{
852 Count: proto.Int32(42),
853 Name: proto.String(`\'xyz`),
854 },
855 },
856
857
858 {
859 in: "count:42 name: '\303\277\302\201\x00\xAB\xCD\xEF'",
860 out: &pb2.MyMessage{
861 Count: proto.Int32(42),
862 Name: proto.String("\303\277\302\201\x00\xAB\xCD\xEF"),
863 },
864 },
865
866
867 {
868 in: `count: 42 name: "\u0047\U00000047\uffff\U0010ffff"`,
869 out: &pb2.MyMessage{
870 Count: proto.Int32(42),
871 Name: proto.String("GG\uffff\U0010ffff"),
872 },
873 },
874
875
876 {
877 in: `inner: < host: "\0" >` + "\n",
878 err: `line 1.15: invalid quoted string "\0": \0 requires 2 following digits`,
879 },
880
881
882 {
883 in: `count: 42 name: "\u000"`,
884 err: `line 1.16: invalid quoted string "\u000": \u requires 4 following digits`,
885 },
886
887
888 {
889 in: `count: 42 name: "\U0000000"`,
890 err: `line 1.16: invalid quoted string "\U0000000": \U requires 8 following digits`,
891 },
892
893
894 {
895 in: `count: 42 name: "\xxx"`,
896 err: `line 1.16: invalid quoted string "\xxx": \xxx contains non-hexadecimal digits`,
897 },
898
899
900 {
901 in: "count: 1 others { key: 123456789012345678901 }",
902 err: "line 1.23: invalid int64: 123456789012345678901",
903 },
904
905
906 {
907 in: "count: 1234567890123",
908 err: "line 1.7: invalid int32: 1234567890123",
909 },
910
911
912 {
913 in: "count: 0x2beef",
914 out: &pb2.MyMessage{
915 Count: proto.Int32(0x2beef),
916 },
917 },
918
919
920 {
921 in: "count: 024601",
922 out: &pb2.MyMessage{
923 Count: proto.Int32(024601),
924 },
925 },
926
927
928 {
929 in: "count: 4 others:< weight: 17.0f >",
930 out: &pb2.MyMessage{
931 Count: proto.Int32(4),
932 Others: []*pb2.OtherMessage{
933 {
934 Weight: proto.Float32(17),
935 },
936 },
937 },
938 },
939
940
941 {
942 in: "count: 4 bigfloat: inf",
943 out: &pb2.MyMessage{
944 Count: proto.Int32(4),
945 Bigfloat: proto.Float64(math.Inf(1)),
946 },
947 },
948
949
950 {
951 in: "count: 4 bigfloat: -inf",
952 out: &pb2.MyMessage{
953 Count: proto.Int32(4),
954 Bigfloat: proto.Float64(math.Inf(-1)),
955 },
956 },
957
958
959 {
960 in: "others:< weight: 12345678901234567890123456789012345678901234567890 >",
961 err: "line 1.17: invalid float: 12345678901234567890123456789012345678901234567890",
962 },
963
964
965 {
966 in: `inner: < host: 12 >` + "\n",
967 err: `line 1.15: invalid string: 12`,
968 },
969
970
971 {
972 in: `count: "12"`,
973 err: `line 1.7: invalid int32: "12"`,
974 },
975
976
977 {
978 in: `others:< weight: "17.4" >`,
979 err: `line 1.17: invalid float: "17.4"`,
980 },
981
982
983 {
984 in: `[`,
985 err: `line 1.0: unclosed type_url or extension name`,
986 },
987
988
989 {
990 in: `count:42 bikeshed: BLUE`,
991 out: &pb2.MyMessage{
992 Count: proto.Int32(42),
993 Bikeshed: pb2.MyMessage_BLUE.Enum(),
994 },
995 },
996
997
998 {
999 in: `count:42 pet: "horsey" pet:"bunny"`,
1000 out: &pb2.MyMessage{
1001 Count: proto.Int32(42),
1002 Pet: []string{"horsey", "bunny"},
1003 },
1004 },
1005
1006
1007 {
1008 in: `count:42 pet: ["horsey", "bunny"]`,
1009 out: &pb2.MyMessage{
1010 Count: proto.Int32(42),
1011 Pet: []string{"horsey", "bunny"},
1012 },
1013 },
1014
1015
1016 {
1017 in: `count:42 others:{} others{} others:<> others:{}`,
1018 out: &pb2.MyMessage{
1019 Count: proto.Int32(42),
1020 Others: []*pb2.OtherMessage{
1021 {},
1022 {},
1023 {},
1024 {},
1025 },
1026 },
1027 },
1028
1029
1030 {
1031 in: `count:42 inner < host: "cauchy.syd" >`,
1032 out: &pb2.MyMessage{
1033 Count: proto.Int32(42),
1034 Inner: &pb2.InnerMessage{
1035 Host: proto.String("cauchy.syd"),
1036 },
1037 },
1038 },
1039
1040
1041 {
1042 in: `name "Dave"`,
1043 err: `line 1.5: expected ':', found "\"Dave\""`,
1044 },
1045
1046
1047 {
1048 in: `count 42`,
1049 err: `line 1.6: expected ':', found "42"`,
1050 },
1051
1052
1053 {
1054 in: `name: "Pawel"`,
1055 err: `required field proto2_test.MyMessage.count not set`,
1056 out: &pb2.MyMessage{
1057 Name: proto.String("Pawel"),
1058 },
1059 },
1060
1061
1062 {
1063 in: `count: 42 we_must_go_deeper < leo_finally_won_an_oscar <> >`,
1064 err: `required field proto2_test.InnerMessage.host not set`,
1065 out: &pb2.MyMessage{
1066 Count: proto.Int32(42),
1067 WeMustGoDeeper: &pb2.RequiredInnerMessage{LeoFinallyWonAnOscar: &pb2.InnerMessage{}},
1068 },
1069 },
1070
1071
1072 {
1073 in: `name: "Rob" name: "Russ"`,
1074 err: `line 1.12: non-repeated field "name" was repeated`,
1075 },
1076
1077
1078 {
1079 in: `count: 17 SomeGroup { group_field: 12 }`,
1080 out: &pb2.MyMessage{
1081 Count: proto.Int32(17),
1082 Somegroup: &pb2.MyMessage_SomeGroup{
1083 GroupField: proto.Int32(12),
1084 },
1085 },
1086 },
1087
1088
1089 {
1090 in: `count:3;name:"Calvin"`,
1091 out: &pb2.MyMessage{
1092 Count: proto.Int32(3),
1093 Name: proto.String("Calvin"),
1094 },
1095 },
1096
1097 {
1098 in: `count:4,name:"Ezekiel"`,
1099 out: &pb2.MyMessage{
1100 Count: proto.Int32(4),
1101 Name: proto.String("Ezekiel"),
1102 },
1103 },
1104
1105
1106 {
1107 in: `count:42 inner { host: "example.com" connected: false }`,
1108 out: &pb2.MyMessage{
1109 Count: proto.Int32(42),
1110 Inner: &pb2.InnerMessage{
1111 Host: proto.String("example.com"),
1112 Connected: proto.Bool(false),
1113 },
1114 },
1115 },
1116
1117 {
1118 in: `count:42 inner { host: "example.com" connected: true }`,
1119 out: &pb2.MyMessage{
1120 Count: proto.Int32(42),
1121 Inner: &pb2.InnerMessage{
1122 Host: proto.String("example.com"),
1123 Connected: proto.Bool(true),
1124 },
1125 },
1126 },
1127
1128 {
1129 in: `count:42 inner { host: "example.com" connected: 0 }`,
1130 out: &pb2.MyMessage{
1131 Count: proto.Int32(42),
1132 Inner: &pb2.InnerMessage{
1133 Host: proto.String("example.com"),
1134 Connected: proto.Bool(false),
1135 },
1136 },
1137 },
1138
1139 {
1140 in: `count:42 inner { host: "example.com" connected: 1 }`,
1141 out: &pb2.MyMessage{
1142 Count: proto.Int32(42),
1143 Inner: &pb2.InnerMessage{
1144 Host: proto.String("example.com"),
1145 Connected: proto.Bool(true),
1146 },
1147 },
1148 },
1149
1150 {
1151 in: `count:42 inner { host: "example.com" connected: f }`,
1152 out: &pb2.MyMessage{
1153 Count: proto.Int32(42),
1154 Inner: &pb2.InnerMessage{
1155 Host: proto.String("example.com"),
1156 Connected: proto.Bool(false),
1157 },
1158 },
1159 },
1160
1161 {
1162 in: `count:42 inner { host: "example.com" connected: t }`,
1163 out: &pb2.MyMessage{
1164 Count: proto.Int32(42),
1165 Inner: &pb2.InnerMessage{
1166 Host: proto.String("example.com"),
1167 Connected: proto.Bool(true),
1168 },
1169 },
1170 },
1171
1172 {
1173 in: `count:42 inner { host: "example.com" connected: False }`,
1174 out: &pb2.MyMessage{
1175 Count: proto.Int32(42),
1176 Inner: &pb2.InnerMessage{
1177 Host: proto.String("example.com"),
1178 Connected: proto.Bool(false),
1179 },
1180 },
1181 },
1182
1183 {
1184 in: `count:42 inner { host: "example.com" connected: True }`,
1185 out: &pb2.MyMessage{
1186 Count: proto.Int32(42),
1187 Inner: &pb2.InnerMessage{
1188 Host: proto.String("example.com"),
1189 Connected: proto.Bool(true),
1190 },
1191 },
1192 },
1193
1194
1195 buildExtStructTest(`count: 42 [proto2_test.Ext.more]:<data:"Hello, world!" >`),
1196 buildExtStructTest(`count: 42 [proto2_test.Ext.more] {data:"Hello, world!"}`),
1197 buildExtDataTest(`count: 42 [proto2_test.Ext.text]:"Hello, world!" [proto2_test.Ext.number]:1729`),
1198 buildExtRepStringTest(`count: 42 [proto2_test.greeting]:"bula" [proto2_test.greeting]:"hola"`),
1199 {
1200 in: `[proto2_test.complex]:<>`,
1201 err: `line 1.20: extension field "proto2_test.complex" does not extend message "proto2_test.MyMessage"`,
1202 },
1203
1204
1205 {
1206 in: "count:42 # Meaning\n" +
1207 `name:"Dave" ` +
1208 `quote:"\"I didn't want to go.\"" ` +
1209 `pet:"bunny" ` +
1210 `pet:"kitty" ` +
1211 `pet:"horsey" ` +
1212 `inner:<` +
1213 ` host:"footrest.syd" ` +
1214 ` port:7001 ` +
1215 ` connected:true ` +
1216 `> ` +
1217 `others:<` +
1218 ` key:3735928559 ` +
1219 ` value:"\x01A\a\f" ` +
1220 `> ` +
1221 `others:<` +
1222 " weight:58.9 # Atomic weight of Co\n" +
1223 ` inner:<` +
1224 ` host:"lesha.mtv" ` +
1225 ` port:8002 ` +
1226 ` >` +
1227 `>`,
1228 out: &pb2.MyMessage{
1229 Count: proto.Int32(42),
1230 Name: proto.String("Dave"),
1231 Quote: proto.String(`"I didn't want to go."`),
1232 Pet: []string{"bunny", "kitty", "horsey"},
1233 Inner: &pb2.InnerMessage{
1234 Host: proto.String("footrest.syd"),
1235 Port: proto.Int32(7001),
1236 Connected: proto.Bool(true),
1237 },
1238 Others: []*pb2.OtherMessage{
1239 {
1240 Key: proto.Int64(3735928559),
1241 Value: []byte{0x1, 'A', '\a', '\f'},
1242 },
1243 {
1244 Weight: proto.Float32(58.9),
1245 Inner: &pb2.InnerMessage{
1246 Host: proto.String("lesha.mtv"),
1247 Port: proto.Int32(8002),
1248 },
1249 },
1250 },
1251 },
1252 },
1253 }
1254
1255 func TestUnmarshalText(t *testing.T) {
1256 for _, test := range unmarshalTextTests {
1257 t.Run("", func(t *testing.T) {
1258 pb := new(pb2.MyMessage)
1259 err := proto.UnmarshalText(test.in, pb)
1260 if test.err == "" {
1261
1262 if err != nil {
1263 t.Errorf("proto.UnmarshalText error: %v", err)
1264 } else if !proto.Equal(pb, test.out) {
1265 t.Errorf("proto.Equal mismatch:\ngot: %v\nwant: %v", pb, test.out)
1266 }
1267 } else {
1268
1269 if err == nil {
1270 t.Errorf("proto.UnmarshalText: got nil error, want %v", test.err)
1271 } else if !strings.Contains(err.Error(), test.err) {
1272 t.Errorf("proto.UnmarshalText error mismatch:\ngot: %v\nwant: %v", err.Error(), test.err)
1273 } else if _, ok := err.(*proto.RequiredNotSetError); ok && test.out != nil && !proto.Equal(pb, test.out) {
1274 t.Errorf("proto.Equal mismatch:\ngot %v\nwant: %v", pb, test.out)
1275 }
1276 }
1277 })
1278 }
1279 }
1280
1281 func TestUnmarshalTextCustomMessage(t *testing.T) {
1282 msg := &textMessage{}
1283 if err := proto.UnmarshalText("custom", msg); err != nil {
1284 t.Errorf("proto.UnmarshalText error: %v", err)
1285 }
1286 if err := proto.UnmarshalText("not custom", msg); err == nil {
1287 t.Errorf("proto.UnmarshalText: got nil error, want non-nil")
1288 }
1289 }
1290
1291
1292 func TestRepeatedEnum(t *testing.T) {
1293 pb := new(pb2.RepeatedEnum)
1294 if err := proto.UnmarshalText("color: RED", pb); err != nil {
1295 t.Fatal(err)
1296 }
1297 exp := &pb2.RepeatedEnum{
1298 Color: []pb2.RepeatedEnum_Color{pb2.RepeatedEnum_RED},
1299 }
1300 if !proto.Equal(pb, exp) {
1301 t.Errorf("proto.Equal mismatch:\ngot: %v\nwant %v", pb, exp)
1302 }
1303 }
1304
1305 func TestProto3TextParsing(t *testing.T) {
1306 m := new(pb3.Message)
1307 const in = `name: "Wallace" true_scotsman: true`
1308 want := &pb3.Message{
1309 Name: "Wallace",
1310 TrueScotsman: true,
1311 }
1312 if err := proto.UnmarshalText(in, m); err != nil {
1313 t.Fatal(err)
1314 }
1315 if !proto.Equal(m, want) {
1316 t.Errorf("proto.Equal mismatch:\ngot: %v\nwant %v", m, want)
1317 }
1318 }
1319
1320 func TestMapParsing(t *testing.T) {
1321 m := new(pb2.MessageWithMap)
1322 const in = `name_mapping:<key:1234 value:"Feist"> name_mapping:<key:1 value:"Beatles">` +
1323 `msg_mapping:<key:-4, value:<f: 2.0>,>` +
1324 `msg_mapping<key:-2 value<f: 4.0>>` +
1325 `msg_mapping:<value:<f: 5.0>>` +
1326 `byte_mapping:<key:true value:"so be it">` +
1327 `byte_mapping:<>`
1328 want := &pb2.MessageWithMap{
1329 NameMapping: map[int32]string{
1330 1: "Beatles",
1331 1234: "Feist",
1332 },
1333 MsgMapping: map[int64]*pb2.FloatingPoint{
1334 -4: {F: proto.Float64(2.0)},
1335 -2: {F: proto.Float64(4.0)},
1336 0: {F: proto.Float64(5.0)},
1337 },
1338 ByteMapping: map[bool][]byte{
1339 false: nil,
1340 true: []byte("so be it"),
1341 },
1342 }
1343 if err := proto.UnmarshalText(in, m); err != nil {
1344 t.Fatal(err)
1345 }
1346 if !proto.Equal(m, want) {
1347 t.Errorf("proto.Equal mismatch:\ngot: %v\nwant %v", m, want)
1348 }
1349 }
1350
1351 func TestOneofParsing(t *testing.T) {
1352 const in = `name:"Shrek"`
1353 m := new(pb2.Communique)
1354 want := &pb2.Communique{Union: &pb2.Communique_Name{"Shrek"}}
1355 if err := proto.UnmarshalText(in, m); err != nil {
1356 t.Fatal(err)
1357 }
1358 if !proto.Equal(m, want) {
1359 t.Errorf("\n got %v\nwant %v", m, want)
1360 }
1361
1362 const inOverwrite = `name:"Shrek" number:42`
1363 m = new(pb2.Communique)
1364 testErr := "line 1.13: field 'number' would overwrite already parsed oneof 'union'"
1365 if err := proto.UnmarshalText(inOverwrite, m); err == nil {
1366 t.Errorf("proto.UnmarshalText: got nil error, want %v", testErr)
1367 } else if err.Error() != testErr {
1368 t.Errorf("error mismatch:\ngot: %v\nwant: %v", err.Error(), testErr)
1369 }
1370 }
1371
View as plain text