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	"bytes"
36	"errors"
37	"io/ioutil"
38	"math"
39	"strings"
40	"sync"
41	"testing"
42
43	"github.com/gogo/protobuf/proto"
44
45	proto3pb "github.com/gogo/protobuf/proto/proto3_proto"
46	pb "github.com/gogo/protobuf/proto/test_proto"
47	"github.com/gogo/protobuf/types"
48)
49
50// textMessage implements the methods that allow it to marshal and unmarshal
51// itself as text.
52type textMessage struct {
53}
54
55func (*textMessage) MarshalText() ([]byte, error) {
56	return []byte("custom"), nil
57}
58
59func (*textMessage) UnmarshalText(bytes []byte) error {
60	if string(bytes) != "custom" {
61		return errors.New("expected 'custom'")
62	}
63	return nil
64}
65
66func (*textMessage) Reset()         {}
67func (*textMessage) String() string { return "" }
68func (*textMessage) ProtoMessage()  {}
69
70func newTestMessage() *pb.MyMessage {
71	msg := &pb.MyMessage{
72		Count: proto.Int32(42),
73		Name:  proto.String("Dave"),
74		Quote: proto.String(`"I didn't want to go."`),
75		Pet:   []string{"bunny", "kitty", "horsey"},
76		Inner: &pb.InnerMessage{
77			Host:      proto.String("footrest.syd"),
78			Port:      proto.Int32(7001),
79			Connected: proto.Bool(true),
80		},
81		Others: []*pb.OtherMessage{
82			{
83				Key:   proto.Int64(0xdeadbeef),
84				Value: []byte{1, 65, 7, 12},
85			},
86			{
87				Weight: proto.Float32(6.022),
88				Inner: &pb.InnerMessage{
89					Host: proto.String("lesha.mtv"),
90					Port: proto.Int32(8002),
91				},
92			},
93		},
94		Bikeshed: pb.MyMessage_BLUE.Enum(),
95		Somegroup: &pb.MyMessage_SomeGroup{
96			GroupField: proto.Int32(8),
97		},
98		// One normally wouldn't do this.
99		// This is an undeclared tag 13, as a varint (wire type 0) with value 4.
100		XXX_unrecognized: []byte{13<<3 | 0, 4},
101	}
102	ext := &pb.Ext{
103		Data: proto.String("Big gobs for big rats"),
104	}
105	if err := proto.SetExtension(msg, pb.E_Ext_More, ext); err != nil {
106		panic(err)
107	}
108	greetings := []string{"adg", "easy", "cow"}
109	if err := proto.SetExtension(msg, pb.E_Greeting, greetings); err != nil {
110		panic(err)
111	}
112
113	// Add an unknown extension. We marshal a pb.Ext, and fake the ID.
114	b, err := proto.Marshal(&pb.Ext{Data: proto.String("3G skiing")})
115	if err != nil {
116		panic(err)
117	}
118	b = append(proto.EncodeVarint(201<<3|proto.WireBytes), b...)
119	proto.SetRawExtension(msg, 201, b)
120
121	// Extensions can be plain fields, too, so let's test that.
122	b = append(proto.EncodeVarint(202<<3|proto.WireVarint), 19)
123	proto.SetRawExtension(msg, 202, b)
124
125	return msg
126}
127
128const text = `count: 42
129name: "Dave"
130quote: "\"I didn't want to go.\""
131pet: "bunny"
132pet: "kitty"
133pet: "horsey"
134inner: <
135  host: "footrest.syd"
136  port: 7001
137  connected: true
138>
139others: <
140  key: 3735928559
141  value: "\001A\007\014"
142>
143others: <
144  weight: 6.022
145  inner: <
146    host: "lesha.mtv"
147    port: 8002
148  >
149>
150bikeshed: BLUE
151SomeGroup {
152  group_field: 8
153}
154/* 2 unknown bytes */
15513: 4
156[test_proto.Ext.more]: <
157  data: "Big gobs for big rats"
158>
159[test_proto.greeting]: "adg"
160[test_proto.greeting]: "easy"
161[test_proto.greeting]: "cow"
162/* 13 unknown bytes */
163201: "\t3G skiing"
164/* 3 unknown bytes */
165202: 19
166`
167
168func TestMarshalText(t *testing.T) {
169	buf := new(bytes.Buffer)
170	if err := proto.MarshalText(buf, newTestMessage()); err != nil {
171		t.Fatalf("proto.MarshalText: %v", err)
172	}
173	s := buf.String()
174	if s != text {
175		t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, text)
176	}
177}
178
179func TestMarshalTextCustomMessage(t *testing.T) {
180	buf := new(bytes.Buffer)
181	if err := proto.MarshalText(buf, &textMessage{}); err != nil {
182		t.Fatalf("proto.MarshalText: %v", err)
183	}
184	s := buf.String()
185	if s != "custom" {
186		t.Errorf("Got %q, expected %q", s, "custom")
187	}
188}
189func TestMarshalTextNil(t *testing.T) {
190	want := "<nil>"
191	tests := []proto.Message{nil, (*pb.MyMessage)(nil)}
192	for i, test := range tests {
193		buf := new(bytes.Buffer)
194		if err := proto.MarshalText(buf, test); err != nil {
195			t.Fatal(err)
196		}
197		if got := buf.String(); got != want {
198			t.Errorf("%d: got %q want %q", i, got, want)
199		}
200	}
201}
202
203func TestMarshalTextUnknownEnum(t *testing.T) {
204	// The Color enum only specifies values 0-2.
205	m := &pb.MyMessage{Bikeshed: pb.MyMessage_Color(3).Enum()}
206	got := m.String()
207	const want = `bikeshed:3 `
208	if got != want {
209		t.Errorf("\n got %q\nwant %q", got, want)
210	}
211}
212
213func TestTextOneof(t *testing.T) {
214	tests := []struct {
215		m    proto.Message
216		want string
217	}{
218		// zero message
219		{&pb.Communique{}, ``},
220		// scalar field
221		{&pb.Communique{Union: &pb.Communique_Number{Number: 4}}, `number:4`},
222		// message field
223		{&pb.Communique{Union: &pb.Communique_Msg{
224			Msg: &pb.Strings{StringField: proto.String("why hello!")},
225		}}, `msg:<string_field:"why hello!" >`},
226		// bad oneof (should not panic)
227		{&pb.Communique{Union: &pb.Communique_Msg{Msg: nil}}, `msg:/* nil */`},
228	}
229	for _, test := range tests {
230		got := strings.TrimSpace(test.m.String())
231		if got != test.want {
232			t.Errorf("\n got %s\nwant %s", got, test.want)
233		}
234	}
235}
236
237func BenchmarkMarshalTextBuffered(b *testing.B) {
238	buf := new(bytes.Buffer)
239	m := newTestMessage()
240	for i := 0; i < b.N; i++ {
241		buf.Reset()
242		proto.MarshalText(buf, m)
243	}
244}
245
246func BenchmarkMarshalTextUnbuffered(b *testing.B) {
247	w := ioutil.Discard
248	m := newTestMessage()
249	for i := 0; i < b.N; i++ {
250		proto.MarshalText(w, m)
251	}
252}
253
254func compact(src string) string {
255	// s/[ \n]+/ /g; s/ $//;
256	dst := make([]byte, len(src))
257	space, comment := false, false
258	j := 0
259	for i := 0; i < len(src); i++ {
260		if strings.HasPrefix(src[i:], "/*") {
261			comment = true
262			i++
263			continue
264		}
265		if comment && strings.HasPrefix(src[i:], "*/") {
266			comment = false
267			i++
268			continue
269		}
270		if comment {
271			continue
272		}
273		c := src[i]
274		if c == ' ' || c == '\n' {
275			space = true
276			continue
277		}
278		if j > 0 && (dst[j-1] == ':' || dst[j-1] == '<' || dst[j-1] == '{') {
279			space = false
280		}
281		if c == '{' {
282			space = false
283		}
284		if space {
285			dst[j] = ' '
286			j++
287			space = false
288		}
289		dst[j] = c
290		j++
291	}
292	if space {
293		dst[j] = ' '
294		j++
295	}
296	return string(dst[0:j])
297}
298
299var compactText = compact(text)
300
301func TestCompactText(t *testing.T) {
302	s := proto.CompactTextString(newTestMessage())
303	if s != compactText {
304		t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v\n===\n", s, compactText)
305	}
306}
307
308func TestStringEscaping(t *testing.T) {
309	testCases := []struct {
310		in  *pb.Strings
311		out string
312	}{
313		{
314			// Test data from C++ test (TextFormatTest.StringEscape).
315			// Single divergence: we don't escape apostrophes.
316			&pb.Strings{StringField: proto.String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and  multiple   spaces")},
317			"string_field: \"\\\"A string with ' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and  multiple   spaces\"\n",
318		},
319		{
320			// Test data from the same C++ test.
321			&pb.Strings{StringField: proto.String("\350\260\267\346\255\214")},
322			"string_field: \"\\350\\260\\267\\346\\255\\214\"\n",
323		},
324		{
325			// Some UTF-8.
326			&pb.Strings{StringField: proto.String("\x00\x01\xff\x81")},
327			`string_field: "\000\001\377\201"` + "\n",
328		},
329	}
330
331	for i, tc := range testCases {
332		var buf bytes.Buffer
333		if err := proto.MarshalText(&buf, tc.in); err != nil {
334			t.Errorf("proto.MarsalText: %v", err)
335			continue
336		}
337		s := buf.String()
338		if s != tc.out {
339			t.Errorf("#%d: Got:\n%s\nExpected:\n%s\n", i, s, tc.out)
340			continue
341		}
342
343		// Check round-trip.
344		pbStrings := new(pb.Strings)
345		if err := proto.UnmarshalText(s, pbStrings); err != nil {
346			t.Errorf("#%d: UnmarshalText: %v", i, err)
347			continue
348		}
349		if !proto.Equal(pbStrings, tc.in) {
350			t.Errorf("#%d: Round-trip failed:\nstart: %v\n  end: %v", i, tc.in, pbStrings)
351		}
352	}
353}
354
355// A limitedWriter accepts some output before it fails.
356// This is a proxy for something like a nearly-full or imminently-failing disk,
357// or a network connection that is about to die.
358type limitedWriter struct {
359	b     bytes.Buffer
360	limit int
361}
362
363var outOfSpace = errors.New("proto: insufficient space")
364
365func (w *limitedWriter) Write(p []byte) (n int, err error) {
366	var avail = w.limit - w.b.Len()
367	if avail <= 0 {
368		return 0, outOfSpace
369	}
370	if len(p) <= avail {
371		return w.b.Write(p)
372	}
373	n, _ = w.b.Write(p[:avail])
374	return n, outOfSpace
375}
376
377func TestMarshalTextFailing(t *testing.T) {
378	// Try lots of different sizes to exercise more error code-paths.
379	for lim := 0; lim < len(text); lim++ {
380		buf := new(limitedWriter)
381		buf.limit = lim
382		err := proto.MarshalText(buf, newTestMessage())
383		// We expect a certain error, but also some partial results in the buffer.
384		if err != outOfSpace {
385			t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", err, outOfSpace)
386		}
387		s := buf.b.String()
388		x := text[:buf.limit]
389		if s != x {
390			t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, x)
391		}
392	}
393}
394
395func TestFloats(t *testing.T) {
396	tests := []struct {
397		f    float64
398		want string
399	}{
400		{0, "0"},
401		{4.7, "4.7"},
402		{math.Inf(1), "inf"},
403		{math.Inf(-1), "-inf"},
404		{math.NaN(), "nan"},
405	}
406	for _, test := range tests {
407		msg := &pb.FloatingPoint{F: &test.f}
408		got := strings.TrimSpace(msg.String())
409		want := `f:` + test.want
410		if got != want {
411			t.Errorf("f=%f: got %q, want %q", test.f, got, want)
412		}
413	}
414}
415
416func TestRepeatedNilText(t *testing.T) {
417	m := &pb.MessageList{
418		Message: []*pb.MessageList_Message{
419			nil,
420			{
421				Name: proto.String("Horse"),
422			},
423			nil,
424		},
425	}
426	want := `Message <nil>
427Message {
428  name: "Horse"
429}
430Message <nil>
431`
432	if s := proto.MarshalTextString(m); s != want {
433		t.Errorf(" got: %s\nwant: %s", s, want)
434	}
435}
436
437func TestProto3Text(t *testing.T) {
438	tests := []struct {
439		m    proto.Message
440		want string
441	}{
442		// zero message
443		{&proto3pb.Message{}, ``},
444		// zero message except for an empty byte slice
445		{&proto3pb.Message{Data: []byte{}}, ``},
446		// trivial case
447		{&proto3pb.Message{Name: "Rob", HeightInCm: 175}, `name:"Rob" height_in_cm:175`},
448		// empty map
449		{&pb.MessageWithMap{}, ``},
450		// non-empty map; map format is the same as a repeated struct,
451		// and they are sorted by key (numerically for numeric keys).
452		{
453			&pb.MessageWithMap{NameMapping: map[int32]string{
454				-1:      "Negatory",
455				7:       "Lucky",
456				1234:    "Feist",
457				6345789: "Otis",
458			}},
459			`name_mapping:<key:-1 value:"Negatory" > ` +
460				`name_mapping:<key:7 value:"Lucky" > ` +
461				`name_mapping:<key:1234 value:"Feist" > ` +
462				`name_mapping:<key:6345789 value:"Otis" >`,
463		},
464		// map with nil value; not well-defined, but we shouldn't crash
465		{
466			&pb.MessageWithMap{MsgMapping: map[int64]*pb.FloatingPoint{7: nil}},
467			`msg_mapping:<key:7 >`,
468		},
469	}
470	for _, test := range tests {
471		got := strings.TrimSpace(test.m.String())
472		if got != test.want {
473			t.Errorf("\n got %s\nwant %s", got, test.want)
474		}
475	}
476}
477
478func TestRacyMarshal(t *testing.T) {
479	// This test should be run with the race detector.
480
481	any := &pb.MyMessage{Count: proto.Int32(47), Name: proto.String("David")}
482	proto.SetExtension(any, pb.E_Ext_Text, proto.String("bar"))
483	b, err := proto.Marshal(any)
484	if err != nil {
485		panic(err)
486	}
487	m := &proto3pb.Message{
488		Name:        "David",
489		ResultCount: 47,
490		Anything:    &types.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any), Value: b},
491	}
492
493	wantText := proto.MarshalTextString(m)
494	wantBytes, err := proto.Marshal(m)
495	if err != nil {
496		t.Fatalf("proto.Marshal error: %v", err)
497	}
498
499	var wg sync.WaitGroup
500	defer wg.Wait()
501	wg.Add(20)
502	for i := 0; i < 10; i++ {
503		go func() {
504			defer wg.Done()
505			got := proto.MarshalTextString(m)
506			if got != wantText {
507				t.Errorf("proto.MarshalTextString = %q, want %q", got, wantText)
508			}
509		}()
510		go func() {
511			defer wg.Done()
512			got, err := proto.Marshal(m)
513			if !bytes.Equal(got, wantBytes) || err != nil {
514				t.Errorf("proto.Marshal = (%x, %v), want (%x, nil)", got, err, wantBytes)
515			}
516		}()
517	}
518}
519