1// Copyright 2016 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package proto_test
6
7import (
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
23var (
24	expandedMarshaler        = proto.TextMarshaler{ExpandAny: true}
25	expandedCompactMarshaler = proto.TextMarshaler{Compact: true, ExpandAny: true}
26)
27
28// anyEqual reports whether two messages which may be google.protobuf.Any or may
29// contain google.protobuf.Any fields are equal. We can't use proto.Equal for
30// comparison, because semantically equivalent messages may be marshaled to
31// binary in different tag order. Instead, trust that TextMarshaler with
32// ExpandAny option works and compare the text marshaling results.
33func anyEqual(got, want proto.Message) bool {
34	// if messages are proto.Equal, no need to marshal.
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
43type golden struct {
44	m    proto.Message
45	t, c string
46}
47
48var goldenMessages = makeGolden()
49
50func 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 = `
103name: "David"
104result_count: 47
105anything: <
106  [type.googleapis.com/proto3_test.Nested]: <
107    bunny: "Monty"
108  >
109>
110`
111		m2Golden = `
112name: "David"
113result_count: 47
114anything: <
115  ["http://[::1]/type.googleapis.com/proto3_test.Nested"]: <
116    bunny: "Monty"
117  >
118>
119`
120		m3Golden = `
121name: "David"
122result_count: 47
123anything: <
124  ["type.googleapis.com/\"/proto3_test.Nested"]: <
125    bunny: "Monty"
126  >
127>
128`
129		m4Golden = `
130name: "David"
131result_count: 47
132anything: <
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 = `
144name: "David"
145result_count: 47
146anything: <
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>
156many_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>
166many_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
188func 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
201func 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
224func 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
242func 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
253func 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
269func 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// textMessage implements the methods that allow it to marshal and unmarshal
284// itself as text.
285type textMessage struct {
286}
287
288func (*textMessage) MarshalText() ([]byte, error) {
289	return []byte("custom"), nil
290}
291
292func (*textMessage) UnmarshalText(bytes []byte) error {
293	if string(bytes) != "custom" {
294		return errors.New("expected 'custom'")
295	}
296	return nil
297}
298
299func (*textMessage) Reset()         {}
300func (*textMessage) String() string { return "" }
301func (*textMessage) ProtoMessage()  {}
302
303func 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		// One normally wouldn't do this.
332		// This is an undeclared tag 13, as a varint (wire type 0) with value 4.
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	// Add an unknown extension. We marshal a pb2.Ext, and fake the ID.
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	// Extensions can be plain fields, too, so let's test that.
355	b = append(proto.EncodeVarint(202<<3|proto.WireVarint), 19)
356	proto.SetRawExtension(msg, 202, b)
357
358	return msg
359}
360
361const text = `count: 42
362name: "Dave"
363quote: "\"I didn't want to go.\""
364pet: "bunny"
365pet: "kitty"
366pet: "horsey"
367inner: <
368  host: "footrest.syd"
369  port: 7001
370  connected: true
371>
372others: <
373  key: 3735928559
374  value: "\001A\007\014"
375>
376others: <
377  weight: 6.022
378  inner: <
379    host: "lesha.mtv"
380    port: 8002
381  >
382>
383bikeshed: BLUE
384SomeGroup {
385  group_field: 8
386}
387/* 18 unknown bytes */
38813: 4
389201: "\t3G skiing"
390202: 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
399func 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
410func 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}
420func 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
434func TestMarshalTextUnknownEnum(t *testing.T) {
435	// The Color enum only specifies values 0-2.
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
444func TestTextOneof(t *testing.T) {
445	tests := []struct {
446		m    proto.Message
447		want string
448	}{
449		// zero message
450		{&pb2.Communique{}, ``},
451		// scalar field
452		{&pb2.Communique{Union: &pb2.Communique_Number{4}}, `number:4`},
453		// message field
454		{&pb2.Communique{Union: &pb2.Communique_Msg{
455			&pb2.Strings{StringField: proto.String("why hello!")},
456		}}, `msg:<string_field:"why hello!" >`},
457		// bad oneof (should not panic)
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
468func compact(src string) string {
469	// s/[ \n]+/ /g; s/ $//;
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
513func 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
520func TestStringEscaping(t *testing.T) {
521	testCases := []struct {
522		in  *pb2.Strings
523		out string
524	}{
525		{
526			// Test data from C++ test (TextFormatTest.StringEscape).
527			// Single divergence: we don't escape apostrophes.
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			// Test data from the same C++ test.
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			// Some UTF-8.
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			// Check round-trip.
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// A limitedWriter accepts some output before it fails.
567// This is a proxy for something like a nearly-full or imminently-failing disk,
568// or a network connection that is about to die.
569type limitedWriter struct {
570	b     bytes.Buffer
571	limit int
572}
573
574var outOfSpace = errors.New("proto: insufficient space")
575
576func (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
588func TestMarshalTextFailing(t *testing.T) {
589	// Try lots of different sizes to exercise more error code-paths.
590	for lim := 0; lim < len(text); lim++ {
591		buf := new(limitedWriter)
592		buf.limit = lim
593		err := proto.MarshalText(buf, newTestMessage())
594		// We expect a certain error, but also some partial results in the buffer.
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
606func 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
627func 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}
639Message {
640  name: "Horse"
641}
642Message {
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
650func TestProto3Text(t *testing.T) {
651	tests := []struct {
652		m    proto.Message
653		want string
654	}{
655		// zero message
656		{&pb3.Message{}, ``},
657		// zero message except for an empty byte slice
658		{&pb3.Message{Data: []byte{}}, ``},
659		// trivial case
660		{&pb3.Message{Name: "Rob", HeightInCm: 175}, `name:"Rob" height_in_cm:175`},
661		// empty map
662		{&pb2.MessageWithMap{}, ``},
663		// non-empty map; map format is the same as a repeated struct,
664		// and they are sorted by key (numerically for numeric keys).
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		// map with nil value; not well-defined, but we shouldn't crash
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
691func TestRacyMarshal(t *testing.T) {
692	// This test should be run with the race detector.
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
733type UnmarshalTextTest struct {
734	in  string
735	err string // if "", no error expected
736	out *pb2.MyMessage
737}
738
739func 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
749func 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
758func 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
768var unmarshalTextTests = []UnmarshalTextTest{
769	// Basic
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	// Empty quoted string
779	{
780		in: `count:42 name:""`,
781		out: &pb2.MyMessage{
782			Count: proto.Int32(42),
783			Name:  proto.String(""),
784		},
785	},
786
787	// Quoted string concatenation with double quotes
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	// Quoted string concatenation with single quotes
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	// Quoted string concatenations with mixed quotes
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	// Quoted string with escaped apostrophe
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	// Quoted string with single quote
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	// Quoted string with all the accepted special characters from the C++ test
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	// Quoted string with quoted backslash
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	// Quoted string with UTF-8 bytes.
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	// Quoted string with unicode escapes.
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	// Bad quoted string
876	{
877		in:  `inner: < host: "\0" >` + "\n",
878		err: `line 1.15: invalid quoted string "\0": \0 requires 2 following digits`,
879	},
880
881	// Bad \u escape
882	{
883		in:  `count: 42 name: "\u000"`,
884		err: `line 1.16: invalid quoted string "\u000": \u requires 4 following digits`,
885	},
886
887	// Bad \U escape
888	{
889		in:  `count: 42 name: "\U0000000"`,
890		err: `line 1.16: invalid quoted string "\U0000000": \U requires 8 following digits`,
891	},
892
893	// Bad \U escape
894	{
895		in:  `count: 42 name: "\xxx"`,
896		err: `line 1.16: invalid quoted string "\xxx": \xxx contains non-hexadecimal digits`,
897	},
898
899	// Number too large for int64
900	{
901		in:  "count: 1 others { key: 123456789012345678901 }",
902		err: "line 1.23: invalid int64: 123456789012345678901",
903	},
904
905	// Number too large for int32
906	{
907		in:  "count: 1234567890123",
908		err: "line 1.7: invalid int32: 1234567890123",
909	},
910
911	// Number in hexadecimal
912	{
913		in: "count: 0x2beef",
914		out: &pb2.MyMessage{
915			Count: proto.Int32(0x2beef),
916		},
917	},
918
919	// Number in octal
920	{
921		in: "count: 024601",
922		out: &pb2.MyMessage{
923			Count: proto.Int32(024601),
924		},
925	},
926
927	// Floating point number with "f" suffix
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	// Floating point positive infinity
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	// Floating point negative infinity
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	// Number too large for float32
959	{
960		in:  "others:< weight: 12345678901234567890123456789012345678901234567890 >",
961		err: "line 1.17: invalid float: 12345678901234567890123456789012345678901234567890",
962	},
963
964	// Number posing as a quoted string
965	{
966		in:  `inner: < host: 12 >` + "\n",
967		err: `line 1.15: invalid string: 12`,
968	},
969
970	// Quoted string posing as int32
971	{
972		in:  `count: "12"`,
973		err: `line 1.7: invalid int32: "12"`,
974	},
975
976	// Quoted string posing a float32
977	{
978		in:  `others:< weight: "17.4" >`,
979		err: `line 1.17: invalid float: "17.4"`,
980	},
981
982	// unclosed bracket doesn't cause infinite loop
983	{
984		in:  `[`,
985		err: `line 1.0: unclosed type_url or extension name`,
986	},
987
988	// Enum
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	// Repeated field
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	// Repeated field with list notation
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	// Repeated message with/without colon and <>/{}
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	// Missing colon for inner message
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	// Missing colon for string field
1041	{
1042		in:  `name "Dave"`,
1043		err: `line 1.5: expected ':', found "\"Dave\""`,
1044	},
1045
1046	// Missing colon for int32 field
1047	{
1048		in:  `count 42`,
1049		err: `line 1.6: expected ':', found "42"`,
1050	},
1051
1052	// Missing required field
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	// Missing required field in a required submessage
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	// Repeated non-repeated field
1072	{
1073		in:  `name: "Rob" name: "Russ"`,
1074		err: `line 1.12: non-repeated field "name" was repeated`,
1075	},
1076
1077	// Group
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	// Semicolon between fields
1089	{
1090		in: `count:3;name:"Calvin"`,
1091		out: &pb2.MyMessage{
1092			Count: proto.Int32(3),
1093			Name:  proto.String("Calvin"),
1094		},
1095	},
1096	// Comma between fields
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	// Boolean false
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	// Boolean true
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	// Boolean 0
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	// Boolean 1
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	// Boolean f
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	// Boolean t
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	// Boolean False
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	// Boolean True
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	// Extension
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	// Big all-in-one
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
1255func 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				// We don't expect failure.
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				// We do expect failure.
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
1281func 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// Regression test; this caused a panic.
1292func 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
1305func 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
1320func 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>,>` + // separating commas are okay
1324		`msg_mapping<key:-2 value<f: 4.0>>` + // no colon after "value"
1325		`msg_mapping:<value:<f: 5.0>>` + // omitted key
1326		`byte_mapping:<key:true value:"so be it">` +
1327		`byte_mapping:<>` // omitted key and value
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
1351func 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