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